0
  • 聊天消息
  • 系統(tǒng)消息
  • 評(píng)論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

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

矜辰所致 ? 來源:矜辰所致 ? 作者:矜辰所致 ? 2022-06-21 10:40 ? 次閱讀
上文說到 RT-Thread 對(duì)臨界區(qū)的處理方式有多種,其中已經(jīng)分析了關(guān)閉調(diào)度器和屏蔽中斷的方式,
本文就來學(xué)學(xué)另外的線程同步方式。

目錄

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

前言

在我們專欄前面的文章中,已經(jīng)學(xué)習(xí)過 RT-Thread 線程操作函數(shù)、軟件定時(shí)器、臨界區(qū)的保護(hù),我們都進(jìn)行了一些底層的分析,能讓我們更加理解 RT-Thread 的內(nèi)核,但是也不要忽略了上層的函數(shù)使用 要理解 RT-Thread 面向?qū)ο蟮乃枷耄瑢?duì)所有的這些線程啊,定時(shí)器,包括要介紹的信號(hào)量,郵箱這些,都是以 對(duì)象 來操作,直白的說來就是 對(duì)于所有這些對(duì)象,都是以結(jié)構(gòu)體的形式來表示,然后通過對(duì)這個(gè)對(duì)象結(jié)構(gòu)體的操作來進(jìn)行的。


本文所要介紹的內(nèi)容屬于 IPC機(jī)制,這些內(nèi)容相對(duì)來說比較簡單,我們重點(diǎn)在于學(xué)會(huì)如何使用以及了解他們的使用場合。

本 RT-Thread 專欄記錄的開發(fā)環(huán)境:
RT-Thread記錄(一、版本開發(fā)環(huán)境及配合CubeMX) + http://srfitnesspt.com/d/1850333.html
RT-Thread記錄(二、RT-Thread內(nèi)核啟動(dòng)流程)+ http://srfitnesspt.com/d/1850347.html
RT-Thread 內(nèi)核篇系列博文鏈接:
RT-Thread記錄(三、RT-Thread線程操作函數(shù))+ http://srfitnesspt.com/d/1850351.html
RT-Thread記錄(四、RTT時(shí)鐘節(jié)拍和軟件定時(shí)器)+ http://srfitnesspt.com/d/1850554.html
RT-Thread記錄(五、RT-Thread 臨界區(qū)保護(hù)) + http://srfitnesspt.com/d/1850712.html


一、IPC機(jī)制

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

本文所要介紹的就是關(guān)于線程同步的信號(hào)量、互斥量、事件 也屬于 IPC機(jī)制。

RT-Thread 中的 IPC機(jī)制包括信號(hào)量、互斥量、事件、郵箱、消息隊(duì)列。對(duì)于學(xué)習(xí) RT-Thread ,這些IPC機(jī)制我們必須要學(xué)會(huì)靈活的使用。

為什么要說一下這個(gè)IPC機(jī)制?

我們前面說到過,RT-Thread 面向?qū)ο蟮乃枷?,所有的這些 IPC 機(jī)制都被當(dāng)成一個(gè)對(duì)象,都有一個(gè)結(jié)構(gòu)體控制塊,我們用信號(hào)量結(jié)構(gòu)體來看一看:

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

Kernel object有哪些,我們可以從基礎(chǔ)內(nèi)核對(duì)象結(jié)構(gòu)體定義下面的代碼找到:

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

本節(jié)說明了 RT-Thread 的 IPC 機(jī)制,同時(shí)通過 信號(hào)量的結(jié)構(gòu)體控制塊再一次的認(rèn)識(shí)了 RT-Thread 面向?qū)ο蟮脑O(shè)計(jì)思想。

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

二、信號(hào)量

信號(hào)量官方的說明是:信號(hào)量是一種輕型的用于解決線程間同步問題的內(nèi)核對(duì)象,線程可以獲取或釋放它,從而達(dá)到同步或互斥的目的。

信號(hào)量非常靈活,可以使用的場合也很多:

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

