網(wǎng)站首頁 編程語言 正文
?
?
任務控制塊數(shù)據(jù)結構
任務控制塊數(shù)據(jù)結構在task.c
聲明
typedef struct tskTaskControlBlock { volatile StackType_t * pxTopOfStack; //棧頂指針 ListItem_t xStateListItem; //任務節(jié)點 StackType_t * pxStack; //任務棧起始地址 char pcTaskName[configMAX_TASK_NAME_LEN];//任務名稱 }tskTCB;
任務創(chuàng)建函數(shù)
下面是用靜態(tài)方式創(chuàng)建任務的函數(shù)
static void prvInitialiseNewTask(TaskFunction_t pxTaskCode,/*任務函數(shù)*/ const char * const pcName,/*任務名稱*/ const uint32_t ulStackDepth,/*任務棧大小,單位字*/ void * const pvParameters, /*任務形參*/ TaskHandle_t* const pxCreatedTask,/*任務句柄*/ TCB_t * pxNewTCB) /*任務控制塊指針*/ { StackType_t * pxTopOfStack; UBaseType_t x; //棧頂?shù)刂? pxTopOfStack = pxNewTCB->pxStack + (ulStackDepth - (uint32_t) 1); //項下做8字節(jié)對齊 pxTopOfStack = (StackType_t*) ( (uint32_t)pxTopOfStack & (~(uint32_t)0x0007) ); //存儲名字 for(x=(UBaseType_t)0;x<(UBaseType_t)configMAX_TASK_NAME_LEN;x++) { pxNewTCB->pcTaskName[x] = pcName[x]; if(pcName[x]=='\0') break; } pxNewTCB->pcTaskName[configMAX_TASK_NAME_LEN-1] = '\0'; //初始化TCB中的xStateListItem列表項 vListInitialiseItem(& (pxNewTCB->xStateListItem)); //設置xStateListItem列表項的擁有者即為傳入的TCB listSET_LIST_ITEM_OWNER( &(pxNewTCB->xStateListItem), pxNewTCB ); //初始化任務棧,并返回更新的棧頂指針 pxNewTCB->pxTopOfStack = pxPortInitialiseStack(pxTopOfStack,pxTaskCode,pvParameters); //返回任務句柄 if((void*)pxCreatedTask !=NULL ) { *pxCreatedTask = (TaskHandle_t) pxNewTCB; } }
下面看pxPortInitialiseStack
是怎么初始化任務棧的
因為是向下生長的滿棧,所以是--
操作
#define portINITIAL_XPSR (0x01000000) #define portSTART_ADDRESS_MASK ((StackType_t)0xfffffffeUL) StackType_t * pxPortInitialiseStack(StackType_t* pxTopOfStack, TaskFunction_t pxCode, void*pvParameters) { //-------------設置內(nèi)核會自動加載的寄存器,且順序不能變 pxTopOfStack--; *pxTopOfStack = portINITIAL_XPSR;//xPSR bit24 pxTopOfStack--; *pxTopOfStack = ( ( StackType_t ) pxCode ) & portSTART_ADDRESS_MASK;//R15 PC pxTopOfStack--; *pxTopOfStack = ( StackType_t ) prvTaskExitError;//R14 LR pxTopOfStack -= 5; //R12 R3 R2 R1 *pxTopOfStack = ( StackType_t ) pvParameters;//R0 //--------------下面8個需要手動保存 pxTopOfStack-=8; //返回更新后的棧頂指針 return pxTopOfStack; }
如下圖
定義就緒表
在task.c
中,就緒表就是列表類型的數(shù)組,元素個數(shù)是configMAX_PRIORITIES
,就緒表把同一優(yōu)先級的任務插入到同一優(yōu)先級的鏈表中,數(shù)組下標表示優(yōu)先級,如下圖
#define configMAX_PRIORITIES 5 List_t pxReadyTasksLists[configMAX_PRIORITIES];
就緒表初始化
就緒表初始化就是把數(shù)組每個元素(即列表)初始化(即調用vListInitialise),結果如下
void prvInitialiseTaskLists(void) { UBaseType_t uxPriority; for(uxPriority = (UBaseType_t)0 ; uxPriority < (UBaseType_t) configMAX_PRIORITIES; uxPriority ++) { vListInitialise(&(pxReadyTasksLists[uxPriority])); } }
啟動調度器
調用關系,這里手動指定第一個運行的任務
vTaskStartScheduler->xPortStartScheduler->prvStartFirstTask
void vTaskStartScheduler(void) { //手動指定一個和要運行的任務 pxCurrentTCB = &Task1TCB; //啟動調度器 if(xPortStartScheduler()!=pdFALSE) { //啟動成功不會跑到這里 } }
//stm32 用4bit表示優(yōu)先級,所以最低優(yōu)先級則是15 #define configKERNEL_INTERRUPT_PRIORITY 15 #define portNVIC_SYSPRI2_REG (*((volatile uint32_t*) 0xe000ed20)) #define portNVIC_PENDSV_PRI (((uint32_t)configKERNEL_INTERRUPT_PRIORITY)<<16UL) #define portNVIC_SYSTICK_PRI (((uint32_t)configKERNEL_INTERRUPT_PRIORITY)<<24UL) BaseType_t xPortStartScheduler(void) { //把pendsv systick中斷優(yōu)先級設置為最低 portNVIC_SYSPRI2_REG |=portNVIC_PENDSV_PRI; portNVIC_SYSPRI2_REG |=portNVIC_SYSTICK_PRI; //啟動第一個任務,該函數(shù)不會返回 prvStartFirstTask(); //不會運行到這里 return 0; }
在CM3中,0xE000ED08存放的是SCB_VTOR寄存器地址,SCB_VTOR是向量表的起始地址,即MSP的地址
__asm void prvStartFirstTask(void) { PRESERVE8 ldr r0, =0xE000ED08 //r0=0xE000ED08,相當于r0=&SCB_VTOR ldr r0, [r0] //r0=*r0,即r0=*((uint32_t*)0xE000ED08),即r0=0x0 ldr r0, [r0] //r0=*r0,即r0=*((uint32_t*)0x0),即r0=msp指針值 msr msp, r0 //msp=r0,msp指針獲得msp地址 cpsie i //開中斷 cpsie f //開中斷 dsb isb svc 0 //觸發(fā)SVC系統(tǒng)調用,此后會進入SVCHandler nop //下面這2個nop不會執(zhí)行 nop }
__asm void vPortSVCHandler(void) { extern pxCurrentTCB; PRESERVE8 //r3=&pxCurrentTCB,注意pxCurrentTCB本身就是一個指針,是指向一個任務的TCB指針 ldr r3, =pxCurrentTCB //r1=*r3,即r1=pxCurrentTCB ldr r1, [r3] //r0=*r1,此時r1=pxCurrentTCB, //又結構體第一個成員(棧頂指針)的地址和結構體地址數(shù)值上是相同的, //所以r0=*(&(pxCurrentTCB.pxTopOfStack)),即r0=pxCurrentTCB.pxTopOfStack, //即r0此時指向當前任務空閑棧頂位置 ldr r0, [r1] //以r0為開始把r4-r11依次保存到當前任務的psp棧 ldmia r0!,{r4-r11} //更新當前任務psp棧指針 msr psp,r0 isb mov r0, #0 msr basepri,r0 //開中斷 //此時r14是0xFFFF_FFF9,表示返回后進入Thread mode,使用MSP,返回Thmub狀態(tài), //這里其實是把bit2置1,要求返回后使用PSP orr r14,#0x0d //返回,這里會自動加載初始化任務棧填寫的xPSR、R15、R14、R12、R3-R0值到內(nèi)核對應寄存器, //所以返回后就直接到pxCurrentTCB指向的任務 bx r14 }
任務切換
這里手動觸發(fā)任務切換,其實就是向中斷控制及狀態(tài)寄存器ICSR(地址0xE000_ED04)PENDSVSET位(bit28)寫1懸起pendsv中斷,在pendsv_handler中尋找下一個要運行的任務并做任務切換
#define taskYIELD() portYIELD()
#define portNVIC_INT_CTRL_REG (*((volatile uint32_t*)0xe000ed04)) #define portNVIC_PENDSVSET_BIT (1<<28UL) #define portSY_FULL_READ_WRITE (15) #define portYIELD() \ { \ //觸發(fā)一次pendsv中斷 portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT; \ __dsb(portSY_FULL_READ_WRITE); \ __isb(portSY_FULL_READ_WRITE); \ }
__asm void xPortPendSVHandler(void) { //進入中斷前會自動保存xPSR、R15、R14、R12、R3-R0到當前任務P棧中,此時任務棧頂指針指向要保存R11的位置 extern pxCurrentTCB; extern vTaskSwitchContext;//這個函數(shù)是用來尋找下一個要運行的任務 PRESERVE8 mrs r0,psp //當前任務棧psp指針存入r0, isb ldr r3,=pxCurrentTCB //r3=&pxCurrentTCB ldr r2,[r3] //r2=*r3, 即r2=pxCurrentTCB //以r0開始遞減的手動保存r4-r11 stmdb r0!,{r4-r11} //r0=*r2,此時r2=pxCurrentTCB, //又結構體第一個成員(棧頂指針)的地址和結構體地址數(shù)值上是相同的, //所以r0=*(&(pxCurrentTCB.pxTopOfStack)),即r0=pxCurrentTCB.pxTopOfStack, //即r0此時指向當前任務空閑棧頂位置 str r0,[r2] //到這里上文就保存完成 //將r3、r14壓入棧保存起來(此時sp使用的是msp) //r14要保存是因為等下要調用函數(shù),避免r14被覆蓋無法從pendsv中斷正常返回 //r3要保存是因為r3保存的是當前正在運行任務控制塊的二級指針&pxCurrentTCB,后面要通過r3來操作pxCurrentTCB來切到下文 stmdb sp!,{r3,r14} //關中斷,閾值是configMAX_SYSCALL_INTERRUPT_PRIORITY mov r0,#configMAX_SYSCALL_INTERRUPT_PRIORITY msr basepri,r0 dsb isb //調用vTaskSwitchContext,功能是找到優(yōu)先級最高的任務,然后讓pxCurrentTCB = &優(yōu)先級最高任務TCB,我們這里手動指定 bl vTaskSwitchContext //開中斷 mov r0,#0 msr basepri,r0 //從msp中恢復r3、r14 ldmia sp!,{r3,r14} //r1=*r3,此時r3=&pxCurrentTCB //即r1=pxCurrentTCB ldr r1,[r3] //r0=*r1則r0=pxCurrentTCB.pxTopOfStack 理由前面講過 ldr r0,[r1] //這里把pxCurrentTCB保存的需要手動加載的值加載到內(nèi)核的r4-r11 ldmia r0!,{r4-r11} //更新加載了r4-r11的任務棧到psp msr psp,r0 isb //返回,這里會以psp自動加載保存了xPSR、R15、R14、R12、R3-R0值到內(nèi)核的xPSR、R15、R14、R12、R3-R0寄存器,所以返回的是pxCurrentTCB任務 bx r14 nop }
這里為了簡單手動指定TCB
extern TCB_t Task1TCB; extern TCB_t Task2TCB; void vTaskSwitchContext(void) { if(pxCurrentTCB == &Task1TCB) { pxCurrentTCB = &Task2TCB; } else { pxCurrentTCB = &Task1TCB; } }
至此任務切換原理講完
main.c
extern List_t pxReadyTasksLists[configMAX_PRIORITIES]; portCHAR flag1; portCHAR flag2; TaskHandle_t Task1_Handle; StackType_t Task1Stack[128]; TCB_t Task1TCB; TaskHandle_t Task2_Handle; StackType_t Task2Stack[128]; TCB_t Task2TCB; void delay(uint32_t x) { for(;x!=0;x--); } void Task1_Fntry(void *arg) { while(1) { flag1=1; delay(100); flag1=0; delay(100); taskYIELD();//手動觸發(fā)切換任務 } } void Task2_Fntry(void *arg) { while(1) { flag2=1; delay(100); flag2=0; delay(100); taskYIELD();//手動觸發(fā)切換任務 } } int main(void) { //初始化就緒列表 prvInitialiseTaskLists(); //創(chuàng)建任務1 Task1_Handle = xTaskCreateStatic(Task1_Fntry,"task1",128,NULL,Task1Stack,&Task1TCB); //將任務添加到就緒列表 vListInsertEnd(&pxReadyTasksLists[1],&((&Task1TCB)->xStateListItem)); //創(chuàng)建任務2 Task2_Handle = xTaskCreateStatic(Task2_Fntry,"task2",128,NULL,Task2Stack,&Task2TCB); //將任務添加到就緒列表 vListInsertEnd(&pxReadyTasksLists[2],&((&Task2TCB)->xStateListItem)); //啟動調度器 vTaskStartScheduler(); for(;;); }
原文鏈接:https://blog.csdn.net/weixin_41572450/article/details/105054916
相關推薦
- 2022-02-18 cv2.error: OpenCV(4.5.4-dev) :-1: error: (-5:Bad a
- 2022-07-27 Python中range函數(shù)的使用方法_python
- 2022-04-19 Windows中Python上傳文件到Liunx下的fastdfs
- 2022-12-04 react?component?function組件使用詳解_React
- 2023-04-01 pytorch和numpy默認浮點類型位數(shù)詳解_python
- 2022-11-12 C語言數(shù)據(jù)結構之單鏈表的查找和建立_C 語言
- 2023-07-10 解決flask (flask-restful)中文亂碼問題
- 2022-07-09 代理模式(靜態(tài)代理和動態(tài)代理)
- 最近更新
-
- window11 系統(tǒng)安裝 yarn
- 超詳細win安裝深度學習環(huán)境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區(qū)別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優(yōu)雅實現(xiàn)加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發(fā)現(xiàn)-Nac
- Spring Security之基于HttpR
- Redis 底層數(shù)據(jù)結構-簡單動態(tài)字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發(fā)送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支