本文導讀
本文介紹了郵箱、消息隊列和自旋鎖的使用方法。信號量只能用于任務間的同步,不能傳遞更多的信息,為此,AWorks提供了郵箱和消息隊列服務,它們的主要區別在于支持的消息長度不同,在郵箱中,每條消息的長度固定為4字節,而在消息隊列中,消息的長度可以自定義。本文為《面向AWorks框架和接口的編程(上)》第三部分軟件篇——第10章——第3~5小節:郵箱、消息隊列和自旋鎖。10.3 郵箱
前面介紹了用于任務間同步的三種信號量,它們相當于資源的鑰匙,獲取到鑰匙的任務可以訪問相關的資源,任務以此確定可以運行的時刻。但是,信號量不能夠提供更多的信息內容。比如,按鍵按下時,釋放一個信號量,任務獲取到該信號量時,只能知道有按鍵按下了,但不能知道按鍵相關的更多信息,比如:具體是哪個按鍵按下了?
當需要在任務間傳遞的更多信息時,可以使用AWorks提供的郵箱服務。郵箱服務是實時內核中一種典型的任務間通信方法,特點是開銷比較低,效率較高。一個郵箱中可以存儲多封郵件,郵箱中的每一封郵件只能容納固定的4字節內容(針對32位處理系統,指針的大小即為4個字節,所以一封郵件恰好能夠容納一個指針)。發送郵件的任務(或中斷服務程序)負責將郵件存入郵箱,接收郵件的任務負責從郵箱中提取郵件。示意圖詳見圖10.4。
圖10.4 郵箱工作示意圖
當郵箱中存在多封郵件時,默認按照先進先出(FIFO)的原則傳遞給接收郵件的任務。郵件的大小固定為4字節,當需要傳遞的消息內容大于4字節時,則可以僅將消息的地址作為郵件內容,任務接收到郵件時,通過該地址即可查找到相應的消息。這種方式使得使用郵箱進行消息傳遞的效率非常高。
AWorks提供了使用郵箱的幾個宏,宏的原型詳見表10.7。
表10.7 郵箱相關的宏(aw_mailbox.h)
1. 定義郵箱實體
AW_MAILBOX_DECL()和
AW_MAILBOX_DECL_STATIC()宏均用于定義一個郵箱實體,為郵箱分配必要的內存空間,包括用于存儲郵件的空間。它們的原型為:
其中,參數mailbox為郵箱實體的標識名。mail_num表示郵箱的容量,即郵箱中存儲郵件的最大條數,由于每封郵件的大小為4字節,因此用于存儲郵件的總內存大小為:mail_num×4。
兩個宏的區別在于:
AW_MAILBOX_DECL_STATIC() 在定義郵箱所需內存時, 使用了關鍵字static ,如此一來, 便可以將郵箱實體的作用域限制在模塊內(文件內), 從而避免模塊之間的郵箱命名沖突,同時,還可以在函數內使用本宏定義郵箱實體。
如使用AW_MAILBOX_DECL()定義一個標識名為mailbox_test的郵箱實體,郵件的最大數目為10,其范例程序詳見程序清單10.49。
程序清單10.49 定義郵箱實體的范例程序
使用AW_MAILBOX_DECL()定義郵箱實體時,可以將郵箱的實體嵌入到另一個數據結構中,其范例程序程序清單10.50。
程序清單10.50 將郵箱實體嵌入到結構體中
也可以使用AW_MAILBOX_DECL_STATIC()定義一個標識名為mailbox_test的郵箱實體,其范例程序詳見程序清單10.51。
程序清單10.51 定義郵箱實體(靜態)的范例程序
2. 初始化郵箱
定義郵箱實體后,必須使用AW_MAILBOX_INIT()初始化后才能使用。其原型為:
其中,mailbox為由AW_MAILBOX_DECL() 或 AW_MAILBOX_DECL_STATIC()定義的郵箱。mail_num表示郵箱可以存儲的郵件條數,其值必須與定義郵箱實體時的mail_num相同。options為郵箱的選項,其決定了阻塞于此郵箱(等待消息中)的任務的排隊方式,可以按照任務優先級或先進先出的順序排隊,它們對應的宏詳見表10.8。
表10.8 郵箱初始化選項宏(aw_mailbox.h)
注意,前面講述了三種信號量,在初始化時同樣可以通過選項指定阻塞于信號量的任務的排隊方式,分為按照優先級和先進先出兩種方式,它們對應的宏名分別為AW_SEM_Q_PRIORITY和AW_SEM_Q_FIFO。與郵箱選項的命名不同,不可混用。
通常排隊方式都選擇按照優先級排隊,初始化郵箱的范例程序詳見程序清單10.52。
程序清單10.52 初始化郵箱的范例程序
AW_MAILBOX_INIT()用于初始化一個郵箱實體,初始化完畢后,將返回該消郵箱的ID,其類型為aw_mailbox_id_t。定義一個該類型的變量保存返回的ID如下:
aw_mailbox_id_t的具體定義用戶無需關心,該ID可作為后文介紹的其它郵箱相關接口函數的參數,用于指定要操作的郵箱。特別地,若返回ID的值為NULL,表明初始化失敗。一般地,若無特殊需求,不會使用該ID,可以不用保存該ID。
3. 從郵箱中獲取一條信息
從郵箱中獲取一條消息的宏原型為:
其中,mailbox為由AW_MAILBOX_DECL() 或 AW_MAILBOX_DECL_STATIC()定義的郵箱。p_data指向用于保存消息的緩沖區,消息獲取成功后,將存儲到p_data指向的緩沖區中,由于消息的大小固定為4字節(32位),因此緩沖區的大小也必須為4字節,例如,可以是一個指向32位數據的指針。timeout指定了超時時間。該宏的返回值為aw_err_t類型的標準錯誤號。注意,由于中斷服務程序不能被阻塞,因此,該函數禁止在中斷中調用。
如果郵箱不為空,包含有效的消息,則本次操作將成功獲取到一條消息,同時,會從郵箱中將該條消息刪除,有效消息的條數減1。此時,AW_MAILBOX_RECV()的返回值為:AW_OK。
如果郵箱為空,沒有任何有效的消息,則不能立即成功獲取到消息,接下來具體的行為將由超時時間timeout的值決定。
(1)若timeout的值為
AW_MAILBOX_WAIT_FOREVER。則任務會阻塞于此,一直等待,直到郵箱中有可用的消息,即其它任務或中斷發送了消息。范例程序詳見程序清單10.53。
程序清單10.53 永久阻塞等待郵箱的范例程序
(2)若timeout的值為
AW_MAILBOX_NO_WAIT。則任務不會被阻塞,立即返回,但不會成功獲取到消息,此時,AW_MAILBOX_RECV()的返回值為:
-AW_EAGAIN(表示當前資源無效,需要重試)。范例程序詳見程序清單10.54。
程序清單10.54 不阻塞等待郵箱的范例程序
(3)若tiemout的值為一個正整數,則表示最長的等待時間(單位為系統節拍),任務會阻塞于此,在timeout規定的時間內,若成功獲取到一條消息,則AW_MAILBOX_RECV()的返回值為:AW_OK;若在timeout規定的時間內,沒有獲取到消息,則返回值為-AW_ETIME(表明超時)。范例程序詳見程序清單10.55。
程序清單10.55 等待郵箱的超時時間為500ms的范例程序
4. 發送一條消息到郵箱中
發送消息到郵箱中的宏原型為:
其中,mailbox為由AW_MAILBOX_DECL() 或 AW_MAILBOX_DECL_STATIC()定義的郵箱。data為發送的32位數據。timeout指定了超時時間。priority指定了消息的優先級。該宏的返回值為aw_err_t類型的標準錯誤號。
若郵箱未滿,可以繼續存儲消息,則該消息發送成功,郵箱中的有效消息條數加1。此時,AW_MAILBOX_SEND()的返回值為:AW_OK。
若郵箱已滿,暫時不能繼續存儲消息,則不能立即成功發送消息,接下來具體的行為將由超時時間timeout的值決定。
(1)若timeout的值為
AW_MAILBOX_WAIT_FOREVER。則任務會阻塞于此,一直等待,直到消息成功放入郵箱中。即其它任務從郵箱中獲取了消息,郵箱中留出了空閑空間。范例程序詳見程序清單10.56。
程序清單10.56 永久阻塞等待郵箱的范例程序
注意,priority參數指定了消息的優先級,其可能的取值有兩個:AW_MAILBOX_PRI_NORMAL和AW_MAILBOX_PRI_URGENT,分別表示普通優先級和緊急優先級。一般地,均使用普通優先級,此時,新的消息按照先進先出的原則,依次排隊存入郵箱,該消息將后于當前郵箱中其它消息被取出。若使用緊急優先級,則新的消息插隊放在郵箱的最前面,該消息將先于當前郵箱中其它消息被取出,即在下次從郵箱中獲取消息時被取出。
(2)若timeout的值為
AW_MAILBOX_NO_WAIT。則任務不會被阻塞,立即返回,但不會成功發送消息,此時,AW_MAILBOX_SEND()的返回值為:-AW_EAGAIN(表示當前資源無效,需要重試)。范例程序詳見程序清單10.57。
程序清單10.57 不阻塞等待郵箱的范例程序
(3)若tiemout的值為一個正整數,則表示最長的等待時間(單位為系統節拍),任務會阻塞于此,在timeout規定的時間內,成功發送了消息,則AW_MAILBOX_SEND()的返回值為:AW_OK;若在timeout規定的時間內,沒有成功發送消息,則返回值為-AW_ETIME(表明超時)。范例程序詳見程序清單10.58。
程序清單10.58 等待郵箱的超時時間為500ms的范例程序
注意,在中斷服務程序中(如按鍵回調函數),可以使用該接口發送消息至郵箱,這是中斷和任務之間很重要的一種通信方式:即在中斷中發送消息,在任務中接收消息并處理,從而減小中斷服務程序的時間。但是,由于中斷不能被阻塞,因此,當在中斷中發送消息時,timeout標志只能為AW_MAILBOX_NO_WAIT。在這種應用中,為了避免消息丟失,應該盡可能避免郵箱被填滿,可以通過增加郵箱的容量以及提高處理消息任務的優先級,使郵箱中的消息被盡快處理。
5. 終止郵箱
當一個郵箱不再使用時,可以終止該郵箱,宏原型為:
其中,mailbox為由AW_MAILBOX_DECL() 或 AW_MAILBOX_DECL_STATIC()定義的郵箱。當郵箱被終止后,若當前系統中還存在等待該郵箱的任務,則任何等待此郵箱的任務將會解阻塞,并返回-AW_ENXIO(表明資源已不存在),其范例詳見程序清單10.59。
程序清單10.59 終止郵箱的范例程序
在講述計數信號量時,使用了單個按鍵控制LED翻轉,由于只使用到了一個按鍵,因此,在發送按鍵消息時,只需要使用計數信號量對按鍵事件進行計數,無需發送更多的消息。若需要使用多個按鍵控制LED,則在發送按鍵消息時,必須攜帶按鍵編碼信息,以便針對不同的按鍵作不同的處理。
例如,要實現一個簡單的應用,通過LED顯示當前按鍵的編碼:
-
KEY_0按下,則LED0熄滅,LED1熄滅,顯示“00”;
-
KEY_1按下,則LED0熄滅,LED1點亮,顯示“01”;
-
KEY_2按下,則LED0點亮,LED1熄滅,顯示“02”;
-
KEY_3按下,則LED0點亮,LED1點亮,顯示“03”。
顯然,為了區分不同按鍵,發送按鍵消息時,需要攜帶按鍵的編碼信息,由于按鍵編碼是int類型的數據,在32位系統中,其恰好為32位,因此,可以使用郵箱來管理按鍵消息。范例程序詳見程序清單10.60。
程序清單10.60 郵箱使用范例程序
在按鍵事件回調函數中發送消息,由于只需要處理按鍵按下事件,因此,僅當按鍵按下時(key_state不為0),才向郵箱中發送消息(按鍵編碼)。在task_led任務中接收消息,當成功獲取到一條消息時,根據消息內容(按鍵編碼)控制LED。
實際上,這里的按鍵處理程序僅僅用于控制LED燈,耗時時間是非常短的,往往比發送一條消息的時間還短,這里使用郵箱并不能優化程序,僅僅只是作為一個使用郵箱的范例,實際應用按鍵的處理通常會復雜得多,則建議使用這種通用的模式,即在按鍵事件的回調函數中,僅僅只是將按鍵編碼發送到郵箱中,實際的處理在任務中完成。這樣可以避免長時間占用中斷,影響系統的實時性,使其它緊急事務得不到處理,同時,郵箱還有一個緩沖的作用,當按鍵來不及處理時,可以暫存到郵箱中,后續空閑時再及時去處理,很大程度上避免了“丟鍵”的可能性,就像PC一樣,有時候系統卡頓,顯示屏卡住,但按鍵輸入的信息后續還是會顯示出來,一般不會丟失。
在上面的范例程序中,由于按鍵編碼的大小為4字節,郵件恰好可以容納,因此,可以直接將按鍵編碼作為消息內容,發送到郵箱中(拷貝一份,存儲至郵箱中)。若要發送的消息大于4字節,顯然不能直接發送了,此時,可以將傳輸內容的地址作為郵件內容,發送到郵箱中,接收者接收到郵件后,再將郵件內容作為地址,從中取出實際的消息內容。范例程序詳見程序清單10.61。
程序清單10.61 郵箱使用范例程序——消息內容大于4字節
程序中,定義了兩個任務:task0和task1。task0負責發送消息,每隔1s將count值加1,若count為奇數,則發送__g_str1字符數組中的信息;若count為偶數,則發送__g_str2字符數組中的信息。__g_str1和__g_str2兩個字符數組分別存放了字符串:
"The count is an oddnumber!"和
"The count is an even number!"。顯然,字符串的長度超過了4個字節,因此,兩個數組的大小也都超過了4字節。在tsak1發送消息時,將字符串數組的地址作為消息發送到了郵箱中。task1用于接收消息,當接收到消息時,將其作為字符數組的地址,使用aw_kprintf()將接收到的字符信息打印出來。以此完成了消息的傳遞。
由于郵箱僅傳輸了兩個字符數組的地址,為了保證接收任務正確提取地址中的實際消息,必須確保接收任務接收到郵件時,地址中的數據仍然有效。因此,在范例程序中,將兩個數組定義為了全局變量,使其內存一直有效。甚至在消息處理完成后,數組的內存空間還是有效的。
在一些應用中,當消息處理完畢后,消息將沒有任何實際意義,其地址中對應的數據可以丟棄,以釋放相關內存。此時,可以使用動態內存來管理消息:發送者動態獲取一段內存空間,填充相關內容后,將這段內存空間的首地址發送到郵箱中,接收者從郵箱中獲取到該地址,然后從地址中提取出實際的消息內容進行處理,處理完畢后,釋放內存。
例如,在程序清單10.60的基礎上,對功能進行簡單的修改:當按鍵按下時,LED顯示當前按鍵的編碼,當按鍵釋放時,熄滅所有LED。顯然,由于需要對按鍵按下和釋放作不同的處理,這就要求在按鍵事件產生后,除需要發送按鍵編碼信息外,還要發送按鍵的狀態(按下或釋放)。此時,消息就需要包含按鍵編碼和按鍵狀態,共計8字節。范例程序詳見程序清單10.62。
程序清單10.62 郵箱使用范例程序——消息內存動態分配
程序中,使用了aw_mem_alloc()和
aw_mem_free()進行消息內存的申請和釋放。
aw_mem_alloc()和aw_mem_free()與標準C的malloc()和free()功能相同,用于動態內存的管理。它們在aw_mem.h文件中聲明。
aw_mem_alloc()的函數原型為:
其用于分配size字節的內存空間,返回void*類型的指針,該指針即指向分配空間的首地址,若內存分配失敗,則返回值為NULL。
aw_mem_free()的函數原型為:
其用于釋放由aw_mem_alloc()分配的空間,ptr參數即為內存空間的首地址,其值必須是由aw_mem_alloc()函數返回的。
10.4 消息隊列
前面介紹了郵箱服務,郵箱固定了消息的大小為4字節,當需要傳輸多余4字節的內容時,往往需要使用指針的形式,即使用郵箱傳遞實際消息的首地址,任務間通過傳遞的地址共享信息。使用地址共享信息的效率很高。但是,這種情況下,就需要特別小心的進行內存的申請和釋放,一個地址中的消息使用完畢后,需要釋放相關內存空間。對于初學者來講,使用起來相對繁瑣,容易出錯。
為此,AWorks提供了另外一種消息通信機制:消息隊列。其和郵箱類似,均用于任務見消息的傳輸,但其支持的消息大小由用戶指定,可以超過4字節。
消息隊列可以存放多條消息,發消息的任務負責將消息發送至隊列,接收消息的任務負責從隊列中提取消息。AWorks提供了使用消息隊列的幾個宏,宏的原型詳見表10.9。
表10.9 消息隊列相關的宏(aw_msgq.h)
1. 定義消息隊列實體
AW_MSGQ_DECL()和
AW_MSGQ_DECL_STATIC()宏均用于定義一個消息隊列實體,為消息隊列分配必要的內存空間,包括用于存儲消息的空間。它們的原型為:
其中,參數msgq為消息隊列實體的標識名。msg_num和msg_size用于分配存儲消息的空間,msg_num表示消息的最大條數,msg_size表示每條消息的大小(字節數)。用于存儲消息的總內存大小即為:msg_num×msg_size。
兩個宏的區別在于,AW_MSGQ_DECL_STATIC() 在定義消息隊列所需內存時, 使用了關鍵字static ,如此一來, 便可以將消息隊列實體的作用域限制在模塊內(文件內), 從而避免模塊之間的消息隊列命名沖突,同時,還可以在函數內使用本宏定義消息隊列實體。
如使用AW_MSGQ_DECL()定義一個標識名為msgq_test的消息隊列實體,消息的最大數目為10,每條消息為一個int類型數據,則消息的大小為4個字節(32位平臺中),其范例程序詳見程序清單10.63。
程序清單10.63 定義消息隊列實體的范例程序
通常,當每條消息為一個int類型的數據時,其長度最好使用sizeof表示,其范例程序詳見程序清單10.64。
程序清單10.64 定義消息隊列實體的范例程序
使用AW_MSGQ_DECL()定義消息隊列實體時,可以將消息隊列的實體嵌入到另一個數據結構中,其范例程序程序清單10.65。
程序清單10.65 將消息隊列實體嵌入到結構體中
也可以使用AW_MSGQ_DECL_STATIC()定義一個標識名為msgq_test的消息隊列實體,其范例程序詳見程序清單10.66。
程序清單10.66 定義消息隊列實體(靜態)的范例程序
2. 初始化消息隊列
定義消息隊列實體后,必須使用
AW_MSGQ_INIT()初始化后才能使用。其原型為:
其中,msgq為由AW_MSGQ_DECL() 或
AW_MSGQ_DECL_STATIC()定義的消息隊列。msg_num表示消息隊列可以存儲的消息條數,其值必須與定義消息隊列實體時的msg_num相同。msg_size表示每條消息的大小(字節數),其值必須與定義消息隊列實體時的msg_size相同。
options為消息隊列的選項,其決定了阻塞于此消息隊列(等待消息中)的任務的排隊方式,可以按照任務優先級或先進先出的順序排隊,它們對應的宏詳見表10.10。
表10.10 消息隊列初始化選項宏(aw_msgq.h)
注意,前面講述了三種信號量,在初始化時同樣可以通過選項指定阻塞于信號量的任務的排隊方式,分為按照優先級和先進先出兩種方式,它們對應的宏名分別為AW_SEM_Q_PRIORITY和AW_SEM_Q_FIFO。與消息隊列的命名不同,不可混用。
通常排隊方式都選擇按照優先級排隊,初始化消息隊列的范例程序詳見程序清單10.67。
程序清單10.67 初始化消息隊列的范例程序
AW_MSGQ_INIT()用于初始化一個消息隊列實體,初始化完畢后,將返回該消息隊列的ID,其類型為aw_msgq_id_t。定義一個該類型的變量保存返回的ID如下:
aw_msgq_id_t的具體定義用戶無需關心,該ID可作為后文介紹的其它消息隊列相關接口函數的參數,用于指定要操作的消息隊列。特別地,若返回ID的值為NULL,表明初始化失敗。一般地,若無特殊需求,不會使用該ID,可以不用保存該ID。
3. 從消息隊列中獲取一條信息
從消息隊列中獲取一條消息的宏原型為:
其中,msgq為由AW_MSGQ_DECL() 或
AW_MSGQ_DECL_STATIC()定義的消息隊列。p_buf指向用于保存消息的緩沖區,消息成功獲取后,將存儲到p_buf指向的緩沖區中。nbytes指定了緩沖區的大小,緩沖區大小必須能夠容納一條消息,其值不得小于定義消息隊列實體時指定的一條消息的長度,通常情況下,nbytes與一條消息的長度是相等的,例如,msgq_test中的消息長度為4字節,則nbytes的值也為4,即p_buf指向的緩存區大小為4字節。timeout指定了超時時間。該宏的返回值為aw_err_t類型的標準錯誤號。注意,由于中斷服務程序不能被阻塞,因此,該函數禁止在中斷中調用。
如果消息隊列不為空,包含有效的消息,則本次操作將成功獲取到一條消息,同時,會從消息隊列中將該條消息刪除,有效消息的條數減1。此時,AW_MSGQ_RECEIVE()的返回值為:AW_OK。
如果消息隊列為空,沒有任何有效的消息,則不能立即成功獲取到消息,接下來具體的行為將由超時時間timeout的值決定。
(1)若timeout的值為
AW_MSGQ_WAIT_FOREVER。則任務會阻塞于此,一直等待,直到消息隊列中有可用的消息,即其它任務或中斷發送了消息。范例程序詳見程序清單10.68。
程序清單10.68 永久阻塞等待消息隊列的范例程序
(2)若timeout的值為AW_MSGQ_NO_WAIT。則任務不會被阻塞,立即返回,但不會成功獲取到消息,此時,AW_MSGQ_RECEIVE()的返回值為:-AW_EAGAIN(表示當前資源無效,需要重試)。范例程序詳見程序清單10.69。
程序清單10.69 不阻塞等待消息隊列的范例程序
(3)若tiemout的值為一個正整數,則表示最長的等待時間(單位為系統節拍),任務會阻塞于此,在timeout規定的時間內,若成功獲取到一條消息,則AW_MSGQ_RECEIVE()的返回值為:AW_OK;若在timeout規定的時間內,沒有獲取到消息,則返回值為-AW_ETIME(表明超時)。范例程序詳見程序清單10.33。
程序清單10.70 等待消息隊列的超時時間為500ms的范例程序
4. 發送一條消息到消息隊列中
發送消息到消息隊列中的宏原型為:
其中,msgq為由AW_MSGQ_DECL() 或
AW_MSGQ_DECL_STATIC()定義的消息隊列。p_buf指向待發送的消息緩沖區。nbytes為消息緩沖區的大小,消息緩沖區的長度不得不得大于定義消息隊列實體時指定的一條消息的長度,通常情況下,nbytes與一條消息的長度是相等的,例如,msgq_test中的消息長度為4字節,則nbytes的值也為4,即p_buf指向的緩存區大小為4字節。timeout指定了超時時間。priority指定了消息的優先級。該宏的返回值為aw_err_t類型的標準錯誤號。
若消息隊列未滿,可以繼續存儲消息,則該消息發送成功,消息隊列的有效消息條數加1。此時,AW_MSGQ_SEND()的返回值為:AW_OK。
若消息隊列已滿,暫時不能繼續存儲消息,則不能立即成功發送消息,接下來具體的行為將由超時時間timeout的值決定。
(1)若timeout的值為
AW_MSGQ_WAIT_FOREVER。則任務會阻塞于此,一直等待,直到消息成功放入消息隊列。即其它任務從消息隊列中獲取了消息,消息隊列留出空閑空間。范例程序詳見程序清單10.71。
程序清單10.71 永久阻塞等待消息隊列的范例程序
注意,priority參數指定了消息的優先級,其可能的取值有兩個:AW_MSGQ_PRI_NORMAL和AW_MSGQ_PRI_URGENT,分別表示普通優先級和緊急優先級。一般地,均使用普通優先級,此時,新的消息按照隊列的組織形式,放在隊列的尾部,將最后被取出。若使用緊急優先級,則新的消息放在隊列的頭部,將在下次從消息隊列中獲取消息時被取出。
(2)若timeout的值為AW_MSGQ_NO_WAIT。則任務不會被阻塞,立即返回,但不會成功發送消息,此時,AW_MSGQ_SEND()的返回值為:-AW_EAGAIN(表示當前資源無效,需要重試)。范例程序詳見程序清單10.72。
程序清單10.72 不阻塞等待消息隊列的范例程序
(3)若tiemout的值為一個正整數,則表示最長的等待時間(單位為系統節拍),任務會阻塞于此,在timeout規定的時間內,成功發送了消息,則AW_MSGQ_SEND()的返回值為:AW_OK;若在timeout規定的時間內,沒有成功發送消息,則返回值為-AW_ETIME(表明超時)。范例程序詳見程序清單10.73。
程序清單10.73 等待消息隊列的超時時間為500ms的范例程序
注意,在中斷服務程序中(如按鍵回調函數),可以使用該接口發送消息至消息隊列,這是中斷和任務之間很重要的一種通信方式:即在中斷中發送消息,在任務中接收消息并處理,從而減小中斷服務程序的時間。但是,由于中斷不能被阻塞,因此,當在中斷中發送消息時,timeout標志只能為AW_MSGQ_NO_WAIT。在這種應用中,為了避免消息丟失,應該盡可能避免消息隊列被填滿,可以通過增加消息隊列的大小以及提高處理消息任務的優先級,使消息隊列中的消息被盡快處理。
5. 終止消息隊列
當一個消息隊列不再使用時,可以終止該消息隊列,宏原型為:
其中,msgq為由AW_MSGQ_DECL() 或 AW_MSGQ_DECL_STATIC()定義的消息隊列。當消息隊列被終止后,若當前系統中還存在等待該消息隊列的任務,則任何等待此消息隊列的任務將會解阻塞, 并返回-AW_ENXIO(表明資源已不存在),其范例詳見程序清單10.74。
程序清單10.74 終止消息隊列的范例程序
在程序清單10.62中,使用了動態內存分配來管理消息的存儲空間,為了避免使用動態內存分配,可以使用消息隊列,將每條消息的長度定義為8,以便存儲按鍵編碼和按鍵狀態。范例程序詳見程序清單10.75。
程序清單10.75 消息隊列使用范例程序
該程序與程序清單10.62所示的程序分別使用郵箱和消息隊列實現了相同的功能。消息隊列避免了使用動態內存分配,在定義消息隊列實體時,就完成了相關內存的靜態分配。避免了使用動態內存分配的種種缺點。但是,當使用消息隊列時,若定義的容量過大,可能造成不必要的內存浪費。
此外,郵箱和消息隊列發送消息的方式是不同的,對于郵箱,其僅僅發送了消息的首地址,接收者接收到地址后,直接從地址中取出相應的消息,這種方式下,消息傳遞的效率很高。
但對于消息隊列,發送消息時,是將整個消息內容(如按鍵編碼和按鍵狀態)拷貝到消息隊列的緩沖區中,接收消息時,再將存儲在消息隊列緩沖區中的消息完整的拷貝到用戶緩沖區中。由此可見,一次消息傳輸存在兩次消息內容的完全拷貝過程,這種傳輸方式效率很低,特別是對于一條消息很大的情況。
因此,建議當一條消息很大時,使用郵箱;而當一條消息較小時,消息的拷貝對性能的影響較弱,則使用消息隊列更加方便快捷。
當使用郵箱時,為了避免使用常規動態內存分配方法造成內存碎片、內存泄漏、分配效率等問題。可以使用AWorks提供的靜態內存池管理技術,其為了避免內存碎片和分配效率等問題,將每次分配內存的大小設定為一個固定值。內存池管理技術將在“內存管理”章節中詳細介紹。
10.5 自旋鎖
互斥信號量用于任務間對共享資源的互斥訪問,在一個任務獲取互斥信號量時,若互斥信號量無效,需要等待時,則任務會主動釋放CPU,內核調度器進而調度CPU去執行其它任務,當互斥信號量恢復有效時,再重新調度CPU繼續執行之前的任務。這樣,在任務等待互斥信號量有效的這段時間里,CPU可以被充分利用,去處理其他任務。
但是,調度過程是需要耗費一定時間的,有些時候,對共享資源的訪問可能非常簡單,消耗CPU的時間很短,也就是說,一個任務占用共享資源的時間非常短,其獲得互斥信號量后很快就會釋放。這種情況下,當一個任務獲取互斥信號量時,即使當前的信號量無效,也意味著該信號量很快就會被釋放,變為有效。若任務在此時釋放CPU,執行任務調度,很可能在任務調度過程中,信號量就被釋放了,系統又不得不在調度結束后重新將CPU再調度回來,這使系統在任務調度上花費了太多的時間成本。這種情況下,任務不釋放CPU將是一種更好的選擇,可以提高任務執行的效率。
AWorks提供了自旋鎖,所謂“自旋”,就是一個“自我輪詢檢查”,當獲取自旋鎖時,若自旋鎖處于無效(被鎖)狀態,則不會釋放CPU,而是輪詢檢查自旋鎖,直到自旋鎖被釋放(解鎖)。檢查到自旋鎖被釋放后,立即獲取該自旋鎖,使之成為鎖住狀態,接著盡快迅速完成對共享資源的訪問,訪問結束后,釋放自旋鎖。
由于當自旋鎖不可用時,任務將一直循環檢查自旋鎖的狀態直到可用而不會釋放CPU, CPU在輪詢等待期間不做任何其它有效的工作,因此,只有在共享資源占用時間極短的情況下,使用自旋鎖才是合理的。否則,應該使用互斥信號量。需要特別注意的是,自旋鎖不支持遞歸使用。
AWorks提供了使用自旋鎖的通用接口,接口的原型詳見表10.11。
表10.11 自旋鎖通用接口(aw_spinlock.h)
在AWorks中,自旋鎖可以在中斷中使用,因而在接口命名中,含有“isr”關鍵字。之所以可以在中斷中使用,是由于在獲取到自旋鎖后,會關閉總中斷,釋放自旋鎖時,再打開總中斷,使得在訪問自旋鎖保護的共享資源時,可以獨占CPU,保證其不會被中斷打斷。否則,若任務在獲取到自旋鎖還未釋放時被中斷打斷,在中斷上下文中再次獲取自旋鎖將造成“死鎖”:任務未釋放自旋鎖,中斷只能等待;中斷占用了CPU,任務只有等待中斷結束返回后才能繼續執行,以釋放自旋鎖。
換句話說,在AWorks中,自旋鎖可以在中斷中使用,任務和中斷對共享資源的訪問是互斥的,當任務訪問共享資源時,中斷會被關閉,以實現互斥。
1. 定義自旋鎖實體
在使用自旋鎖前,必須先使用aw_spinlock_isr_t類型定義自旋鎖實體,該類型在aw_spinlock.h中定義,具體類型的定義用戶無需關心,僅需使用該類型定義自旋鎖實體,即:
其地址即可作為各個接口中p_lock參數的實參傳遞,表示具體要操作的自旋鎖。
2. 初始化自旋鎖
定義自旋鎖實體后,必須使用該接口初始化后才能使用。其原型為:
其中,p_lock指向待初始化的自旋鎖。flags為自旋鎖的標志,當前無任何可用標志,該值需設置為0。初始化自旋鎖的范例程序詳見程序清單10.76。
程序清單10.76 初始化自旋鎖
3. 獲取自旋鎖
獲取自旋鎖的函數原型為:
其中,p_lock指向需要獲取的自旋鎖。若自旋鎖有效,則獲取成功,并將自旋鎖設置為無效狀態;若自旋鎖無效,則會輪詢等待(不會像互斥信號量那樣釋放CPU),直到自旋鎖有效(占用該鎖的任務釋放自旋鎖)后返回。該接口可以在中斷上下文中使用。獲取自旋鎖的范例程序詳見程序清單10.77。
程序清單10.77 獲取自旋鎖
4. 釋放自旋鎖
釋放自旋鎖的函數原型為:
其中,p_lock指向需要釋放的自旋鎖。自旋鎖的獲取和釋放操作應該成對出現,即在一個任務(或中斷上下文)中,先獲取自旋鎖,再訪問由該自旋鎖保護的共享資源,訪問結束后釋放自旋鎖。不可一個任務(或中斷上下文)僅獲取自旋鎖,另一個任務(或中斷上下文)僅釋放自旋鎖。釋放自旋鎖的范例程序詳見程序清單10.78。
程序清單10.78 釋放自旋鎖
在互斥信號量的范例程序中(詳見程序清單10.25),使用了兩個任務互斥訪問共享資源(調試串口)進行了舉例說明,由于調試串口輸出信息的速度慢,輸出一條字符串信息耗時往往在毫秒級別,因此,這種情況下,使用自旋鎖是不合適的。一般來講,操作硬件設備都不建議使用自旋鎖,自旋鎖往往用于互斥訪問類似于全局變量的共享資源。
例如,有兩個任務task1和task2。在task1中,每隔50ms對全局變量進行加1操作,在task2中,檢查全局變量的值,若達到10,則將全局變量的值重置為0,并翻轉一次LED。
由于兩個任務均需對全局變量進行操作,為了避免沖突,需要兩個任務互斥訪問該全局變量,顯然,加值操作是非常快的,占用時間極短,可以使用自旋鎖實現互斥訪問,范例程序詳見程序清單10.79。
程序清單10.79 自旋鎖使用范例程序
-
周立功
+關注
關注
38文章
130瀏覽量
37616 -
致遠電子
+關注
關注
13文章
406瀏覽量
31301 -
AWorks
+關注
關注
1文章
16瀏覽量
5704
原文標題:AWorks軟件篇 — 實時內核(郵箱、消息隊列和自旋鎖)
文章出處:【微信號:ZLG_zhiyuan,微信公眾號:ZLG致遠電子】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
評論