在 FreeRTOS 中存在二值信號(hào)量,但是 RT-Thread 中已經(jīng)沒有了,官方有說明:

在這里插入圖片描述

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

2.1 信號(hào)量控制塊

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

#ifdef RT_USING_SEMAPHORE
/**
 * Semaphore structure
 * value 信號(hào)量的值,直接表明目前信號(hào)量的數(shù)量
 */
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 結(jié)構(gòu)體的指針類型
*/
typedef struct rt_semaphore *rt_sem_t;
#endif

2.2 信號(hào)量操作

2.2.1 創(chuàng)建和刪除

同以前的線程那些一樣,動(dòng)態(tài)的方式,先定義一個(gè)信號(hào)量結(jié)構(gòu)體的指針變量,接收創(chuàng)建好的句柄。

創(chuàng)建信號(hào)量:

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

對(duì)于最后的參數(shù) flag,決定了當(dāng)信號(hào)量不可用時(shí)(就是當(dāng)信號(hào)量為0的時(shí)候),多個(gè)線程等待的排隊(duì)方式。只有RT_IPC_FLAG_FIFO (先進(jìn)先出)或 RT_IPC_FLAG_PRIO(優(yōu)先級(jí)等待)兩種 flag。

關(guān)于用哪一個(gè),要看具體的情況,官方有特意說明:

poYBAGKxL4KAYSAVAABtsu0TwBo813.png

刪除信號(hào)量:

/*
參數(shù):
sem 	rt_sem_create() 創(chuàng)建的信號(hào)量對(duì)象,信號(hào)量句柄
返回值:
RT_EOK 	刪除成功
*/
rt_err_t rt_sem_delete(rt_sem_t sem)

2.2.2 初始化和脫離

靜態(tài)的方式,先定義一個(gè)信號(hào)量結(jié)構(gòu)體,然后對(duì)他進(jìn)行初始化。

初始化信號(hào)量:

/**
參數(shù)的含義:
1、sem 		信號(hào)量對(duì)象的句柄,就是開始定義的信號(hào)量結(jié)構(gòu)體變量
2、name 	信號(hào)量名稱
3、value 	信號(hào)量初始值
4、flag 	信號(hào)量標(biāo)志,它可以取如下數(shù)值: 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)

脫離信號(hào)量:

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

2.2.3 獲取信號(hào)量

當(dāng)信號(hào)量值大于零時(shí),線程將獲得信號(hào)量,并且相應(yīng)的信號(hào)量值會(huì)減 1。

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

注意!要等待的時(shí)間是系統(tǒng)時(shí)鐘節(jié)拍(OS Tick)。

無等待獲取信號(hào)量

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

當(dāng)線程申請(qǐng)的信號(hào)量資源實(shí)例為0時(shí),直接返回 - RT_ETIMEOUT。

2.2.4 釋放信號(hào)量

釋放信號(hào)量可以使得該信號(hào)量+1,如果有線程在等待這個(gè)信號(hào)量,可以喚醒這個(gè)線程。

/**
參數(shù):
sem 	信號(hào)量對(duì)象的句柄
返回值:
RT_EOK 	成功釋放信號(hào)量
 */
rt_err_t rt_sem_release(rt_sem_t sem)

2.2.5 信號(hào)量控制

信號(hào)量控制函數(shù),用來重置信號(hào)量,使得信號(hào)量恢復(fù)為設(shè)定的值:


/**
 * This function can get or set some extra attributions of a semaphore object.
參數(shù):
sem 	信號(hào)量對(duì)象的句柄
cmd    信號(hào)量控制命令 ,支持命令:RT_IPC_CMD_RESET 
arg    暫時(shí)不知道
返回值:
RT_EOK 	成功釋放信號(hào)量

 */
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)

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

在官方論壇有如下說明:
在rt_sem_release后使用rt_sem_control的目的是因?yàn)樵谀承?yīng)用中必須rt_sem_take和rt_sem_release依次出現(xiàn),而不允許rt_sem_release被連續(xù)多次調(diào)用,一旦出現(xiàn)這種情況會(huì)被認(rèn)為是出現(xiàn)了異常,通過調(diào)用rt_sem_control接口來重新初始化 sem_ack恢復(fù)異常。

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

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

