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


1. 臨界區保護

臨界區是僅容許一個線程訪問的共享資源。它能夠是一個具體的硬件設備,也能夠是一個變量、一個緩衝區。多個線程必須互斥的對他們進行訪問html

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的緩存區。生產者的工做是生產一段數據,只有緩衝區沒滿時,生產者才能把消息放入到緩衝區,不然必須等待,如此反覆。只有緩衝區非空時,消費者才能從中取出數據,一次消費一段數據,不然必須等待。問題的核心是:ide

  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. 互斥量

互斥量控制塊是操做系統用於管理互斥量的一個數據結構。ui

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

關注博主:若是您以爲該文章對您有幫助,能夠點擊文章右下角推薦一下,您的支持將成爲我最大的動力!

相關文章
相關標籤/搜索