從0開始學FreeRTOS-(任務調度)-4

来源:https://www.cnblogs.com/iot-dev/archive/2019/10/15/11681020.html
-Advertisement-
Play Games

大家晚上好,我是傑傑,最近挺忙的,好久沒有更新了,今天周末就吐血更新一下吧! 前言 是一個是實時內核,任務是程式執行的最小單位,也是調度器處理的基本單位,移植了 ,則避免不了對任務的管理,在多個任務運行的時候,任務切換顯得尤為重要。而任務切換的效率會決定了系統的穩定性與效率。 的任務切換是幹嘛的呢, ...


大家晚上好,我是傑傑,最近挺忙的,好久沒有更新了,今天周末就吐血更新一下吧!

前言

FreeRTOS是一個是實時內核,任務是程式執行的最小單位,也是調度器處理的基本單位,移植了FreeRTOS,則避免不了對任務的管理,在多個任務運行的時候,任務切換顯得尤為重要。而任務切換的效率會決定了系統的穩定性與效率。

FreeRTOS的任務切換是幹嘛的呢,rtos的實際是永遠運行的是具有最高優先順序的運行態任務,而那些之前在就緒態的任務怎麼變成運行態使其得以運行呢,這就是我們FreeRTOS任務切換要做的事情,它要做的是找到最高優先順序的就緒態任務,並且讓它獲得cpu的使用權,這樣,它就能從就緒態變成運行態,這樣子,整個系統的實時性就會很好,響應也會很好,而不會讓程式阻塞卡死。

要知道怎麼實現任務切換,那就要知道任務切換的機制,在不同的cpu(mcu)中,觸發的方式可能會不一樣,現在是以Cortex-M3為例來講講任務的切換。為了大家能看懂本文,我就拋轉引玉一下,引用《Cortex-M3權威指南-中文版》的部分語句(如涉及侵權,請聯繫傑傑刪除)

SVC 和 PendSV

SVC(系統服務調用,亦簡稱系統調用)和 PendSVPended System Call,可懸起系統調用),它們多用於在操作系統之上的軟體開發中。SVC 用於產生系統函數的調用請求。例如,操作系統不讓用戶程式直接訪問硬體,而是通過提供一些系統服務函數,用戶程式使用 SVC 發出對系統服務函數的呼叫請求,以這種方法調用它們來間接訪問硬體。因此,當用戶程式想要控制特定的硬體時,它就會產生一個 SVC 異常,然後操作系統提供的 SVC 異常服務常式得到執行,它再調用相關的操作系統函數,後者完成用戶程式請求的服務。

另一個相關的異常是PendSV(可懸起的系統調用),它和 SVC 協同使用。一方面,SVC異常是必須立即得到響應的(若因優先順序不比當前正處理的高,或是其它原因使之無法立即響應,將發生硬 fault——譯者註),應用程式執行 SVC 時都是希望所需的請求立即得到響應。另一方面,PendSV 則不同,它是可以像普通的中斷一樣被懸起的(不像 SVC 那樣)。OS 可以利用它“緩期執行”一個異常——直到其它重要的任務完成後才執行動作。懸起 PendSV 的方法是:手工往 NVICPendSV 懸起寄存器中寫 1。懸起後,如果優先順序不夠高,則將緩期等待執行。

如果一個發生的異常不能被即刻響應,就稱它被“懸起”(pending)。不過,少數 fault異常是不允許被懸起的。一個異常被懸起的原因,可能是系統當前正在執行一個更高優先順序異常的服務常式,或者因相關掩蔽位的設置導致該異常被除能。對於每個異常源,在被懸起的情況下,都會有一個對應的“懸起狀態寄存器”保存其異常請求,直到該異常能夠執行為止,這與傳統的 ARM 是完全不同的。在以前,是由產生中斷的設備保持住請求信號。現在NVIC 的懸起狀態寄存器的出現解決了這個問題,即使後來設備已經釋放了請求信號,曾經的中斷請求也不會錯失。

系統任務切換的工程分析

在系統中正常執行的任務(假設沒有外部中斷IRQ),用Systick直接做上下文切換是完全沒有問題的,如圖:

switch