示例中,我們使用兩個(gè)不同的按鍵來模擬車輛的進(jìn)出,但是考慮到我們還沒有學(xué)設(shè)備和驅(qū)動(dòng),沒有添加按鍵驅(qū)動(dòng),所以我們用古老的方式來實(shí)現(xiàn)按鍵操作:

按鍵key3,代表車輛離開:

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

按鍵key2,代表車輛進(jìn)入:

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

信號(hào)量的創(chuàng)建,初始10個(gè)車位:

pYYBAGKxL4OAAKKMAAAexk-g3H8704.png

當(dāng)然不要忘了,車輛進(jìn)入和車輛離開(兩個(gè)按鍵)是需要兩個(gè)線程的。

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

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

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

具體的分析需要看rt_sem_release函數(shù)源碼,里面會(huì)判斷是否需要值+1,以及是否需要調(diào)度:

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); //去抖動(dòng)
            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); //去抖動(dòng)
            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);} //去抖動(dòng)
            }
        }
        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);

        /* 如果獲得線程控制塊,啟動(dòng)這個(gè)線程 */
        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);

        /* 如果獲得線程控制塊,啟動(dòng)這個(gè)線程 */
        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);

    /* 如果獲得線程控制塊,啟動(dòng)這個(gè)線程 */
    if (led2_thread != RT_NULL)
        rt_thread_startup(led2_thread);
}

MSH_CMD_EXPORT(led2_Blink, Led2 sample);

三、互斥量

互斥量是一種特殊的二值信號(hào)量。互斥量的狀態(tài)只有兩種,開鎖或閉鎖(兩種狀態(tài)值)。

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

互斥量可以解決優(yōu)先級(jí)翻轉(zhuǎn)問題,它能夠?qū)崿F(xiàn)優(yōu)先級(jí)繼承。

互斥量互斥量不能在中斷服務(wù)例程中使用。

3.1 優(yōu)先級(jí)翻轉(zhuǎn)

優(yōu)先級(jí)翻轉(zhuǎn),我以前寫過:

poYBAGKxL4SAUwRIAABs3UaqWig058.png

再用官方的圖加深理解:

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

3.2 優(yōu)先級(jí)繼承

優(yōu)先級(jí)繼承,以前也寫過:

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

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

3.3 互斥量控制塊

#ifdef RT_USING_MUTEX
/**
 * Mutual exclusion (mutex) structure
 * parent 				繼承ipc類
 * value 				互斥量的值
 * original_priority 	持有線程的原始優(yōu)先級(jí)
 * hold 				持有線程的持有次數(shù),可以多次獲得
 * *owner				當(dāng)前擁有互斥量的線程
 */
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 為指向互斥量結(jié)構(gòu)體的指針類型  */
typedef struct rt_mutex *rt_mutex_t;
#endif

3.4 互斥量操作

3.4.1 創(chuàng)建和刪除

先定義一個(gè)指向互斥量結(jié)構(gòu)體的指針變量,接收創(chuàng)建好的句柄。

創(chuàng)建互斥量:

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

刪除互斥量:

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

3.4.2 初始化和脫離

靜態(tài)的方式,先定義一個(gè)互斥量結(jié)構(gòu)體,然后對(duì)他進(jìn)行初始化。

初始化互斥量:

/**
參數(shù)的含義:
1、mutex 互斥量對(duì)象的句柄,指向互斥量對(duì)象的內(nèi)存塊,開始定義的結(jié)構(gòu)體
2、name 	互斥量名稱
3、flag 	該標(biāo)志已經(jīng)作廢,按照 RT_IPC_FLAG_PRIO (優(yōu)先級(jí))處理
返回值:
RT_EOK 	初始化成功
 */
rt_err_t rt_mutex_init(rt_mutex_t mutex, const char *name, rt_uint8_t flag)

