RT-Thread學習筆記2-互斥量與訊號量

CrazyCatJack發表於2021-02-18


1. 臨界區保護

臨界區是僅允許一個執行緒訪問的共享資源。它可以是一個具體的硬體裝置,也可以是一個變數、一個緩衝區。多個執行緒必須互斥的對他們進行訪問

1.1 方法一:關閉系統排程保護臨界區

禁止排程

/* 排程器商鎖,上鎖後不再切換到其他執行緒,僅響應中斷 */
rt_enter_critical();
/* 臨界操作 */
rt_exit_critical();

關閉中斷:因為所有的執行緒的排程都是建立在中斷的基礎上的,所以當關閉中斷後系統將不能再進行排程,執行緒自身也自然不會被其他執行緒搶佔了

rt_base_t level;
/* 關閉中斷 */
level = rt_hw_interrupt_disable();
/* 臨界操作 */
rt_hw_interrupt_enable(level);

1.2 方法二:互斥特性保護臨界區

訊號量、互斥量

2. 訊號量

嵌入式系統執行的程式碼主要包括執行緒和ISR,在它們的執行過程中,它們的執行步驟有時需要同步(按照預定的先後次序執行),有時訪問的資源需要互斥(一個時刻只允許一個執行緒訪問資源),有時也需要比本次交換資料。這些機制成為程式間通訊IPC。RT-Thread中的IPC機制包括訊號量、互斥量、事件、郵箱、訊息佇列。通過IPC,可以協調多個執行緒(包括ISR)默契的工作。訊號量是一種輕型的用於解決執行緒間同步問題的核心物件,執行緒可以獲取或釋放它,從而達到同步或互斥的目的。每個訊號量物件都有一個訊號量值和一個執行緒等待佇列。訊號量的值對應訊號量物件的例項數目(資源數目),如果訊號量值N,則表示有N個訊號量例項(資源)可被使用。當值為0時,再請求該訊號量的的執行緒,就會被掛起在該訊號量的等待佇列上。

2.1 訊號量的定義

struct rt_semaphore
{
    struct rt_ipc_object parent; /* IPC物件繼承而來 */
    rt_uint16_t value; /* 訊號量的值 */
}

靜態訊號量:struct rt_semaphore static_sem
動態訊號量:rt_sem_t dynamic_sem

typedef struct rt_semaphore *rt_sem_t;

2.2 訊號量的操作

  1. 初始化與脫離
靜態訊號量:
rt_err_t rt_sem_init(rt_sem_t sem, const char *name, rt_uint32_t value, rt_uint8_t flag)
rt_err_t rt_sem_detach(rt_sem_t sem) //將不用的靜態訊號量從系統中脫離
  1. 建立與刪除
判斷一下返回值是不是RT_NULL
動態訊號量:
rt_sem_t rt_sem_create(const char *name, rt_uint32_t value, rt_uint8_t flag) //等待佇列中的執行緒,當有資源的時候: RT_IPC_FLAG_FIFO先來先服務,先後順序排列 RT_IPC_FLAG_PRIO按照執行緒優先順序排列
rt_err_t rt_sem_delete(rt_sem_t sem)//釋放系統資源
  1. 獲取訊號量
rt_err_t rt_sem_take(rt_sem_t sem, rt_int32_t time) //RT_WAITING_FOREVER = -1, 以系統滴答時鐘為單位。即100HZ,等待10ms的倍數。如果超時則返回-RT_ETIMEOUT.切忌該函式不可在中斷中呼叫,因為它會導致執行緒被掛起。只能線上程呼叫
rt_err_t rt_sem_trytake(rt_sem_t sem) //時間引數為0,一秒鐘都不等待
  1. 釋放訊號量
rt_err_t rt_sem_release(rt_sem_t sem) // 既可以線上程,也可以在中斷中呼叫。因為它不會導致執行緒被掛起

3. 生產者、消費者問題