但是問題是幾乎很少嵌入式的設備會不用其豐富的中斷響應,所以,直接用systick做系統的上下文切換那是不實際的,這存在很大的風險,因為假設systick打斷了一個中斷(IRQ),立即做出上下文切換的話,則觸犯用法 fault異常,除了重啟你沒有其他辦法了,這樣子做出來的產品就是垃圾!!用我老闆的話說就是寫的什麼狗屎!!!如圖所示:

IRQ-switch

那這麼說這樣不行那也不行,怎麼辦啊?請看看前面接介紹的PendSV,是不是有點豁然開朗了?PendSV 來完美解決這個問題。PendSV 異常會自動延遲上下文切換的請求,直到其它的ISR 都完成了處理後才放行。為實現這個機制,需要把 PendSV 編程為最低優先順序的異常。如果OS檢測到某 IRQ正在活動並且被 SysTick 搶占,它將懸起一個 PendSV 異常,以便緩期執行上下文切換。

懂了嗎?就是說,只要將PendSV的優先順序設為最低的,systick即使是打斷了IRQ,它也不會馬上進行上下文切換,而是等到IRQ執行完,PendSV 服務常式才開始執行,並且在裡面執行上下文切換。過程如圖所示:

pendsv-switch

任務切換的源碼實現

過程差不多瞭解了,那看看FreeRTOS中怎麼實現吧!!

FreeRTOS有兩種方法觸發任務切換:

  1. 一種就是systick觸發PendSV異常,這是最經常使用的。

  2. 另一種是主動進行切換任務,執行系統調用,比如普通任務可以使用taskYIELD()強制任務切換,中斷服務程式中使用portYIELD_FROM_ISR()強制任務切換。

第一種

先說說第一種吧,就在systick中斷中調用xPortSysTickHandler();

下麵是源碼:

void xPortSysTickHandler( void )
{
    vPortRaiseBASEPRI();
    {
        /* Increment the RTOS tick. */
        if( xTaskIncrementTick() != pdFALSE )
        {
            /* A context switch is required.  Context switching is performed in
            the PendSV interrupt.  Pend the PendSV interrupt. */
            portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT;
        }
    }
    vPortClearBASEPRIFromISR();
}

它的執行過程是這樣子的,屏蔽所有中斷,因為SysTick以最低的中斷優先順序運行,所以當這個中斷執行時所有中斷必須被屏蔽。vPortRaiseBASEPRI();就是屏蔽所有中斷的。而且並不需要保存本次中斷的值,因為systick的中斷優先順序是已知的,執行完直接恢復所有中斷即可。

xTaskIncrementTick()中會對tick的計數值進行自加,然後檢查有沒有處於就緒態的最優先順序任務,如果有,則返回非零值,然後表示需要進行任務切換,而並非馬上進行任務切換,此處要註意,它只是向中斷狀態寄存器bit28位寫入1,只是將PendSV掛起,假如沒有比PendSV更高優先順序的中斷,它才會進入PendSV中斷服務函數進行任務切換。

#define portNVIC_PENDSVSET_BIT        ( 1UL << 28UL )

然後解除屏蔽所有中斷。

vPortClearBASEPRIFromISR();

第二種

另一種方法是主動進行任務切換,不管是使用taskYIELD()還是portYIELD_FROM_ISR(),最終都會執行下麵的代碼:

#define portYIELD()                                                                \
{                                                                                \
    /* Set a PendSV to request a context switch. */                             \
    portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT;                             \                                                                       
    __dsb( portSY_FULL_READ_WRITE );                                            \
    __isb( portSY_FULL_READ_WRITE );                                            \
}

portYIELD()其實是一個巨集定義來的。同樣是向中斷狀態寄存器bit28位寫入1,將PendSV掛起,然後等待任務的切換。

具體的任務切換源碼

一直在說怎麼進行任務切換的,好像還沒看到任務切換的源碼啊,哎,下麵來看看任務切換的真面目!!

