从零理解RTOS机制:基于Cortex-M3的任务切换、抢占式调度与软延时
文章目录一、 RTOS 角色代入微型工厂的日常二、 任务切换到底发生了什么三、 软延时与抢占式调度告别傻等四、 代码演示基于Cortex-M3内核刚开始接触 RTOS实时操作系统的底层原理时各种寄存器、栈指针和汇编代码常常让人一头雾水。今天我们抛开复杂的代码用一家“工厂”的运作模式把 RTOS 最核心的任务切换和延时机制扒个底朝天基于Cortex-M3内核一、 RTOS 角色代入微型工厂的日常要理解系统是怎么运作的首先要认清系统里的几个核心角色任务函数 (task1Entry,task2Entry) 打工人。他们被招进来就是为了在自己的工位上死循环干活的。私有堆栈 (task1Env,task2Env数组) 私人储物柜。每个打工人都必须有一个自己的柜子用来放干活时的工具和状态CPU 寄存器。任务控制块 (tTask结构体) 员工档案卡。这张卡片很薄上面只记录了一条关键信息“这个人的储物柜当前用到了哪一格栈顶指针stack”。currentTask和nextTask 车间主任手里的排班表。主任指着谁谁就是当前的焦点。PendSV_Handler(汇编代码) 无情的车间保安。他不看你是谁只要他一被叫醒他只干一件事把正在干活的人currentTask踢走没收他的工具塞回他自己的柜子然后把下一个接班人nextTask的柜子打开把工具还给他让他滚去干活。二、 任务切换到底发生了什么当保安PendSV登场要求两个任务交接班时底层的 Cortex-M3 硬件和软件配合完成了以下 7 个标准动作保存一半家当任务 A 正在跑触发切换。硬件自动保存 A 的一半家当R0-R3, R12, LR, PC, xPSR。保存剩下家当发现不是第一次启动软件把 A 剩下的一半家当R4-R11存进 A 的专属栈。记录位置把 A 现在的栈顶位置记在currentTask-stack里。交接棒让currentTask等于nextTask比如指向任务 B。取出新家当找到 B 的专属栈顶把 B 的R4-R11取出来。强制切栈设置 PSP 指向 B 的栈顶修改 LR 要求 CPU 使用 PSP 运行。灵魂附体退出异常硬件自动把 B 剩下的一半家当弹出来。CPU 瞬间跳转开始执行任务 B三、 软延时与抢占式调度告别傻等在裸机开发中延时往往是靠死循环死等实现的极大地浪费了 CPU。而在 RTOS 中我们引入了软计时器和 SysTick硬件滴答定时器实现了完美的“闭环延时与唤醒”Task1 想休息调用tTaskDelay(100)- 设置自己的专属闹钟delayTicks 10- 立刻主动交出 CPU。系统兜底调度器发现 Task2 也正在延时CPU 不能停于是把 CPU 交给优先级最低的idleTask空闲任务兜底运行。时间流逝硬件每 10ms 进一次SysTick_Handler中断在后台悄悄把 Task1 的delayTicks减 1。闹钟响起100ms 后Task1 的delayTicks刚好减到了 0。重回战场SysTick 触发的调度器发现 Task1 醒了立刻呼叫保安PendSV把 CPU 从idleTask手里抢过来切回 Task1 继续干活通过这种机制CPU 永远只分配给真正需要干活的任务效率得到了质的飞跃。四、 代码演示基于Cortex-M3内核 main.c#includetinyOS.h#includeARMCM3.htTask*currentTask;//Current tasks structure pointertTask*nextTask;//Next tasks structure pointertTask*idleTask;//Idle tasks structure pointertTask*taskTable[2];//Task listvoidtTaskDelay(uint32_tms);//Task delay functionvoidtTaskInit(tTask*task,void(*entry)(void*),void*param,tTaskStack*stack);//Task initial functionvoidtask1Entry(void*param);//Task1 functionvoidtask2Entry(void*param);//Task2 functionvoididleTaskEntry(void*param);//Idle task functionvoidtSetSysTickPeriod(uint32_tms);//SysTick timer initial functioninttask1Flag;//Task1 flaginttask2Flag;//Task2 flagtTask tTask1;//Task1 structuretTask tTask2;//Task2 structuretTask tTaskIdle;//Idle task structuretTaskStack task1Env[1024];//Task1 stacktTaskStack task2Env[1024];//Task2 stacktTaskStack idleTaskEnv[1024];//Idle task stackintmain(void){tTaskInit(tTask1,task1Entry,(void*)0x11111111,task1Env[1024]);//Task1 initiatetTaskInit(tTask2,task2Entry,(void*)0x22222222,task2Env[1024]);//Task2 initiatetTaskInit(tTaskIdle,idleTaskEntry,(void*)0,idleTaskEnv[1024]);//Idle task initiatetaskTable[0]tTask1;taskTable[1]tTask2;idleTasktTaskIdle;nextTasktaskTable[0];//The first task to be ran is task1tTaskRunFirst();}voidtTaskDelay(uint32_tms)//Task delay function, the parameter ms must be integral multiple of 10{currentTask-delayTicksms*0.1;tTaskSched();}voidtTaskInit(tTask*task,void(*entry)(void*),void*param,tTaskStack*stack)//Task initial function{//These data are pushed or popped using PSP pointer by the hardware automaticly*(--stack)(unsignedlong)(124);//xPSR*(--stack)(unsignedlong)entry;//PC(R15)*(--stack)(unsignedlong)0x14;//R14*(--stack)(unsignedlong)0x12;//R12*(--stack)(unsignedlong)0x3;//R3*(--stack)(unsignedlong)0x2;//R2*(--stack)(unsignedlong)0x1;//R1*(--stack)(unsignedlong)param;//param(R0)//These data are pushed or popped using task structure stack pointer by user manually*(--stack)(unsignedlong)0x11;//R11*(--stack)(unsignedlong)0x10;//R10*(--stack)(unsignedlong)0x9;//R9*(--stack)(unsignedlong)0x8;//R8*(--stack)(unsignedlong)0x7;//R7*(--stack)(unsignedlong)0x6;//R6*(--stack)(unsignedlong)0x5;//R5*(--stack)(unsignedlong)0x4;//R4task-stackstack;//Initiate the task stack pointertask-delayTicks0;//Initiate the task delay counter}voidtask1Entry(void*param)//Task1 function{tSetSysTickPeriod(10);//Timing cycle is 10msfor(;;){task1Flag1;tTaskDelay(100);task1Flag0;tTaskDelay(100);}}voidtask2Entry(void*param)//Task2 function{for(;;){task2Flag1;tTaskDelay(10);task2Flag0;tTaskDelay(10);}}voididleTaskEntry(void*param)//Idele task function{for(;;){}}voidtSetSysTickPeriod(uint32_tms)//SysTick Timer initial function{SysTick-LOADms*SystemCoreClock/1000-1;//Set the reload registerNVIC_SetPriority(SysTick_IRQn,1(__NVIC_PRIO_BITS-1));//The priority of - is higher than that of SysTick-VAL0;SysTick-CTRLSysTick_CTRL_CLKSOURCE_Msk|SysTick_CTRL_TICKINT_Msk|SysTick_CTRL_ENABLE_Msk;//Enable SysTick IRQ and SysTick Time}voidSysTick_Handler(void){inti;for(i0;i2;i){if(taskTable[i]-delayTicks0)taskTable[i]-delayTicks--;}tTaskSched();//Schedule the task} switch.c#includetinyOS.h#includeARMCM3.h__asmvoidPendSV_Handler(void)//PendSV service funtion{IMPORT currentTask IMPORT nextTask MRS R0,PSP CBZ R0,PendSVHandler_NoSave STMDB R0!,{R4-R11}//Push stackLDR R1,currentTask LDR R1,[R1]STR R0,[R1]//Update task structure stack pointerPendSVHandler_NoSave LDR R0,currentTask LDR R1,nextTask LDR R1,[R1]STR R1,[R0]LDR R0,[R1]LDMIA R0!,{R4-R11}//Pop stackMSR PSP,R0 ORR LR,LR,#0X04//Using PSPBX LR//Exit PendSV}voidtTaskRunFirst(void)//This functionll be called when the tinyOS ran at the first time{__set_PSP(0);MEM8(NVIC_INIT_SYSPRI2)NVIC_INIT_PENDSV_PRI;//Configure the priority of Pend_SVC-the lowestMEM32(NVIC_INIT_CTRL)NVIC_INIT_PENDSVSET;//Trigger the Pend_SVC}voidtTaskSched(void)//Task schedule function{if(currentTaskidleTask)//Current task is idle task{if(taskTable[0]-delayTicks0)nextTasktaskTable[0];elseif(taskTable[1]-delayTicks0)nextTasktaskTable[1];elsereturn;}elseif(currentTasktaskTable[0])//Current task is task1{if(taskTable[1]-delayTicks0)nextTasktaskTable[1];elseif(currentTask-delayTicks!0)nextTaskidleTask;elsereturn;}elseif(currentTasktaskTable[1])//Current task is task2{if(taskTable[0]-delayTicks0)nextTasktaskTable[0];elseif(currentTask-delayTicks!0)nextTaskidleTask;elsereturn;}tTaskSwitch();}voidtTaskSwitch(void)//Task switch function{MEM32(NVIC_INIT_CTRL)NVIC_INIT_PENDSVSET;//Directly trigger PendSV} tinyOS.h#ifndefTINYOS_H#defineTINYOS_H#includestdint.h//The official data type defination head file#defineNVIC_INIT_CTRL0xE000ED04//The address of NVIC_INIT_CTRL register#defineNVIC_INIT_PENDSVSET0x10000000//The value to be written to the NVIC_INIT_CTRL#defineNVIC_INIT_SYSPRI20xE000ED22//The address of NVIC_INIT_SYSPRI2 register#defineNVIC_INIT_PENDSV_PRI0x000000FF//The value to be written to the NVIC_INIT_PENDSV_PRI#defineMEM8(addr)*(volatileunsignedchar*)addr#defineMEM32(addr)*(volatileunsignedlong*)addrtypedefuint32_ttTaskStack;//Task stack data typetypedefstruct_tTask//Task structure data type{uint32_t*stack;//Task stack pointeruint32_tdelayTicks;//Task delay counter}tTask;externtTask*currentTask;//Current tasks structure pointerexterntTask*nextTask;//Next tasks structure pointerexterntTask*idleTask;//Idle tasks structure pointerexterntTask*taskTable[2];//Task listvoidtTaskRunFirst(void);//This functionll be called when the tinyOS ran at the first timevoidtTaskSched(void);//Task schedule functionvoidtTaskSwitch(void);//Task switch function#endif