1. 概述
1.1 基本概念
訊號量(Semaphore)是一種實現任務間通訊的機制,實現任務之間同步或臨界資源的互斥訪問。常用於協助一組相互競爭的任務來訪問臨界資源。
在多工系統中,各任務之間需要同步或互斥實現臨界資源的保護,訊號量功能可以為使用者提供這方面的支援。
通常一個訊號量的計數值用於對應有效的資源數,表示剩下的可被佔用的互斥資源數。其值的含義分兩種情況:
- 0,表示沒有積累下來的Post操作,且有可能有在此訊號量上阻塞的任務。
- 正值,表示有一個或多個Post下來的釋放操作。
以同步為目的的訊號量和以互斥為目的的訊號量在使用有如下不同:
- 用作互斥時,訊號量建立後記數是滿的,在需要使用臨界資源時,先取訊號量,使其變空,這樣其他任務需要使用臨界資源時就會因為無法取到訊號量而阻塞,從而保證了臨界資源的安全。
- 用作同步時,訊號量在建立後被置為空,任務1取訊號量而阻塞,任務2在某種條件發生後,釋放訊號量,於是任務1得以進入READY或RUNNING態,從而達到了兩個任務間的同步。
1.2 運作機制
1.2.1 訊號量控制塊
/**
* @ingroup los_sem
* Semaphore control structure.
*/
typedef struct
{
UINT8 usSemStat; /**是否使用標誌位*/
UINT16 uwSemCount; /**訊號量索引號*/
UINT32 usSemID; /**訊號量計數*/
LOS_DL_LIST stSemList; /**掛接阻塞於該訊號量的任務*/
}SEM_CB_S;
1.2.2 訊號量運作原理
訊號量初始化,為配置的N個訊號量申請記憶體(N值可以由使用者自行配置,受記憶體限制,詳見第十章 配置參考),並把所有的訊號量初始化成未使用,並加入到未使用連結串列中供系統使用。
訊號量建立,從未使用的訊號量連結串列中獲取一個訊號量資源,並設定初值。
訊號量申請,若其計數器值大於0,則直接減1返回成功。否則任務阻塞,等待其它任務釋放該訊號量,等待的超時時間可設定。當任務被一個訊號量阻塞時,將該任務掛到訊號量等待任務佇列的隊尾。
訊號量釋放,若沒有任務等待該訊號量,則直接將計數器加1返回。否則喚醒該訊號量等待任務佇列上的第一個任務。
訊號量刪除,將正在使用的訊號量置為未使用訊號量,並掛回到未使用連結串列。
訊號量允許多個任務在同一時刻訪問同一資源,但會限制同一時刻訪問此資源的最大任務數目。訪問同一資源的任務數達到該資源的最大數量時,會阻塞其他試圖獲取該資源的任務,直到有任務釋放該訊號量。
2. 開發指導
2.1 使用場景
訊號量是一種非常靈活的同步方式,可以運用在多種場合中,實現鎖、同步、資源計數等功能,也能方便的用於任務與任務,中斷與任務的同步中。
2.2 功能
Huawei LiteOS 系統中的訊號量模組為使用者提供下面幾種功能。
功能分類 | 介面名 | 描述 |
---|---|---|
訊號量的建立和刪除 | LOS_SemCreate | 建立訊號量 |
- | LOS_SemDelete | 刪除指定的訊號量 |
訊號量的申請和釋放 | LOS_SemPend | 申請指定的訊號量 |
- | LOS_SemPost | 釋放指定的訊號量 |
2.3 開發流程
訊號量的開發典型流程:
- 建立訊號量LOS_SemCreate。
- 申請訊號量LOS_SemPend。
訊號量有三種申請模式:無阻塞模式、永久阻塞模式、定時阻塞模式
- 無阻塞模式:任務需要申請訊號量,若當前訊號量的任務數沒有到訊號量設定的上限,則申請成功。否則,立即返回申請失敗
- 永久阻塞模式:任務需要申請訊號量,若當前訊號量的任務數沒有到訊號量設定的上限,則申請成功。否則,該任務進入阻塞態,系統切換到就緒任務中優先順序最高者繼續執行。任務進入阻塞態後,直到有其他任務釋放該訊號量,阻塞任務才會重新得以執行
- 定時阻塞模式:任務需要申請訊號量,若當前訊號量的任務數沒有到訊號量設定的上限,則申請成功。否則,該任務進入阻塞態,系統切換到就緒任務中優先順序最高者繼續執行。任務進入阻塞態後,指定時間超時前有其他任務釋放該訊號量,或者使用者指定時間超時後,阻塞任務才會重新得以執行
- 釋放訊號量LOS_SemPost。
- 如果有任務阻塞於指定訊號量,則喚醒該訊號量阻塞佇列上的第一個任務。該任務進入就緒態,並進行排程
- 如果沒有任務阻塞於指定訊號量,釋放訊號量成功
- 刪除訊號量LOS_SemDelet
2.4 訊號量錯誤碼
對可能導致訊號量操作失敗的情況,包括建立訊號量、申請訊號量、釋放訊號量、刪除訊號量等,均需要返回對應的錯誤碼,以便快速定位錯誤原因。
序 號 | 定義 | 實際數值 | 描述 | 參考解決方案 |
---|---|---|---|---|
1 | LOS_ERRNO_SEM_NO_MEMORY | 0x02000700 | 記憶體空間不足 | 分配更大的記憶體分割槽 |
2 | LOS_ERRNO_SEM_INVALID | 0x02000701 | 非法傳參 | 改變傳數為合法值 |
3 | LOS_ERRNO_SEM_PTR_NULL | 0x02000702 | 傳入空指標 | 傳入合法指標 |
4 | LOS_ERRNO_SEM_ALL_BUSY | 0x02000703 | 訊號量控制塊不可用 | 釋放資源訊號量資源 |
5 | LOS_ERRNO_SEM_UNAVAILABLE | 0x02000704 | 定時時間非法 | 傳入正確的定時時間 |
6 | LOS_ERRNO_SEM_PEND_INTERR | 0x02000705 | 中斷期間非法呼叫LOS_SemPend | 中斷期間禁止呼叫LOS_SemPend |
7 | LOS_ERRNO_SEM_PEND_IN_LOCK | 0x02000706 | 任務被鎖,無法獲得訊號量 | 在任務被鎖時,不能呼叫LOS_SemPend |
8 | LOS_ERRNO_SEM_TIMEOUT | 0x02000707 | 獲取訊號量時間超時 | 將時間設定在合理範圍內 |
錯誤碼定義:錯誤碼是一個32位的儲存單元, 31~24位表示錯誤等級, 23~16位表示錯誤碼標誌, 15~8位代表錯誤碼所屬模組, 7~0位表示錯誤碼序號,如下
#define LOS_ERRNO_OS_NORMAL(MID,ERRNO) \
(LOS_ERRTYPE_NORMAL | LOS_ERRNO_OS_ID | ((UINT32)(MID) << 8) | (ERRNO))
LOS_ERRTYPE_NORMAL :Define the error level as critical
LOS_ERRNO_OS_ID :OS error code flag.
MID:OS_MOUDLE_ID
ERRNO:error ID number
例如:
LOS_ERRNO_SEM_NO_MEMORY LOS_ERRNO_OS_ERROR(LOS_MOD_SEM, 0x00))
2.5 平臺差異性
2.6 注意事項
- 由於中斷不能被阻塞,因此在申請訊號量時,阻塞模式不能在中斷中使用。
3. 程式設計例項
3.1 例項描述
本例項實現如下功能;
- 測試任務Example_TaskEntry建立一個訊號量,鎖任務排程,建立兩個任務Example_SemTask1、 Example_SemTask2,Example_SemTask2優先順序高於Example_SemTask1,兩個任務中申請同一訊號量,解鎖任務排程後兩任務阻塞,測試任務Example_TaskEntry釋放訊號量。
- Example_SemTask2得到訊號量,被排程,然後任務休眠20Tick,Example_SemTask2延遲, Example_SemTask1被喚醒。
- Example_SemTask1定時阻塞模式申請訊號量,等待時間為10Tick,因訊號量仍被Example_SemTask2持有, Example_SemTask1掛起, 10Tick後仍未得到訊號量,Example_SemTask1被喚醒,試圖以永久阻塞模式申請訊號量, Example_SemTask1掛起。
- 20Tick後Example_SemTask2喚醒, 釋放訊號量後, Example_SemTask1得到訊號量被排程執行,最後釋放訊號量。
- Example_SemTask1執行完, 40Tick後任務Example_TaskEntry被喚醒,執行刪除訊號量,刪除兩個任務。
3.2 程式設計示例
前提條件:
- 在los_config.h中,將LOSCFG_BASE_IPC_SEM配置為YES。
- 配置使用者定義的LOSCFG_BASE_IPC_SEM_LIMIT最大的訊號量數,如1024。
程式碼實現如下
#include "los_sem.h"
/*任務PID*/
static UINT32 g_TestTaskID01,g_TestTaskID02;
/*測試任務優先順序*/
#define TASK_PRIO_TEST 5
/*訊號量結構體ID*/
static SEM_HANDLE_T g_usSemID;
VOID Example_SemTask1(void)
{
UINT32 uwRet;
printf("Example_SemTask1 try get sem g_usSemID ,timeout 10 ticks.\n");
/*定時阻塞模式申請訊號量,定時時間為10Tick*/
uwRet = LOS_SemPend(g_usSemID, 10);
/*申請到訊號量*/
if(LOS_OK == uwRet)
{
LOS_SemPost(g_usSemID);
return;
}
/*定時時間到,未申請到訊號量*/
if(LOS_ERRNO_SEM_TIMEOUT == uwRet)
{
printf("Example_SemTask1 timeout and try get sem g_usSemID wait forever.\n");
/*永久阻塞模式申請訊號量*/
uwRet = LOS_SemPend(g_usSemID, LOS_WAIT_FOREVER);
printf("Example_SemTask1 wait_forever and get sem g_usSemID .\n");
if(LOS_OK == uwRet)
{
LOS_SemPost(g_usSemID);
return;
}
}
return;
}
VOID Example_SemTask2(void)
{
UINT32 uwRet;
printf("Example_SemTask2 try get sem g_usSemID wait forever.\n");
/*永久阻塞模式申請訊號量*/
uwRet = LOS_SemPend(g_usSemID, LOS_WAIT_FOREVER);
if(LOS_OK == uwRet)
printf("Example_SemTask2 get sem g_usSemID and then delay 20ticks .\n");
/*任務休眠20 Tick*/
LOS_TaskDelay(20);
printf("Example_SemTask2 post sem g_usSemID .\n");
/*釋放訊號量*/
LOS_SemPost(g_usSemID);
return;
}
UINT32 Example_TaskEntry()
{
UINT32 uwRet;
TSK_INIT_PARAM_S stTask1;
TSK_INIT_PARAM_S stTask2;
/*建立訊號量*/
LOS_SemCreate(0,&g_usSemID);
/*鎖任務排程*/
LOS_TaskLock();
/*建立任務1*/
memset(&stTask1, 0, sizeof(TSK_INIT_PARAM_S));
stTask1.pfnTaskEntry = (TSK_ENTRY_FUNC)Example_SemTask1;
stTask1.pcName = "MutexTsk1";
stTask1.uwStackSize = OS_TSK_DEFAULT_STACK_SIZE;
stTask1.usTaskPrio = TASK_PRIO_TEST;
uwRet = LOS_TaskCreate(&g_TestTaskID01, &stTask1);
if(uwRet != LOS_OK)
{
printf("task1 create failed .\n");
return LOS_NOK;
}
/*建立任務2*/
memset(&stTask2, 0, sizeof(TSK_INIT_PARAM_S));
stTask2.pfnTaskEntry = (TSK_ENTRY_FUNC)Example_SemTask2;
stTask2.pcName = "MutexTsk2";
stTask2.uwStackSize = OS_TSK_DEFAULT_STACK_SIZE;
stTask2.usTaskPrio = (TASK_PRIO_TEST - 1);
uwRet = LOS_TaskCreate(&g_TestTaskID02, &stTask2);
if(uwRet != LOS_OK)
{
printf("task2 create failed .\n");
return LOS_NOK;
}
/*解鎖任務排程*/
LOS_TaskUnlock();
uwRet = LOS_SemPost(g_usSemID);
/*任務休眠40 Tick*/
LOS_TaskDelay(40);
/*刪除訊號量*/
LOS_SemDelete(g_usSemID);
/*刪除任務1*/
uwRet = LOS_TaskDelete(g_TestTaskID01);
if(uwRet != LOS_OK)
{
printf("task1 delete failed .\n");
return LOS_NOK;
}
/*刪除任務2*/
uwRet = LOS_TaskDelete(g_TestTaskID02);
if(uwRet != LOS_OK)
{
printf("task2 delete failed .\n");
return LOS_NOK;
}
return LOS_OK;
}
3.3 結果驗證
編譯執行得到的結果為:
Example_SemTask2 try get sem g_usSemID wait forever.
Example_SemTask1 try get sem g_usSemID ,timeout 10 ticks.
Example_SemTask2 get sem g_usSemID and then delay 20ticks .
Example_SemTask1 timeout and try get sem g_usSemID wait forever.
Example_SemTask2 post sem g_usSemID .
Example_SemTask1 wait_forever and get sem g_usSemID