__asm void xPortPendSVHandler(void)
{
    extern uxCriticalNesting;
    extern pxCurrentTCB;
    extern vTaskSwitchContext;
    PRESERVE8
    mrs r0, psp
    isb
    ldr r3, =pxCurrentTCB       /* Get the location of the current TCB. */
    ldr r2, [r3]
    stmdb r0!, {r4-r11}         /* Save the remaining registers. */
    str r0, [r2]                /* Save the new top of stack into the first member of the TCB. */
    stmdb sp!, {r3, r14}
    mov r0, #configMAX_SYSCALL_INTERRUPT_PRIORITY
    msr basepri, r0
    dsb
    isb
    bl vTaskSwitchContext
    mov r0, #0
    msr basepri, r0
    ldmia sp!, {r3, r14}
    ldr r1, [r3]
    ldr r0, [r1]                /* The first item in pxCurrentTCB is the task top of stack. */
    ldmia r0!, {r4-r11}         /* Pop the registers and the critical nesting count. */
    msr psp, r0
    isb
    bx r14
    nop
}

不是我不想看,是我看到彙編就頭大啊,這幾天我也在看源碼,實在是頭大。

找到核心的函數看看就好啦,不管那麼多,有興趣的可以研究一下中斷代碼,有不懂的也很歡迎你們來問我,一起研究研究,也是不錯的選擇。

下麵是看重點的地方了:

mov r0,             #configMAX_SYSCALL_INTERRUPT_PRIORITY
msr basepri, r0

這兩句代碼是關閉中斷的。關中斷就得幹活了,嘿嘿嘿~

bl vTaskSwitchContext

BL是跳轉指令嘛,這個我還是有點懂的。

調用函數vTaskSwitchContext(),尋找新的任務運行,通過使變數pxCurrentTCB指向新的任務來實現任務切換,然後就是打開中斷,退出去了。

尋找下一個要運行任務

是不是感覺沒什麼大不了的樣子,如果你是這樣子覺得的,可能還沒學到家,趕緊去看看FreeRTOS的源碼,在config.h配置文件中是不是有一個叫做硬體查找下一個運行的任務呢?configUSE_PORT_OPTIMISED_TASK_SELECTION,這個在FreeRTOS中叫做特殊方法,其實也是硬體查找啦,但是並不是每種單片機都支持的,如果是不支持的話,只能選擇軟體查找的方法了,就是所謂的通用方法。通用方法我就不多說了,因為我用的是STM32,他是支持硬體方法的,這樣子效率更高,所以我也沒必要去研究他的軟體方法,假如有興趣的小伙伴可以研讀一下源碼,有不懂的可以向我提問,源碼如下:

#define taskSELECT_HIGHEST_PRIORITY_TASK()                                                            \
    {                                                                                                   \
    UBaseType_t uxTopPriority = uxTopReadyPriority;                                                     \
                                                                                                        \
        /* Find the highest priority queue that contains ready tasks. */                                \
        while( listLIST_IS_EMPTY( &( pxReadyTasksLists[ uxTopPriority ] ) ) )                           \
        {                                                                                               \
            configASSERT( uxTopPriority );                                                              \
            --uxTopPriority;                                                                            \
        }                                                                                               \
                                                                                                        \
        /* listGET_OWNER_OF_NEXT_ENTRY indexes through the list, so the tasks of                        \
        the same priority get an equal share of the processor time. */                                  \
        listGET_OWNER_OF_NEXT_ENTRY( pxCurrentTCB, &( pxReadyTasksLists[ uxTopPriority ] ) );           \
        uxTopReadyPriority = uxTopPriority;                                                             \
    } /* taskSELECT_HIGHEST_PRIORITY_TASK */

而硬體的方法源碼則在下麵:

    #define taskSELECT_HIGHEST_PRIORITY_TASK()                                                      \
    {                                                                                               \
        UBaseType_t uxTopPriority;                                                                  \
                                                                                                    \
        /* Find the highest priority list that contains ready tasks. */                             \
        portGET_HIGHEST_PRIORITY( uxTopPriority, uxTopReadyPriority );                              \
        configASSERT( listCURRENT_LIST_LENGTH( &( pxReadyTasksLists[ uxTopPriority ] ) ) > 0 );     \
        listGET_OWNER_OF_NEXT_ENTRY( pxCurrentTCB, &( pxReadyTasksLists[ uxTopPriority ] ) );       \
    } /* taskSELECT_HIGHEST_PRIORITY_TASK() */

其方法是利用硬體提供的計算前導零指令CLZ,具體巨集定義為:

#define portGET_HIGHEST_PRIORITY( uxTopPriority, uxReadyPriorities ) uxTopPriority = ( 31UL - ( uint32_t ) __clz( ( uxReadyPriorities ) ) )

