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



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 許可協議。轉載請注明出處!

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



免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM