<noframes id="fbbb7">

<sub id="fbbb7"></sub>

<track id="fbbb7"></track><address id="fbbb7"></address>

      <track id="fbbb7"></track>
      <cite id="fbbb7"><th id="fbbb7"><ol id="fbbb7"></ol></th></cite>
        創作

        完善資料讓更多小伙伴認識你,還能領取20積分哦, 立即完善>

        3天內不再提示

        RT-Thread記錄(六、IPC機制之信號量互斥量事件集)

        矜辰所致 ? 來源:矜辰所致 ? 作者:矜辰所致 ? 2022-06-21 10:40 ? 次閱讀
        上文說到 RT-Thread 對臨界區的處理方式有多種,其中已經分析了關閉調度器和屏蔽中斷的方式,
        本文就來學學另外的線程同步方式。

        目錄

        前言
        一、IPC機制
        二、信號量
        2.1 信號量控制塊
        2.2 信號量操作
        2.2.1 創建和刪除
        2.2.2 初始化和脫離
        2.2.3 獲取信號量
        2.2.4 釋放信號量
        2.2.5 信號量控制
        2.3 示例(典型停車場模型)
        三、互斥量
        3.1 優先級翻轉
        3.2 優先級繼承
        3.3 互斥量控制塊
        3.4 互斥量操作
        3.2.1 創建和刪除
        3.2.2 初始化和脫離
        3.2.3 獲取互斥量
        3.2.4 釋放互斥量
        3.5 示例(優先級繼承)
        四、事件集
        4.1 事件集控制塊
        4.2 事件集操作
        4.2.1 創建和刪除
        4.2.2 初始化和脫離
        4.2.3 發送事件
        4.2.4 接收事件
        4.3 示例(邏輯與和邏輯或)
        結語

        前言

        在我們專欄前面的文章中,已經學習過 RT-Thread 線程操作函數、軟件定時器、臨界區的保護,我們都進行了一些底層的分析,能讓我們更加理解 RT-Thread 的內核,但是也不要忽略了上層的函數使用 要理解 RT-Thread 面向對象的思想,對所有的這些線程啊,定時器,包括要介紹的信號量,郵箱這些,都是以 對象 來操作,直白的說來就是 對于所有這些對象,都是以結構體的形式來表示,然后通過對這個對象結構體的操作來進行的。


        本文所要介紹的內容屬于 IPC機制,這些內容相對來說比較簡單,我們重點在于學會如何使用以及了解他們的使用場合。

        本 RT-Thread 專欄記錄的開發環境:
        RT-Thread記錄(一、版本開發環境及配合CubeMX) + http://www.xinanjipiao.com/d/1850333.html
        RT-Thread記錄(二、RT-Thread內核啟動流程)+ http://www.xinanjipiao.com/d/1850347.html
        RT-Thread 內核篇系列博文鏈接:
        RT-Thread記錄(三、RT-Thread線程操作函數)+ http://www.xinanjipiao.com/d/1850351.html
        RT-Thread記錄(四、RTT時鐘節拍和軟件定時器)+ http://www.xinanjipiao.com/d/1850554.html
        RT-Thread記錄(五、RT-Thread 臨界區保護) + http://www.xinanjipiao.com/d/1850712.html


        一、IPC機制

        在嵌入式操作系統中,運行代碼主要包括線程 和 ISR,在他們的運行過程中,因為應用或者多線程模型帶來的需求,有時候需要同步,有時候需要互斥,有時候也需要彼此交換數據。操作系統必須提供相應的機制來完成這些功能,這些機制統稱為 線程間通信(IPC機制)。

        本文所要介紹的就是關于線程同步的信號量、互斥量、事件 也屬于 IPC機制。

        RT-Thread 中的 IPC機制包括信號量、互斥量、事件、郵箱、消息隊列。對于學習 RT-Thread ,這些IPC機制我們必須要學會靈活的使用。

        為什么要說一下這個IPC機制?

        我們前面說到過,RT-Thread 面向對象的思想,所有的這些 IPC 機制都被當成一個對象,都有一個結構體控制塊,我們用信號量結構體來看一看:

        watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA55-c6L6w5omA6Ie0,size_20,color_FFFFFF,t_70,g_se,x_16

        Kernel object有哪些,我們可以從基礎內核對象結構體定義下面的代碼找到:

        watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA55-c6L6w5omA6Ie0,size_10,color_FFFFFF,t_70,g_se,x_16

        本節說明了 RT-Thread 的 IPC 機制,同時通過 信號量的結構體控制塊再一次的認識了 RT-Thread 面向對象的設計思想。

        在我的 FreeRTOS 專欄中,對于FreeRTOS 的信號量,互斥量,事件集做過說明和測試。在這個部分,實際上 RT-Thread 與 FreeRTOS 是類似的,都是一樣的思想。所以如果屬熟悉FreeRTOS的話,這部分是簡單的,我們要做的就是記錄一下 對象的控制塊,和操作函數,加以簡單的示例測試。

        二、信號量

        信號量官方的說明是:信號量是一種輕型的用于解決線程間同步問題的內核對象,線程可以獲取或釋放它,從而達到同步或互斥的目的。

        信號量非常靈活,可以使用的場合也很多:

        • 比如 一個典型的應用場合就是停車位模型,總共有多少個車位,就是多少個信號量,入口進入一輛車信號量-1,出口離開一輛車信號量+1。
        • 比如 兩個線程之間的同步,信號量的值初始化成 0,而嘗試獲得該信號量的線程,一定需要等待另一個釋放信號量的線程先執行完。

        在 FreeRTOS 中存在二值信號量,但是 RT-Thread 中已經沒有了,官方有說明:

        在這里插入圖片描述

        信號量記住一句話基本就可以,釋放一次信號量就+1,獲取一次就-1,如果信號量數據為0,那么嘗試獲取的線程就會掛機,直到有線程釋放信號量使得信號量大于0。

        2.1 信號量控制塊

        老規矩用源碼,解釋看注釋(使用起來也方便復制 ~ ~?。?/p>

        #ifdef RT_USING_SEMAPHORE
        /**
         * Semaphore structure
         * value 信號量的值,直接表明目前信號量的數量
         */
        struct rt_semaphore
        {
            struct rt_ipc_object parent;                        /**< inherit from ipc_object */
        
            rt_uint16_t          value;                         /**< value of semaphore. */
            rt_uint16_t          reserved;                      /**< reserved field */
        };
        /*
        rt_sem_t 是指向 semaphore 結構體的指針類型
        */
        typedef struct rt_semaphore *rt_sem_t;
        #endif

        2.2 信號量操作

        2.2.1 創建和刪除

        同以前的線程那些一樣,動態的方式,先定義一個信號量結構體的指針變量,接收創建好的句柄。

        創建信號量:

        /*
        參數的含義:
        1、name 	信號量名稱
        2、value 	信號量初始值
        3、flag 	信號量標志,它可以取如下數值: RT_IPC_FLAG_FIFO 或 RT_IPC_FLAG_PRIO
        返回值:
        信號量創建成功,返回信號量的控制塊指針
        信號量創建失敗,返回RT_BULL 
        */
        rt_sem_t rt_sem_create(const char *name, rt_uint32_t value, rt_uint8_t flag)
        

        對于最后的參數 flag,決定了當信號量不可用時(就是當信號量為0的時候),多個線程等待的排隊方式。只有RT_IPC_FLAG_FIFO (先進先出)或 RT_IPC_FLAG_PRIO(優先級等待)兩種 flag。

        關于用哪一個,要看具體的情況,官方有特意說明:

        poYBAGKxL4KAYSAVAABtsu0TwBo813.png

        刪除信號量:

        /*
        參數:
        sem 	rt_sem_create() 創建的信號量對象,信號量句柄
        返回值:
        RT_EOK 	刪除成功
        */
        rt_err_t rt_sem_delete(rt_sem_t sem)

        2.2.2 初始化和脫離

        靜態的方式,先定義一個信號量結構體,然后對他進行初始化。

        初始化信號量:

        /**
        參數的含義:
        1、sem 		信號量對象的句柄,就是開始定義的信號量結構體變量
        2、name 	信號量名稱
        3、value 	信號量初始值
        4、flag 	信號量標志,它可以取如下數值: RT_IPC_FLAG_FIFO 或 RT_IPC_FLAG_PRIO
        返回值:
        RT_EOK 	初始化成功
         */
        rt_err_t rt_sem_init(rt_sem_t    sem,
                             const char *name,
                             rt_uint32_t value,
                             rt_uint8_t  flag)

        脫離信號量:

        /*
        參數:
        sem 	信號量對象的句柄
        返回值:
        RT_EOK 	脫離成功
        */
        rt_err_t rt_sem_detach(rt_sem_t sem);

        2.2.3 獲取信號量

        當信號量值大于零時,線程將獲得信號量,并且相應的信號量值會減 1。

        /**
        參數:
        1、sem 		信號量對象的句柄
        2、time 	指定的等待時間,單位是操作系統時鐘節拍(OS Tick)
        返回值:
        RT_EOK 			成功獲得信號量
        -RT_ETIMEOUT 	超時依然未獲得信號量
        -RT_ERROR 		其他錯誤
         */
        rt_err_t rt_sem_take(rt_sem_t sem, rt_int32_t time)

        注意!要等待的時間是系統時鐘節拍(OS Tick)。

        無等待獲取信號量

        //就是上面獲取的等待時間為0的方式
        rt_err_t rt_sem_trytake(rt_sem_t sem)
        {
            return rt_sem_take(sem, 0);
        }

        當線程申請的信號量資源實例為0時,直接返回 - RT_ETIMEOUT。

        2.2.4 釋放信號量

        釋放信號量可以使得該信號量+1,如果有線程在等待這個信號量,可以喚醒這個線程。

        /**
        參數:
        sem 	信號量對象的句柄
        返回值:
        RT_EOK 	成功釋放信號量
         */
        rt_err_t rt_sem_release(rt_sem_t sem)

        2.2.5 信號量控制

        信號量控制函數,用來重置信號量,使得信號量恢復為設定的值:

        
        /**
         * This function can get or set some extra attributions of a semaphore object.
        參數:
        sem 	信號量對象的句柄
        cmd    信號量控制命令 ,支持命令:RT_IPC_CMD_RESET 
        arg    暫時不知道
        返回值:
        RT_EOK 	成功釋放信號量
        
         */
        rt_err_t rt_sem_control(rt_sem_t sem, int cmd, void *arg)
        {
            rt_ubase_t level;
        
            /* parameter check */
            RT_ASSERT(sem != RT_NULL);
            RT_ASSERT(rt_object_get_type(&sem->parent.parent) == RT_Object_Class_Semaphore);
        
            if (cmd == RT_IPC_CMD_RESET)
            {
                rt_ubase_t value;
        
                /* get value */
                value = (rt_ubase_t)arg;
                /* disable interrupt */
                level = rt_hw_interrupt_disable();
        
                /* resume all waiting thread */
                rt_ipc_list_resume_all(&sem->parent.suspend_thread);
        
                /* set new value */
                sem->value = (rt_uint16_t)value;
        
                /* enable interrupt */
                rt_hw_interrupt_enable(level);
        
                rt_schedule();
        
                return RT_EOK;
            }
        
            return -RT_ERROR;
        }

        使用示例:

        rt_err_t result;
        rt_uint32_t value;
        
        value = 10; /* 重置的值,即重置為10 */
        result = rt_sem_control(sem, RT_IPC_CMD_RESET, (void*)value)
        
        /* 重置為0 */
        rt_sem_control(sem, RT_IPC_CMD_RESET, RT_NULL)

        對sem重置后,會先把sem上掛起的所有任務進行喚醒(任務的error是-RT_ERROR),然后把sem的值會重新初始化成設定的值。

        在官方論壇有如下說明:
        在rt_sem_release后使用rt_sem_control的目的是因為在某些應用中必須rt_sem_take和rt_sem_release依次出現,而不允許rt_sem_release被連續多次調用,一旦出現這種情況會被認為是出現了異常,通過調用rt_sem_control接口來重新初始化 sem_ack恢復異常。

        2.3 示例(典型停車場模型)

        前面說到過,信號量非常靈活,可以使用的場合也很多,官方也有很多例子,我們這里做個典型的示例
        — 停車場模型(前面用截圖做解釋,后面會附帶源碼)。

        示例中,我們使用兩個不同的按鍵來模擬車輛的進出,但是考慮到我們還沒有學設備和驅動,沒有添加按鍵驅動,所以我們用古老的方式來實現按鍵操作:

        按鍵key3,代表車輛離開:

        watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA55-c6L6w5omA6Ie0,size_20,color_FFFFFF,t_70,g_se,x_16

        按鍵key2,代表車輛進入:

        watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA55-c6L6w5omA6Ie0,size_20,color_FFFFFF,t_70,g_se,x_16

        信號量的創建,初始10個車位:

        pYYBAGKxL4OAAKKMAAAexk-g3H8704.png

        當然不要忘了,車輛進入和車輛離開(兩個按鍵)是需要兩個線程的。

        我們來看看測試效果,說明如圖:

        watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA55-c6L6w5omA6Ie0,size_8,color_FFFFFF,t_70,g_se,x_16

        注意上圖測試最后的細節,雖然 one car get out! 但是打印出來的停車位還是0,可以這么理解,key3_thread_entry線程釋放了信號量以后還沒來得及打印,等待信號量的線程key2_thread_entry就獲取到了信號量。

        具體的分析需要看rt_sem_release函數源碼,里面會判斷是否需要值+1,以及是否需要調度:

        watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA55-c6L6w5omA6Ie0,size_20,color_FFFFFF,t_70,g_se,x_16

        附上上面測試代碼:

        /*
         * Copyright (c) 2006-2022, RT-Thread Development Team
         *
         * SPDX-License-Identifier: Apache-2.0
         *
         * Change Logs:
         * Date           Author       Notes
         * 2022-02-16     RT-Thread    first version
         */
        
        #include 
        #include "main.h"
        #include "usart.h"
        #include "gpio.h"
        
        #define DBG_TAG "main"
        #define DBG_LVL DBG_LOG
        #include 
        
        static struct rt_thread led1_thread;    //led1線程
        static char led1_thread_stack[256];
        
        static rt_thread_t led2_thread = RT_NULL; //led2線程
        
        static rt_thread_t key2_thread = RT_NULL; //
        
        static rt_thread_t key3_thread = RT_NULL; //
        
        rt_sem_t mysem;
        
        
        static void led1_thread_entry(void *par){
            while(1){
                LED1_ON;
                rt_thread_mdelay(1000);
                LED1_OFF;
                rt_thread_mdelay(1000);
            }
        }
        
        static void led2_thread_entry(void *par){
            while(1){
                LED2_ON;
                rt_thread_mdelay(500);
                LED2_OFF;
                rt_thread_mdelay(500);
            }
        }
        
        static void key2_thread_entry(void *par){
            static rt_err_t result;
            while(1){
                if(key2_read == 0){
                    rt_thread_mdelay(10); //去抖動
                    if(key2_read == 0){
                        result = rt_sem_take(mysem, 1000);
                        if (result != RT_EOK)
                        {
                            rt_kprintf("the is no parking spaces now...\r\n");
                        }
                        else
                        {
                            rt_kprintf("one car get in!,we have %d parking spaces now...\r\n",mysem->value);
                        }
                        while(key2_read == 0){rt_thread_mdelay(10);}
                    }
                }
                rt_thread_mdelay(1);
            }
        }
        
        static void key3_thread_entry(void *par){
            while(1){
                if(key3_read == 0){
                    rt_thread_mdelay(10); //去抖動
                    if(key3_read == 0){
                        if(mysem->value < 10){
                            rt_sem_release(mysem);
                            rt_kprintf("one car get out!,we have %d parking spaces now...\r\n",mysem->value);
                        }
                        while(key3_read == 0){rt_thread_mdelay(10);} //去抖動
                    }
                }
                rt_thread_mdelay(1);
            }
        }
        int main(void)
        {
            MX_GPIO_Init();
            MX_USART1_UART_Init();
        
        
            rt_err_t rst2;
            rst2 = rt_thread_init(&led1_thread,
                                "led1_blink ",
                                led1_thread_entry,
                                RT_NULL,
                                &led1_thread_stack[0],
                                sizeof(led1_thread_stack),
                                RT_THREAD_PRIORITY_MAX -1,
                                50);
        
            if(rst2 == RT_EOK){
                rt_thread_startup(&led1_thread);
            }
        
        
            mysem = rt_sem_create("my_sem1", 10, RT_IPC_FLAG_FIFO);
            if(RT_NULL == mysem){
                LOG_E("create sem failed!...\n");
            }
            else LOG_D("we have 10 parking spaces now...\n");
        
            key2_thread = rt_thread_create("key2_control",
                                        key2_thread_entry,
                                        RT_NULL,
                                        512,
                                        RT_THREAD_PRIORITY_MAX -2,
                                        50);
        
                /* 如果獲得線程控制塊,啟動這個線程 */
                if (key2_thread != RT_NULL)
                    rt_thread_startup(key2_thread);
        
             key3_thread = rt_thread_create("key3_control",
                                        key3_thread_entry,
                                        RT_NULL,
                                        512,
                                        RT_THREAD_PRIORITY_MAX -2,
                                        50);
        
                /* 如果獲得線程控制塊,啟動這個線程 */
                if (key3_thread != RT_NULL)
                    rt_thread_startup(key3_thread);
            return RT_EOK;
        }
        
        
        void led2_Blink(){
            led2_thread = rt_thread_create("led2_blink",
                                    led2_thread_entry,
                                    RT_NULL,
                                    256,
                                    RT_THREAD_PRIORITY_MAX -1,
                                    50);
        
            /* 如果獲得線程控制塊,啟動這個線程 */
            if (led2_thread != RT_NULL)
                rt_thread_startup(led2_thread);
        }
        
        MSH_CMD_EXPORT(led2_Blink, Led2 sample);

        三、互斥量

        互斥量是一種特殊的二值信號量?;コ饬康臓顟B只有兩種,開鎖或閉鎖(兩種狀態值)。

        互斥量支持遞歸,持有該互斥量的線程也能夠再次獲得這個鎖而不被掛起。自己能夠再次獲得互斥量。

        互斥量可以解決優先級翻轉問題,它能夠實現優先級繼承。

        互斥量互斥量不能在中斷服務例程中使用。

        3.1 優先級翻轉

        優先級翻轉,我以前寫過:

        poYBAGKxL4SAUwRIAABs3UaqWig058.png

        再用官方的圖加深理解:

        watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA55-c6L6w5omA6Ie0,size_20,color_FFFFFF,t_70,g_se,x_16

        3.2 優先級繼承

        優先級繼承,以前也寫過:

        watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA55-c6L6w5omA6Ie0,size_20,color_FFFFFF,t_70,g_se,x_16

        再用官方的圖加深理解:

        watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA55-c6L6w5omA6Ie0,size_20,color_FFFFFF,t_70,g_se,x_16

        需要切記的是互斥量不能在中斷服務例程中使用。

        3.3 互斥量控制塊

        #ifdef RT_USING_MUTEX
        /**
         * Mutual exclusion (mutex) structure
         * parent 				繼承ipc類
         * value 				互斥量的值
         * original_priority 	持有線程的原始優先級
         * hold 				持有線程的持有次數,可以多次獲得
         * *owner				當前擁有互斥量的線程
         */
        struct rt_mutex
        {
            struct rt_ipc_object parent;              /**< inherit from ipc_object */
            rt_uint16_t          value;                /**< value of mutex */
            rt_uint8_t           original_priority;    /**< priority of last thread hold the mutex */
            rt_uint8_t           hold;                 /**< numbers of thread hold the mutex */
        
            struct rt_thread    *owner;               /**< current owner of mutex */
        };
        /* rt_mutext_t 為指向互斥量結構體的指針類型  */
        typedef struct rt_mutex *rt_mutex_t;
        #endif

        3.4 互斥量操作

        3.4.1 創建和刪除

        先定義一個指向互斥量結構體的指針變量,接收創建好的句柄。

        創建互斥量:

        /**
        參數的含義:
        1、name 	互斥量名稱
        2、flag 	該標志已經作廢,無論用戶選擇 RT_IPC_FLAG_PRIO 還是 RT_IPC_FLAG_FIFO,
        			內核均按照 RT_IPC_FLAG_PRIO 處理
        返回值:
        互斥量創建成功,返回互斥量的控制塊指針
        互斥量創建失敗,返回RT_BULL 
         */
        rt_mutex_t rt_mutex_create(const char *name, rt_uint8_t flag)

        刪除互斥量:

        /**
        參數:
        mutex	互斥量對象的句柄
        返回值:
        RT_EOK 	刪除成
         */
        rt_err_t rt_mutex_delete(rt_mutex_t mutex)

        3.4.2 初始化和脫離

        靜態的方式,先定義一個互斥量結構體,然后對他進行初始化。

        初始化互斥量:

        /**
        參數的含義:
        1、mutex 互斥量對象的句柄,指向互斥量對象的內存塊,開始定義的結構體
        2、name 	互斥量名稱
        3、flag 	該標志已經作廢,按照 RT_IPC_FLAG_PRIO (優先級)處理
        返回值:
        RT_EOK 	初始化成功
         */
        rt_err_t rt_mutex_init(rt_mutex_t mutex, const char *name, rt_uint8_t flag)

        脫離互斥量:

        /**
        參數:
        mutex	互斥量對象的句柄
        返回值:
        RT_EOK 	成功
         */
        rt_err_t rt_mutex_detach(rt_mutex_t mutex)

        3.4.3 獲取互斥量

        一個時刻一個互斥量只能被一個線程持有。

        如果互斥量沒有被其他線程控制,那么申請該互斥量的線程將成功獲得該互斥量。如果互斥量已經被當前線程線程控制,則該互斥量的持有計數加 1,當前線程也不會掛起等待。

        /**
        參數:
        1、mutex	互斥量對象的句柄
        2、time 	指定的等待時間,單位是操作系統時鐘節拍(OS Tick)
        返回值:
        RT_EOK 			成功獲得互斥量
        -RT_ETIMEOUT 	超時依然未獲得互斥量
        -RT_ERROR 		獲取失敗
         */
        rt_err_t rt_mutex_take(rt_mutex_t mutex, rt_int32_t time)

        3.4.4 釋放互斥量

        在獲得互斥量后,應該盡可能的快釋放互斥量。

        /**
        參數:
        mutex 	互斥量對象的句
        返回值:
        RT_EOK 	成功
         */
        rt_err_t rt_mutex_release(rt_mutex_t mutex)

        3.5 示例(優先級繼承)

        互斥量做一個簡單的示例,但是即便簡單,也能體現出優先級繼承這個機制。

        示例中,我們使用兩個按鍵,key2按鍵,按一次獲取互斥量,再按一次釋放互斥量,打印自己初始優先級,當前優先級,互斥量占有線程優先級這幾個量。key3按鍵,按一次,獲取互斥量,立馬就釋放,也打印幾個優先級。

        互斥量的創建,和兩個線程的優先級:

        watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA55-c6L6w5omA6Ie0,size_15,color_FFFFFF,t_70,g_se,x_16

        key2操作:

        watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA55-c6L6w5omA6Ie0,size_20,color_FFFFFF,t_70,g_se,x_16

        key3操作:

        watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA55-c6L6w5omA6Ie0,size_20,color_FFFFFF,t_70,g_se,x_16

        測試結果說明圖:

        watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA55-c6L6w5omA6Ie0,size_20,color_FFFFFF,t_70,g_se,x_16

        示例中為了更好的演示并沒有快進快出,實際使用還是需要快進快出,除非你自己就是有這種特出需求。

        還有一個細節,就是 RT-Thread 中對象的 名字,只能顯示8個字符長度,長了會截斷,并不影響使用。

        四、事件集

        事件集這部分與 FreeRTOS 基本一樣。

        事件集主要用于線程間的同步,它的特點是可以實現一對多,多對多的同步。即一個線程與多個事件的關系可設置為:其中任意一個事件喚醒線程,或幾個事件都到達后才喚醒線程進行后續的處理;同樣,事件也可以是多個線程同步多個事件。

        RT-Thread 定義的事件集有以下特點:

        • 事件只與線程相關,事件間相互獨立:每個線程可擁有 32 個事件標志,采用一個 32 bit 無符號整型數進行記錄,每一個 bit 代表一個事件;
        • 事件僅用于同步,不提供數據傳輸功能;
        • 事件無排隊性,即多次向線程發送同一事件 (如果線程還未來得及讀走),其效果等同于只發送一次。

        4.1 事件集控制塊

        #ifdef RT_USING_EVENT
        /**
         * flag defintions in event
         * 邏輯與
         * 邏輯或
         * 清除標志位
         */
        #define RT_EVENT_FLAG_AND               0x01            /**< logic and */
        #define RT_EVENT_FLAG_OR                0x02            /**< logic or */
        #define RT_EVENT_FLAG_CLEAR             0x04            /**< clear flag */
        
        /*
         * event structure
         * set:事件集合,每一 bit 表示 1 個事件,bit 位的值可以標記某事件是否發生
         */
        struct rt_event
        {
            struct rt_ipc_object parent;                        /**< inherit from ipc_object */
        
            rt_uint32_t          set;                           /**< event set */
        };
        /* rt_event_t 是指向事件結構體的指針類型  */
        typedef struct rt_event *rt_event_t;
        #endif

        4.2 事件集操作

        4.2.1 創建和刪除

        先定義一個指向事件集結構體的指針變量,接收創建好的句柄。

        創建事件集:

        /**
        參數的含義:
        1、name 	事件集的名稱
        2、flag 	事件集的標志,它可以取如下數值: RT_IPC_FLAG_FIFO 或 RT_IPC_FLAG_PRIO理
        返回值:
        事件集創建成功,返回事件集的控制塊指針
        事件集創建失敗,返回RT_BULL 
         */
        rt_event_t rt_event_create(const char *name, rt_uint8_t flag)

        flag 使用哪一個,解釋和信號量一樣,可參考信號量創建部分說明。

        刪除事件集:

        /**
        參數:
        event	事件集對象的句柄
        返回值:
        RT_EOK 	成功
         */
        rt_err_t rt_event_delete(rt_event_t event)

        4.2.2 初始化和脫離

        靜態的方式,先定義一個事件集結構體,然后對他進行初始化。

        初始化事件集:

        /**
        參數的含義:
        1、event	事件集對象的句柄
        2、name 	事件集的名稱
        3、flag 	事件集的標志,它可以取如下數值: RT_IPC_FLAG_FIFO 或 RT_IPC_FLAG_PRIO
        返回值:
        RT_EOK 	初始化成功
         */
        rt_err_t rt_event_init(rt_event_t event, const char *name, rt_uint8_t flag)

        脫離事件集:

        /**
        參數:
        event	事件集對象的句柄
        返回值:
        RT_EOK 	成功
         */
        rt_err_t rt_event_detach(rt_event_t event)

        4.2.3 發送事件

        發送事件函數可以發送事件集中的一個或多個事件。

        /**
        參數的含義:
        1、event	事件集對象的句柄
        2、set		發送的一個或多個事件的標志值
        返回值:
        RT_EOK 	成功
         */
        rt_err_t rt_event_send(rt_event_t event, rt_uint32_t set)

        4.2.4 接收事件

        內核使用 32 位的無符號整數來標識事件集,它的每一位代表一個事件,因此一個事件集對象可同時等待接收 32 個事件,內核可以通過指定選擇參數 “邏輯與” 或“邏輯或”來選擇如何激活線程。

        /**
        參數的含義:
        1、event		事件集對象的句柄
        2、set			接收線程感的事件
        3、option 		接收選項,可取的值為
        #define RT_EVENT_FLAG_AND               0x01       邏輯與    
        #define RT_EVENT_FLAG_OR                0x02       邏輯或    
        #define RT_EVENT_FLAG_CLEAR             0x04     選擇清除重置事件標志位       
        4、timeout		指定超時時間
        5、recved		指向接收到的事件,如果不在意,可以使用 NULL
        返回值:
        RT_EOK 			成功
        -RT_ETIMEOUT 	超時
        -RT_ERROR 		錯誤
         */
        rt_err_t rt_event_recv(rt_event_t   event,
                               rt_uint32_t  set,
                               rt_uint8_t   option,
                               rt_int32_t   timeout,
                               rt_uint32_t *recved)

        4.3 示例(邏輯與和邏輯或)

        事件集通過示例可以很好的理解怎么使用,我們示例中,用按鈕發送事件,其他線程接收事件,進行對應的處理。

        按鍵操作:

        watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA55-c6L6w5omA6Ie0,size_20,color_FFFFFF,t_70,g_se,x_16

        線程邏輯或處理:

        watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA55-c6L6w5omA6Ie0,size_19,color_FFFFFF,t_70,g_se,x_16

        邏輯或測試結果:

        watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA55-c6L6w5omA6Ie0,size_16,color_FFFFFF,t_70,g_se,x_16

        線程邏輯與處理:

        watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA55-c6L6w5omA6Ie0,size_19,color_FFFFFF,t_70,g_se,x_16

        邏輯與測試結果:

        watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA55-c6L6w5omA6Ie0,size_17,color_FFFFFF,t_70,g_se,x_16

        結語

        本文雖然只是介紹了信號量、互斥量和事件集這幾個比較簡單的線程同步操作,但是最終完成了后發現內容還是很多的。

        洋洋灑灑這么多字,最終看下來自己還是挺滿意的,希望我把該表述的都表達清楚了,希望大家多多提意見,讓博主能給大家帶來更好的文章。

        那么下一篇的 RT-Thread 記錄,就要來說說與線程通訊 有關的 郵箱、消息隊列和信號內容了。

        謝謝!

        • IPC
          IPC
          +關注

          關注

          3

          文章

          216

          瀏覽量

          48544
        • RT-Thread
          +關注

          關注

          27

          文章

          398

          瀏覽量

          33671
        • 信號量
          +關注

          關注

          0

          文章

          18

          瀏覽量

          7408
        收藏 人收藏

          評論

          相關推薦

          RT-Thread串口使用指南及問題解決方式匯總

          1、這篇文章主要講解如何適配串口V2驅動,下面以STM32F411RE-NUCLEO的BSP為例,講解如何快速適配到該開發板上 ...
          發表于 06-23 15:29 ? 1798次 閱讀

          怎么去解決armv8a移植到rt-smart分支系統中出錯的問題呢

          系統環境 armv8-a 64位 4核cortex-a35 移植到rt-smart分支系統中 由于rt沒有線程支持aarch64 cortex-a35,因此參考cortex-a72...
          發表于 06-23 14:57 ? 905次 閱讀

          NUC980開發板應用 基于NK-980IoT的國學唐詩學習機

          基于NK-980IoT的國學唐詩學習機 1 項目背景 最近一直在陪小孩學習國學精髓,比如唐詩、宋詞這....
          的頭像 RTThread物聯網操作系統 發表于 06-23 14:55 ? 296次 閱讀

          Nuvoton M487開發板環境搭建和程序下載測試入門

          一、開發板介紹1. 前言很感謝RT-Thread 和 新唐給予的這次評測機會,本次評測的開發板是 新唐的 Nuvoton M487,我們拿到手的...
          發表于 06-23 14:36 ? 1012次 閱讀

          基于RT-Thread+RA6M4的智能安防系統詳解

          ??無論是在家里還是在公司,安防都尤為的重要,與其亡羊補牢,更重要的是防患于未然。安全是目的,防范是....
          的頭像 物聯網技術分享 發表于 06-23 14:34 ? 51次 閱讀
          基于RT-Thread+RA6M4的智能安防系統詳解

          在RT-Thread studio + NK-980IOT環境下使用iperf命令測試四種模式帶寬

          測試環境開發板:NK-980IOT V1.0 開發環境:RT-Thread studio 2.2.1 + NuWriter v1.18 RT-Thread版本:4.0.5 開...
          發表于 06-23 14:20 ? 1087次 閱讀

          怎樣對基于NK-980IOT開發板的SPI NAND Flash進行讀寫測試呢

          SPI協議其實是包括:Standard SPI、Dual SPI和Queued SPI三種協議接口,分別對應3-wire, 4-wire, 6-wire。 (1)...
          發表于 06-23 12:05 ? 1185次 閱讀

          Nuvoton M487開發板的PWM驅動評測步驟分享

          一、M487 PWM簡介1、EPWM ? 8個獨立PWM 輸出,16位計數器,12位預分頻,最大時鐘 192MHz ? 12位死區時間 ? 計...
          發表于 06-23 11:56 ? 448次 閱讀

          簡要分析N9H30開發板RTThread框架下的LVGL demo源碼

          續接上一篇文章,分析LVGL的demo源碼。官方的widget例程代碼lv_demo_widgets()函數體如下,下面逐步分析。雖然之前也簡單看過...
          發表于 06-23 11:42 ? 357次 閱讀

          基于NK880IOT和RT-Thread的TIMER硬件使用說明

          1、NK880IOT-NK9TIMER簡介開發環境簡介NK-980IOT V1.0 (NUC980DK61Y)RT-Thread Studio + J-Link ...
          發表于 06-23 11:21 ? 772次 閱讀

          分享一種基于RT-Thread和N32G457的運動姿態解算設計

          1、基于RT-Thread和N32G457的運動姿態解算基本框架如上圖所示,設計框架由硬件和軟件兩部分組成,其中硬件主要包括N32G45...
          發表于 06-23 10:24 ? 1071次 閱讀

          rt-thread 優化系列(五)lwip 裁剪

          很久之前就開始整理下面的優化項列表了,但是有很多問題研究不深,一時不敢冒失推出。
          的頭像 出出 發表于 06-23 10:21 ? 828次 閱讀

          RT-Thread記錄(八、理解RT-Thread內存管理)

          記得最初學習 RT-Thread ,對于內存管理我也是簡單看看然后一筆帶過,當時覺得用不上,在我做的....
          的頭像 矜辰所致 發表于 06-23 10:11 ? 188次 閱讀
          RT-Thread記錄(八、理解RT-Thread內存管理)

          rt-thread 驅動篇(八)hwtimer 重載算法優化

          區別于 rt-thread 內核實現的兩種定時器,這種定時器依賴芯片內置的定時器外設,依靠穩定高速的....
          的頭像 出出 發表于 06-23 10:10 ? 826次 閱讀
          rt-thread 驅動篇(八)hwtimer 重載算法優化

          rt-thread 優化系列(四)信號對 ipc 的影響

          信號 signal,并不是線程間同步的信號量 semaphore。后者是線程間同步機制的一種,而前者....
          的頭像 出出 發表于 06-23 09:51 ? 814次 閱讀

          rt-thread優化系列(三)軟定時器的定時漂移問題分析

          所謂軟定時器,是由一個線程運行維護的定時器列表。由線程調用定時器回調函數。
          的頭像 出出 發表于 06-23 09:35 ? 826次 閱讀

          GD32 RISC-V系列 BSP框架制作與移植

          ? 手把手教你使用RT-Thread制作GD32 RISC-V系列BSP 熟悉RT-Thread的朋....
          的頭像 嵌入式大雜燴 發表于 06-22 19:44 ? 1517次 閱讀
          GD32 RISC-V系列 BSP框架制作與移植

          基于RT-Thread操作系統衍生rt-smart實時操作系統簡介

          1、rt-smart 實時操作系統簡介RT-Thread Smart(簡稱 rt-smart)嵌入式實時操作系統是基于 RT-Thread 操作系統衍生的新分...
          發表于 06-22 17:56 ? 2372次 閱讀

          RT-Thread記錄(七、IPC機制之郵箱、消息隊列)

          講完了線程同步的機制,我們要開始線程通訊的學習,
          的頭像 矜辰所致 發表于 06-22 10:06 ? 179次 閱讀
          RT-Thread記錄(七、IPC機制之郵箱、消息隊列)

          rt-thread 驅動篇(三) serialX 壓力測試

          本周筆者花了好多天的時間,計劃從多個方面對串口驅動做個比較。下面就從以下幾個角度做個對比測試。
          的頭像 出出 發表于 06-22 09:22 ? 1247次 閱讀

          rt-thread 驅動篇(二) serialX 理論實現

          在前一篇文章里,大致提出了我的串口驅動框架理論。里面做了一些對串口驅動特性的幻想。也在 NUC970....
          的頭像 出出 發表于 06-22 09:03 ? 1345次 閱讀
          rt-thread 驅動篇(二) serialX 理論實現

          GD32407V-START開發板的BSP框架制作與移植

          熟悉RT-Thread的朋友都知道,RT-Thread提供了許多BSP,但不是所有的板子都能找到相應....
          的頭像 嵌入式大雜燴 發表于 06-22 08:54 ? 1286次 閱讀
          GD32407V-START開發板的BSP框架制作與移植

          rt-thread 驅動篇(一) serialX 框架理論

          串口驅動三種工作模式:輪詢、中斷、DMA。
          的頭像 出出 發表于 06-21 10:37 ? 2010次 閱讀
          rt-thread 驅動篇(一) serialX 框架理論

          rt-thread 優化系列(二) 之 同步和消息關中斷分析

          書接前文,上篇優化聊的是關中斷操作,在很多地方過保護,導致關中斷時間太久,可能引起其它中斷不能及時響....
          的頭像 出出 發表于 06-21 09:47 ? 1836次 閱讀

          rt-thread 優化系列(一) 之 過多關中斷

          關于優化的話題永遠不過時,沒期限。
          的頭像 出出 發表于 06-21 09:03 ? 1421次 閱讀

          rt-thread 優化系列(0) SysTick 優化分析

          論壇里有人提出了一個疑問,說 STM32 系列 bsp 在初始化系統時鐘的過程中使用到了 tick ....
          的頭像 出出 發表于 06-21 08:55 ? 1246次 閱讀

          RT-Thread記錄(五、RT-Thread 臨界區保護)

          本文聊聊臨界區,以及RT-Thread對臨界區的處理
          的頭像 矜辰所致 發表于 06-20 16:06 ? 2095次 閱讀
          RT-Thread記錄(五、RT-Thread 臨界區保護)

          usbhost驅動相關疑問與調試記錄

          調試 stm32 的usb host 的艱辛歷程。希望有遇到相同問題的人能從中發現點兒什么。
          的頭像 出出 發表于 06-20 15:24 ? 176次 閱讀

          RT-Thread記錄(四、RTT時鐘節拍和軟件定時器)

          RT-Thread第4課,聽聽 RT-Thread 的心跳,再學習一下基于心跳的軟件定時器使用。
          的頭像 矜辰所致 發表于 06-20 11:50 ? 2064次 閱讀
          RT-Thread記錄(四、RTT時鐘節拍和軟件定時器)

          rt-thread 驅動篇(六)serialX弊端及解決方法

          serialX 作為一個非阻塞串口驅動框架,在遇到一些異常時,需要做一些特殊處理,今天,筆者帶大家來....
          的頭像 出出 發表于 06-20 11:43 ? 239次 閱讀

          RT-Thread記錄(三、RT-Thread線程操作函數)

          講完了RT-Thread開發環境,啟動流程,啟動以后當然是開始跑線程了,那么自然我們得學會如何創建線....
          的頭像 矜辰所致 發表于 06-20 00:31 ? 1729次 閱讀
          RT-Thread記錄(三、RT-Thread線程操作函數)

          RT-Thread記錄(二、RT-Thread內核啟動流程)

          在前面我們RT-Thread Studio工程基礎之上講一講RT-Thread內核啟動流程.
          的頭像 矜辰所致 發表于 06-20 00:30 ? 1797次 閱讀
          RT-Thread記錄(二、RT-Thread內核啟動流程)

          RT-Thread記錄(一、版本開發環境及配合CubeMX)

          RT-Thread 學習記錄的第一篇文章,RT-Thread記錄(一、RT-Thread 版本、RT....
          的頭像 矜辰所致 發表于 06-20 00:28 ? 1801次 閱讀
          RT-Thread記錄(一、版本開發環境及配合CubeMX)

          基于Select/Poll實現并發服務器(二)

          開發環境: RT-Thread版本:4.0.3 操作系統:Windows10 Keil版本:V5.3....
          的頭像 嵌入式大雜燴 發表于 06-20 00:26 ? 1834次 閱讀
          基于Select/Poll實現并發服務器(二)

          基于Select/Poll實現并發服務器(一)

          ? 開發環境: RT-Thread版本:4.0.3 操作系統:Windows10 Keil版本:V5....
          的頭像 嵌入式大雜燴 發表于 06-20 00:20 ? 1761次 閱讀
          基于Select/Poll實現并發服務器(一)

          MPU6050簡介及rt-thread軟件包使用

          小伙伴們大家好,好久不更新RT-Thread實戰筆記啦,今天來搞一搞MPU6050,話不多說,淦!
          的頭像 RTThread物聯網操作系統 發表于 06-17 10:42 ? 233次 閱讀

          iceoryx低延遲進程間通信中間件

          ./oschina_soft/iceoryx.zip
          發表于 06-17 10:29 ? 14次 閱讀
          iceoryx低延遲進程間通信中間件

          使用memheap內存管理算法對片內RAM和片外SDRAM進行管理的方法

          ??在開發中由于單片機自帶的 RAM 空間比較小,有時候需要擴展片外的 RAM 以供使用,RT-Th....
          的頭像 RT-Thread 操作系統 發表于 06-17 08:53 ? 262次 閱讀
          使用memheap內存管理算法對片內RAM和片外SDRAM進行管理的方法

          RT-Thread自動初始化機制

          ??在分析之前首先查閱 RT-Thread 的官方文檔 [RT-Thread 自動初始化機制](ht....
          的頭像 RT-Thread 操作系統 發表于 06-17 08:52 ? 203次 閱讀
          RT-Thread自動初始化機制

          rt-thread 驅動篇(五)serialX 小試牛刀

          終于來到了 serialX 的實踐篇,期待很久了。
          的頭像 出出 發表于 06-16 11:29 ? 241次 閱讀
          rt-thread 驅動篇(五)serialX 小試牛刀

          AT組件的實現過程和代碼的調用邏輯

          AT組件的核心處理邏輯是將收到的 AT 模組的應答信息放到 recv_line_buf 緩沖區中,然....
          的頭像 RT-Thread 操作系統 發表于 06-15 09:21 ? 204次 閱讀
          AT組件的實現過程和代碼的調用邏輯

          想要使用AI卻不會AI建模嗎

          人類經歷了三次工業革命,無論是蒸汽機、電力還是電子信息技術,每一次革命都給人類的生產力帶來了幾十倍的....
          的頭像 科技綠洲 發表于 06-14 14:29 ? 729次 閱讀

          nr_micro_shell介紹及使用方法

          在進行調試和維護時,常常需要與單片機進行交互,獲取、設置某些參數或執行某些操作,nr_micro_s....
          的頭像 MCU開發加油站 發表于 06-13 16:58 ? 381次 閱讀

          IPC2221印制板設計標準

          本標準是為有機剛性印制板設計提供詳細的資料。這里詳細描述了設計要求的所有方面和細節、使其成為以下設計....
          發表于 06-13 14:39 ? 36次 閱讀

          rt-thread 驅動篇(七)GPIO驅動

          一提 GPIO 可能會讓很多人覺得不屑,這么簡單的東西有什么可說的,也就是一個拉低拉高,誰不會呢。
          的頭像 出出 發表于 06-13 09:48 ? 400次 閱讀

          rt-thread 驅動篇(四)serialX 多架構適配

          自筆者提出 serialX 串口驅動到今天近半年了,當初只在 STM32F4 NUC970 兩個系列....
          的頭像 出出 發表于 06-10 10:21 ? 303次 閱讀

          RT-Thread專業版實現對于龍芯全系列處理器支持

          近日,在龍芯中科與睿賽德科技的共同努力下,RT-Thread專業版已實現了對LoongArch32和....
          的頭像 科技綠洲 發表于 06-09 16:45 ? 756次 閱讀

          龍芯中科LoongArch自主指令系統為產業生態保駕護航

          目前,龍芯中科業務已全部轉向 LoongArch 自主指令系統架構,龍芯 2K1000LA 處理器的....
          的頭像 科技綠洲 發表于 06-08 11:10 ? 331次 閱讀

          實時操作系統FreeRTOS信號量應用

          二值信號量通常用于互斥訪問或同步,二值信號量和互斥信號量非常相似,但還是有細微差別,互斥信號量擁有優....
          的頭像 嵌入式技術 發表于 06-08 09:24 ? 1091次 閱讀
          實時操作系統FreeRTOS信號量應用

          RT-Thread 4.1.0正式添加對Arm Compiler 6支持

          在 RT-Thread 4.1.0 正式發布版中,添加了對 Arm Compiler 6 的支持,用....
          的頭像 科技綠洲 發表于 06-01 15:20 ? 335次 閱讀
          RT-Thread 4.1.0正式添加對Arm Compiler 6支持

          RT-Thread 4.1.0 正式發布版中的ArmClang

          ArmClang 相較于 Armcc, 支持的架構更多,Armcc支持到Armv7架構,armcla....
          的頭像 RTThread物聯網操作系統 發表于 06-01 09:24 ? 271次 閱讀

          RT-Thread Studio如何測試Ethernet連網

          RT-Thread 是一款開源實時操作系統,包括 RT-Thread 內核、FinSH 命令行工具、....
          的頭像 科技綠洲 發表于 05-31 11:17 ? 370次 閱讀
          RT-Thread Studio如何測試Ethernet連網

          基于標準庫的keil移植到RT-thread例程

          打開pack安裝包查看如下的顯示為安裝好的RT-Rhread。
          的頭像 嵌入式應用開發 發表于 05-30 14:22 ? 282次 閱讀
          基于標準庫的keil移植到RT-thread例程

          RT-Thread全球技術大會:CherryUSB協議棧的原理與使用

          API太多導致不知道用哪些,分類亂沒有規律,協議棧文件太多,代碼處理復雜導致代碼冗長
          的頭像 倩倩 發表于 05-28 15:17 ? 507次 閱讀
          RT-Thread全球技術大會:CherryUSB協議棧的原理與使用

          RT-Thread技術大會:RNDIS主機驅動軟件應用說明

          在RT-Thread全球技術大會上,開發者詳細介紹了RNDIS主機驅動軟件的應用說明,據圖如下圖:
          的頭像 姚小熊27 發表于 05-28 14:57 ? 580次 閱讀
          RT-Thread技術大會:RNDIS主機驅動軟件應用說明

          RT-Thread全球技術大會:RNDIS設備驅動框架

          RNDIS (RemoteNDIS)設備: Remote Network Driver Interf....
          的頭像 倩倩 發表于 05-28 14:43 ? 557次 閱讀
          RT-Thread全球技術大會:RNDIS設備驅動框架

          RT-Thread全球技術大會:RNDIS通信協議原理及主機驅動框架

          在RT-Thread全球技術大會直播上,開發者詳細介紹了RNDIS通信協議的原理及主機驅動框架,具體....
          的頭像 姚小熊27 發表于 05-28 14:43 ? 480次 閱讀
          RT-Thread全球技術大會:RNDIS通信協議原理及主機驅動框架

          RT-Thread全球技術大會:RNDIS設備介紹及應用

          RT-Thread全球技術大會上,以RNDIS設備驅動框架為主題,首先介紹了RNDIS設備介紹及應用....
          的頭像 姚小熊27 發表于 05-28 14:36 ? 590次 閱讀
          RT-Thread全球技術大會:RNDIS設備介紹及應用