靜態變數uxTopReadyPriority包含了處於就緒態任務的最高優先順序的信息,因為FreeRTOS運行的永遠是處於最高優先順序的運行態,而下個處於最高優先順序的就緒態則必定會在下次任務切換的時候運行,uxTopReadyPriority使用每一位來表示任務是否處於就緒態,比如變數uxTopReadyPrioritybit0為1,則表示存在優先順序為0的任務處於就緒態,bit6為1則表示存在優先順序為6的任務處於就緒態。並且,由於bit0的優先順序高於bit6,那麼下個任務就是bit0的任務運行了(數組越低優先順序越高)。由於32位整形數最多只有32位,因此使用這種特殊方法限定最大可用優先順序數目為32,即優先順序0~31。得到了下個處於最高優先順序就緒態任務了,就調用listGET_OWNER_OF_NEXT_ENTRY來獲取下一個任務的列表項,然後將該列表項的任務控制塊TCB賦值給pxCurrentTCB,那麼我們就得到下一個要運行的任務了。

至此,任務切換已經完成。

END

關註我

歡迎關註我公眾號

更多資料歡迎關註“物聯網IoT開發”公眾號!


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • awk介紹 awk的工作原理 awk基本格式 awk之print格式 awk變數 awk之printf awk操作符 awk PATTERN awk action awk控制語句 awk控制語句if-else awk控制語句 while迴圈 awk控制語句do-while迴圈 awk控制語句 for ...
  • 準備 在移植之前,我們首先要獲取到FreeRTOS的官方的源碼包。這裡我們提供兩個下載鏈接: 一個是官網:http://www.freertos.org/ 另外一個是代碼托管網站:https://sourceforge.net/projects/freertos/files/FreeRTOS/ 這裡 ...
  • 寫在前面:傑傑這個月很忙~所以並沒有時間更新,現在健身房閉館裝修,晚上有空就更新一下!其實在公眾號沒更新的這段日子,每天都有兄弟在來關註我的公眾號,這讓我受寵若驚,在這裡謝謝大家的支持啦!!謝謝^ 在這裡我們就跟著火哥的書來學習一下FreeRTOS的消息隊列,這本書我覺得寫得很好,基本都講解到了,關 ...
  • 寫在前面 主要是為剛接觸 FreeRTOS 的用戶指出那些新手通常容易遇到的問題。這裡把最主要的篇幅放在棧溢出以及棧溢出j檢測上,因為棧相關的問題是初學者遇到最多的問題。 printf stdarg.c 當調用 C 標準庫 的函數時,棧空間使用量可能會急劇上升,特別是 IO 與字元串處理函數,比如 ...
  • 沒研究過操作系統的源碼都不算學過操作系統 FreeRTOS 時間管理 時間管理包括兩個方面:系統節拍以及任務延時管理。 系統節拍: 在前面的文章也講得很多,想要系統正常運行,那麼時鐘節拍是必不可少的, 的時鐘節拍通常由 提供,它周期性的產生定時中斷,所謂的時鐘節拍管理的核心就是這個定時中斷的服務程式 ...
  • FreeRTOS列表&列表項的源碼解讀 第一次看列表與列表項的時候,感覺很像是鏈表,雖然我自己的鏈表也不太會,但是就是感覺很像。 在FreeRTOS中,列表與列表項使用得非常多,是FreeRTOS的一個數據結構,學習過數據結構的同學都知道,數據結構能使我們處理數據更加方便快速,能快速找到數據,在Fr ...
  • 問題解答 曾經有人問我,FreeRTOS那麼多API,到底怎麼記住呢? 我想說,其實API不難記,就是有點難找,因為FreeRTOS的API很多都是帶參巨集,所以跳來跳去的比較麻煩,而且註釋也很多,要找還真不是那麼容易,不過也沒啥,一般都會有API手冊的,我就告訴大家一下: FreeRTOS Kern ...
  • FreeRTOS列表&列表項的源碼解讀 第一次看列表與列表項的時候,感覺很像是鏈表,雖然我自己的鏈表也不太會,但是就是感覺很像。 在 中,列表與列表項使用得非常多,是 的一個數據結構,學習過數據結構的同學都知道,數據結構能使我們處理數據更加方便快速,能快速找到數據,在 中,這種列表與列表項更是必不可 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...