1. 程式人生 > >RT-Thread學習筆記3-執行緒間通訊 & 定時器

RT-Thread學習筆記3-執行緒間通訊 & 定時器

[toc] *** ## 1. 事件集的使用 單個指定事件喚醒執行緒,任意事件喚醒執行緒,多個指定事件一起喚醒執行緒。訊號量主要用於“一對一”的執行緒同步,當需要“一對多”、“多對一”、“多對多”的同步時,就需要事件集來處理了。RT-Thread中的事件集用一個32位無符號整型變數來表示,變數中的一個位代表一個事件,執行緒通過“邏輯與”或“邏輯或”與一個或多個事件建立關聯形成一個事件組合。 - 事件的“邏輯或”也稱為是獨立型同步,指的是執行緒與任何事件之一發生同步,只要有一個事件發生,即滿足條件 - 事件的“邏輯與”,也稱為是關聯型同步,指的是執行緒與若干事件都發生同步,只有這些事件全部發生,才滿足條件 ### 1.1 事件集控制塊 ``` struct rt_event { struct rt_ipc_object parent; // 從ipc_object繼承而來 rt_uint32_t set; // 事件集 set } typedef struct rt_event *rt_event_t; 靜態事件集:struct rt_event static_evt; 動態事件集:rt_event_t dynamic_evt; ``` ### 1.2 事件集操作 1. 初始化與脫離 ``` 靜態事件集操作 rt_err_t rt_event_init(rt_event_t event, const char *name, rt_uint8_t falg) //上同 rt_err_t rt_event_detach(rt_event_t event) ``` 2. 建立與刪除 ``` 動態事件集操作 rt_event_t rt_event_create(const char *name, rt_uint8_t flag) rt_err_t rt_event_delete(rt_event event) ``` 3. 傳送事件 ``` // set的值為0x01則代表第0個事件發生了,0x08則代表第3個事件發生了 // 1 << 0, 1 << 3 rt_err_t rt_event_send(rt_event_t event, rt_uint32_t set) ``` 4. 接受事件 ``` rt_err_t rt_event_recv(rt_event_t event, rt_uint32_t_t set, rt_uint8_t option, rt_int32_t timeout, rt_uint32_t *recved) // set的值表示對哪個事件感興趣。例如0x01 | 0x08,則表示對第0個事件和第3個事件感興趣 // option: RT_EVENT_FLAG_AND:都發生才喚醒 RT_EVENT_FLAG_OR:有一個發生就喚醒 RT_EVENT_FLAG_CLEAR:執行緒喚醒後,系統會將事件的set對應的位清零。否則系統不會清除對應位 // timeout: 上同 // recved: 儲存接收到的事件set ``` ## 2. 郵箱的使用 郵箱用於執行緒間通訊,特點是開銷比較低,效率較高。郵箱中的每一封郵件只能容納固定的4位元組內容(針對32位處理系統,指標的大小即為4個位元組,所以一封郵件恰好能夠容納一個指標)。執行緒或中斷服務例程把一封4位元組長度的郵件傳送到郵箱中,而其他需要的執行緒可以從郵箱中接受這些郵件並進行處理 [![BOC0xK.png](https://s1.ax1x.com/2020/11/10/BOC0xK.png)](https://imgchr.com/i/BOC0xK) ### 2.1 郵箱控制塊 ``` struct rt_mailbox { struct rt_ipc_object parent; //從IPC物件繼承而來 rt_uint32_t *msg_pool; //指向郵箱訊息的緩衝區的地址 rt_uint16_t size; //郵箱的容量,可以放多少個郵件 rt_uint16_t entry; // 郵箱中郵件的數目 rt_uint16_t in_offset; // 郵箱進偏移量 rt_uint16_t out_offset; // 郵箱出偏移量 rt_list_t suspend_sender_thread; // 記錄了掛起在該郵箱的執行緒。比如郵箱滿了,這些執行緒就不能發了,要掛起等待 } typedef struct rt_mailbox *rt_mailbox_t; 靜態郵箱:struct rt_mailbox static_mb; 動態郵箱:rt_mailbox_t dynamic_mb; ``` ### 2.2 郵箱的操作 1. 初始化與脫離 ``` // 靜態郵箱。flag: RT_IPC_FLAG_FIFO, RT_IPC_FLAG_PRIO // 如果size=10,則 msgpool就要有4 * 10 = 40byte的空間 rt_err_t rt_mb_init(rt_mailbox_t mb, const char *name, void *msgpool, rt_size_t size, rt_uint8_t flag) rt_err_t rt_mb_detach(rt_mailbox_t mb) ``` 2. 建立與刪除 ``` // 動態郵箱 rt_mailbox_t rt_mb_create(const char *name, rt_size_t size, rt_uint8_t flag) rt_err_t rt_mbdelete(rt_mailbox_t mb) ``` 3. 傳送郵件 ``` // value就是郵件的內容,4位元組。如果傳送的內容<4位元組,則直接賦值給value即可,如果很多,那麼可以傳送地址。 // 如果郵箱已經滿了,那麼會直接返回錯誤 // 可以線上程和中斷中呼叫 rt_err_t rt_mb_send(rt_mailbox_t mb, rt_uint32_t value); // 如果郵箱滿了,則最多等待timeout時間。 // 只能在執行緒中呼叫,因為它會造成阻塞 rt_err_t rt_mb_send_wait(rt_mailbox_t mb, rt_uint32_t value, rt_int32_t timeout) ``` 4. 接收郵件 ``` rt_err_t rt_mb_recv(rt_mailbox_t mb, rt_uint32_t *value, rt_int32_t timeout) ``` ## 3. 訊息佇列 訊息佇列是RT-Thread另一種常用的執行緒間通訊方式,訊息佇列是對郵箱的擴充套件。訊息佇列能夠接收來自執行緒或中斷服務例程中發出的==不固定長度==的訊息,並把訊息==快取在自己的記憶體空間==中,而其它執行緒能夠從訊息佇列中讀取相應的訊息,並進行相應的處理。支援緊急訊息傳送,即將緊急訊息連結到訊息列表連結串列頭。當訊息佇列滿,還往訊息佇列傳送訊息,該傳送就會失敗。當訊息佇列空,還從訊息佇列接收訊息,該接收就會失敗。 [![BXrVk6.png](https://s1.ax1x.com/2020/11/11/BXrVk6.png)](https://imgchr.com/i/BXrVk6) ### 3.1 訊息佇列控制塊 ``` struct rt_messagequeue { struct rt_ipc_object parent; // 繼承自IPC物件 void *msg_pool; // 指向訊息佇列空間的地址 rt_uint16_t msg_size; // 訊息最大長度.在rtconfig.h 中定義對其位元組,一般是4位元組對齊。因此最小為4,應設定為4的倍數 rt_uint16_t max_msgs; // 訊息佇列容量,能容納最多訊息個數。例如設定msg_pool的大小為1024byte,那麼max_msgs = 1024 / (msg_size + 4指標地址) = 1024/8 rt_uint16_t entry; // 訊息佇列中訊息個數 void *msg_queue_head; // 訊息佇列頭指標 void *msg_queue_tail; // 訊息佇列尾指標 void *msg_queue_free; // 訊息佇列未被使用的訊息框 } typedef struct rt_messagequeue *rt_mq_t; 靜態訊息佇列:struct rt_messagequeue static_mq 動態訊息佇列:rt_mq_t dynamic_mq ``` ### 3.2 訊息佇列的操作 1. 初始化與脫離 ``` rt_err_t rt_mq_init(rt_mq_t mq, const char *name, void *msgpool, rt_size_t msg_size, rt_size_t pool_size, rt_uint8_t flag) // RT_IPC_FLAG_FIFO, RT_IPC_FLAG_PRIO rt_err_t rt_mq_detach(rt_mq_t mq) ``` 2. 創建於刪除 ``` rt_mq_t rt_mq_create(const char *name, rt_size_t msg_size, rt_size_t max_msgs, rt_uint8_t flag) rt_err_t rt_mq_delete(rt_mq_t mq) ``` 3. 傳送訊息 ``` // size <= msg_size.將訊息放在尾部 rt_err_t rt_mq_send(rt_mq_t mq, void *buffer, rt_size_t size) // 緊急訊息,訊息放在列表頭部 rt_err_t rt_mq_urgent(rt_mq_t mq, void *buffer, rt_size_t size) ``` 4. 接收訊息 ``` // timeout不等於0,則收不到訊息就會掛起 rt_err_t rt_mq_recv(rt_mq_t mq, void *buffer, rt_size_t size, rt_int32_t timeout) ``` ## 4. 軟體定時器 軟體定時器是由作業系統提供的一類系統介面,構建在硬體定時器基礎之上(系統滴答定時器),軟體定時器使系統能夠提供不受數目限制的定時器服務。RT-Thread提供的軟體定時器,以系統節拍(OS Tick)的時間長度為定時單位,提供了基於系統節拍整數倍的定時能力,即定時器數值是OS Tick的整數倍。例如一個OS Tick是10ms,那麼上層軟體定時器只能提供10的倍數的定時。到時之後,會呼叫使用者設定的回撥函式 ### 4.1 定時器模式 #### 4.1.1 HARDTIMER模式 超時函式在中斷上下文環境中執行。此模式在定時器初始化時指定。對於超時函式的要求和中斷服務例程的要求相同。==執行時間應該儘量短,執行時不應導致當前上下文掛起==,HARD_TIMER模式是RT-Thread軟體定時器的==預設方式== #### 4.1.2 SOFTTIMER模式 超時函式在系統timer執行緒的執行緒上下文中執行。通過巨集定義RT_USING_TIMER_SOFT來決定是否啟用該模式。當啟動SOFTTIMER模式後,可以在定時器初始化時指定定時器工作在SOFTTIMER模式 ### 4.2 軟體定時器控制塊 ``` struct rt_timer { struct rt_object parent; // 從系統物件繼承而來 rt_list_t row[RT_TIMER_SKIP_LIST_LEVEL]; // 系統定時器連結串列 void (*timeout_func)(void *parameter); // 超時回撥函式 void *parameter; // 回撥函式輸入引數 rt_tick_t init_tick; // 指定超時的時鐘節拍。例如60 * 10 = 600ms rt_tick_t timeout_tick; // 系統在超時時的系統節拍 }; typedef struct rt_timer *rt_timer_t; 靜態軟體定時器:struct rt_timer static_timer 動態軟體定時器:rt_timer_t dynamic_timer ``` ### 4.3 軟體定時器的操作 1. 初始化與脫離 ``` void rt_timer_init(rt_timer_t timer, const char *name, void(*timeout)(void *parameter), void *parameter, rt_tick_t time, rt_uint8_t flag); // RT_TIMER_FLAG_ONE_SHOT, RT_TIMER_FLAG_PERIODIC, RT_TIMER_FLAG_HARD_TIMER, RT_TIMER_FLAG_SOFT_TIMER 第1和第2個選一個 | 第3和第4個選一個 // 沒有回撥引數就傳RT_NULL rt_err_t rt_timer_detach(rt_timer_t timer) ``` 2. 建立與刪除 ``` rt_timer_t rt_timer_create(const char *name, void(*timeout)(void *parameter), void *parameter, rt_tick_t time, rt_uint8_t flag) rt_err_t rt_timer_delete(rt_timer_t timer) ``` 3. 啟動定時器 ``` rt_err_t rt_timer_start(rt_timer_t timer) ``` 4. 停止定時器 ``` rt_err_t rt_timer_stop(rt_timer_t timer) ``` ## 5. 記憶體池 動態記憶體堆可以分配任意大小的記憶體塊,非常靈活和方便,但其存在明顯的缺點:一是分配效率不高,在每次分配時,都要進行空閒記憶體塊查詢;二是容易產生記憶體碎片。為了提高記憶體分配效率,並且避免記憶體碎片,RT-Thread提供了另一種記憶體管理方法:記憶體池。記憶體池是一種記憶體分配方式,用於分配大量大小相同的小記憶體塊,使用記憶體池可以極大的加快記憶體分配與釋放的速度,且能儘量避免記憶體碎片化。RT-Thread的記憶體池支援執行緒掛起,當記憶體池無空閒記憶體塊時,申請執行緒會被掛起,直到記憶體池中有新的可用記憶體塊,再將掛起的執行緒喚醒。基於這個特點記憶體池非常適合需要通過記憶體資源進行同步的場景。 [![BjCAbD.png](https://s1.ax1x.com/2020/11/11/BjCAbD.png)](https://imgchr.com/i/BjCAbD) ### 5.1 記憶體池控制塊 ``` struct rt_mempool { struct rt_object parent; // 從系統物件繼承 void *start_address; // 記憶體池起始地址 rt_size_t size; // 記憶體池大小 rt_size_t block_size; // 記憶體池中一個記憶體塊大小 rt_uint8_t *block_list; // 記憶體池中小記憶體塊連結串列 rt_size_t block_total_count; // 記錄記憶體池中能容納多少小記憶體塊 rt_size_t block_free_count; // 記憶體池中空閒記憶體塊個數 rt_list_t suspend_thread; // 掛載在該資源上的執行緒 rt_size_t suspend_thread_count; // 掛載在該資源上的執行緒個數 }; typedef struct rt_mempool *rt_mp_t; 靜態記憶體池:struct rt_mempool static_mp; 動態記憶體池:rt_mp_t dynamic_mp; ``` ### 5.2 記憶體池操作 1. 初始化與脫離 ``` 靜態記憶體池 // block_size仍然要遵循位元組對齊。例如4位元組對齊,就必須設定成4的整數倍。有了block_size和size就可以計算記憶體塊數量=size/(block_size + 4)。其中4為指標大小 rt_err_t rt_mp_init(struct rt_mempool *mp, const char *name, void *start, rt_size_t size, rt_size_t block_size) rt_err_t rt_mp_detach(struct rt_mempool *mp) ``` 2. 建立與刪除 ``` rt_mp_t rt_mp_create(const char *name, rt_size_t block_count, rt_size_t block_size) rt_err_t rt_mp_delete(rt_mp_t mp) ``` 3. 申請記憶體塊 ``` // time引數為0,則立刻返回結果,如果time大於0,則無記憶體塊會掛起執行緒,如果time小於0,則無記憶體塊會一直掛起執行緒,直到有記憶體塊 void *rt_mp_alloc(rt_mp_t mp, rt_int32_t time) ``` 4. 釋放記憶體塊 ``` // 輸入記憶體塊的地址 void rt_mp_free(void *block) ``` ## 參考文獻 1. [RT-Thread視訊中心核心入門](https://www.rt-thread.org/page/video.html) 2. [RT-Thread文件中心](https://www.rt-thread.org/document/site/tutorial/quick-start/introduction/introduction/) *** **本文作者:** CrazyCatJack **本文連結:** [https://www.cnblogs.com/CrazyCatJack/p/14408849.html](https://www.cnblogs.com/CrazyCatJack/p/14408849.html) **版權宣告:**本部落格所有文章除特別宣告外,均採用 [BY-NC-SA](https://creativecommons.org/licenses/by-nc-nd/4.0/) 許可協議。轉載請註明出處! **關注博主:**如果您覺得該文章對您有幫助,可以點選文章右下角**推薦**一下,您的支援將成為我最大的動