兩個執行緒,一個生產者執行緒和一個消費者執行緒,兩個執行緒共享一個初始為空、固定大小為n的快取區。生產者的工作是生產一段資料,只有緩衝區沒滿時,生產者才能把訊息放入到緩衝區,否則必須等待,如此反覆。只有緩衝區非空時,消費者才能從中取出資料,一次消費一段資料,否則必須等待。問題的核心是:

  1. 保證不讓生產者在快取還是滿的時候仍然要向內寫資料
  2. 不讓消費者試圖從空的快取中取出資料

解決生產者消費者問題實際上是要解決執行緒間互斥關係和同步關係問題。由於緩衝區是臨界資源,一個時刻只允許一個生產者放入訊息,或者一個消費者從中取出訊息。這裡需要解決一個互斥訪問的問題。同時生產者和消費者又是一個相互協作的關係,只有生產者生產之後,消費者才能消費,所以還需要解決一個同步的問題
BballV.png

/* 生成者執行緒入口 */
void producer_thread_entry(void* parameter)
{
    int cnt = 0;

    /* 執行100次 */
    while( cnt < 100)
    {
        /* 獲取一個空位 */
        rt_sem_take(&sem_empty, RT_WAITING_FOREVER);

        /* 修改array內容,上鎖 */
        rt_sem_take(&sem_lock, RT_WAITING_FOREVER);
        array[set%MAXSEM] = cnt + 1;
        rt_kprintf("the producer generates a number: %d\n", array[set%MAXSEM]);
        set++;
        rt_sem_release(&sem_lock);

        /* 釋出一個滿位 */
        rt_sem_release(&sem_full);
        cnt++;

        /* 暫停一段時間 */
        rt_thread_delay(50);
    }

    rt_kprintf("the producer exit!\n");
}

/* 消費者執行緒入口 */
void consumer_thread_entry(void* parameter)
{
    rt_uint32_t no;
    rt_uint32_t sum;

    /* 第n個執行緒,由入口引數傳進來 */
    no = (rt_uint32_t)parameter;

    sum = 0;
    while(1)
    {
        /* 獲取一個滿位 */
        rt_sem_take(&sem_full, RT_WAITING_FOREVER);

        /* 臨界區,上鎖進行操作 */
        rt_sem_take(&sem_lock, RT_WAITING_FOREVER);
        sum += array[get%MAXSEM];
        rt_kprintf("the consumer[%d] get a number: %d\n", no, array[get%MAXSEM] );
        get++;
        rt_sem_release(&sem_lock);

        /* 釋放一個空位 */
        rt_sem_release(&sem_empty);

        /* 生產者生產到100個數目,停止,消費者執行緒相應停止 */
        if (get == 100) break;

        /* 暫停一小會時間 */
        rt_thread_delay(10);
    }

    rt_kprintf("the consumer[%d] sum is %d \n ", no, sum);
    rt_kprintf("the consumer[%d] exit!\n");
}

int semaphore_producer_consumer_init()
{
    /* 初始化3個訊號量 */
    rt_sem_init(&sem_lock , "lock",     1,      RT_IPC_FLAG_FIFO);
    rt_sem_init(&sem_empty, "empty",    MAXSEM, RT_IPC_FLAG_FIFO);
    rt_sem_init(&sem_full , "full",     0,      RT_IPC_FLAG_FIFO);

    /* 建立執行緒1 */
    producer_tid = rt_thread_create("producer",
                                    producer_thread_entry, RT_NULL, /* 執行緒入口是producer_thread_entry, 入口引數是RT_NULL */
                                    THREAD_STACK_SIZE, THREAD_PRIORITY - 1, THREAD_TIMESLICE);
    if (producer_tid != RT_NULL)
        rt_thread_startup(producer_tid);
    else
        tc_stat(TC_STAT_END | TC_STAT_FAILED);

    /* 建立執行緒2 */
    consumer_tid = rt_thread_create("consumer",
                                    consumer_thread_entry, RT_NULL, /* 執行緒入口是consumer_thread_entry, 入口引數是RT_NULL */
                                    THREAD_STACK_SIZE, THREAD_PRIORITY + 1, THREAD_TIMESLICE);
    if (consumer_tid != RT_NULL)
        rt_thread_startup(consumer_tid);
    else
        tc_stat(TC_STAT_END | TC_STAT_FAILED);

    return 0;
}

