<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          FreeRTOS系列第22篇---FreeRTOS任務(wù)切換分析

          共 11270字,需瀏覽 23分鐘

           ·

          2021-06-12 22:12


          關(guān)注、星標公眾號,直達精彩內(nèi)容

          ID:技術(shù)讓夢想更偉大

          整理:李肖遙


          FreeRTOS任務(wù)相關(guān)的代碼大約占總代碼的一半左右,這些代碼都在為一件事情而努力,即找到優(yōu)先級最高的就緒任務(wù),并使之獲得CPU運行權(quán)。

          任務(wù)切換是這一過程的直接實施者,為了更快的找到優(yōu)先級最高的就緒任務(wù),任務(wù)切換的代碼通常都是精心設(shè)計的,甚至?xí)玫絽R編指令或者與硬件相關(guān)的特性,比如Cortex-M3的CLZ指令。

          因此任務(wù)切換的大部分代碼是由硬件移植層提供的,不同的平臺,實現(xiàn)發(fā)方法也可能不同。

          ?

          這篇文章以Cortex-M3為例,講述FreeRTOS任務(wù)切換的過程。

          ?

          「FreeRTOS有兩種方法觸發(fā)任務(wù)切換:」

          • 執(zhí)行系統(tǒng)調(diào)用,比如普通任務(wù)可以使用taskYIELD()強制任務(wù)切換,中斷服務(wù)程序中使用portYIELD_FROM_ISR()強制任務(wù)切換;
          • 系統(tǒng)節(jié)拍時鐘中斷

          對于Cortex-M3平臺,這兩種方法的實質(zhì)是一樣的,都會使能一個PendSV中斷,在PendSV中斷服務(wù)程序中,找到最高優(yōu)先級的就緒任務(wù),然后讓這個任務(wù)獲得CPU運行權(quán),從而完成任務(wù)切換。

          對于第一種任務(wù)切換方法,不管是使用taskYIELD()還是portYIELD_FROM_ISR(),最終都會執(zhí)行宏portYIELD(),這個宏的定義如下:

          #define portYIELD()      \
          {        \
           /*產(chǎn)生PendSV中斷*/                          \
           portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT;  \
          }

          對于第二種任務(wù)切換方法,在系統(tǒng)節(jié)拍時鐘中斷服務(wù)函數(shù)中,首先會更新tick計數(shù)器的值、查看是否有任務(wù)解除阻塞,如果有任務(wù)解除阻塞的話,則使能PandSV中斷,代碼如下所示:

          void xPortSysTickHandler( void )
          {
           /* 設(shè)置中斷掩碼 */
           vPortRaiseBASEPRI();
           {
            /* 增加tick計數(shù)器值,并檢查是否有任務(wù)解除阻塞 */
            if( xTaskIncrementTick() != pdFALSE )
            {
             /* 需要任務(wù)切換。產(chǎn)生PendSV中斷 */
             portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT;
            }
           }
           vPortClearBASEPRIFromISR();
          }

          從上面的代碼中可以看出,PendSV中斷的產(chǎn)生是通過代碼:

          portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT

          實現(xiàn)的,它向中斷狀態(tài)寄存器bit28位寫入1,將PendSV中斷設(shè)置為掛起狀態(tài);

          等到優(yōu)先級高于PendSV的中斷執(zhí)行完成后,PendSV中斷服務(wù)程序?qū)⒈粓?zhí)行,進行任務(wù)切換工作。

          Cortex-M3架構(gòu)下,PendSV中斷服務(wù)程序源碼如下所示,這篇文章重點分析這段代碼。

          __asm void xPortPendSVHandler( void )
          {
           extern uxCriticalNesting;
           extern pxCurrentTCB;            /* 指向當(dāng)前激活的任務(wù) */
           extern vTaskSwitchContext;      
           
           PRESERVE8
           
           mrs r0, psp                   /* PSP內(nèi)容存入R0 */    
           isb                           /* 指令同步隔離,清流水線 */
           
           ldr r3, =pxCurrentTCB     /* 當(dāng)前激活的任務(wù)TCB指針存入R2 */
           ldr r2, [r3]
           
           stmdb r0!, {r4-r11}          /* 保存剩余的寄存器,異常處理程序執(zhí)行前,硬件自動將xPSR、PC、LR、R12、R0-R3入棧 */
           str r0, [r2]       /* 將新的棧頂保存到任務(wù)TCB的第一個成員中 */
           
           stmdb sp!, {r3, r14}         /* 將R3和R14臨時壓入堆棧,因為即將調(diào)用函數(shù)vTaskSwitchContext,調(diào)用函數(shù)時,返回地址自動保存到R14中,所以一旦調(diào)用發(fā)生,R14的值會被覆蓋,因此需要入棧保護; R3保存的當(dāng)前激活的任務(wù)TCB指針(pxCurrentTCB)地址,函數(shù)調(diào)用后會用到,因此也要入棧保護*/
           mov r0, #configMAX_SYSCALL_INTERRUPT_PRIORITY   /* 進入臨界區(qū) */
           msr basepri, r0
           dsb                         /* 數(shù)據(jù)和指令同步隔離 */
           isb
           bl vTaskSwitchContext        /* 調(diào)用函數(shù),尋找新的任務(wù)運行,通過使變量pxCurrentTCB指向新的任務(wù)來實現(xiàn)任務(wù)切換 */
           mov r0, #0                   /* 退出臨界區(qū)*/
           msr basepri, r0
           ldmia sp!, {r3, r14}         /* 恢復(fù)R3和R14*/
           
           ldr r1, [r3]
           ldr r0, [r1]       /* 當(dāng)前激活的任務(wù)TCB第一項保存了任務(wù)堆棧的棧頂,現(xiàn)在棧頂值存入R0*/
           ldmia r0!, {r4-r11}      /* 出棧*/
           msr psp, r0
           isb
           bx r14                      /* 異常發(fā)生時,R14中保存異常返回標志,包括返回后進入線程模式還是處理器模式、使用PSP堆棧指針還是MSP堆棧指針,當(dāng)調(diào)用 bx r14指令后,硬件會知道要從異常返回,然后出棧,這個時候堆棧指針PSP已經(jīng)指向了新任務(wù)堆棧的正確位置,當(dāng)新任務(wù)的運行地址被出棧到PC寄存器后,新的任務(wù)也會被執(zhí)行。*/
           nop
          }

          為了便于理解上面的代碼,我們先用流程圖的方式將整個過程畫出來,然后再逐句分析代碼。因為圖形可以簡化程序,并且信息更容易接受。

          圖1-1:任務(wù)切換流程

          先強調(diào)圖1-1中的幾個術(shù)語,首先是“主堆棧指針MSP”和“進程堆棧指針PSP”。

          對于Cortex-M3硬件,當(dāng)系統(tǒng)復(fù)位后,默認使用MSP指針。

          MSP指針用于操作系統(tǒng)內(nèi)核以及處理異常(也就是說中斷服務(wù)程序中默認強制使用MSP指針,這是硬件自動設(shè)置的)。

          任務(wù)(進程)使用PSP指針,操作系統(tǒng)負責(zé)從MSP指針切換到PSP指針。

          這個過程在《FreeRTOS高級篇3---啟動調(diào)度器》一文的最后部分中「進行了講解」

          在SVC中斷服務(wù)程序中啟動第一個任務(wù),當(dāng)從SVC中斷服務(wù)退出前,通過向r14寄存器最后4位按位或上0x0D,使得硬件在退出時使用進程堆棧指針PSP完成出棧操作并返回后進入線程模式、返回Thumb狀態(tài)。

          其次,“堆棧”和“任務(wù)堆棧”也值得強調(diào)一下。

          每個任務(wù)都有自己的“任務(wù)堆棧”,在任務(wù)創(chuàng)建時會創(chuàng)建指定大小的任務(wù)堆棧,這是任務(wù)能夠獨立運行的前提條件之一。

          在任務(wù)中定義的局部變量,會優(yōu)先使用寄存器,寄存器不夠時就使用任務(wù)堆棧的空間。

          如果在任務(wù)中調(diào)用其它函數(shù),則調(diào)用前的保存信息也存到任務(wù)堆棧中去。

          根據(jù)任務(wù)代碼來估算任務(wù)堆棧的大小是件十分重要的技能。

          前面也說了,Cortex-M3硬件有兩個堆棧指針,操作系統(tǒng)內(nèi)核以及異常處理程序中使用MSP指針,所以它們也需要一個堆棧空間,我們稱之為“堆棧”;

          這個堆棧空間和任務(wù)堆棧空間在物理上是絕對不可以重疊的,圖1-2展示了一個編譯好的程序可能的RAM分配情況(堆棧向下生長)。

          圖1-2:RAM中的變量和堆棧分布示意圖

          有了上面的基礎(chǔ),接下來我們來分析PendSV中斷服務(wù)程序。

          mrs r0, psp 

          是將任務(wù)堆棧指針PSP的值保存到寄存器R0中,因為接下來我們會將寄存器R4~R11也保存到任務(wù)堆棧中,但是我們沒有哪個匯編指令能直接操作PSP完成入棧,所以只能借助R0。

          ldr r3, =pxCurrentTCB      /* 當(dāng)前激活的任務(wù)TCB指針存入R2 */
          ldr r2, [r3]

          這兩句代碼是獲取當(dāng)前激活的任務(wù)TCP指針,指針pxCurrentTCB前面文章已經(jīng)提到過很多次了,它是位于tasks.c文件中定義的唯一一個全局指針型變量,指向當(dāng)前激活的任務(wù)TCB。

          stmdb r0!, {r4-r11}

          這句代碼用于將寄存器R4~R11保存到當(dāng)前激活的程序任務(wù)堆棧中,并且同步更新寄存器R0的值。

          str r0, [r2]

          寄存器R2中保存當(dāng)前激活的任務(wù)TCB指針,在《FreeRTOS高級篇2---FreeRTOS任務(wù)創(chuàng)建分析》中講任務(wù)TCB數(shù)據(jù)結(jié)構(gòu)時我們知道,任務(wù)TCB數(shù)據(jù)結(jié)構(gòu)第一個成員一定是指向任務(wù)當(dāng)前堆棧棧頂?shù)闹羔樧兞?code style="overflow-wrap: break-word;margin-right: 2px;margin-left: 2px;font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(53, 148, 247);background: rgba(59, 170, 250, 0.1);display: inline-block;padding-right: 2px;padding-left: 2px;border-radius: 2px;height: 21px;line-height: 22px;">pxTopOfStack。

          這句代碼將R0的內(nèi)容保存到任務(wù)TCB數(shù)據(jù)結(jié)構(gòu)的第一個成員pxTopOfStack中,也就是將最新的任務(wù)堆棧指針保存到任務(wù)TCB的pxTopOfStack字段中。

          當(dāng)任務(wù)被激活時,就是從這個字段中獲取任務(wù)堆棧指針,然后完成數(shù)據(jù)出棧操作的。

          stmdb sp!, {r3, r14}

          將R3和R14臨時壓入堆棧,因為即將調(diào)用函數(shù)vTaskSwitchContext。調(diào)用函數(shù)時,返回地址自動保存到R14中,所以一旦調(diào)用發(fā)生,R14的值會被覆蓋,因此需要入棧保護。

          R3保存的當(dāng)前激活的任務(wù)TCB指針(pxCurrentTCB)地址,函數(shù)調(diào)用后會用到,因此也要入棧保護。

          mov r0, #configMAX_SYSCALL_INTERRUPT_PRIORITY   
          msr basepri, r0

          這兩句代碼用來進入臨界區(qū),中斷優(yōu)先級號大于等于configMAX_SYSCALL_INTERRUPT_PRIORITY的中斷都會被屏蔽。

          bl vTaskSwitchContext

          調(diào)用函數(shù),選擇下一個要執(zhí)行的任務(wù),也就是尋找處于就緒態(tài)的最高優(yōu)先級任務(wù)。

          變量pxCurrentTCB指向找到的任務(wù)TCB。這個函數(shù)是核心中的核心,所有的其它代碼都是為了保證這個函數(shù)能正確運行。

          某些運行FreeRTOS的硬件有兩種方法:「通用方法和特定于硬件的方法」(以下簡稱“特殊方法”)。

          1. 對于通用方法:
          • configUSE_PORT_OPTIMISED_TASK_SELECTION設(shè)置為0或者硬件不支持這種特殊方法。
          • 可以用于所有FreeRTOS支持的硬件。
          • 完全用C實現(xiàn),效率略低于特殊方法。
          • 不強制要求限制最大可用優(yōu)先級數(shù)目
          1. 對于特殊方法:
          • 并非所有硬件都支持。
          • 必須將configUSE_PORT_OPTIMISED_TASK_SELECTION設(shè)置為1。
          • 依賴一個或多個特定架構(gòu)的匯編指令(一般是類似計算前導(dǎo)零[CLZ]指令)。
          • 比通用方法更高效。
          • 一般強制限定最大可用優(yōu)先級數(shù)目為32(0~31)。

          Cortex-M3即支持通用方法也支持特殊方法,默認的移植層使用特殊方法。我們先來看一下通用方法如何找到下一個要執(zhí)行的任務(wù)。

          在函數(shù)vTaskSwitchContext中使用宏taskSELECT_HIGHEST_PRIORITY_TASK()完成任務(wù)尋址工作,使用通用方法時,這個宏的代碼如下所示。

          pxReadyTasksLists是定義在tasks.c中的靜態(tài)列表數(shù)組,表示就緒任務(wù)列表數(shù)組。

          在《FreeRTOS高級篇2---FreeRTOS任務(wù)創(chuàng)建分析》中講過這個變量:新創(chuàng)建任務(wù)的過程中,任務(wù)TCB中的狀態(tài)列表項xStateListItem會掛接到就緒任務(wù)列表數(shù)組中。

          uxTopReadyPriority也是定義在tasks.c中的靜態(tài)變量,在此之前,它已經(jīng)代表處于就緒態(tài)任務(wù)的最高優(yōu)先級值;

          在FreeRTOS任務(wù)創(chuàng)建與分析一文中,我們也講到了這個變量:每次任務(wù)創(chuàng)建,都會判斷新任務(wù)的優(yōu)先級是否大于這個變量,如果大于,還會更新這個變量的值。

          while()循環(huán)從優(yōu)先級uxTopReadyPriority開始,從就緒列表數(shù)組pxReadyTasksLists中找出優(yōu)先級最高的任務(wù),然后調(diào)用宏listGET_OWNER_OF_NEXT_ENTRY獲取最高優(yōu)先級列表中的下一個列表項,并從該列表項中獲取任務(wù)TCB指針賦給變量pxCurrentTCB

          #define taskSELECT_HIGHEST_PRIORITY_TASK()        \
          {                 \
            /* 從就緒列表數(shù)組中找出最高優(yōu)先級列表*/    \
            while( listLIST_IS_EMPTY( &( pxReadyTasksLists[ uxTopReadyPriority ] ) ) )  \
            {                \
              configASSERT( uxTopReadyPriority );        \
              --uxTopReadyPriority;           \
            }                \
                                            \
            /* 相同優(yōu)先級的任務(wù)使用時間片共享處理器就是通過這個宏實現(xiàn)*/   \
            listGET_OWNER_OF_NEXT_ENTRY(pxCurrentTCB, &( pxReadyTasksLists[ uxTopReadyPriority ] ) );   \
          } /* taskSELECT_HIGHEST_PRIORITY_TASK */

          對于Cortex-M3硬件,還支持特殊方法選擇下一個要執(zhí)行的任務(wù),那就是利用硬件提供的計算前導(dǎo)零指令CLZ。

          特殊方法時,宏taskSELECT_HIGHEST_PRIORITY_TASK()的代碼如下所示。

          #define taskSELECT_HIGHEST_PRIORITY_TASK()        \
          {                 \
          UBaseType_t uxTopPriority;            \
                                            \
            /* 從就緒列表數(shù)組中找出最高優(yōu)先級列表*/              \
            portGET_HIGHEST_PRIORITY( uxTopPriority, uxTopReadyPriority );   \
            listGET_OWNER_OF_NEXT_ENTRY(pxCurrentTCB, &( pxReadyTasksLists[ uxTopPriority ] ) ); \
          } /* taskSELECT_HIGHEST_PRIORITY_TASK() */

          與通用方法相比,可以發(fā)現(xiàn)從就緒列表數(shù)組中找出最高優(yōu)先級列表代碼不同了,特殊方法使用宏portGET_HIGHEST_PRIORITY來實現(xiàn),將宏定義替換后,代碼為:

          uxTopPriority = ( 31UL - ( uint32_t ) __clz( (uxTopReadyPriority) ) )

          在此之前,靜態(tài)變量uxTopReadyPriority同樣已經(jīng)包含處于就緒態(tài)任務(wù)的最高優(yōu)先級的信息。

          與通用方法中使用任務(wù)優(yōu)先級數(shù)值不同,在特殊方法中,uxTopReadyPriority使用每一位來表示任務(wù),比如變量uxTopReadyPriority的bit0為1,則表示存在優(yōu)先級為0的就緒任務(wù),bit10為1則表示存在優(yōu)先級為10的就緒任務(wù)。

          由于32位整形數(shù)最多只有32位,因此使用這種特殊方法限定最大可用優(yōu)先級數(shù)目為32,即優(yōu)先級0~31。

          我們這來看看__clz( (uxTopReadyPriority)是什么意思,__clz()會被匯編指令CLZ替換掉,這個指令用來計算一個變量從最高位開始的連續(xù)零的個數(shù)。

          舉個例子,假如變量uxTopReadyPriority為0x09(二進制為:0000 0000 0000 0000 0000 0000 0000 1001),即bit3和bit0為1,表示存在優(yōu)先級為0和3的就緒任務(wù)。

          __clz( (uxTopReadyPriority)的值為28,uxTopPriority =31-28=3,即優(yōu)先級為3的任務(wù)是就緒態(tài)最高優(yōu)先級任務(wù)。

          下面的代碼跟通用方法一樣,調(diào)用宏listGET_OWNER_OF_NEXT_ENTRY獲取最高優(yōu)先級列表中的下一個列表項,并從該列表項中獲取任務(wù)TCB指針賦給變量pxCurrentTCB

          mov r0, #0                   /* 退出臨界區(qū)*/
          msr basepri, r0

          這兩句代碼用來退出臨界區(qū),通過向寄存器BASEPRI寫入數(shù)值0來實現(xiàn)。

          ldmia sp!, {r3, r14}

          這句代碼將寄存器R3和R14從堆棧中恢復(fù),現(xiàn)在R3保存變量pxCurrentTCB的地址;

          「需要注意的是」,變量pxCurrentTCB在函數(shù)vTaskSwitchContext中可能已被修改,指向新的最高優(yōu)先級就緒任務(wù);R14保存退出異常需要的信息。

          ldr r1, [r3]
          ldr r0, [r1]

          這兩句代碼獲取變量pxCurrentTCB指向的任務(wù)TCB指針,并將TCB的第一個成員——當(dāng)前堆棧棧頂?shù)闹羔樧兞?code style="overflow-wrap: break-word;margin-right: 2px;margin-left: 2px;font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(53, 148, 247);background: rgba(59, 170, 250, 0.1);display: inline-block;padding-right: 2px;padding-left: 2px;border-radius: 2px;height: 21px;line-height: 22px;">pxTopOfStack的值保存到寄存器R0中,也就是將即將運行的任務(wù)堆棧棧頂值存入R0。

          ldmia r0!, {r4-r11}

          將寄存器R4~R11出棧,并同時更新R0的值。

          msr psp, r0

          將最新的任務(wù)堆棧棧頂賦值給線程堆棧指針PSP。

          bx r14

          從異常中斷服務(wù)程序退出。異常發(fā)生時,R14中保存異常返回標志,包括返回后進入線程模式還是處理器模式、使用PSP堆棧指針還是MSP堆棧指針。

          當(dāng)調(diào)用 bx r14指令后,硬件會知道要從異常返回,然后出棧,這個時候堆棧指針PSP已經(jīng)指向了新任務(wù)堆棧的正確位置,當(dāng)新任務(wù)的運行地址被出棧到PC寄存器后,新的任務(wù)也會被執(zhí)行。

          至此,任務(wù)切換完成。

          ????????????????  END  ????????????????
          推薦閱讀:

          嵌入式編程專輯
          Linux 學(xué)習(xí)專輯
          C/C++編程專輯
          Qt進階學(xué)習(xí)專輯

          關(guān)注我的微信公眾號,回復(fù)“加群”按規(guī)則加入技術(shù)交流群。

          點擊“閱讀原文”查看更多分享。
          瀏覽 89
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  亚洲精品久久久久久久久蜜桃 | 《精品 模特私拍秘 泄密》学院派 | 亚洲视频完整版在线观看 | 久久久久久无码日韩欧美电影 | 亚洲AV无码成人精品国产五月天 |