脫離互斥量:

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

3.4.3 獲取互斥量

一個(gè)時(shí)刻一個(gè)互斥量只能被一個(gè)線程持有。

如果互斥量沒有被其他線程控制,那么申請(qǐng)?jiān)摶コ饬康木€程將成功獲得該互斥量。如果互斥量已經(jīng)被當(dāng)前線程線程控制,則該互斥量的持有計(jì)數(shù)加 1,當(dāng)前線程也不會(huì)掛起等待。

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

3.4.4 釋放互斥量

在獲得互斥量后,應(yīng)該盡可能的快釋放互斥量。

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

3.5 示例(優(yōu)先級(jí)繼承)

互斥量做一個(gè)簡單的示例,但是即便簡單,也能體現(xiàn)出優(yōu)先級(jí)繼承這個(gè)機(jī)制。

示例中,我們使用兩個(gè)按鍵,key2按鍵,按一次獲取互斥量,再按一次釋放互斥量,打印自己初始優(yōu)先級(jí),當(dāng)前優(yōu)先級(jí),互斥量占有線程優(yōu)先級(jí)這幾個(gè)量。key3按鍵,按一次,獲取互斥量,立馬就釋放,也打印幾個(gè)優(yōu)先級(jí)。

互斥量的創(chuàng)建,和兩個(gè)線程的優(yōu)先級(jí):

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

測試結(jié)果說明圖:

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

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

還有一個(gè)細(xì)節(jié),就是 RT-Thread 中對(duì)象的 名字,只能顯示8個(gè)字符長度,長了會(huì)截?cái)?,并不影響使用?/strong>

四、事件集

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

事件集主要用于線程間的同步,它的特點(diǎn)是可以實(shí)現(xiàn)一對(duì)多,多對(duì)多的同步。即一個(gè)線程與多個(gè)事件的關(guān)系可設(shè)置為:其中任意一個(gè)事件喚醒線程,或幾個(gè)事件都到達(dá)后才喚醒線程進(jìn)行后續(xù)的處理;同樣,事件也可以是多個(gè)線程同步多個(gè)事件。

RT-Thread 定義的事件集有以下特點(diǎn):

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

4.1 事件集控制塊

#ifdef RT_USING_EVENT
/**
 * flag defintions in event
 * 邏輯與
 * 邏輯或
 * 清除標(biāo)志位
 */
#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 個(gè)事件,bit 位的值可以標(biāo)記某事件是否發(fā)生
 */
struct rt_event
{
    struct rt_ipc_object parent;                        /**< inherit from ipc_object */

    rt_uint32_t          set;                           /**< event set */
};
/* rt_event_t 是指向事件結(jié)構(gòu)體的指針類型  */
typedef struct rt_event *rt_event_t;
#endif

4.2 事件集操作

4.2.1 創(chuàng)建和刪除

先定義一個(gè)指向事件集結(jié)構(gòu)體的指針變量,接收創(chuàng)建好的句柄。

創(chuàng)建事件集:

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

flag 使用哪一個(gè),解釋和信號(hào)量一樣,可參考信號(hào)量創(chuàng)建部分說明。

刪除事件集:

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

4.2.2 初始化和脫離

靜態(tài)的方式,先定義一個(gè)事件集結(jié)構(gòu)體,然后對(duì)他進(jìn)行初始化。

初始化事件集:

