FreeRTOS和RT-Thread的資源管理

性感的小君君發表於2020-12-21

資源管理

一、簡述

  • 什麼是資源管理

    防止不同的任務,對同一資源在併發訪問的時候出現髒讀,藏寫現象。對此進行管理的操作叫資源管理。在單執行緒多工的嵌入式領域,資源管理又分為執行緒間同步和執行緒間通訊二個模組的知識。

  • 資源管理的具體實現方式

    1. 關閉中斷: 保證 a 在訪問資源的時候,為了不被 b 中斷,直接關閉中斷功能,導致即使 b 無法產生中斷來打斷 a 的操作。
    2. 關閉資源資源排程器: a 在訪問資源的時候,直接將資源排程器掛起,即使 b 的中斷產生了,但是排程器休眠, 也無法打斷 a 的操作。
    3. 守護執行緒: 有些資源只有 c 才可以訪問, a 和 b 都想訪問的時候,必須通過給 c 傳送訊息,讓 c 來直接操作資源,a 和 b 同時發訊息給 c, 讓 c 來決定先執行誰,在執行誰。

個人總結

就是通過一些操作,防止並行訪問資源的時候,出現髒讀藏寫的管理稱之為資源管理。

二、保證原子操作的二種方式

  • 原子性

    • 通俗解釋

    物理上認為原子是最小的粒子,不能在進行分割。計算機的原子性講的就是操作不能被打斷,不能先執行一部分,過一會子執行另一部分,必須一次性完全執行完成。

    • 專業解釋

    指事務的不可分割性,一個事務的所有操作要麼不間斷地全部被執行,要麼一個也沒有執行。

1. 臨界區

  • 概念

臨界區指的是一個訪問共用資源(例如:共用裝置或是共用儲存器)的程式片段,而這些共用資源又無法同時被多個執行緒訪問的特性。

  • 簡介

每個程式中訪問臨界資源的那段程式碼稱為臨界區(Critical Section)(臨界資源是一次僅允許一個程式使用的共享資源)。每次只准許一個程式進入臨界區,進入後不允許其他程式進入。不論是硬體臨界資源,還是軟體臨界資源,多個程式必須互斥地對它進行訪問。

  • 簡述

把某優先順序以下的中斷全部關閉。進行原子操作。

  • 原理

把中斷全部關掉,或是關掉優先順序在 、configMAX_SYSCAL_INTERRUPT_PRIORITY 及以下的中斷——依賴於具體使用的 FreeRTOS 移植。搶佔式上下文切換隻可能在某個中斷中完成,所以呼叫 taskENTER_CRITICAL() 的任務可以在中斷關閉的時段一直保持執行態,直到退出臨界區.

API

臨界區必須只具有很短的時間,否則會反過來影響中斷響應時間。在每次呼叫 taskENTER_CRITICAL() 之後,必須儘快地配套呼叫一個 taskEXIT_CRITICAL(), 讀寫這種比較耗費時間的操作,儘量不要使用臨界區這種保護機制。

  • FreeRTOS
void vPrintString( const portCHAR *pcString )
{
    /* 往stdout中寫字串,使用臨界區這種原始的方法實現互斥。 */
    taskENTER_CRITICAL();
    {
        printf( "%s", pcString );
        fflush( stdout );
    }
    taskEXIT_CRITICAL();
    /* 允許按任意鍵停止應用程式執行。實際的應用程式如果有使用到鍵值,還需要對鍵盤輸入進行保護。 */
    if( kbhit() )
    {
    	vTaskEndScheduler();
    }
}
  • RT-Thread
1)呼叫 rt_hw_interrupt_disable() 進入臨界區,呼叫 rt_hw_interrupt_enable() 退出臨界區;
2)呼叫 rt_enter_critical() 進入臨界區,呼叫 rt_exit_critical() 退出臨界區。

2. 掛起排程器

直接把任務排程器給掛起鎖死,這個時候外部中斷雖然使能,但是無法操作。

需要建立臨界區除了 FreeRTOS 作業系統提供的函式外,還可以通過掛起排程器的操作來建立一個臨界區。但是重新喚醒(resuming, or un-suspending)排程器是一個費時的操作。所以評估使用臨界區函式來實現原子操作還是掛起排程器實現原子操作,需要結合實際情況。

在排程器處於掛起狀態時,不能呼叫 FreeRTOS API 函式。

  • API
void vPrintString( const portCHAR *pcString )
{
    /* Write the string to stdout, suspending the scheduler as a method
    of mutual exclusion. */
    vTaskSuspendScheduler();
    {
        printf( "%s", pcString );
        fflush( stdout );
    }
    xTaskResumeScheduler();
    /* Allow any key to stop the application running. A real application that
    actually used the key value should protect access to the keyboard input too. */
    if( kbhit() )
    {
        vTaskEndScheduler();
    }
}

個人總結

就是將外部中斷給遮蔽,原本的操作就不會被打斷,這樣操作起來就具有原子性了。

三、互斥量(mutex)

互斥量是一種特殊的二值訊號量,用於控制在兩個或多個任務間訪問共享資源。

  • 互斥量的特點

    用於互斥的訊號量必須歸還。

    用於同步的訊號量通常是完成同步之後便丟棄,不再歸還

  • API

