1.1.信號量
在多任務(wù)操作系統(tǒng)中,不同的任務(wù)之間需要同步運行,信號量功能可以為用戶提供這方面的支持。信號量(Semaphore)是一種實現(xiàn)任務(wù)間通信的機制,實現(xiàn)任務(wù)之間同步或臨界資源的互斥訪問。
1.2. 信號量的使用方式
信號量可以被任務(wù)獲取或者申請,不同的信號量通過信號量索引號來唯一確定,每個信號量都有一個計數(shù)值和任務(wù)隊列。
通常一個信號量的計數(shù)值用于對應(yīng)有效的資源數(shù),表示剩下的可被占用的互斥資源數(shù),其值的含義分兩種情況:
0:表示沒有積累下來的 Post 操作,且有可能有在此信號量上阻塞的任務(wù);
正值:表示有一個或多個 Post 下來的釋放操作;
當(dāng)任務(wù)申請(Pend)信號量時,如果申請成功,則信號量的計數(shù)值遞減,如若申請失敗,則掛起在該信號量的等待任務(wù)隊列上,一旦有任務(wù)釋放該信號量,則等待任務(wù)隊列中的任務(wù)被喚醒開始執(zhí)行。
1.3. 信號量的使用場景
信號量是一種非常靈活的同步方式,可以運用在多種場合中,實現(xiàn)鎖、同步、資源計數(shù)等功能,也能方便的用于任務(wù)與任務(wù),中斷與任務(wù)的同步中。
互斥鎖
用作互斥時,信號量創(chuàng)建后記數(shù)是滿的,在需要使用臨界資源時,先申請信號量,使其變空,這樣其他任務(wù)需要使用臨界資源時就會因為無法申請到信號量而阻塞,從而保證了臨界資源的安全。
任務(wù)間同步
用作同步時,信號量在創(chuàng)建后被置為空,任務(wù)1申請信號量而阻塞,任務(wù)2在某種條件發(fā)生后,釋放信號量,于是任務(wù)1得以進入 READY 或 RUNNING 態(tài),從而達到了兩個任務(wù)間的同步。
資源計數(shù)
用作資源計數(shù)時,信號量的作用是一個特殊的計數(shù)器,可以遞增或者遞減,但是值永遠不能為負值,典型的應(yīng)用場景是生產(chǎn)者與消費者的場景。
中斷與任務(wù)的同步
用作中斷與任務(wù)的同步時,可以在中斷未觸發(fā)時將信號量的值置為0,從而堵塞斷服務(wù)處理任務(wù),一旦中斷被觸發(fā),則喚醒堵塞的中斷服務(wù)處理任務(wù)進行中斷處理。
2. 信號量API
Huawei LiteOS 系統(tǒng)中的信號量模塊為用戶提供創(chuàng)建/刪除信號量、申請/釋放信號量的功能。
Huawei LiteOS 系統(tǒng)中提供的信號量 API 都是以 LOS 開頭,但是這些 API 使用起來比較復(fù)雜,所以本文中我們使用 Huawei IoT Link SDK 提供的統(tǒng)一API接口進行實驗,這些接口底層已經(jīng)使用 LiteOS 提供的API實現(xiàn),對用戶而言更為簡潔,API列表如下:
osal的api接口聲明在
相關(guān)的接口定義在osal.c中,基于LiteOS的接口實現(xiàn)在 liteos_imp.c文件中:
osal_semp_create | 信號量創(chuàng)建 |
osal_semp_del | 信號量刪除 |
osal_semp_pend | 信號量申請 |
osal_semp_post | 信號量釋放 |
接口名 | 功能描述 |
---|
2.1. osal_semp_create
osal_semp_create接口用于創(chuàng)建一個信號量,其接口原型如下:
bool_t??osal_semp_create(osal_semp_t?*semp,int?limit,int?initvalue) {????bool_t?ret?=?false;????if((NULL?!=?s_os_cb)?&&(NULL?!=?s_os_cb->ops)?&&(NULL?!=?s_os_cb->ops->semp_create)) ????{ ????????ret?=?s_os_cb->ops->semp_create(semp,limit,initvalue); ????}????return?ret; }
該接口的參數(shù)說明如下表:
semp | 信號量索引ID的地址 |
limit | 信號量計數(shù)值的最大值 |
initvalue | 信號量計數(shù)值的初始值 |
返回值 | false - 創(chuàng)建失敗 |
返回值 | true - 創(chuàng)建成功 |
參數(shù) | 描述 |
---|
2.2. osal_semp_del
osal_semp_del接口用于刪除一個信號量,其接口原型如下:
bool_t??osal_semp_del(osal_semp_t?semp) {????bool_t?ret?=?false;????if((NULL?!=?s_os_cb)?&&(NULL?!=?s_os_cb->ops)?&&(NULL?!=?s_os_cb->ops->semp_del)) ????{ ????????ret?=?s_os_cb->ops->semp_del(semp); ????}????return?ret; }
該接口的參數(shù)說明如下表:
semp | 信號量索引ID |
返回值 | false - 刪除失敗 |
返回值 | true - 刪除成功 |
參數(shù) | 描述 |
---|
2.3. osal_semp_pend
osal_semp_pend接口用于申請一個信號量,其接口原型如下:
bool_t??osal_semp_pend(osal_semp_t?semp,int?timeout) {????bool_t?ret?=?false;????if((NULL?!=?s_os_cb)?&&(NULL?!=?s_os_cb->ops)?&&(NULL?!=?s_os_cb->ops->semp_pend)) ????{ ????????ret?=?s_os_cb->ops->semp_pend(semp,timeout); ????}????return?ret; }
該接口的參數(shù)說明如下表:
semp | 信號量索引ID |
timeout | 32位值,具體見下文 |
返回值 | false - 申請失敗 |
返回值 | true - 申請成功 |
參數(shù) | 描述 |
---|
信號量有三種申請模式:非阻塞模式、永久阻塞模式、定時阻塞模式,用?timeout?參數(shù)的值選擇。
非阻塞模式(0):
任務(wù)需要申請信號量,若當(dāng)前信號量的任務(wù)數(shù)沒有到信號量設(shè)定的上限,則申請成功。否則,立即返回申請失敗
永久阻塞模式(cn_osal_timeout_forever或0xFFFFFFFF):
任務(wù)需要申請信號量,若當(dāng)前信號量的任務(wù)數(shù)沒有到信號量設(shè)定的上限,則申請成功。否則,該任務(wù)進入阻塞態(tài),系統(tǒng)切換到就緒任務(wù)中優(yōu)先級最高者繼續(xù)執(zhí)行。任務(wù)進入阻塞態(tài)后,直到有其他任務(wù)釋放該信號量,阻塞任務(wù)才會重新得以執(zhí)行
定時阻塞模式(任意定時值,32bit):
任務(wù)需要申請信號量,若當(dāng)前信號量的任務(wù)數(shù)沒有到信號量設(shè)定的上限,則申請成功。否則,該任務(wù)進入阻塞態(tài),系統(tǒng)切換到就緒任務(wù)中優(yōu)先級最高者繼續(xù)執(zhí)行。任務(wù)進入阻塞態(tài)后,指定時間超時前有其他任務(wù)釋放該信號量,或者用戶指定時間超時后,阻塞任務(wù)才會重新得以執(zhí)行
由于中斷不能被阻塞,因此在申請信號量時,阻塞模式不能在中斷中使用。
2.4. osal_semp_post
osal_semp_post接口用于釋放一個信號量,如果有任務(wù)阻塞于該信號量,則喚醒該信號量阻塞隊列上的第一個任務(wù),該任務(wù)進入就緒態(tài),并進行調(diào)度。
其接口原型如下:
bool_t??osal_semp_post(osal_semp_t?semp) {????bool_t?ret?=?false;????if((NULL?!=?s_os_cb)?&&(NULL?!=?s_os_cb->ops)?&&(NULL?!=?s_os_cb->ops->semp_post)) ????{ ????????ret?=?s_os_cb->ops->semp_post(semp); ????}????return?ret; }
該接口的參數(shù)說明如下表:
semp | 信號量索引ID |
返回值 | false - 釋放失敗 |
返回值 | true - 釋放成功 |
參數(shù) | 描述 |
---|
3. 動手實驗 —— 使用信號量進行任務(wù)間同步
實驗內(nèi)容
本實驗中將創(chuàng)建兩個任務(wù),一個低優(yōu)先級任務(wù)task1,一個高優(yōu)先級任務(wù)task2,兩個任務(wù)之間使用信號量同步運行,在串口終端中觀察兩個任務(wù)的運行情況。
實驗代碼
首先打開上一篇使用的 HelloWorld 工程,基于此工程進行實驗。
在Demo文件夾右擊,新建文件夾osal_kernel_demo用于存放內(nèi)核的實驗文件(如果已有請忽略這一步)。
接下來在此文件夾中新建一個實驗文件?osal_semp_demo.c,開始編寫代碼:
/*?使用osal接口需要包含該頭文件?*/#include?
編寫完成之后,要將我們編寫的osal_semp_demo文件添加到makefile中,加入整個工程的編譯:
這里有個較為簡單的方法,直接修改Demo文件夾下的user_demo.mk配置文件,添加如下代碼:
#example?for?osal_semp_demo ifeq?($(CONFIG_USER_DEMO),?"osal_semp_demo") user_demo_src??=?${wildcard?$(TOP_DIR)/targets/STM32L431_BearPi/Demos/osal_kernel_demo/osal_semp_demo.c} user_demo_defs?=?-D?CONFIG_OSAL_SEMP_DEMO_ENABLE=1 endif
添加位置如圖:
這段代碼的意思是:
如果 CONFIG_USER_DEMO 宏定義的值是osal_semp_demo,則將osal_semp_demo.c文件加入到makefile中進行編譯。
那么,如何配置 CONFIG_USER_DEMO 宏定義呢?在工程根目錄下的.sdkconfig文件中的末尾即可配置:
因為我們修改了mk配置文件,所以點擊重新編譯按鈕進行編譯,編譯完成后點擊下載按鈕燒錄程序。
編譯之后會有警告,提示user_task1_entry和user_task2_entry函數(shù)沒有返回值,這里使用了while(1),不會返回,所以不用管。
實驗現(xiàn)象
程序燒錄之后,即可看到程序已經(jīng)開始運行,在串口終端中可看到實驗的輸出內(nèi)容:
linkmain:V1.2.1?AT?11:30:59?ON?Nov?28?2019?sync_semp?semp?create?success.WELCOME?TO?IOT_LINK?SHELLLiteOS:/>task2?is?waiting?for?a?semp...task?1?post?a?semp!task?2?access?a?semp!task2?is?waiting?for?a?semp...task?1?post?a?semp!task?2?access?a?semp!task2?is?waiting?for?a?semp...task?1?post?a?semp!task?2?access?a?semp!……
評論
查看更多