freertos内核 任务定义与切换 原理分析
freertos內(nèi)核 任務(wù)定義與切換 原理分析
- 主程序
- 任務(wù)控制塊
- 任務(wù)創(chuàng)建函數(shù)
- 任務(wù)棧初始化
- 就緒列表
- 調(diào)度器
- 總結(jié)任務(wù)切換
主程序
這個(gè)程序目的就是,使用freertos讓兩個(gè)任務(wù)不斷切換。看兩個(gè)任務(wù)中變量的變化情況(波形)。
下面這個(gè)圖是任務(wù)函數(shù)里面delay(100)的結(jié)果。
下面這個(gè)圖是任務(wù)函數(shù)里面delay(2)的結(jié)果.
多任務(wù)系統(tǒng),CPU好像在同時(shí)做兩件事,也就是說(shuō),最好預(yù)期就是,兩變量的波形應(yīng)該是完全相同的。
這個(gè)實(shí)驗(yàn),delay減少了,他們兩變量波形中間間距仍然沒(méi)有減少,說(shuō)明這個(gè)實(shí)驗(yàn)只是一個(gè)入門(mén),遠(yuǎn)沒(méi)達(dá)到RTOS的效能。
這個(gè)實(shí)驗(yàn)特點(diǎn),就是具有任務(wù)主動(dòng)切換能力,這是如何實(shí)現(xiàn)的呢,值得研究。
下面兩個(gè)圖,直觀顯示了程序的主動(dòng)切換。觀察CurrentTCB這個(gè)參數(shù),可以發(fā)現(xiàn)它是一直變動(dòng)的。
它究竟為什么變動(dòng)呢,采用逐步debug的方式,可找到,是因?yàn)檎{(diào)用了一個(gè)SwitchContext函數(shù)。
那么先看一下main里面都有啥:
從下面可知,這里面有任務(wù)棧、任務(wù)控制塊、有任務(wù)函數(shù)、還得創(chuàng)建任務(wù)。有就緒列表、有調(diào)度器。
任務(wù)棧:
#define TASK1_STACK_SIZE 20 StackType_t Task1Stack[TASK1_STACK_SIZE]; #define TASK2_STACK_SIZE 20 StackType_t Task2Stack[TASK2_STACK_SIZE];任務(wù)函數(shù)(任務(wù)入口):
void Task1_Entry( void *p_arg ) {for( ;; ){flag1 = 1;delay( 100 ); flag1 = 0;delay( 100 );/* 任務(wù)切換,這里是手動(dòng)切換 */taskYIELD();} } void Task2_Entry( void *p_arg ) {for( ;; ){flag2 = 1;delay( 100 ); flag2 = 0;delay( 100 );/* 任務(wù)切換,這里是手動(dòng)切換 */taskYIELD();} }任務(wù)控制塊:
TCB_t Task1TCB; TCB_t Task2TCB;就緒列表初始化:
prvInitialiseTaskLists();創(chuàng)建任務(wù):
typedef void * TaskHandle_t; TaskHandle_t Task1_Handle; Task1_Handle = xTaskCreateStatic( (TaskFunction_t)Task1_Entry, /* 任務(wù)入口 */(char *)"Task1", /* 任務(wù)名稱,字符串形式 */(uint32_t)TASK1_STACK_SIZE , /* 任務(wù)棧大小,單位為字 */(void *) NULL, /* 任務(wù)形參 */(StackType_t *)Task1Stack, /* 任務(wù)棧起始地址 */(TCB_t *)&Task1TCB ); /* 任務(wù)控制塊 */任務(wù)添加到就緒列表:
vListInsertEnd( &( pxReadyTasksLists[1] ), &( ((TCB_t *)(&Task1TCB))->xStateListItem ) );啟動(dòng)調(diào)度器:
vTaskStartScheduler();任務(wù)控制塊
多任務(wù)系統(tǒng),任務(wù)執(zhí)行由系統(tǒng)調(diào)度。任務(wù)的信息很多,于是就用任務(wù)控制塊表示任務(wù),這樣方便系統(tǒng)調(diào)度。
任務(wù)控制塊類型,包含了任務(wù)的所有信息,比如棧頂指針pxTopOfStack、任務(wù)節(jié)點(diǎn)xStateListItem、任務(wù)棧起始地址pxStack、任務(wù)名稱pcTaskName。
typedef struct tskTaskControlBlock {volatile StackType_t *pxTopOfStack; /* 棧頂 */ListItem_t xStateListItem; /* 任務(wù)節(jié)點(diǎn) */StackType_t *pxStack; /* 任務(wù)棧起始地址 *//* 任務(wù)名稱,字符串形式 */char pcTaskName[ configMAX_TASK_NAME_LEN ]; } tskTCB; typedef tskTCB TCB_t;任務(wù)創(chuàng)建函數(shù)
main里面調(diào)用xTaskCreateStatic創(chuàng)建了任務(wù),觀察可知這個(gè)函數(shù)其實(shí)改變的是Task1TCB任務(wù)控制塊,這個(gè)任務(wù)控制塊誕生之初,就沒(méi)有進(jìn)行過(guò)初始化。調(diào)用任務(wù)創(chuàng)建函數(shù)目的就是初始化任務(wù)控制塊。
Task1_Handle = xTaskCreateStatic( (TaskFunction_t)Task1_Entry, /* 任務(wù)入口 */(char *)"Task1", /* 任務(wù)名稱,字符串形式 */(uint32_t)TASK1_STACK_SIZE , /* 任務(wù)棧大小,單位為字 */(void *) NULL, /* 任務(wù)形參 */(StackType_t *)Task1Stack, /* 任務(wù)棧起始地址 */(TCB_t *)&Task1TCB ); /* 任務(wù)控制塊 */直觀表述這個(gè)函數(shù)內(nèi)部:
任務(wù)控制塊里面的任務(wù)節(jié)點(diǎn):下面代碼是初始化過(guò)程,其實(shí)就是進(jìn)行鏈表的普通節(jié)點(diǎn)初始化。
/* 初始化TCB中的xStateListItem節(jié)點(diǎn) */vListInitialiseItem( &( pxNewTCB->xStateListItem ) );/* 設(shè)置xStateListItem節(jié)點(diǎn)的擁有者 */listSET_LIST_ITEM_OWNER( &( pxNewTCB->xStateListItem ), pxNewTCB );這個(gè)任務(wù)入口體現(xiàn)在哪呢,其實(shí)是體現(xiàn)在任務(wù)棧里面。在main.c里面初始化任務(wù)棧,僅僅開(kāi)辟了一段內(nèi)存空間,里面放什么東西都沒(méi)有具體說(shuō)明。調(diào)用任務(wù)創(chuàng)建函數(shù)之后,其實(shí)也一并初始化了任務(wù)棧(往里面放東西),任務(wù)入口就放到這個(gè)棧里了。任務(wù)棧也初始化完的時(shí)候,任務(wù)控制塊才算圓滿的初始化完了。
所以任務(wù)創(chuàng)建函數(shù)里面還得調(diào)用任務(wù)棧初始化函數(shù)。
任務(wù)棧初始化
初始化任務(wù)棧的函數(shù)代碼在下面:
StackType_t *pxPortInitialiseStack( StackType_t *pxTopOfStack, TaskFunction_t pxCode, void *pvParameters ) {/* 異常發(fā)生時(shí),自動(dòng)加載到CPU寄存器的內(nèi)容 */pxTopOfStack--;*pxTopOfStack = portINITIAL_XPSR; /* xPSR的bit24必須置1 */pxTopOfStack--;*pxTopOfStack = ( ( StackType_t ) pxCode ) & portSTART_ADDRESS_MASK; /* PC,即任務(wù)入口函數(shù) */pxTopOfStack--;*pxTopOfStack = ( StackType_t ) prvTaskExitError; /* LR,函數(shù)返回地址 */pxTopOfStack -= 5; /* R12, R3, R2 and R1 默認(rèn)初始化為0 */*pxTopOfStack = ( StackType_t ) pvParameters; /* R0,任務(wù)形參 *//* 異常發(fā)生時(shí),手動(dòng)加載到CPU寄存器的內(nèi)容 */ pxTopOfStack -= 8; /* R11, R10, R9, R8, R7, R6, R5 and R4默認(rèn)初始化為0 *//* 返回棧頂指針,此時(shí)pxTopOfStack指向空閑棧 */return pxTopOfStack; } static void prvTaskExitError( void ) {/* 函數(shù)停止在這里 */for(;;); }棧頂指針就是pxTopOfStack。pxStack是一個(gè)指針指向任務(wù)棧起始地址,ulStackDepth是任務(wù)棧大小。下面是獲取棧頂指針的代碼。棧是后進(jìn)先出,先進(jìn)去的后出。其實(shí)也就是,先進(jìn)棧的被壓到最底下去了(下標(biāo)最靠后)。所以,如果棧里面什么都沒(méi)有,棧頂?shù)奈恢玫迷谧詈竺?也就是地址最高的哪個(gè)位置)。
/* 獲取棧頂?shù)刂?*/ pxTopOfStack = pxNewTCB->pxStack + ( ulStackDepth - ( uint32_t ) 1 );下面兩個(gè)圖表述的都是一個(gè)意思,只不過(guò)右邊的可能好懂點(diǎn)(先進(jìn)棧的被壓到最底下去了)。
初始化任務(wù)棧的函數(shù)運(yùn)行完,棧就發(fā)生了變化,里面有內(nèi)容了,如下圖所示。可以看到任務(wù)入口地址存進(jìn)去了,任務(wù)形參也存進(jìn)去了。
#define portINITIAL_XPSR ( 0x01000000 )至此,通過(guò)任務(wù)創(chuàng)建函數(shù),已經(jīng)圓滿的初始好了任務(wù)控制塊,同時(shí)填充了任務(wù)棧,任務(wù)棧聯(lián)系了任務(wù)入口地址(任務(wù)的函數(shù)實(shí)體)。任務(wù)控制塊成員變量里面有棧頂指針,聯(lián)系了任務(wù)棧。那么,任務(wù)的棧、任務(wù)的函數(shù)實(shí)體、任務(wù)的控制塊通過(guò)任務(wù)創(chuàng)建函數(shù)就聯(lián)系起來(lái)了。
這里面插一句:任務(wù)棧一個(gè)元素占四個(gè)字節(jié)!上面那個(gè)圖,如果r0地址是0x40,那么pxTopOfStack地址就是0x20(因?yàn)?x40-0x20=32),32÷4=8,也就是說(shuō)八個(gè)元素。
#define portSTACK_TYPE uint32_t typedef portSTACK_TYPE StackType_t; StackType_t Task1Stack[TASK1_STACK_SIZE];uint32_t u:代表 unsigned 即無(wú)符號(hào),即定義的變量不能為負(fù)數(shù); int:代表類型為 int 整形; 32:代表四個(gè)字節(jié),即為 int 類型; _t:代表用 typedef 定義的; 整體代表:用 typedef 定義的無(wú)符號(hào) int 型宏定義; 位(bit):每一位只有兩種狀態(tài)0或1。計(jì)算機(jī)能表示的最小數(shù)據(jù)單位。 字節(jié)(Byte):8位二進(jìn)制數(shù)為一個(gè)字節(jié)。計(jì)算機(jī)基本存儲(chǔ)單元內(nèi)容用字節(jié)表示。就緒列表
下面是main里面就緒列表的定義、初始化,添加任務(wù)到就緒列表。
首先緒列表的定義,簡(jiǎn)而言之,就緒列表是一個(gè)List_t類型的數(shù)組(其實(shí)數(shù)組中每個(gè)元素就相當(dāng)于根節(jié)點(diǎn)),數(shù)組下標(biāo)對(duì)應(yīng)任務(wù)的優(yōu)先級(jí)。
#define configMAX_PRIORITIES /* 任務(wù)就緒列表 */ List_t pxReadyTasksLists[ configMAX_PRIORITIES ]; /* 初始化與任務(wù)相關(guān)的列表,如就緒列表 */ prvInitialiseTaskLists(); /* 將任務(wù)添加到就緒列表 */ vListInsertEnd( &( pxReadyTasksLists[1] ), &( ((TCB_t *)(&Task1TCB))->xStateListItem ) ); /* 將任務(wù)添加到就緒列表 */ vListInsertEnd( &( pxReadyTasksLists[2] ), &( ((TCB_t *)(&Task2TCB))->xStateListItem ) );就緒列表初始化函數(shù)如下,簡(jiǎn)而言之,就是對(duì)List_t類型的數(shù)組里面每個(gè)元素進(jìn)行初始化(根節(jié)點(diǎn)初始化)。
/* 初始化任務(wù)相關(guān)的列表 */ void prvInitialiseTaskLists( void ) {UBaseType_t uxPriority;for( uxPriority = ( UBaseType_t ) 0U; uxPriority < ( UBaseType_t ) configMAX_PRIORITIES; uxPriority++ ){vListInitialise( &( pxReadyTasksLists[ uxPriority ] ) );} }添加任務(wù)到就緒列表的函數(shù)是vListInsertEnd,這個(gè)在之前雙向循環(huán)鏈表說(shuō)過(guò),其實(shí)就是把普通節(jié)點(diǎn)插到根節(jié)點(diǎn)后。
就緒列表在不同任務(wù)之間建立一種聯(lián)系,圖示如下。
調(diào)度器
啟動(dòng)調(diào)度器,是用了一個(gè)SVC中斷。
從下面代碼可以看出,pxCurrentTCB指向的是Task1TCB(任務(wù)控制塊)的地址。
typedef struct tskTaskControlBlock {volatile StackType_t *pxTopOfStack; /* 棧頂 */ListItem_t xStateListItem; /* 任務(wù)節(jié)點(diǎn) */StackType_t *pxStack; /* 任務(wù)棧起始地址 *//* 任務(wù)名稱,字符串形式 */char pcTaskName[ configMAX_TASK_NAME_LEN ]; } tskTCB; typedef tskTCB TCB_t;//void vTaskStartScheduler( void )函數(shù)里 pxCurrentTCB = &Task1TCB;下面這個(gè)svc的中斷函數(shù),里面第一步就是把任務(wù)棧的棧頂指針給r0寄存器。
可以認(rèn)為:r0=pxTopOfStack(任務(wù)棧的棧頂指針的地址)。
//__asm void vPortSVCHandler( void )函數(shù)里 ldr r3, =pxCurrentTCB //加載pxCurrentTCB的地址到r3 ldr r1, [r3] //把r3指向的內(nèi)容給r1,內(nèi)容就是Task1TCB的地址 ldr r0, [r1] //把r1指向的內(nèi)容給r0,內(nèi)容就是Task1TCB的地址里面的第一個(gè)內(nèi)容,也就是pxTopOfStack接下來(lái):以r0(任務(wù)棧的棧頂指針的地址)為基地址,將任務(wù)棧里面向上增長(zhǎng)的8字節(jié)內(nèi)容加載到CPU寄存器r4-r11。
ldmia r0!, {r4-r11}然后將r0存到psp里。
msr psp, r0下面這個(gè)代碼,目的是改EXC_RETURN值為0xFFFFFFD,這樣的話中斷返回就進(jìn)入線程模式,使用線程堆棧(sp=psp)。
orr r14, #0xd看下面這個(gè)圖,異常返回時(shí),出棧用的是PSP指針。PSP指針把任務(wù)棧里面剩余的內(nèi)容(沒(méi)有讀到寄存器里的內(nèi)容)全部給弄出去(自動(dòng)將棧中的剩余內(nèi)容加載到cpu寄存器)。那么任務(wù)函數(shù)的地址就給到了PC,程序就跳到任務(wù)函數(shù)的地方繼續(xù)運(yùn)行。
圖1如下:注意,動(dòng)的是psp,pxTopOfStack是不動(dòng)的。
下面是實(shí)驗(yàn)證明上面關(guān)于psp指針運(yùn)動(dòng)描述的正確性:
r0一開(kāi)始存的就是pxTopOfStack的值(任務(wù)棧的棧頂指針的地址)
接下來(lái)把運(yùn)動(dòng)過(guò)的r0給psp,此時(shí)的psp位置就在圖1psp2那個(gè)地方。
下圖這個(gè)psp地址仍然是0x40。
程序運(yùn)行完bx r14,就跑到任務(wù)函數(shù)里面了,此時(shí)的psp=0x60,位置就在圖1的psp3。
現(xiàn)在程序跑到任務(wù)函數(shù)里面去了,任務(wù)函數(shù)里面調(diào)了taskYIELD()函數(shù),目的就是觸發(fā)PendSV中斷(優(yōu)先級(jí)最低,沒(méi)有其他中斷運(yùn)行時(shí)才響應(yīng))。下面這個(gè)圖是進(jìn)到PendSV中斷服務(wù)函數(shù)之前的寄存器組狀態(tài)。
下面這個(gè)圖是進(jìn)到PendSV中斷服務(wù)函數(shù)時(shí)的寄存器組狀態(tài)。可以觀察psp,從0x60變成了0x40。
現(xiàn)在psp的位置就可以知道了,如下圖所示。這是因?yàn)?#xff0c;進(jìn)到xPortPendSVHandler函數(shù)之后,上個(gè)任務(wù)運(yùn)行的環(huán)境將會(huì)自動(dòng)存儲(chǔ)到任務(wù)的棧中,同時(shí)psp自動(dòng)更新。
下面這個(gè)代碼,把psp的值存到r0里面。
//__asm void xPortPendSVHandler( void )函數(shù) mrs r0, psp //void vTaskStartScheduler( void )函數(shù)里 pxCurrentTCB = &Task1TCB;/*pxCurrentTCB有一個(gè)地址,這個(gè)地址里面的內(nèi)容是當(dāng)前任務(wù)的地址*//*當(dāng)前任務(wù)地址的第一個(gè)內(nèi)容就是當(dāng)前任務(wù)的棧頂指針*///__asm void xPortPendSVHandler( void )函數(shù)里 ldr r3, =pxCurrentTCB /* 加載pxCurrentTCB的地址到r3 */ ldr r2, [r3] /* 把r3指向的內(nèi)容給r2,內(nèi)容就是Task1TCB(當(dāng)前任務(wù))的地址*//*[r2]是當(dāng)前任務(wù)棧的棧頂指針*/ stmdb r0!, {r4-r11} /* 將CPU寄存器r4~r11的值存儲(chǔ)到r0指向的地址 */ str r0, [r2] /* 把r0的地址給當(dāng)前任務(wù)棧的棧頂指針 */經(jīng)過(guò)上面這個(gè)代碼,現(xiàn)在r0的位置如下。psp在上面這個(gè)過(guò)程是沒(méi)變化的,變的只有r0。
對(duì)照著下面這個(gè)圖,更清晰點(diǎn)。r2存的是當(dāng)前任務(wù)的地址。r0存的是棧頂指針的地址。
下面對(duì)r3進(jìn)行說(shuō)明:r3=0x2000000C,這個(gè)地址里面存的第一個(gè)內(nèi)容是當(dāng)前任務(wù)塊的地址0x20000068如下圖所示。
下面對(duì)當(dāng)前任務(wù)塊的地址進(jìn)行說(shuō)明:當(dāng)前任務(wù)塊的地址0x20000068里面存的第一個(gè)內(nèi)容就是棧頂指針的地址。
下面對(duì)棧頂指針的地址進(jìn)行說(shuō)明:棧頂指針地址里面內(nèi)容剛好就是當(dāng)前任務(wù)的任務(wù)棧。
可以對(duì)比下圖,觀察當(dāng)前任務(wù)棧里面的內(nèi)容,與此同時(shí)內(nèi)容也對(duì)應(yīng)了地址,地址就可以通過(guò)上圖推出,比如,0x20000060地址里面存的就是0x10000000。
下面這個(gè)代碼:目的是將r3和r14臨時(shí)壓入主棧(MSP指向的棧),因?yàn)榻酉聛?lái)需要調(diào)用任務(wù)切換函數(shù),調(diào)用函數(shù)時(shí),返回地址自動(dòng)保存到r14里面。r3的內(nèi)容是當(dāng)前任務(wù)塊的地址(ldr r3, =pxCurrentTCB),調(diào)用函數(shù)后,pxCurrentTCB會(huì)被更新。
stmdb sp!, {r3, r14}執(zhí)行代碼之前,MSP指向0x20000058這個(gè)地址。
執(zhí)行代碼之后,MSP指向的地址少了8個(gè)字節(jié),與此同時(shí)r3和r14存到了MSP指向的地址里面。
msp指向的棧里面的具體信息其實(shí)可以反推出來(lái),如下綠字:
下面這個(gè)代碼:basepri是中斷屏蔽寄存器,下面這個(gè)設(shè)置,優(yōu)先級(jí)大于等于11的中斷都將被屏蔽。相當(dāng)于關(guān)中斷進(jìn)入臨界段。
mov r0, #configMAX_SYSCALL_INTERRUPT_PRIORITY msr basepri, r0 /* #define configMAX_SYSCALL_INTERRUPT_PRIORITY 191 /* 高四位有效,即等于0xb0,或者是11 */ 191轉(zhuǎn)成二進(jìn)制就是11000000,高四位就是1100 */下面這個(gè)代碼:調(diào)用了函數(shù)vTaskSwitchContext,這個(gè)函數(shù)目的是選擇優(yōu)先級(jí)最高的任務(wù),然后更新pxCurrentTCB。目前這里面使用的是手動(dòng)切換。
bl vTaskSwitchContext void vTaskSwitchContext( void ) { /* 兩個(gè)任務(wù)輪流切換 */if( pxCurrentTCB == &Task1TCB ){pxCurrentTCB = &Task2TCB;}else{pxCurrentTCB = &Task1TCB;} }現(xiàn)在說(shuō)明一下調(diào)用這個(gè)函數(shù)產(chǎn)生什么后果:
從下圖可知,此時(shí)r3=0x2000000C,這個(gè)地址里面的的內(nèi)容就是當(dāng)前任務(wù)塊的地址。
進(jìn)行到下面這一步,當(dāng)前任務(wù)塊的地址變了,與此同時(shí),0x2000000C地址里面的的內(nèi)容也變了。也就是說(shuō),走出調(diào)用函數(shù)之后,通過(guò)r3就能找到變化后新的任務(wù)地址了。
那么此時(shí)豁然開(kāi)朗,為什么調(diào)用函數(shù)前要把r3入棧呢,看下圖正中間上方的匯編代碼,這個(gè)c語(yǔ)言背后的匯編代碼是調(diào)用寄存器r0、r1存一些中間變量,為了防止運(yùn)行函數(shù)時(shí)往r3寄存器里面存中間變量,才把r3入棧保護(hù)起來(lái)。想一下,如果往r3寄存器里面存中間變量,那么0x2000000C地址就不存到r3寄存器里了,那也無(wú)法通過(guò)r3找到變化后新的任務(wù)地址了。
下面這個(gè)代碼:優(yōu)先級(jí)高于0的中斷被屏蔽,相當(dāng)于是開(kāi)中斷退出臨界段。
mov r0, #0 /* 退出臨界段 */ msr basepri, r0下面這個(gè)代碼恢復(fù)r3和r14
ldmia sp!, {r3, r14} /* 恢復(fù)r3和r14 */如下圖,r3和r14被恢復(fù),而且MSP從0x20000550變成了0x20000558。
這里面有個(gè)細(xì)節(jié),MSP變動(dòng)之后,MSP指向的棧前面的數(shù)(存的r3和r14)卻被留了下來(lái)。這讓人不禁思考出棧究竟是什么意思,這里不就只是動(dòng)了MSP指針嗎。
此時(shí)觀察psp地址里面的內(nèi)容,可發(fā)現(xiàn),還是之前的那個(gè)任務(wù)棧。看了出棧和c語(yǔ)言里面實(shí)體的出(c語(yǔ)言里面出棧后,出去的內(nèi)容就不在棧里面了)還不太一樣,這個(gè)出棧,動(dòng)的是指針,內(nèi)容還在棧里面。
下面這個(gè)代碼,進(jìn)行完,r0里面存的是當(dāng)前任務(wù)棧的棧頂指針的地址。
ldr r1, [r3] ldr r0, [r1] /* 當(dāng)前激活的任務(wù)TCB第一項(xiàng)保存了任務(wù)堆棧的棧頂,現(xiàn)在棧頂值存入R0*/下面是當(dāng)前的任務(wù)棧里面的內(nèi)容。
ldmia r0!, {r4-r11} /* 出棧 */這個(gè)時(shí)候r0位置變到了0x200000c0。
然后下面把r0給了psp。記得吧,之前psp指向的可是0x20000040,也就是上一個(gè)任務(wù)的任務(wù)棧,這里面切到了另一個(gè)任務(wù)的任務(wù)棧里面了。也就是psp指向0x200000c0。
msr psp, r0下面這個(gè)代碼運(yùn)行完效果如下圖。
bx r14仔細(xì)觀察,異常退出時(shí),會(huì)以psp作為基地址,將任務(wù)棧里面剩下的內(nèi)容自動(dòng)加載到CPU寄存器。然后PC指針就拿到了任務(wù)棧里面的任務(wù)函數(shù)地址,然后就跳到任務(wù)函數(shù)里了。至此,切換完成。
最后,觀察一下psp:由下面兩張圖,就明白了,psp出棧是什么意思。
下面是返回Thread Mode后(進(jìn)入到了任務(wù)函數(shù)里面)psp的指向。
下圖是沒(méi)有返回到Thread Mode時(shí)psp的指向。
總結(jié)任務(wù)切換
總結(jié)一下核心思路:
1.首先是這張圖,在任務(wù)函數(shù)里面,處于Thread Mode狀態(tài)(為什么呢,因?yàn)閎x r14 指令,里面r14的值設(shè)置的是0xFFFFFFFD),然后通過(guò)任務(wù)函數(shù)里面的taskYIELD()函數(shù),進(jìn)入Handler Mode狀態(tài),里面進(jìn)行了任務(wù)切換操作,就是說(shuō),psp指向的任務(wù)棧切換了(所以一會(huì)pc指向的任務(wù)函數(shù)也改了),然后結(jié)束異常的時(shí)候,psp出棧,pc現(xiàn)在指向的是切換后的任務(wù)函數(shù)地址,于是就又跳到另一個(gè)任務(wù)函數(shù)里。
2.要明白切到任務(wù)函數(shù)里面的原理
之前創(chuàng)建任務(wù)時(shí),已經(jīng)把任務(wù)函數(shù)保存在了任務(wù)棧內(nèi)。
出棧的話,psp指向的棧里面剩下的東西,會(huì)加載到寄存器里面,如下圖所示:那么任務(wù)函數(shù)地址就給到pc指針了,那么異常返回之后,程序就跳到任務(wù)函數(shù)的地方繼續(xù)運(yùn)行,那么就切到任務(wù)函數(shù)里了。
總結(jié)
以上是生活随笔為你收集整理的freertos内核 任务定义与切换 原理分析的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: java 密码生成器_Java课程设计-
- 下一篇: c语言 数据结构 list、queue、