static void prvNewPrintString( const portCHAR *pcString )
{
    /* 互斥量在排程器啟動之前就已建立,所以在此任務執行時訊號量就已經存在了。
    試圖獲得互斥量。如果互斥量無效,則將阻塞,進入無超時等待。xSemaphoreTake()只可能在成功獲得互
    斥量後返回,所以無需檢測返回值。如果指定了等待超時時間,則程式碼必須檢測到xSemaphoreTake()返回
    pdTRUE後,才能訪問共享資源(此處是指標準輸出)。 */
    xSemaphoreTake( xMutex, portMAX_DELAY );
    {
        /* 程式執行到這裡表示已經成功持有互斥量。現在可以自由訪問標準輸出,因為任意時刻只會有一個任
        務能持有互斥量。 */
        printf( "%s", pcString );
        fflush( stdout );
    }
    /* 互斥量必須歸還! */
    xSemaphoreGive( xMutex );
    
    /* Allow any key to stop the application running. A real application that
    actually used the key value should protect access to the keyboard too. A
    real application is very unlikely to have more than one task processing
    key presses though! */
    if( kbhit() )
    {
    	vTaskEndScheduler();
    }
}


int main( void )
{
    /* 訊號量使用前必須先建立。本例建立了一個互斥量型別的訊號量。 */
    xMutex = xSemaphoreCreateMutex();
    /* 本例中的任務會使用一個隨機延遲時間,這裡給隨機數發生器生成種子。 */
    srand( 567 );
    /* Check the semaphore was created successfully before creating the tasks. */
    if( xMutex != NULL )
    {
        /* Create two instances of the tasks that write to stdout. The string
        they write is passed in as the task parameter. The tasks are created
        at different priorities so some pre-emption will occur. */
        xTaskCreate( prvPrintTask, "Print1", 1000,
        "Task 1 ******************************************\r\n", 1, NULL );
        xTaskCreate( prvPrintTask, "Print2", 1000,
        "Task 2 ------------------------------------------\r\n", 2, NULL );
        /* Start the scheduler so the created tasks start executing. */
        vTaskStartScheduler();
    }
    /* 如果一切正常,main()函式不會執行到這裡,因為排程器已經開始執行任務。但如果程式執行到了這裡,
    很可能是由於系統記憶體不足而無法建立空閒任務。 */
    for( ;; );
}

image-20201220233035060

image-20201220233211865

優先順序反轉

高優先順序任務被低優先順序任務阻塞推遲的行為被稱為”優先順序反轉”。

優先順序反轉可能會產生重大問題。但是在一個小型的嵌入式系統中,通常可以在設計階段就通過規劃好資源的訪問方式避免出現這個問題。’

優先順序繼承

優先順序繼承是最小化優先順序反轉負面影響的一種方案-其並不能修正優先順序反轉帶來的問題,僅僅是減小優先順序反轉的影響。優先順序繼承
使得系統行為的數學分析更為複雜,所以如果可以避免的話,並不建議系統實現對優先
級繼承有所依賴。

優先順序繼承暫時地將互斥量持有者的優先順序提升至所有等待此互斥量的任務所具有的最高優先順序。

image-20201220233749531

死鎖

  1. 任務 A 執行,併成功獲得了互斥量 X。
  2. 任務 A 被任務 B 搶佔。
  3. 任務 B 成功獲得了互斥量 Y,之後又試圖獲取互斥量 X——但互斥量 X 已經被任務 A 持有,所以對任務 B 無效。任務 B 選擇進入阻塞態以等待互斥量 X 被釋放。
  4. 任務 A 得以繼續執行。其試圖獲取互斥量 Y——但互斥量 Y 已經被任務 B持有而對任務 A 無效。任務 A 也選擇進入阻塞態以等待互斥量 Y 被釋放。

任務 A 在等待一個被任務 B 持有的互斥量,而任務 B 也在等待一個被任務 A 持有的互斥量。死鎖於是發生,因為兩個任務都不可能再執行下
去了。

四、守護任務

  1. 守護任務是對某個資源具有唯一所有權的任務。只有守護任務才可以直接訪問其守護的資源——其它任務要訪問該資源只能間接地通過守護任務提供的服務

  2. 守護任務提供了一種乾淨利落的方法來實現互斥功能,而不用擔心會發生優先順序反轉和死鎖。

守護任務使用了一個 FreeRTOS 佇列來對終端實現序列化訪問。該任務內部實現不必考慮互斥,因為它是唯一能夠直接訪問終端的任務。

  • 總結

有些資源只有守護執行緒才能訪問,使用的時候發訊息給守護執行緒,讓守護執行緒去訪問資源,從而避免死鎖和優先順序反轉的問題

零: 執行緒間同步的名字解釋

1. 訊號量

訊號量是一種輕型的用於解決執行緒間同步問題的核心物件,執行緒可以獲取或釋放它,從而達到同步或互斥的目的。

訊號量(Semaphore),有時被稱為訊號燈,是在多執行緒環境下使用的一種設施,是可以用來保證兩個或多個關鍵程式碼段不被併發呼叫。在進入一個關鍵程式碼段之前,執行緒必須獲取一個訊號量;一旦該關鍵程式碼段完成了,那麼該執行緒必須釋放訊號量。其它想進入該關鍵程式碼段的執行緒必須等待直到第一個執行緒釋放訊號量 引用自: 百度百科-訊號量

image-20201221005005945

互斥量和訊號量

  1. 互斥量用於執行緒的互斥,訊號量用於執行緒的同步。

    這是互斥量和訊號量的根本區別,也就是互斥和同步之間的區別。

2.互斥量無法保證執行緒對資源的有序訪問,訊號量可以。

二值訊號量和互斥鎖的區別

相關文章