4. 互斥量

互斥量控制塊是作業系統用於管理互斥量的一個資料結構。

4.1 互斥量控制塊

struct rt_mutex
{
    struct rt_ipc_object parent; /* IPC物件繼承而來 */
    rt_uint16_t value; /* 只有LOCK和UNLOCK兩種值 */
    rt_uint8_t original_priority; /* 上一次獲得該鎖的執行緒的優先順序 */
    rt_uint8_t hold; /* 該執行緒獲取了多少次該互斥鎖 */
    struct rt_thread *owner; /* 當前擁有該鎖的執行緒控制程式碼 */
}

靜態互斥量:struct rt_mutex static_mutex;
動態互斥量:rt_mutex_t dynamic_mutex;

4.2 互斥量的操作

  1. 初始化與脫離
靜態互斥量
rt_err_t rt_mutex_init(rt_mutex_t mutex, const char *name, rt_uint8_t flag); //RT_IPC_FIFO RT_IPC_FLAG_PRIO
rt_err_t rt_mutex_detach(rt_mutex_t mutex);
  1. 建立與刪除
動態互斥量
rt_mutex rt_mutex_create(const char *name, rt_uint8_t flag);
rt_err_t rt_mutex_delete(rt_mutex_t mutex);
  1. 獲取互斥量
只能線上程中呼叫,且同一個執行緒能夠take多次同一個互斥量,其成員hold+1
rt_err_t rt_mutex_take(rt_mutex_t mutex, rt_int32_t time) // RT_WAITING_FOREVER = -1
  1. 釋放互斥量
只能線上程中呼叫,不能在中斷中呼叫。必須同一個執行緒獲取的同一個互斥量,才能在該執行緒釋放該互斥量
rt_err_t rt_mutex_release(rt_mutex_t mutex)

4.3 互斥量和訊號量的差別

  1. 訊號量可以由任何執行緒(以及中斷)釋放,它用於同步的時候就像交通燈,執行緒只有在獲得許可的時候,才能執行,強調的是執行步驟;互斥量只能由持有它的執行緒釋放,即只有鎖上它的哪個執行緒,才有鑰匙開啟它,強調的是許可和許可權
  2. 使用訊號量可能導致優先順序反轉,互斥量可通過優先順序整合的方法解決優先順序反轉問題

5. 執行緒優先順序翻轉

當一個高優先順序執行緒試圖通過某種互斥IPC物件機制訪問共享資源時,如果該IPC物件已經被一個低優先順序的執行緒所持有,而且這個低優先順序執行緒執行過程中可能又被其他一些中等優先順序的執行緒搶佔,因此造成高優先順序執行緒被許多具有較低優先順序的執行緒阻塞的情況。導致高優先順序的實時性得不到保證

5.1 優先順序繼承

在RT-Thread中,通過互斥量的優先順序繼承演算法,可有有效解決優先順序翻轉問題。優先順序繼承是指提高某個佔有某種共享資源的低優先順序執行緒優先順序,使之與所有等待該資源的執行緒中優先順序最高的那個執行緒的優先順序相等,從而得到更快地執行然後釋放共享資源,當這個低優先順序執行緒釋放該資源時,優先順序重新回到初始設定值。繼承優先順序的執行緒,避免了系統共享資源被任何中間優先順序的執行緒搶佔

優先順序翻轉線向提醒程式設計人員對共享資源進行互斥訪問的程式碼段應儘量短。讓低優先順序執行緒儘快完成工作,釋放共享資源

參考文獻

  1. RT-Thread視訊中心核心入門
  2. RT-Thread文件中心

本文作者: CrazyCatJack

本文連結: https://www.cnblogs.com/CrazyCatJack/p/14408842.html

版權宣告:本部落格所有文章除特別宣告外,均採用 BY-NC-SA 許可協議。轉載請註明出處!

關注博主:如果您覺得該文章對您有幫助,可以點選文章右下角推薦一下,您的支援將成為我最大的動力!


相關文章