<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系列第21篇---FreeRTOS調(diào)度器啟動過程分析

          共 10191字,需瀏覽 21分鐘

           ·

          2021-04-28 14:15


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

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

          整理:李肖遙


          使用FreeRTOS,一個最基本的程序架構(gòu)如下所示:

          int main(void)
          {  
              必要的初始化工作;
              創(chuàng)建任務(wù)1;
              創(chuàng)建任務(wù)2;
              ...
             vTaskStartScheduler();  /*啟動調(diào)度器*/
              while(1);   
          }

          任務(wù)創(chuàng)建完成后,靜態(tài)變量指針pxCurrentTCB(見《FreeRTOS高級篇2---FreeRTOS任務(wù)創(chuàng)建分析》第7節(jié)內(nèi)容)指向優(yōu)先級最高的就緒任務(wù)。

          但此時任務(wù)并不能運行,因為接下來還有關(guān)鍵的一步:啟動FreeRTOS調(diào)度器。

          調(diào)度器是FreeRTOS操作系統(tǒng)的核心,主要負(fù)責(zé)任務(wù)切換,即找出最高優(yōu)先級的就緒任務(wù),并使之獲得CPU運行權(quán)。

          調(diào)度器并非自動運行的,需要人為啟動它。

          API函數(shù)vTaskStartScheduler()用于啟動調(diào)度器,它會創(chuàng)建一個空閑任務(wù)、初始化一些靜態(tài)變量,最主要的,它會初始化系統(tǒng)節(jié)拍定時器并設(shè)置好相應(yīng)的中斷,然后啟動第一個任務(wù)。

          這篇文章用于「分析啟動調(diào)度器的過程」,和上一篇文章一樣,啟動調(diào)度器也涉及到硬件特性(比如系統(tǒng)節(jié)拍定時器初始化等)。

          ?

          本文仍然以Cortex-M3架構(gòu)為例。

          ?

          啟動調(diào)度器的API函數(shù)vTaskStartScheduler()的源碼精簡后如下所示:

          void vTaskStartScheduler( void )
          {
          BaseType_t xReturn;
          StaticTask_t *pxIdleTaskTCBBuffer= NULL;
          StackType_t *pxIdleTaskStackBuffer= NULL;
          uint16_t usIdleTaskStackSize =tskIDLE_STACK_SIZE;

            /*如果使用靜態(tài)內(nèi)存分配任務(wù)堆棧和任務(wù)TCB,則需要為空閑任務(wù)預(yù)先定義好任務(wù)內(nèi)存和任務(wù)TCB空間*/
            #if(configSUPPORT_STATIC_ALLOCATION == 1 )
            {
               vApplicationGetIdleTaskMemory( &pxIdleTaskTCBBuffer, &pxIdleTaskStackBuffer, &usIdleTaskStackSize);
            }
            #endif /*configSUPPORT_STATIC_ALLOCATION */

            /* 創(chuàng)建空閑任務(wù),使用最低優(yōu)先級*/
            xReturn =xTaskGenericCreate( prvIdleTask, "IDLE",usIdleTaskStackSize, ( void * ) NULL, ( tskIDLE_PRIORITY | portPRIVILEGE_BIT), &xIdleTaskHandle,pxIdleTaskStackBuffer,pxIdleTaskTCBBuffer, NULL );

            if( xReturn == pdPASS )
            {
                /* 先關(guān)閉中斷,確保節(jié)拍定時器中斷不會在調(diào)用xPortStartScheduler()時或之前發(fā)生.當(dāng)?shù)谝粋€任務(wù)啟動時,會重新啟動中斷*/
               portDISABLE_INTERRUPTS();

                /* 初始化靜態(tài)變量 */
               xNextTaskUnblockTime = portMAX_DELAY;
               xSchedulerRunning = pdTRUE;
                xTickCount = ( TickType_t ) 0U;

                /* 如果宏configGENERATE_RUN_TIME_STATS被定義,表示使用運行時間統(tǒng)計功能,則下面這個宏必須被定義,用于初始化一個基礎(chǔ)定時器/計數(shù)器.*/
               portCONFIGURE_TIMER_FOR_RUN_TIME_STATS();

                /* 設(shè)置系統(tǒng)節(jié)拍定時器,這與硬件特性相關(guān),因此被放在了移植層.*/
                if(xPortStartScheduler() != pdFALSE )
                {
                    /* 如果調(diào)度器正確運行,則不會執(zhí)行到這里,函數(shù)也不會返回*/
                }
                else
                {
                    /* 僅當(dāng)任務(wù)調(diào)用API函數(shù)xTaskEndScheduler()后,會執(zhí)行到這里.*/
                }
            }
            else
            {
                /* 執(zhí)行到這里表示內(nèi)核沒有啟動,可能因為堆棧空間不夠 */
               configASSERT( xReturn );
            }

            /* 預(yù)防編譯器警告*/
            ( void ) xIdleTaskHandle;
          }

          這個API函數(shù)首先創(chuàng)建一個空閑任務(wù),空閑任務(wù)使用最低優(yōu)先級(0級),空閑任務(wù)的任務(wù)句柄存放在靜態(tài)變量xIdleTaskHandle中,可以調(diào)用API函數(shù)xTaskGetIdleTaskHandle()獲得空閑任務(wù)句柄。

          如果任務(wù)創(chuàng)建成功,則關(guān)閉中斷(調(diào)度器啟動結(jié)束時會再次使能中斷的),初始化一些靜態(tài)變量,然后調(diào)用函數(shù)xPortStartScheduler()來啟動系統(tǒng)節(jié)拍定時器并啟動第一個任務(wù)。

          因為設(shè)置系統(tǒng)節(jié)拍定時器涉及到硬件特性,因此函數(shù)xPortStartScheduler()由移植層提供,不同的硬件架構(gòu),這個函數(shù)的代碼也不相同。

          對于Cortex-M3架構(gòu),函數(shù)xPortStartScheduler()的實現(xiàn)如下所示:

          BaseType_t xPortStartScheduler( void )
          {
            #if(configASSERT_DEFINED == 1 )
            {
              volatile uint32_tulOriginalPriority;
              /* 中斷優(yōu)先級寄存器0:IPR0 */
              volatile uint8_t * constpucFirstUserPriorityRegister = ( uint8_t * ) (portNVIC_IP_REGISTERS_OFFSET_16 +portFIRST_USER_INTERRUPT_NUMBER );
              volatile uint8_tucMaxPriorityValue;

              /* 這一大段代碼用來確定一個最高ISR優(yōu)先級,在這個ISR或者更低優(yōu)先級的ISR中可以安全的調(diào)用以FromISR結(jié)尾的API函數(shù).*/

              /* 保存中斷優(yōu)先級值,因為下面要覆寫這個寄存器(IPR0) */
             ulOriginalPriority = *pucFirstUserPriorityRegister;

              /* 確定有效的優(yōu)先級位個數(shù). 首先向所有位寫1,然后再讀出來,由于無效的優(yōu)先級位讀出為0,然后數(shù)一數(shù)有多少個1,就能知道有多少位優(yōu)先級.*/
              *pucFirstUserPriorityRegister= portMAX_8_BIT_VALUE;
             ucMaxPriorityValue = *pucFirstUserPriorityRegister;

              /* 冗余代碼,用來防止用戶不正確的設(shè)置RTOS可屏蔽中斷優(yōu)先級值 */
             ucMaxSysCallPriority =configMAX_SYSCALL_INTERRUPT_PRIORITY &ucMaxPriorityValue;

              /* 計算最大優(yōu)先級組值 */
             ulMaxPRIGROUPValue =portMAX_PRIGROUP_BITS;
              while( (ucMaxPriorityValue &portTOP_BIT_OF_BYTE ) ==portTOP_BIT_OF_BYTE )
              {
                 ulMaxPRIGROUPValue--;
                 ucMaxPriorityValue <<= ( uint8_t ) 0x01;
              }
             ulMaxPRIGROUPValue <<=portPRIGROUP_SHIFT;
             ulMaxPRIGROUPValue &=portPRIORITY_GROUP_MASK;

              /* 將IPR0寄存器的值復(fù)原*/
              *pucFirstUserPriorityRegister= ulOriginalPriority;
            }
            #endif /*conifgASSERT_DEFINED */

            /* 將PendSV和SysTick中斷設(shè)置為最低優(yōu)先級*/
           portNVIC_SYSPRI2_REG |=portNVIC_PENDSV_PRI;
           portNVIC_SYSPRI2_REG |=portNVIC_SYSTICK_PRI;

            /* 啟動系統(tǒng)節(jié)拍定時器,即SysTick定時器,初始化中斷周期并使能定時器*/
           vPortSetupTimerInterrupt();

            /* 初始化臨界區(qū)嵌套計數(shù)器 */
           uxCriticalNesting = 0;

            /* 啟動第一個任務(wù) */
           prvStartFirstTask();

            /* 永遠(yuǎn)不會到這里! */
            return 0;
          }

          從源碼中可以看到,開始的一大段都是冗余代碼。

          因為Cortex-M3的中斷優(yōu)先級有些違反直覺:Cortex-M3中斷優(yōu)先級數(shù)值越大,表示優(yōu)先級越低。

          而FreeRTOS的任務(wù)優(yōu)先級則與之相反:優(yōu)先級數(shù)值越大的任務(wù),優(yōu)先級越高。

          根據(jù)官方統(tǒng)計,在Cortex-M3硬件上使用FreeRTOS,絕大多數(shù)的問題都出在優(yōu)先級設(shè)置不正確上。

          因此,為了使FreeRTOS更健壯,F(xiàn)reeRTOS的作者在編寫Cortex-M3架構(gòu)移植層代碼時,特意增加了冗余代碼。

          ?

          關(guān)于詳細(xì)的Cortex-M3架構(gòu)中斷優(yōu)先級設(shè)置,參考《FreeRTOS系列第7篇---Cortex-M內(nèi)核使用FreeRTOS特別注意事項》一文。

          ?

          在Cortex-M3架構(gòu)中,F(xiàn)reeRTOS為了任務(wù)啟動和任務(wù)切換使用了「三個異常」:SVC、PendSV和SysTick。

          「SVC」(系統(tǒng)服務(wù)調(diào)用)用于任務(wù)啟動,有些操作系統(tǒng)不允許應(yīng)用程序直接訪問硬件,而是通過提供一些系統(tǒng)服務(wù)函數(shù),通過SVC來調(diào)用;

          「PendSV」(可掛起系統(tǒng)調(diào)用)用于完成任務(wù)切換,它的最大特性是如果當(dāng)前有優(yōu)先級比它高的中斷在運行,PendSV會推遲執(zhí)行,直到高優(yōu)先級中斷執(zhí)行完畢;

          「SysTick」用于產(chǎn)生系統(tǒng)節(jié)拍時鐘,提供一個時間片,如果多個任務(wù)共享同一個優(yōu)先級,則每次SysTick中斷,下一個任務(wù)將獲得一個時間片。

          ?

          關(guān)于詳細(xì)的SVC、PendSV異常描述,推薦《Cortex-M3權(quán)威指南》一書的“異常”部分。

          ?

          這里將PendSV和SysTick異常優(yōu)先級設(shè)置為最低,這樣任務(wù)切換不會打斷某個中斷服務(wù)程序,中斷服務(wù)程序也不會被延遲,這樣簡化了設(shè)計,有利于系統(tǒng)穩(wěn)定。

          接下來調(diào)用函數(shù)vPortSetupTimerInterrupt()設(shè)置SysTick定時器中斷周期并使能定時器運行這個函數(shù)比較簡單,就是設(shè)置SysTick硬件的相應(yīng)寄存器。

          再接下來有一個關(guān)鍵的函數(shù)是prvStartFirstTask(),這個函數(shù)用來啟動第一個任務(wù)。我們先看一下源碼:

          __asm void prvStartFirstTask( void )
          {
              PRESERVE8
           
              /* Cortext-M3硬件中,0xE000ED08地址處為VTOR(向量表偏移量)寄存器,存儲向量表起始地址*/
              ldr r0, =0xE000ED08    
              ldr r0, [r0]
              /* 取出向量表中的第一項,向量表第一項存儲主堆棧指針MSP的初始值*/
              ldr r0, [r0]   
           
              /* 將堆棧地址存入主堆棧指針 */
              msr msp, r0
              /* 使能全局中斷*/
              cpsie i
              cpsie f
              dsb
              isb
              /* 調(diào)用SVC啟動第一個任務(wù) */
              svc 0
              nop
              nop
          }

          程序開始的幾行代碼用來復(fù)位主堆棧指針MSP的值,表示從此以后MSP指針被FreeRTOS接管,需要注意的是,Cortex-M3硬件的中斷也使用MSP指針。

          之后使能中斷,使用匯編指令svc 0觸發(fā)SVC中斷,完成啟動第一個任務(wù)的工作。我們看一下SVC中斷服務(wù)函數(shù):

          __asm void vPortSVCHandler( void )
          {
              PRESERVE8
           
              ldr r3, =pxCurrentTCB   /* pxCurrentTCB指向處于最高優(yōu)先級的就緒任務(wù)TCB */
              ldr r1, [r3]            /* 獲取任務(wù)TCB地址 */
              ldr r0, [r1]            /* 獲取任務(wù)TCB的第一個成員,即當(dāng)前堆棧棧頂pxTopOfStack */
              ldmia r0!, {r4-r11}     /* 出棧,將寄存器r4~r11出棧 */
              msr psp, r0             /* 最新的棧頂指針賦給線程堆棧指針PSP */
              isb
              mov r0, #0
              msr basepri, r0
              orrr14, #0xd           /* 這里0x0d表示:返回后進(jìn)入線程模式,從進(jìn)程堆棧中做出棧操作,返回Thumb狀態(tài)*/
              bx r14
          }

          通過上一篇介紹任務(wù)創(chuàng)建的文章,我們已經(jīng)認(rèn)識了指針pxCurrentTCB

          這是定義在tasks.c中的唯一一個全局變量,指向處于最高優(yōu)先級的就緒任務(wù)TCB。

          我們知道「FreeRTOS的核心功能」是確保處于最高優(yōu)先級的就緒任務(wù)獲得CPU權(quán)限,因此可以說這個指針指向的任務(wù)要么正在運行中,要么即將運行(調(diào)度器關(guān)閉),所以這個變量才被命名為pxCurrentTCB

          根據(jù)《FreeRTOS高級篇2---FreeRTOS任務(wù)創(chuàng)建分析》第三節(jié)我們可以知道,一個任務(wù)創(chuàng)建時,會將它的任務(wù)堆棧初始化的像是經(jīng)過一次任務(wù)切換一樣,如圖1-1所示。

          對于Cortex-M3架構(gòu),需要依次入棧xPSR、PC、LR、R12、R3~R0、R11~R4,其中r11~R4需要人為入棧,其它寄存器由硬件自動入棧。

          寄存器PC被初始化為任務(wù)函數(shù)指針vTask_A,這樣當(dāng)某次任務(wù)切換后,任務(wù)A獲得CPU控制權(quán),任務(wù)函數(shù)vTask_A被出棧到PC寄存器,之后會執(zhí)行任務(wù)A的代碼;

          LR寄存器初始化為函數(shù)指針prvTaskExitError,這是由移植層提供的一個出錯處理函數(shù)。

          任務(wù)TCB結(jié)構(gòu)體成員pxTopOfStack表示當(dāng)前堆棧的棧頂,它指向最后一個入棧的項目,所以在圖中它指向R4;

          TCB結(jié)構(gòu)體另外一個成員pxStack表示堆棧的起始位置,所以在圖中它指向堆棧的最開始處。

          圖1-1:任務(wù)創(chuàng)建后任務(wù)堆棧分布情況

          所以,SVC中斷服務(wù)函數(shù)一開始就使用全局指針pxCurrentTCB獲得第一個要啟動的任務(wù)TCB,從而獲得任務(wù)的當(dāng)前堆棧棧頂指針。

          先將人為入棧的寄存器R4~R11出棧,將最新的堆棧棧頂指針賦值給線程堆棧指針PSP,再取消中斷掩蔽。

          到這里,只要發(fā)生中斷,就都能夠被響應(yīng)了。

          中斷服務(wù)函數(shù)通過下面兩句匯編返回。

          Cortex-M3架構(gòu)中,r14的值決定了從異常返回的模式,這里r14最后四位按位或上0x0d,表示返回時從進(jìn)程堆棧中做出棧操作、返回后進(jìn)入線程模式、返回Thumb狀態(tài)。

          orr r14, #0xd       
          bx r14

          執(zhí)行bx  r14指令后,硬件自動將寄存器xPSR、PC、LR、R12、R3~R0出棧,這時任務(wù)A的任務(wù)函數(shù)指針vTask_A會出棧到PC指針中,從而開始執(zhí)行任務(wù)A。

          至此,任務(wù)vTask_A獲得CPU執(zhí)行權(quán),調(diào)度器正式開始工作。

          ????????????????  END  ????????????????

          推薦閱讀:


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

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


          這是我另一個技術(shù)號,程序員的編程學(xué)習(xí)基地,注重編程思想,歡迎關(guān)注!


          點擊“閱讀原文”查看更多分享。

          瀏覽 47
          點贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

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

          手機(jī)掃一掃分享

          分享
          舉報
          <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>
                  福利黄色片:片 | 欧色一级黄色视频 | 人人操人人操人摸 | 天天躁日日躁精品人妻 | 日本在线影院 |