/**
參數(shù)的含義:
1、event	事件集對(duì)象的句柄
2、name 	事件集的名稱
3、flag 	事件集的標(biāo)志,它可以取如下數(shù)值: 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)

脫離事件集:

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

4.2.3 發(fā)送事件

發(fā)送事件函數(shù)可以發(fā)送事件集中的一個(gè)或多個(gè)事件。

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

4.2.4 接收事件

內(nèi)核使用 32 位的無符號(hào)整數(shù)來標(biāo)識(shí)事件集,它的每一位代表一個(gè)事件,因此一個(gè)事件集對(duì)象可同時(shí)等待接收 32 個(gè)事件,內(nèi)核可以通過指定選擇參數(shù) “邏輯與” 或“邏輯或”來選擇如何激活線程。

/**
參數(shù)的含義:
1、event		事件集對(duì)象的句柄
2、set			接收線程感的事件
3、option 		接收選項(xiàng),可取的值為
#define RT_EVENT_FLAG_AND               0x01       邏輯與    
#define RT_EVENT_FLAG_OR                0x02       邏輯或    
#define RT_EVENT_FLAG_CLEAR             0x04     選擇清除重置事件標(biāo)志位       
4、timeout		指定超時(shí)時(shí)間
5、recved		指向接收到的事件,如果不在意,可以使用 NULL
返回值:
RT_EOK 			成功
-RT_ETIMEOUT 	超時(shí)
-RT_ERROR 		錯(cuò)誤
 */
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 示例(邏輯與和邏輯或)

事件集通過示例可以很好的理解怎么使用,我們示例中,用按鈕發(fā)送事件,其他線程接收事件,進(jìn)行對(duì)應(yīng)的處理。

按鍵操作:

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

邏輯或測試結(jié)果:

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

邏輯與測試結(jié)果:

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

結(jié)語

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

洋洋灑灑這么多字,最終看下來自己還是挺滿意的,希望我把該表述的都表達(dá)清楚了,希望大家多多提意見,讓博主能給大家?guī)砀玫奈恼隆?/p>

那么下一篇的 RT-Thread 記錄,就要來說說與線程通訊 有關(guān)的 郵箱、消息隊(duì)列和信號(hào)內(nèi)容了。

謝謝!

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請(qǐng)聯(lián)系本站處理。 舉報(bào)投訴
  • IPC
    IPC
    +關(guān)注

    關(guān)注

    3

    文章

    337

    瀏覽量

    51726
  • RT-Thread
    +關(guān)注

    關(guān)注

    31

    文章

    1251

    瀏覽量

    39751
  • 信號(hào)量
    +關(guān)注

    關(guān)注

    0

    文章

    53

    瀏覽量

    8297
