一、郵箱控制塊:在include/rtdef.h中
#ifdef RT_USING_MAILBOX /** * mailbox structure */ struct rt_mailbox { struct rt_ipc_object parent; /**< inherit from ipc_object */ //繼承自IPC對象 rt_uint32_t *msg_pool; /**< start address of message buffer */ //消息緩沖地址 rt_uint16_t size; /**< size of message pool */ //可存放的消息最大條數 rt_uint16_t entry; /**< index of messages in msg_pool */ //當前郵箱中存放的消息條數 rt_uint16_t in_offset; /**< input offset of the message buffer */ //消息存入的偏移位置 rt_uint16_t out_offset; /**< output offset of the message buffer */ //消息取出時的偏移位置 rt_list_t suspend_sender_thread; /**< sender thread suspended on this mailbox *///郵箱發送線程掛起鏈表(當沒有取走時,發送線程會被掛起) }; typedef struct rt_mailbox *rt_mailbox_t; #endif
二、郵箱相關接口:在src/ipc.c中
創建郵箱: rt_mailbox_t rt_mb_create(const char *name, rt_size_t size, rt_uint8_t flag); 創建郵箱對象時會先創建一個郵箱對象控制塊,然后給郵箱分配一塊內存空間用來存放郵件,這塊內存的大小等於郵件大小(4字節)與郵箱容量的乘積,接着初始化接收郵件和發送郵件在郵箱中的偏移量。 刪除郵箱: rt_err_t rt_mb_delete(rt_mailbox_t mb); 刪除郵箱時,如果有線程被掛起在該郵箱對象上,內核先喚醒掛起在該郵箱上的所有線程(線程獲得返回值是-RT_ERROR),然后再釋放郵箱使用的內存,最后刪除郵箱對象。
初始化郵箱: rt_err_t rt_mb_init(rt_mailbox_t mb, //郵箱對象的句柄 const char *name, //郵箱名稱 void *msgpool, //緩沖區指針 rt_size_t size, //郵箱容量 rt_uint8_t flag); //郵箱標志(FIFO/PRIO) 與創建郵箱不同的是,此處靜態郵箱對象所使用的內存空間是由用戶線程指定的一個緩沖區空間,用戶把緩沖區的指針傳遞給郵箱對象控制塊,其余的初始化工作與創建郵箱時相同。 注: 這里的size參數指定的是郵箱的容量,即如果msgpool的字節數是N,那么郵箱容量應該是N/4 脫離郵箱: rt_err_t rt_mb_detach(rt_mailbox_t mb); 使用該函數接口后,內核先喚醒所有掛在該郵箱上的線程(線程獲得返回值是-RT_ERROR ),然后將該郵箱對象從內核對象管理器中刪除。
等待方式發送郵件: rt_err_t rt_mb_send_wait(rt_mailbox_t mb, rt_uint32_t value, rt_int32_t timeout); rt_mb_send_wait與rt_mb_send的區別在於,如果郵箱已經滿了,那么發送線程將根據設定的timeout參數等待郵箱中因為收取郵件而空出空間。如果設置的超時時間到達依然沒有空出空間,這是發送線程將被喚醒返回錯誤碼。 發送郵件: rt_err_t rt_mb_send(rt_mailbox_t mb, rt_uint32_t value); 此函數與rt_mb_send_wait(mb, value, 0)等價。發送的郵件可以是32位任意格式的數據,一個整型值或者一個指向緩沖區的指針。
當郵箱中的郵件已經滿時,發送郵件的線程或者中斷程序會收到-RT_EFULL 的返回值。發送函數在郵箱滿的時候會掛起當前所處的發送線程。 接收郵件: rt_err_t rt_mb_recv(rt_mailbox_t mb, rt_uint32_t *value, rt_int32_t timeout); 只有當接收者接收的郵箱中有郵件時,接收者才能立即取到郵件並返回RT_EOK的返回值,否則接收線程會根據超時時間設置,或掛起在郵箱的等待線程隊列上,或直接返回。接收郵件時,接收者需指定接收郵件的郵箱句柄,並指定接收到的郵件存放位置以及最多能夠等待的超時時間。如果接收時設定了超時,當指定的時間內依然未收到郵件時,將返回-RT_ETIMEOUT。函數的整個流程為:
(1)接收郵件函數會判斷當前郵箱是否為空且時間參數為0, 如果是則直接返回,然后進行一個while循環,在這個while循環中的判斷條件是郵箱為空,為什么要這么做呢?因此程序將在這個 while循環內一直等待郵件的到達,只有郵件到達才會跳出這個循環(如果等待超時則直接函數返回了)。
(2)進入while循環后,程序再次判斷當前的等待時間參數是否為0,如果是則直接返回。接着掛起當前線程,再次判斷時間參數是否大於0,這里主要是掃除timeout=RT_WAITING_FOREVER的情況,因為RT_WAITING_FOREVER宏定義為-1.在if語句內記錄當前的時間點,然后設置一定時器並開啟,接着重新調度。在重新調度后,系統可能切換到其它線程,假設一段時間內,系統再次切換回來,原因可能有多種,1:郵箱被脫離,此時當前線程thread->error=-RT_ERROR;2 定時器時間到達,但是郵件還未到達,此時thread->error=-RT_ETIMEOUT;3:郵件到達,本線程在發送郵件函數中被喚醒(注:發送郵件函數中只是喚醒第一條等待郵件的線程),此時,thread->error還是保持原值RT_EOK不變;4:其它原因假設一段時間后線程切換回來,此時error的值也一直保持原樣RT_EOK不變.因此,在重新調度了線程之后,才會有一個if語句通過判斷thread->error的值是否為RT_EOK來判斷當前線程是否被發送郵件函數喚醒。如果不是,則直接返回錯誤。
(3)接下來,按原則上說,當前線程一定是被發送郵件函數喚醒,因此,當前一定會存在接收的郵件,但是接下來的幾行代碼卻是再次判斷時間參數大於0的情況下,計算還剩余多多時間,然后返回到while循環接着循環。其原因為判斷郵箱中是否真正存在郵件的唯一標准是while循環的判斷條件,即郵箱內的接收信件條數不能為空,或為空,則判斷循環,或不為空,則自然不會進行到while循環中了。但如果這時發現原來郵箱還是為空,那么當前線程則應該繼續等待了,此時就應該計算出下一次循環中需要等待的剩下時間,好讓下一循環進行精確等待這段時間。
(4)接下來就是取出接收到的郵件,並更新郵箱的進出口偏移位置及郵箱中的郵件數減1,這樣操作過后,不要忘記郵箱內可能還保留着因之前郵箱空間不中而掛起的發送線程,這個時候由於讀取郵件操作,那么肯定是至少有一個空出的位置,那么是時候喚醒可能掛起的發送線程了(如果存在的話)。最后重新調度一下。
控制郵箱: rt_err_t rt_mb_control(rt_mailbox_t mb, rt_uint8_t cmd, void *arg); 只支持RT_IPC_CMD_RESET這一命令,表示復位郵箱(重新初始化郵箱)。
三、小結
郵箱相關源碼主要是在發送與接收上。發送時,由於當前郵箱可能空間已滿,放不下要發送的郵件,此時,不得不掛起當前發送線程(如果存在時間參數的話),只要在接收函數讀取出一條郵件時才會喚醒它。同理,如果當前郵箱為空,則接收函數會掛起當前的接收線程,直到有新的郵件到達(在發送函數中喚醒接收線程)或等待超時。
另外需要注意地是,rt-thread操作系統支持多個發送線程和多個接收線程,多個發送線程倒還好,倒是多個接收線程就不太好控制了,一般這種情況也不會用的,如果真的需要這種情況,那么多個接收線程就得好好控制了,因為,到底是哪個接收線程接收到郵件還不好說。