【连载】从单片机到操作系统⑥——FreeRTOS任务切换机制详解
大家晚上好,我是杰杰,最近挺忙的,好久沒有更新了,今天周末就吐血更新一下吧!
前言
? ? ?FreeRTOS是一個是實時內(nèi)核,任務(wù)是程序執(zhí)行的最小單位,也是調(diào)度器處理的基本單位,移植了FreeRTOS,則避免不了對任務(wù)的管理,在多個任務(wù)運行的時候,任務(wù)切換顯得尤為重要。而任務(wù)切換的效率會決定了系統(tǒng)的穩(wěn)定性與效率。
? ? FreeRTOS的任務(wù)切換是干嘛的呢,rtos的實際是永遠運行的是具有最高優(yōu)先級的運行態(tài)任務(wù),而那些之前在就緒態(tài)的任務(wù)怎么變成運行態(tài)使其得以運行呢,這就是我們FreeRTOS任務(wù)切換要做的事情,它要做的是找到最高優(yōu)先級的就緒態(tài)任務(wù),并且讓它獲得cpu的使用權(quán),這樣,它就能從就緒態(tài)變成運行態(tài),這樣子,整個系統(tǒng)的實時性就會很好,響應(yīng)也會很好,而不會讓程序阻塞卡死。
? ??要知道怎么實現(xiàn)任務(wù)切換,那就要知道任務(wù)切換的機制,在不同的cpu(mcu)中,觸發(fā)的方式可能會不一樣,現(xiàn)在是以Cortex-M3為例來講講任務(wù)的切換。為了大家能看懂本文,我就拋轉(zhuǎn)引玉一下,引用《Cortex-M3權(quán)威指南-中文版》的部分語句(如涉及侵權(quán),請聯(lián)系杰杰刪除)
SVC?和?PendSV
? ? SVC(系統(tǒng)服務(wù)調(diào)用,亦簡稱系統(tǒng)調(diào)用)和 PendSV(Pended System Call,可懸起系統(tǒng)調(diào)用),它們多用于在操作系統(tǒng)之上的軟件開發(fā)中。SVC 用于產(chǎn)生系統(tǒng)函數(shù)的調(diào)用請求。例如,操作系統(tǒng)不讓用戶程序直接訪問硬件,而是通過提供一些系統(tǒng)服務(wù)函數(shù),用戶程序使用 SVC 發(fā)出對系統(tǒng)服務(wù)函數(shù)的呼叫請求,以這種方法調(diào)用它們來間接訪問硬件。因此,當(dāng)用戶程序想要控制特定的硬件時,它就會產(chǎn)生一個 SVC 異常,然后操作系統(tǒng)提供的 SVC 異常服務(wù)例程得到執(zhí)行,它再調(diào)用相關(guān)的操作系統(tǒng)函數(shù),后者完成用戶程序請求的服務(wù)。
???另一個相關(guān)的異常是 PendSV(可懸起的系統(tǒng)調(diào)用),它和 SVC 協(xié)同使用。一方面,SVC異常是必須立即得到響應(yīng)的(若因優(yōu)先級不比當(dāng)前正處理的高,或是其它原因使之無法立即
響應(yīng),將上訪成硬 fault——譯者注),應(yīng)用程序執(zhí)行 SVC 時都是希望所需的請求立即得到響應(yīng)。另一方面,PendSV 則不同,它是可以像普通的中斷一樣被懸起的(不像 SVC 那樣會上訪)。OS 可以利用它“緩期執(zhí)行”一個異常——直到其它重要的任務(wù)完成后才執(zhí)行動作。懸起 PendSV?的方法是:手工往 NVIC 的 PendSV 懸起寄存器中寫 1。懸起后,如果優(yōu)先級不夠高,則將緩期等待執(zhí)行。
????如果一個發(fā)生的異常不能被即刻響應(yīng),就稱它被“懸起”(pending)。不過,少數(shù) fault異常是不允許被懸起的。一個異常被懸起的原因,可能是系統(tǒng)當(dāng)前正在執(zhí)行一個更高優(yōu)先級異常的服務(wù)例程,或者因相關(guān)掩蔽位的設(shè)置導(dǎo)致該異常被除能。對于每個異常源,在被懸起的情況下,都會有一個對應(yīng)的“懸起狀態(tài)寄存器”保存其異常請求,直到該異常能夠執(zhí)行為止,這與傳統(tǒng)的 ARM 是完全不同的。在以前,是由產(chǎn)生中斷的設(shè)備保持住請求信號。現(xiàn)在NVIC 的懸起狀態(tài)寄存器的出現(xiàn)解決了這個問題,即使后來設(shè)備已經(jīng)釋放了請求信號,曾經(jīng)的中斷請求也不會錯失。
系統(tǒng)任務(wù)切換的工程分析
???在系統(tǒng)中正常執(zhí)行的任務(wù)(假設(shè)沒有外部中斷IRQ),用Systick直接做上下文切換是完全沒有問題的,如圖:
?但是問題是幾乎很少嵌入式的設(shè)備會不用其豐富的中斷響應(yīng),所以,直接用systick做系統(tǒng)的上下文切換那是不實際的,這存在很大的風(fēng)險,因為假設(shè)systick打斷了一個中斷(IRQ),立即做出上下文切換的話,則觸犯用法 fault 異常,除了重啟你沒有其他辦法了,這樣子做出來的產(chǎn)品就是垃圾!!用我老板的話說就是寫的什么狗屎!!!如圖所示:
????那這么說這樣不行那也不行,怎么辦啊?請看看前面接介紹的PendSV,是不是有點豁然開朗了?PendSV 來完美解決這個問題。PendSV 異常會自動延遲上下文切換的請求,直到其它的 ISR 都完成了處理后才放行。為實現(xiàn)這個機制,需要把 PendSV 編程為最低優(yōu)先級的異常。如果 OS 檢測到某 IRQ 正在活動并且被 SysTick 搶占,它將懸起一個 PendSV 異常,以便緩期執(zhí)行上下文切換。
? ?懂了嗎?就是說,只要將PendSV的優(yōu)先級設(shè)為最低的,systick即使是打斷了IRQ,它也不會馬上進行上下文切換,而是等到IRQ執(zhí)行完,PendSV 服務(wù)例程才開始執(zhí)行,并且在里面執(zhí)行上下文切換。過程如圖所示:
任務(wù)切換的源碼實現(xiàn)
過程差不多了解了,那看看FreeRTOS中怎么實現(xiàn)吧!!
FreeRTOS有兩種方法觸發(fā)任務(wù)切換:
??一種就是systick觸發(fā)PendSV異常,這是最經(jīng)常使用的。
?另一種是主動進行切換任務(wù),執(zhí)行系統(tǒng)調(diào)用,比如普通任務(wù)可以使用taskYIELD()強制任務(wù)切換,中斷服務(wù)程序中使用portYIELD_FROM_ISR()強制任務(wù)切換。
1
先說說第一種吧,就在systick中斷中調(diào)用xPortSysTickHandler();
下面是源碼: void?xPortSysTickHandler(?void?) {vPortRaiseBASEPRI();{/*?Increment?the?RTOS?tick.?*/if(?xTaskIncrementTick()?!=?pdFALSE?){/*?A?context?switch?is?required.??Context?switching?is?performed?inthe?PendSV?interrupt.??Pend?the?PendSV?interrupt.?*/portNVIC_INT_CTRL_REG?=?portNVIC_PENDSVSET_BIT;}}vPortClearBASEPRIFromISR(); }? ? ?它的執(zhí)行過程是這樣子的,屏蔽所有中斷,因為SysTick以最低的中斷優(yōu)先級運行,所以當(dāng)這個中斷執(zhí)行時所有中斷必須被屏蔽。vPortRaiseBASEPRI();就是屏蔽所有中斷的。而且并不需要保存本次中斷的值,因為systick的中斷優(yōu)先級是已知的,執(zhí)行完直接恢復(fù)所有中斷即可。
????在xTaskIncrementTick()中會對tick的計數(shù)值進行自加,然后檢查有沒有處于就緒態(tài)的最優(yōu)先級任務(wù),如果有,則返回非零值,然后表示需要進行任務(wù)切換,而并非馬上進行任務(wù)切換,此處要注意,它只是向中斷狀態(tài)寄存器bit28位寫入1,只是將PendSV掛起,假如沒有比PendSV更高優(yōu)先級的中斷,它才會進入PendSV中斷服務(wù)函數(shù)進行任務(wù)切換。
?然后解除屏蔽所有中斷。
vPortClearBASEPRIFromISR();2
?另一種方法是主動進行任務(wù)切換,不管是使用taskYIELD()還是portYIELD_FROM_ISR(),最終都會執(zhí)行下面的代碼:
__dsb(?portSY_FULL_READ_WRITE?);????????????????????????????????????????????\__isb(?portSY_FULL_READ_WRITE?);????????????????????????????????????????????\ }? 這portYIELD()其實是一個宏定義來的。同樣是向中斷狀態(tài)寄存器bit28位寫入1,將PendSV掛起,然后等待任務(wù)的切換。
具體的任務(wù)切換源碼
?????一直在說怎么進行任務(wù)切換的,好像還沒看到任務(wù)切換的源碼啊,哎,下面來看看任務(wù)切換的真面目!!
__asm?void?xPortPendSVHandler(void) {extern?uxCriticalNesting;extern?pxCurrentTCB;extern?vTaskSwitchContext;PRESERVE8mrs?r0,?pspisbldr?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,?msr?basepri,?r0dsbisbbl?vTaskSwitchContextmov?r0,?msr?basepri,?r0ldmia?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,?r0isbbx?r14nop }? 不是我不想看,是我看到匯編就頭大啊,這幾天我也在看源碼,實在是頭大。
? 找到核心的函數(shù)看看就好啦,不管那么多,有興趣的可以研究一下中斷代碼,有不懂的也很歡迎你們來問我,一起研究研究,也是不錯的選擇。
下面是看重點的地方了:
mov?r0,?????????????#configMAX_SYSCALL_INTERRUPT_PRIORITY msr?basepri,?r0這兩句代碼是關(guān)閉中斷的。關(guān)中斷就得干活了,嘿嘿嘿~
bl?vTaskSwitchContextBL是跳轉(zhuǎn)指令嘛,這個我還是有點懂的。
? 調(diào)用函數(shù)vTaskSwitchContext(),尋找新的任務(wù)運行,通過使變量pxCurrentTCB指向新的任務(wù)來實現(xiàn)任務(wù)切換,然后就是打開中斷,退出去了。
尋找下一個要運行任務(wù)
? 是不是感覺沒什么大不了的樣子,如果你是這樣子覺得的,可能還沒學(xué)到家,趕緊去看看FreeRTOS的源碼,在config.h配置文件中是不是有一個叫做硬件查找下一個運行的任務(wù)呢?configUSE_PORT_OPTIMISED_TASK_SELECTION,這個在FreeRTOS中叫做特殊方法,其實也是硬件查找啦,但是并不是每種單片機都支持的,如果是不支持的話,只能選擇軟件查找的方法了,就是所謂的通用方法。通用方法我就不多說了,因為我用的是STM32,他是支持硬件方法的,這樣子效率更高,所以我也沒必要去研究他的軟件方法,假如有興趣的小伙伴可以研讀一下源碼,有不懂的可以向我提問,源碼如下:
{???????????????????????????????????????????????????????????????????????????????????????????????????\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?*/而硬件的方法源碼則在下面:
????其方法是利用硬件提供的計算前導(dǎo)零指令CLZ,具體宏定義為:
? 靜態(tài)變量uxTopReadyPriority包含了處于就緒態(tài)任務(wù)的最高優(yōu)先級的信息,因為FreeRTOS運行的永遠是處于最高優(yōu)先級的運行態(tài),而下個處于最高優(yōu)先級的就緒態(tài)則必定會在下次任務(wù)切換的時候運行,uxTopReadyPriority使用每一位來表示任務(wù)是否處于就緒態(tài),比如變量uxTopReadyPriority的bit0為1,則表示存在優(yōu)先級為0的任務(wù)處于就緒態(tài),bit6為1則表示存在優(yōu)先級為6的任務(wù)處于就緒態(tài)。并且,由于bit0的優(yōu)先級高于bit6,那么下個任務(wù)就是bit0的任務(wù)運行了(數(shù)組越低優(yōu)先級越高)。由于32位整形數(shù)最多只有32位,因此使用這種特殊方法限定最大可用優(yōu)先級數(shù)目為32,即優(yōu)先級0~31。得到了下個處于最高優(yōu)先級就緒態(tài)任務(wù)了,就調(diào)用listGET_OWNER_OF_NEXT_ENTRY來獲取下一個任務(wù)的列表項,然后將該列表項的任務(wù)控制塊TCB賦值給pxCurrentTCB,那么我們就得到下一個要運行的任務(wù)了。
至此,任務(wù)切換已經(jīng)完成。
END
更多好文章請關(guān)注杰杰。
【連載】從單片機到操作系統(tǒng)⑤——FreeRTOS列表&列表項的源碼解讀
【連載】從單片機到操作系統(tǒng)④——FreeRTOS創(chuàng)建任務(wù)&開啟調(diào)度詳解
?【連載】從單片機到操作系統(tǒng)③——走進FreeRTOS
?STM32進階之串口環(huán)形緩沖區(qū)實現(xiàn)
本文是杰杰原創(chuàng),轉(zhuǎn)載請說明出處。本文直接引用了《Cortex-M3權(quán)威指南》的部分語句(如涉及侵權(quán),請聯(lián)系杰杰刪除)。
總結(jié)
以上是生活随笔為你收集整理的【连载】从单片机到操作系统⑥——FreeRTOS任务切换机制详解的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: docx行间距怎么设置_word全文行距
- 下一篇: 手撸架构,MongDB 面试50问