文章目錄
-
- 1. 背景
- 2. 概述
-
- 2.1. 主要機制及應用
- 2.2. 同步或通信的基本方式
- 3. 信号量
-
- 3.1. 主要機制及應用
- 3.2. 分類
- 3.3. 互斥信号量
-
- 3.3.1. 嵌套(遞歸)資源通路
- 3.3.2. 删除安全
- 3.4. 各種互斥機制的比較
- 3.5. 二值信号量
- 3.6. 計數信号量
- 4. uC/OS-II中實作
-
- 4.1. 建立信号量
- 4.2. 擷取(申請)信号量
- 4.3. 釋放信号量
- 4.4. 删除信号量
1. 背景
前段時間老師上課講到了uC/OS中的信号量機制,是以我想要結合《μC/OS-III源碼分析筆記》和中國大學MOOC-電子科技大學《嵌入式系統及應用》PPT寫一篇筆記對這部分的内容進行總結。
2. 概述
2.1. 主要機制及應用

Figure 1. 模型 \text{Figure 1. 模型} Figure 1. 模型
-
在單處理器平台上,嵌入式作業系統核心提供的同步、互斥與通信機制
主要包括:
- 信号量(semaphore),用于互斥與同步。
- 事件(組)(event group),用于同步。
- 郵箱(mailbox)、消息隊列(message queue),用于消息通信。
- 異步信号(asynchronous signal),用于同步。
- 管道(pipe),提供非結構化資料交換(通信)和實作同步。
- 單處理器或多處理器系統中,還有其它一些機制也可用于同步與通信:
- 全局變量
- 共享記憶體
- Sockets
- 遠端過程調用(Remote Procedure Call)
2.2. 同步或通信的基本方式
Figure 2. 同步或通信的基本方式 \text{Figure 2. 同步或通信的基本方式} Figure 2. 同步或通信的基本方式
3. 信号量
3.1. 主要機制及應用
- 在單處理器平台上,嵌入式作業系統核心提供的同步、互斥與通信機制 主要包括:
- 信号量(semaphore),用于互斥與同步。
- 事件(組)(event group),用于同步。
- 郵箱(mailbox)、消息隊列(message queue),用于消息通信。
- 異步信号(asynchronous signal),用于同步。
- 管道(pipe),提供非結構化資料交換(通信)和實作同步。
- 單處理器或多處理器系統中,還有其它一些機制也可用于同步與通信: 全局變量、共享記憶體、Sockets、遠端過程調用(Remote Procedure Call)。
3.2. 分類
信号量用于實作任務與任務之間、任務與中斷處理程式之間的同步與 互斥。信号量一般分為三種:
- 互斥信号量:用于解決互斥問題,可能會引起優先級反轉問題。
- 二值信号量:用于解決同步問題
- 計數信号量:用于解決資源計數問題
3.3. 互斥信号量
- 用互斥信号量保護的代碼區稱作
,臨界區代碼通常用 于對共享資源的通路。臨界區
- 共享資源可能是一段存儲器空間、一個資料結構或I/O裝置,也 可能是被兩個或多個并發任務共享的任何内容。
- 使用
,保證隻有成功 地擷取互斥信号量的任務才能夠釋放它。互斥信号量可以實作對共享資源的串行通路
- 互斥信号量基本特點:互斥信号量的值被初始化成1,最多隻有 一個任務可以進入
。臨界區
Figure 3. 互斥信号量狀态圖 \text{Figure 3. 互斥信号量狀态圖} Figure 3. 互斥信号量狀态圖
- 互斥信号量是一種特殊的二值信号量,一般它
和一些避免支援所有權、遞歸訪 問、任務删除安全
互斥所固 有問題的協定。優先級反轉、饑餓、死鎖等
- 所有權:當一個任務通過擷取互斥信号量而将其鎖定時,得到該互 斥信号量的所有權。相反,當一個任務釋放信号量時,失去對其的 所有權。
3.3.1. 嵌套(遞歸)資源通路
- 嵌套(遞歸)資源通路:如果Task1調用RoutineA,而RoutineA 又調用RoutineB,并且三者通路相同的共享資源,就發生了遞歸共 享資源的通路同步問題。
- 一個遞歸的互斥信号量允許嵌套鎖定互斥信号量,而不引起死鎖。
- 每個擷取信号量的調用必須與釋放信号量的調用相比對。
- 用于
,否則任務會被永久阻塞。同步的信号量不支援嵌套通路
3.3.2. 删除安全
- 删除安全:在一個受信号量保護的臨界區,經常需要保護在臨界區執行的任務不會被意外地删除。
- 删除一個在臨界區執行的任務可能引起意想不到的後果,造成信号量不可用或資源破壞。
- 解決方法:提供
和任務保護
原語對。解除任務保護
3.4. 各種互斥機制的比較
Figure 4. 各種互斥機制的比較 \text{Figure 4. 各種互斥機制的比較} Figure 4. 各種互斥機制的比較
3.5. 二值信号量
二值信号量主要用于任務與任務之間、任務與中斷服務程式之間的 同步。二值信号量的初始值為0,表示同步事件尚未産生。
Figure 5. 二值信号量狀态圖 \text{Figure 5. 二值信号量狀态圖} Figure 5. 二值信号量狀态圖
用二值信号量實作任務間雙向同步時,申請者和釋放者不是同一個任務,與互斥信号量不同。互斥信号量必須誰申請,誰釋放。
Figure 6. 用二值信号量實作任務間雙向同步 \text{Figure 6. 用二值信号量實作任務間雙向同步} Figure 6. 用二值信号量實作任務間雙向同步
3.6. 計數信号量
- 計數信号量用于控制系統中共享資源的多個執行個體的使用,允許多個 任務同時通路同一種資源的多個執行個體。
- 計數信号量被初始化為一個非負整數n,即該種共享資源的數目。
Figure 7. 計數信号量狀态圖 \text{Figure 7. 計數信号量狀态圖} Figure 7. 計數信号量狀态圖
Figure 8. 有界緩沖問題1 \text{Figure 8. 有界緩沖問題1} Figure 8. 有界緩沖問題1
Figure 9. 有界緩沖問題 \text{Figure 9. 有界緩沖問題} Figure 9. 有界緩沖問題
4. uC/OS-II中實作
4.1. 建立信号量
OS_EVENT* OSSemCreate(INT16U cnt)
{
OS_EVENT* pevent;
pevent = OSEventFreeList; //從空閑事件控制塊鍊中取得一個ECB
if (OSEventFreeList != (OS_EVENT*)0) {
OSEventFreeList = (OS_EVENT*) { OSEventFreeList->OSEventPtr; }
if (pevent != (OS_EVENT*)0) { //初始化ECB的各個域
pevent->OSEventType = OS_EVENT_TYPE_SEM; //事件類型為信号量
//信号量的初始計數值
pevent->OSEventCnt = cnt;
pevent->OSEventPtr = (void*)0;
OS_EventWaitListInit(pevent); //初始化等待任務清單
}
}
return (pevent); //調用者需檢查傳回值,如果為NULL則表示建立失敗
}
說明:這是針對計數信号量和二值信号量的實作。 μC/OS-II中互斥 信号量的實作參見源碼os_mutex.c檔案。
Figure 10. 基本流程 \text{Figure 10. 基本流程} Figure 10. 基本流程
4.2. 擷取(申請)信号量
void OSSemPend(OS_EVENT* pevent, INT16U timeout, INT8U* err)
{
//信号量值大于0,成功獲得信号量并傳回
if (pevent->OSEventCnt > 0) {
pevent->OSEventCnt--;
*err = OS_NO_ERR;
return;
}
//設定任務狀态為等待信号量
OSTCBCur->OSTCBStat |= OS_STAT_SEM;
//設定等待時限
OSTCBCur->OSTCBDly = timeout;
//将任務放置到信号量的等待清單中
OS_EventTaskWait(pevent);
//核心實施任務排程,系統切換到另一就緒任務執行
OS_Sched();
//判斷任務恢複執行的原因,如果等待時限逾時但仍然未獲得信号量,則傳回逾時資訊
if (OSTCBCur->OSTCBStat & OS_STAT_SEM) {
OSEventTO(pevent);
*err = OS_TIMEOUT;
return;
}
OSTCBCur->OSTCBEventPtr = (OS_EVENT*)0;
*err = OS_NO_ERR; //任務由于獲得信号量而恢複執行,本調用成功傳回
}
說明:這裡同樣是針對計數信号量和二值信号量的實作。
OSSemAccept
也可以實作擷取(申請)信号量的功能。
INT16U OSSemAccept(OS_EVENT* pevent)
{
INT16U cnt;
cnt = pevent->OSEventCnt;
if (cnt > 0) {
pevent->OSEventCnt--;
}
return (cnt);
}
- 功能:無等待的擷取或申請一個信号量。
- 注意:即使不能成功獲得信号量(傳回值為0),調用者也不 會被阻塞。此函數可以在中斷處理程式中使用。
4.3. 釋放信号量
Figure 12. 釋放信号量 \text{Figure 12. 釋放信号量} Figure 12. 釋放信号量
INT8U OSSemPost(OS_EVENT* pevent)
{
if (pevent->OSEventGrp != 0x00) { //如果有任務在等待該信号量
OS_EventTaskRdy(pevent, (void*)0, OS_STAT_SEM); //使等待清單中優先級最高的任務就緒
OS_Sched(); //核心實施任務排程
return (OS_NO_ERR); //成功傳回
}
if (pevent->OSEventCnt < 65535) { //如果沒有任務等待該信号量,并且信号量的值未溢出
pevent->OSEventCnt++; //信号量的值加1
return (OS_NO_ERR); //成功傳回
}
return (OS_SEM_OVF); //信号量溢出
}
4.4. 删除信号量
Figure 13. 删除信号量 \text{Figure 13. 删除信号量} Figure 13. 删除信号量
OS_EVENT* OSSemDel(OS_EVENT* pevent, INT8U opt, INT8U* err)
{
BOOLEAN tasks_waiting;
if(pevent->OSEventGrp!=0x00{//根據是否有任務在等待信号量設定等待标志
tasks_waiting = TRUE;
}else{
tasks_waiting = FALSE;
}
switch(opt){
case OS_DEL_NO_PEND: //如果有任務等待信号量則不删除信号量
if(task_waiting==FALSE{ //沒有任務等待,釋放ECB回空閑鍊
pevent->OSEventType = OS_EVENT_TYPE_UNUSED;
pevent->OSEventPtr = OSEventFreeList;
OSEventFreeList = pevent; //調整空閑ECB鍊頭指針
*err = OS_NO_ERR;
return ((OS_EVENT)0);
}else{
*err = OS_ERR_TASK_WAITING; //有任務等待,删除信号量失敗
return (pevent);
}
case OS_DEL_ALWAYS: //無論有無任務等待都删除信号量
//将等待清單中的每個任務都設定成就緒
while (pevent->OSEventGrp != 0x00) {
OS_EventTaskRdy(pevent, (void*)0, OS_STAT_SEM);
}
pevent->OSEventType = OS_EVENT_TYPE_UNUSED;
pevent->OSEventFreeList;
OSEventFreeList = pevent; //釋放該信号量的ECB回空閑控制塊鍊
if (tasks_waiting == TRUE) {
OS_Sched();
} //如果之前有任務等待信号量,核心實施任務排程
*err = OS_NO_ERR;
return ((OS_EVENT*)0);
default:
*err = OS_ERR_INVALID_OPT;
return (pevent);
}
}
聯系郵箱:[email protected]
CSDN:https://me.csdn.net/qq_41729780
知乎:https://zhuanlan.zhihu.com/c_1225417532351741952
公衆号:複雜網絡與機器學習
歡迎關注/轉載,有問題歡迎通過郵箱交流。