劉中華 王宜懷 劉長勇 王浩波
1(蘇州大學計算機科學與技術學院 江蘇 蘇州 215006)
2(武夷學院數(shù)學與計算機學院 福建 武夷山 354300)
隨著嵌入式實時操作系統(tǒng)(RTOS)[1-2]不斷發(fā)展,對于共享數(shù)據(jù)訪問的不一致現(xiàn)象屢見不鮮,而多任務[3]的并發(fā)調度是造成這一現(xiàn)象的主要原因。面對操作系統(tǒng)的同步問題,1968年荷蘭計算機科學家艾茲格·迪杰斯特拉提出了信號量(Semaphore)的概念[4-5],用來實現(xiàn)對操作系統(tǒng)的資源管理[6]和多任務調度。信號量機制在常用的RTOS中一直有被應用,無論是早期出現(xiàn)的MQX,還是之后陸續(xù)出現(xiàn)的諸如μC/OS、FreeRTOS及2014年Arm公司出品的mbedOS等RTOS中,信號量機制始終被保留并不斷完善[7]。因此,充分理解信號量的調度機制,有助于開發(fā)人員設計出實時性強、穩(wěn)定性好的RTOS。目前,有關操作系統(tǒng)的信號量機制剖析主要集中在Linux、FreeRTOS、VxWorks等操作系統(tǒng),并且不同的RTOS中信號量的名稱和實現(xiàn)細節(jié)不太一樣,例如FreeRTOS有二進制信號量、計數(shù)信號量、互斥量和遞歸互斥量,mbedOS只有互斥信號量和計數(shù)信號量;FreeRTOS中信號量的創(chuàng)建通過隊列實現(xiàn),mbedOS通過構造結構體來創(chuàng)建信號量[8]。但對mbedOS中的信號量調度剖析方面缺乏資料。為此,本文對mbedOS中的信號量調度機制進行理論分析,重點剖析關鍵函數(shù)的實現(xiàn)原理并加以流程圖分析,利用STM32L431RC芯片結合SD-mbedOS工程框架[9]作為軟硬件環(huán)境,通過多個任務使用信號量機制的并行調度實驗,將實驗的整個調度流程以及當前所運行的時間通過printf函數(shù)[10]進行輸出顯示,最后對調度機制的理論執(zhí)行時間和實際執(zhí)行時間進行對比,從而分析mbedOS信號量調度機制的實時性。通過對信號量調度機制進行全面剖析并分析其實時性,有助于理解調度機制的執(zhí)行流程,更加了解多任務的并發(fā)調度機制,同時也為分析其他RTOS的信號量調度機制提供了基礎[11]。
在RTOS中,信號量通常被定義成一個提供信號的非負整型變量,來保證在多任務并發(fā)的環(huán)境下,能使得操作系統(tǒng)不會發(fā)生沖突,穩(wěn)定運行。在操作系統(tǒng)的信號量機制的管理下,對共享資源的訪問同步問題都可以用信號量來實現(xiàn)。比如一個讀取數(shù)據(jù)任務和一個寫入數(shù)據(jù)任務要訪問共享資源緩沖區(qū)的問題,就能通過三個信號量來實現(xiàn):SEM_Read,允許任務對緩沖區(qū)進行讀取操作;SEM_Write,允許任務對緩存區(qū)進行寫入操作;SEM_Mutex,限制緩沖區(qū)的互斥訪問。在同一時刻只能允許一個讀/寫任務訪問緩沖區(qū),對緩沖區(qū)進行訪問之前必須先獲取信號量SEM_Mutex,并且任務執(zhí)行完成后需釋放信號量[12]。在任何一個任務中,獲取信號量和釋放信號量是同時存在的,意味著在任務結束的時候,并不會占用信號量。在RTOS中,信號量的調度機制如圖1所示。
圖1 信號量調度的一般流程
正是信號量這種有序的特性,使得信號量能應用到很多場合:多任務之間的同步進行;對共享資源的訪問;為了實現(xiàn)更好的性能而控制任務的并發(fā)數(shù)等。
在RTOS中的同步與通信機制中,與設置事件字來表達多種可能的情況相比,信號量是一種簡單的同步手段。
采用信號量作為任務與任務間或中斷與任務間的同步與通信的方法時,則必定有任務(或中斷)創(chuàng)建信號量,同時有任務等待獲取信號量[13]。信號量的獲取與釋放必須在同一任務中,例如在mbedOS中,任務調用Wait()函數(shù)獲取信號量,實際是通過判斷信號量控制塊結構體的Tokens變量來決定是否允許獲取信號量;在FreeRTOS中則是調用xSemaphoreTake()函數(shù)來獲取信號量,判斷隊列句柄xHandle中的uxMessageWaiting變量來決定是否允許獲取信號量。在任務執(zhí)行操作完成后,會將信號量釋放,例如在mbedOS中通過調用Release()函數(shù)來釋放信號量;在FreeRTOS中調用xSemaphoreGive()函數(shù)來釋放信號量[14]。若當前等待隊列存在任務因等待獲取信號量而阻塞,則會將等待隊列中的優(yōu)先級最高的任務移入就緒隊列等待調度。
信號量作為RTOS中任務同步與通信的重要方法之一,其主要功能是實現(xiàn)任務之間的同步或多任務并發(fā)執(zhí)行。在信號量調度機制過程中所涉及到的關鍵要素有信號量的創(chuàng)建、獲取、釋放、響應、調度等[15]。
(1) 信號量的創(chuàng)建:指明信號量的名稱,初始化信號量控制塊結構體并設置信號量數(shù)值的大小。
(2) 信號量的獲?。褐该髂膫€任務或中斷中請求獲取信號量,等待獲取信號量的時間為多少。
(3) 信號量的釋放:在任務獲取到信號量并執(zhí)行完相關操作之后,釋放信號量,若信號量的等待隊列不為空,則取出任務準備進行調度。
(4) 信號量的響應:當信號量被獲取后,獲取信號量的任務會繼續(xù)往下執(zhí)行,當操作完成后,會釋放信號量。
(5) 信號量的調度:當有任務釋放信號量時,會將等待隊列中優(yōu)先級最高的任務與正在運行的任務的優(yōu)先級進行比較,判斷是否需要重新進行任務調度。
mbedOS信號量機制首先從創(chuàng)建信號量開始,從程序開始運行到主任務執(zhí)行app_init()函數(shù)后,會調用Semaphore()函數(shù)來創(chuàng)建信號量。任務可以分別調用Wait()函數(shù)和Release()函數(shù)來進行信號量的獲取和釋放[16]。
下面將著重分析信號量創(chuàng)建、信號量獲取和信號量釋放的過程以及函數(shù)調用。
在mbedOS中使用信號量控制塊結構體來描述信號量,數(shù)據(jù)結構如下:
typedef struct{
uint8_t
id;//信號量ID
uint8_t reserved_state;
//互斥量狀態(tài)
uint8_t
flags;//信號量標志
uint8_t reserved;
const char
*name;//信號量名稱
osRtxThread_t *thread_list;
//信號量等待隊列
uint16_t
tokens;//當前信號量的數(shù)值
uint16_t max_tokens;
//信號量的最大數(shù)值
}osRtxSemaphore_t;
信號量創(chuàng)建函數(shù)調用順序為Semaphore()→Constructor()→osSemaphoreNew()→_svcSemaphoreNew()→SVC_Handler()→svcRtxSemaphoreNew()。信號量創(chuàng)建的流程如圖2所示。
圖2 信號量創(chuàng)建的流程
信號量的創(chuàng)建調用Semaphore()函數(shù),傳入?yún)?shù)count表示創(chuàng)建信號量的數(shù)值大小。緊接著調用Constructor()函數(shù),在該函數(shù)中初始化信號量屬性結構體osSemaphoreAttr_t和信號量控制塊結構體osRtxSemaphore_t。當初始化結構體后,調用osSemaphoreNew()函數(shù)來創(chuàng)建信號量。在任務模式下會調用_svcSemaphoreNew()函數(shù),從而觸發(fā)SVC中斷,轉而去執(zhí)行SVC_Handler中斷處理函數(shù)。然后實際執(zhí)行的函數(shù)是svcRtxSemaphoreNew(),由該函數(shù)來執(zhí)行信號量的創(chuàng)建。當信號量創(chuàng)建之后,會對信號量等待隊列thread_list是否為空進行判斷,若不為空,則說明存在任務等待獲取信號量,則從信號量等待隊列中取出優(yōu)先級最高的任務放入就緒隊列,等待調度[17]。
信號量獲取函數(shù)調用順序為Wait()→OsSemaphoreAcquire()→_svcSemaphoreAcquire()→SVC_Handler()→svcRtxSemaphoreAcquire()。信號量獲取的流程如圖3所示。
圖3 信號量獲取的流程
信號量獲取通過調用Wait()函數(shù),傳入?yún)?shù)millisec設置等待信號量的時間,在該函數(shù)中調用_wait()函數(shù),然后再調用osSemaphoreAcquire()函數(shù)。由于處于任務模式下,則會調用_svcSemaphoreAcquire()函數(shù),在該函數(shù)中會觸發(fā)SVC中斷,轉而去執(zhí)行SVC_Handler中斷處理函數(shù)。而實際執(zhí)行的是svcRtxSemaphoreAcquire()函數(shù),在函數(shù)內部來判斷Tokens的數(shù)值是否大于0,若大于0,則表示任務可以獲取信號量,此時首先要屏蔽系統(tǒng)中斷,然后對信號量的數(shù)值進行減一操作,否則可能會多任務訪問信號量數(shù)據(jù)出現(xiàn)不一致;若不大于0,則會根據(jù)參數(shù)millisec進行阻塞當前任務或者返回獲取信號量失敗。
信號量釋放函數(shù)調用順序為Release()→OsSemaphoreRelease()→_svcSemaphoreRelease()→SVC_Handler()→svcRtxSemaphoreRelease()。信號量釋放的流程如圖4所示。
圖4 信號量釋放的流程
信號量的釋放和信號量獲取的過程大致相同。首先調用Release()函數(shù),請求釋放信號量,然后會跳轉到osSemaphoreRelease()函數(shù)。當前處于任務模式下,則會調用_svcSemaphoreRelease()函數(shù),從而觸發(fā)SVC中斷。而實際調用的是svcRtxSemaphoreRelease()函數(shù),在該函數(shù)的執(zhí)行過程中,對信號量阻塞隊列進行判斷,若為空,則直接調用SemaphoreTokenIncrement()函數(shù)進行釋放信號量;若不為空,則調用osRtxThreadListGet()函數(shù)喚醒隊列中優(yōu)先級最高的任務,重新進行任務調度。
以ARM Cortex-M4為內核的STM32L431RC開發(fā)芯片結合意法半導體(ST)公司研發(fā)了STM32CubeIDE為開發(fā)環(huán)境對mbedOS中的信號量調度機制進行實踐。STM32L431RC芯片為64引腳LQFP封裝,Flash內存為256 KB(共有128個扇區(qū)),RAM內存為64 KB。在信號量調度機制的實踐中,使用了printf打樁輸出調試方法,對關鍵步驟進行文字輸出,可以更好地了解整個程序的運行。
在SD-mbedOS工程框架下創(chuàng)建工程實例,實例的功能是:創(chuàng)建了三個優(yōu)先級相同的任務Td1、Td2和Td3,數(shù)值為2的信號量SP,按照Td1、Td2和Td3的順序啟動三個任務。在Td1任務中,先請求獲取信號量,獲取成功后Td1任務延時5 s;在Td2任務中,獲取信號量成功后,延時2 s。在Td3任務中,獲取信號量后,延時5 s,然后切換STM32L431RC芯片上的綠燈的亮暗。在信號量獲取和釋放的前后,輸出當前系統(tǒng)的運行時間,以便算出實際執(zhí)行時間。三個任務(Td1、Td2和Td3)的內存地址分別為0x200016BC、0x2000177C和0x2000183C。實例的功能流程如圖5所示。
圖5 實例工程的功能流程
結合實例對mbedOS中信號量機制的調度過程進行更細致的分析,將當前運行的任務、任務的狀態(tài)、系統(tǒng)所執(zhí)行的時間用printf函數(shù)的方式進行輸出。
(1) 任務啟動。芯片上電啟動最后轉到主任務函數(shù)中執(zhí)行,先后啟動三個任務,然后阻塞該函數(shù)的運行,由mbedOS負責對任務的調度運行。printf輸出結果如下(下同):
Td1、Td2和Td3任務啟動完成,同時阻塞主任務。
(2) Td1任務請求獲取信號量。在主任務阻塞后,mbedOS從就緒隊列中取出優(yōu)先級最高的任務(此時為Td1)開始執(zhí)行。任務啟動后請求獲取信號量,初始信號量數(shù)值為2,Td1任務獲取信號量成功,信號量數(shù)值減2變?yōu)?。
Td1任務(200016BC)請求獲取SP,當前時間:3.441 44 s。
SP=2!=0,表示當前任務(200016BC)可獲取SP。
Td1任務獲取SP成功,當前時間:3.445 627 s,延時5 s。
(3) Td2任務請求獲取信號量。當前信號量SP的數(shù)值為1,Td2任務可以獲取信號量SP。
Td2任務(2000177C)請求獲取SP,當前時間:3.447 029 s。
SP=1!=0,表示當前任務(2000177C)可獲取SP。
Td2任務獲取SP成功,當前時間:3.461 119 s,延時2 s。
(4) Td3任務請求獲取信號量。由于當前SP的數(shù)值為0,Td3任務請求獲取信號量失敗,會將Td3任務添加到信號量阻塞隊列和延時等待隊列中。
Td3任務(2000183C)請求獲取SP,當前時間:3.452 028 s。
SP=0,表示當前任務(2000183C)獲取SP失敗。
將當前任務(2000183C)放入等待隊列和SP阻塞隊列,獲取就緒隊列中的任務,當前時間:3.466 121 s。
(5) Td2任務釋放信號量。當信號量SP被Td1和Td2獲取之后,在Td2任務延時2 s后會釋放信號量,由于在信號量阻塞隊列中有一個Td3任務等待獲取信號量,因此,在Td2任務釋放信號量之后,會將Td3任務從延時等待隊列和信號量阻塞隊列中取出,并放入就緒隊列中準備運行。此時Td3任務已經(jīng)獲取到信號量,可以看成是Td2任務將信號量轉移給Td3任務,當前信號量數(shù)值還是為0。
Td2任務釋放SP,當前時間:8.053 098 s。
從等待隊列和SP阻塞隊列中獲取等待SP的任務(2000183C),當前時間:8.055 977 s。
Td2任務釋放SP成功,當前時間:8.056 314 s。
Td3任務獲取SP成功,當前時間:8.062 021 s,延時5 s并切換綠燈亮暗。
(6) Td2任務開始新一輪的請求獲取信號量。Td2任務釋放信號量后,重新開始獲取信號量SP,此時信號量被Td1任務和Td3任務占據(jù),信號量數(shù)值為0。因此,Td2任務放入信號量阻塞隊列和延時等待隊列中,同時從就緒隊列中取出Td1任務準備運行。
Td2任務(2000177C)請求獲取SP,當前時間:11.505 674 s。
SP=0,表示當前任務(2000177C)獲取SP失敗。
將當前任務(2000177C)放入等待隊列和SP阻塞隊列,獲取就緒隊列中的任務,當前時間:11.519 806 s。
(7) Td1任務釋放信號量。Td1任務延時5 s結束,釋放信號量。此時信號量阻塞隊列中有Td2任務在等待獲取信號量,當Td1任務釋放信號量之后,將Td2任務從延時等待隊列和信號量阻塞隊列中取出,并放入就緒隊列中準備運行。
Td1任務釋放SP,當前時間:16.074 655 s。
從等待隊列和SP阻塞隊列中獲取等待SP的任務(2000177C),當前時間:16.082 538 s。
Td1任務釋放SP成功,當前時間:16.083 962 s。
Td2任務獲取SP成功,當前時間:16.090 021 s,延時2 s。
(8) Td1任務開始新一輪的請求獲取信號量SP。Td1任務請求獲取信號量SP,當前SP數(shù)值為0,將Td1任務放入延時等待隊列和信號量阻塞隊列中。
Td1任務(200016BC)請求獲取SP,當前時間:19.533 285 s。
SP=0,表示當前任務(200016BC)獲取SP失敗。
將當前任務(200016BC)放入等待隊列和SP阻塞隊列,獲取就緒隊列中的任務,當前時間:19.547 460 s。
(9) Td2和Td3任務釋放信號量。Td2任務延時結束后,釋放信號量。同時將Td1任務從延時等待隊列和信號量阻塞隊列中移出,并放入就緒隊列中運行。在Td2任務釋放信號量后,Td3任務延時結束釋放信號量(幾乎可以看作同時),此時信號量數(shù)值為1,故Td2獲取信號量成功,開始運行。
Td2任務釋放SP,當前時間:21.834 034 s。
從等待隊列和SP阻塞隊列中獲取等待SP的任務(200016BC),當前時間:21.836 s。
Td2任務釋放SP成功,當前時間:21.837 424 s。
Td3任務釋放SP,當前時間:21.838 129 s。
Td3任務釋放SP成功,當前時間:21.841 097 s。
Td1任務獲取SP成功,當前時間:21.843 022 s,延時5 s。
(10) Td1、Td2和Td3任務新一輪的請求獲取信號量。此時開始的運行情況和之前一樣,循環(huán)之前的過程。按照Td1、Td2和Td3的順序反復獲取信號量執(zhí)行。任務的調度時序圖如圖6所示。
圖6 基于信號量機制的任務調度時序圖
任務信號量的獲取和釋放的理論時間是判斷mbedOS中信號量機制的實時性好與壞的性能標準。在SD-mbedOS架構下,系統(tǒng)時鐘頻率為48 MHz,一個指令周期的時間為0.020 8 μs。以任務請求獲取信號量為例,進行理論時間和實際執(zhí)行時間的比較,在信號量數(shù)值不為0的情況下,任務申請獲取信號量的機器指令有461條,機器指令的條數(shù)是以執(zhí)行一條_NOP指令所花費的時間為基準,所有執(zhí)行的機器指令都能在編譯之后生成的.lst文件中找到,關鍵函數(shù)及其對應的機器碼和匯編指令如表1所示。
表1 關鍵函數(shù)及其對應的機器碼和匯編指令
根據(jù)計算得:信號量獲取的理論時間為10.44 μs,而在單個任務執(zhí)行的情況下,信號量獲取(信號量數(shù)值不為0)的實際執(zhí)行時間為14.8 μs,理論時間和實際時間的誤差在微秒級別,誤差在可接受的范圍內。
在工程實例中,將任務請求獲取信號量前后、釋放信號量前后的系統(tǒng)運行時間輸出。三個任務具體的調度時間如表2所示。
表2 信號量獲取和釋放的實際執(zhí)行時間 單位:ms
表2中獲取信號量I和獲取信號量II分別表示:獲取信號量時信號量數(shù)值不為0和為0,獲取信號量II中的時間I表示信號量數(shù)值為0,將任務添加到相應隊列的時間,時間II表示在任務添加到隊列中后,到獲取信號量成功的時間。釋放信號量I和釋放信號量II分別表示:釋放信號量時信號量阻塞隊列為空和不為空,而時間III表示從隊列中移出任務的時間,時間IV表示其他時間。
表2中的時間是結合實例工程中三個任務的延遲時間計算的,由于實例中是多任務并發(fā),并且系統(tǒng)的運行狀態(tài)用printf方法進行輸出,故信號量調度機制中的操作耗時較多??偟膩砜?信號量的獲取和釋放需要的時間很短,具有很好的實時性。
mbedOS的信號量調度機制是一個較為復雜的過程,其中涉及到多任務并發(fā)調度、任務對信號量的獲取和釋放、就緒隊列和等待隊列等的管理,其中的函數(shù)調用關系也較為復雜,觸發(fā)到的中斷函數(shù)有SVC中斷和Systick中斷等。本文重點剖析mbedOS中的信號量調度機制及其關鍵函數(shù),加以流程圖總結,通過多任務并發(fā)的調度實驗,將調度過程中任務的切換、狀態(tài)的變化、當前系統(tǒng)運行時間進行輸出,給出時序圖分析,進一步驗證信號量調度機制理論分析的正確性,最后還對調度過程進行實時性能剖析,結果表明信號量調度機制的實時性能較好。通過對信號量調度機制的剖析,有助于更好地理解mbedOS的多任務并發(fā)機制,也為其他RTOS的信號量機制分析提供了基礎。