收藏 人收藏

    評(píng)論

    相關(guān)推薦

    RT-thread內(nèi)核互斥

    便于恢復(fù)。hold: 表示此互斥鎖被同一線程成功take的次數(shù),一般情況下一個(gè)線程只會(huì)take一次互斥鎖,但rt-thread也允許線程重復(fù)take同一線程,此時(shí)hold的值就用來做記錄
    發(fā)表于 03-06 17:23

    第15章 互斥信號(hào)量

    轉(zhuǎn)rtx操作系統(tǒng) 本章節(jié)開始講解RTX的另一個(gè)重要的資源共享機(jī)制---互斥信號(hào)量(Mutex,即Mutual Exclusion的縮寫)。注意,建議初學(xué)者學(xué)習(xí)完上個(gè)章節(jié)的信號(hào)量后再學(xué)習(xí)
    發(fā)表于 10-06 16:40

    RT-Thread信號(hào)量刪除后釋放信號(hào)量跟獲取信號(hào)量還是成功

    RT-Thread中創(chuàng)建了一個(gè)動(dòng)態(tài)的信號(hào)量,運(yùn)行10次這個(gè)線程后刪除這個(gè)動(dòng)態(tài)信號(hào)量,但是問題是10次后他再次釋放信號(hào)量跟獲取信號(hào)量還是成功的
    發(fā)表于 01-15 05:04

    互斥源碼分析測試

    文章目錄互斥源碼分析測試參考資料:RTT官網(wǎng)文檔關(guān)鍵字:分析RT-Thread源碼、stm32、RTOS、互斥。
    發(fā)表于 08-24 06:01

    淺析RT-Thread中事件的工作機(jī)制

    RT-Thread 中的事件,也就是其他 RTOS 中的事件標(biāo)志組。事件也是線程(任務(wù))間同步的一種機(jī)制。前面介紹的兩種線程間同步的方式(信號(hào)量
    發(fā)表于 04-11 15:31

    【原創(chuàng)精選】RT-Thread征文精選技術(shù)文章合集

    RT-Thread記錄(五、RT-Thread 臨界區(qū)保護(hù))RT-Thread記錄
    發(fā)表于 07-26 14:56

    RT-Thread操作系統(tǒng)互斥的使用方法與場合介紹

    可以進(jìn)入。互斥工作機(jī)制互斥信號(hào)量不同的是:擁有互斥
    發(fā)表于 08-03 11:26

    Rt-thread里面的mem.c函數(shù)保護(hù)lfree全局變量為什么用信號(hào)量

    Rt-thread 里面的mem.c函數(shù)保護(hù)lfree全局變量為什么用信號(hào)量而不是互斥信號(hào)量,用信號(hào)量保護(hù)全局變量不怕造成線程優(yōu)先級(jí)翻轉(zhuǎn)嗎
    發(fā)表于 08-08 10:43

    RT-Thread互斥優(yōu)先級(jí)問題求解

    RT Thread優(yōu)先級(jí)問題,官網(wǎng)視頻,互斥一節(jié),明明是線程2的優(yōu)先級(jí)比線程1高,但線程1會(huì)優(yōu)先運(yùn)行,不知是有什么機(jī)理還是Bug?經(jīng)反復(fù)測試發(fā)現(xiàn),將線程優(yōu)先級(jí)配置到接近線程優(yōu)先級(jí)的最
    發(fā)表于 12-09 15:43

    信號(hào)量互斥在使用過程中會(huì)存在這樣的死鎖嗎?

    如果A線程已經(jīng)獲取了信號(hào)量互斥,但此時(shí)B線程打斷了A線程,信號(hào)量互斥沒有釋放,并且在B線
    發(fā)表于 01-10 15:37

    Linux IPC System V 信號(hào)量

    semctl() //刪除信號(hào)量 ftok()//獲取key值, key值是System V IPC的標(biāo)識(shí)符,成功返回key,失敗返回-1設(shè)errno//同
    發(fā)表于 04-02 14:46 ?303次閱讀

    詳解互斥信號(hào)量的概念和運(yùn)行

    1 、互 斥 信 號(hào) 1.1 互斥信號(hào)量的概念及其作用 互斥信號(hào)量的主要作用是對(duì)資源實(shí)現(xiàn)互斥
    的頭像 發(fā)表于 10-22 11:57 ?1.1w次閱讀
    詳解<b class='flag-5'>互斥</b><b class='flag-5'>信號(hào)量</b>的概念和運(yùn)行

    RT-Thread隱藏的寶藏completion

    completion 直接翻譯過來是完成,所以我們可以稱 rt_completion 為 完成。在 RT-Thread 的文檔中心 中講線程間同步時(shí),介紹了 信號(hào)量...
    發(fā)表于 01-25 18:54 ?0次下載
    <b class='flag-5'>RT-Thread</b>隱藏的寶藏<b class='flag-5'>之</b>completion

    Free RTOS的互斥信號(hào)量

    二進(jìn)制信號(hào)量互斥非常相似,但確實(shí)有一些細(xì)微的區(qū)別。互斥體包含優(yōu)先級(jí)繼承機(jī)制,而二進(jìn)制信號(hào)量
    的頭像 發(fā)表于 02-10 15:36 ?1080次閱讀
    Free RTOS的<b class='flag-5'>互斥</b><b class='flag-5'>信號(hào)量</b>

    使用Linux信號(hào)量實(shí)現(xiàn)互斥點(diǎn)燈

    信號(hào)量常用于控制對(duì)共享資源的訪問,有計(jì)數(shù)型信號(hào)量和二值信號(hào)量之分。初始化時(shí)信號(hào)量值大于1的,就是計(jì)數(shù)型信號(hào)量,計(jì)數(shù)型
    的頭像 發(fā)表于 04-13 15:12 ?759次閱讀
    使用Linux<b class='flag-5'>信號(hào)量</b>實(shí)現(xiàn)<b class='flag-5'>互斥</b>點(diǎn)燈