文章目录概要RTOS任务调度原理RTOS上电后工作流程手动编写任务调度RTOS工程创建就绪任务链表实现链表初始化、和节点插入操作任务的调度机制核心是链表调度算法配置FreeRTOS中任务调度过程理解FreeRTOS任务调度标准流程编写TCB任务控制块静态任务创建函数开始创建具体的任务线程编写SVC中断实现首个任务启动编写PendSV中断触发函数编写PendSV中断实现任务间上下文切换概要在学习FreeRTOS过程中结合韦东山-FreeRTOS手册和视频、及网上资源。基于ARM Cortex-M内核手动构建双向链表与TCB结构汇编语言编写SVC启动首任务、PendSV实现上下文切换完成极简RTOS双任务调度系统深入理解操作系统底层机制。如有内容理解不正确之处欢迎大家指出共同进步。RTOS任务调度原理systick检查链表需要任务切换触发pensv异常在这个异常里执行调度器将除了硬件压栈的8个寄存器压到psp°然后检查是否有浮点运算器决定压栈然后把psp的地址保存到tcb中然后切换到接下来要运行的任务的tcb然后从tcb取出psp地址修改psp地址按照顺序出栈然后退出异常。RTOS上电后工作流程手动编写任务调度RTOS工程利用CubeMX工具生成工程项目选择最常见的STM32f103C8t6芯片。配置仿真口和时钟分别选择四线仿真口和TIM1作为时基SYStick后续需要在 main() 或RTOS启动代码中手动配置SysTick作为操作系统心跳配置时钟选择外部晶振并设置好时钟源、PLL倍频数、APB1总线分频数最后设置好工程名称和路径点击项目生成即可转到Keil5打开工程编译。创建就绪任务链表typedefstructxList_Item//双向链表节点{structxList_Item*pxNext;//指向后一节点指针structxList_Item*pxPrevious;//指向前一节点指针uint32_tx_Item_Value;//后续操作系统添加延时使用void*pxOwner;//本节点挂载的对应TCBvoid*pvContainer;//本节点挂载在哪一个链表上就绪、运行、挂起、阻塞}List_Item;typedefstructxList{//链表结构体 RTOS中的列表项unsignedlongNumber_Of_Item;//挂在的节点数List_Item*PxIndex;//当前链表查询的节点指针List_Item x_List_End;//链表尾节点哨兵节点}List_t;对链表节点其基本结构示意图如下拥有两个节点的就绪双向环形链表示意图如下实现链表初始化、和节点插入操作//初始化一下链表把永远挂载的尾节点放好voidList_Init(List_t*constList){List-PxIndex(List-x_List_End);List-x_List_End.x_Item_Value0xffffffffUL;//List-x_List_End.pxNext(List-x_List_End);//哨兵节点尾节点后向指针指向自己List-x_List_End.pxPrevious(List-x_List_End);//哨兵节点尾节点的前向指针指向自己List-Number_Of_Item(uint32_t)0;}//链表的插入节点voidList_Insert_End(List_t*constList,List_Item*constNew_List_Item){////创建哨兵节点保存链表的尾节点List_Item*constpxIndex(List-x_List_End);//(List_Item *)(List-x_List_End);//新节点的下一节点指向 哨兵节点链表尾节点New_List_Item-pxNextpxIndex;//新节点的前一节点指向哨兵节点的前一节点New_List_Item-pxPreviouspxIndex-pxPrevious;//哨兵节点前一节点的下一节点指向新节点pxIndex-pxPrevious-pxNextNew_List_Item;//哨兵节点的的前一节点指向新节点pxIndex-pxPreviousNew_List_Item;//新节点的所属的链表标记New_List_Item-pvContainer(void*)List;//带深入//挂在的节点数1(List-Number_Of_Item);}链表初始化主要用于创建哨兵尾节点并建立自环结构实现空链表边界条件的统一处理避免后续插入删除操作中的空指针判断简化双向链表操作逻辑。链表的新建任务节点插入函数主要用于在链表尾部哨兵节点之前插入新任务节点维护双向链表的完整性同时更新节点的挂载链表与链表节点计数实现任务控制块在就绪链表中的快速挂载。任务的调度机制核心是链表a. 使用链表来管理任务b. 谁进行调度?SYSTICK中断!每隔固定时间会产生的一个定时器中断单核系统中任一时刻仅有一个任务处于运行态其余任务处于非运行态阻塞、挂起、就绪。就绪态任务可被调度器按优先级挑选运行阻塞态任务需等待时间或同步事件发生才转入就绪。调度算法配置调度算法——可抢占时间片轮转空闲任务让步最高优先级的任务先运行相同优先级的任务轮流运行高优先级的任务未执行完低优先级的任务无法运行一旦高优先级任务就绪马上运行最高优先级的任务有多个它们轮流运行FreeRTOS中任务调度过程理解 任务状态链表:就绪链表数组pxReadyTasksLists[configMAX_PRIORITIES]其中configMAX_PRIORITIES 56就绪链表有56个优先级空闲任务优先级为 0放入pxReadyTasksLists[0]链表中osPriorityNormal24当任务的优先级设为osPriorityNormal时则该任务在pxReadyTasksLists[24]链表中当发生Tick中断时会发生调度从上到下遍历pxReadyTasksLists链表找到第一个非空的List把pxCurrentTCB指向下一个任务启动它阻塞链表pxDelayedTaskList当调用vTaskDelay(xTicksToDelay);后:任务从就绪链表pxReadyTasksLists删除放入阻塞链表pxDelayedTaskList触发调度从上到下遍历pxReadyTasksLists链表找到第一个非空的List。链表中会有一个记录项IndexIndex会指向上一次运行的任务。在调度时会取出下一个任务上一次运行的任务的下一个来运行。当xTicksToDelay时间到后在每个Tick中断中都会判断pxDelayedTaskList中的任务xTicksToDelay是否到了到的话放入pxReadyTasksLists调度从上到下遍历pxReadyTasksLists链表找到第一个非空的List把pxCurrentTCB指向下一个任务启动它挂起链表xSuspendedTaskList当调用vTaskSuspend(TaskHandle);后会使TaskHandle任务从就绪链表或阻塞链表中删除放入挂起链表xSuspendedTaskList处于挂起链表中的任务并不能通过Tick中断将其唤醒需要调用vTaskResume(TaskHandle);当调用vTaskResume(TaskHandle);后任务就从挂起链表中删除重新放入就绪链表pxReadyTasksListsFreeRTOS任务调度标准流程编写TCB任务控制块typedefstructTask_Control_Block//TCB任务控制块{volatileuint32_t*Top_Of_Stack;//栈顶地址List_Item xState_List_Item;//链表节点uint32_t*pxStack;//堆栈起始地址charTask_Name[16];//任务名称//uint32_t Tciks_to_Delay; //任务延时时间//uint32_t TCB_Priority; //任务优先级}TCB_t;主要用于统一管理任务的栈顶指针、链表节点、堆栈起始地址及任务名称等关键属性实现任务与就绪链表的关联挂载为后续上下文切换提供完整的任务状态信息。在实践中每个任务都必须额外定义了一个任务控制块这个任务控制块就相当于任务的身份证里面存有任务的所有信息比如任务的栈指针任务名称任务的形参等。静态任务创建函数用于在用户提供的静态内存缓冲区上初始化任务控制块完成栈空间分配与8字节对齐、任务名称拷贝、链表节点所有者绑定及ARM异常返回栈帧的初始化最终返回可用的TCB指针供调度器管理。/*** * 这个函数按照 ARM Cortex-M 的异常返回栈帧格式 布置栈 * 硬件自动保存 软件手动保存 组合: * 硬件保存xPSR, PC, LR, R12, R3, R2, R1, R0异常发生时自动入栈 * 软件保存R11~R4上下文切换时手动入栈 * 当调度器第一次切换到这个任务时会从 Top_Of_Stack 弹出寄存器 * 最终 PC指向 Task_CodeR0指向 Parameter任务就开始执行 * ***/uint32_t*Initialise_Stack(uint32_t*Top_Of_Stack,void*Task_Code,void*Parameter){Top_Of_Stack--;*Top_Of_Stack0x01000000;// xPSRTop_Of_Stack--;*Top_Of_Stack(uint32_t)Task_Code0xfffffffeUL;// PC (任务入口)Top_Of_Stack--;*Top_Of_Stack(uint32_t)0;// LRTop_Of_Stack-5;// 跳过 R12,R3,R2,R1 (软件保存)*Top_Of_Stack(uint32_t)Parameter;// R0 (任务参数)Top_Of_Stack-8;// 跳过 R11~R4 (10-17) 最终栈顶调度器从这里恢复returnTop_Of_Stack;}//静态任务创建函数void*Task_Creat_Static(void*Task_Code,//任务函数名constchar*constTask_Name,//任务名constuint32_tStack_Depth,//任务堆栈大小void*constParameters,//传入参数uint32_t*constStack_Start,//静态任务起始地址任务函数名TCB_t*constpxTaskBuffer,//TCB任务控制块uint32_tTCB_Priority)//任务优先级{TCB_t*New_TCB;void*Return_Value;uint32_t*Top_Of_Stack;unsignedlongx;New_TCB(TCB_t*)pxTaskBuffer;New_TCB-pxStack(void*)Stack_Start;//堆栈起始地址传参// 初始栈顶计算 传入数组指针 传入的任务堆栈大小得到。 栈通常向下增长从高地址向低地址Top_Of_StackNew_TCB-pxStack(Stack_Depth-(uint32_t)1);Top_Of_Stack(uint32_t*)(((uint32_t)Top_Of_Stack)(~((uint32_t)0x0007)));//8字节对齐//任务名称赋值for(x(unsignedlong)0;x(unsignedlong)16;x){New_TCB-Task_Name[x]Task_Name[x];//任务名称遇到0终止if(Task_Name[x]0x00){break;}}New_TCB-Task_Name[15]\0;New_TCB-xState_List_Item.pxOwner(void*)New_TCB;//让链表节点反向指向所属TCBif(TCB_Priority10){TCB_Priority9;// 强制限制最大优先级为9}/*** * 重新预留堆栈空间更新栈顶地址涉及到M系列内核进入/退出的中断机制有关系 * ***/New_TCB-Top_Of_StackInitialise_Stack(Top_Of_Stack,Task_Code,Parameters);Return_Value(void*)New_TCB;returnReturn_Value;}开始创建具体的任务线程声明全局任务资源就绪链表、双任务TCB及栈缓冲区与当前任务指针。编写test_task具体任务函数用于主动触发PendSV中断后的验证任务切换机制。List_t test_list;//实例化就绪态任务链表TCB_t test_task_tcb;//创建任务ATCB_t test_task_tcb2;//创建任务Buint32_ttest_stack[128];//任务堆栈大小uint32_ttest_stack2[128];TCB_t*CurrentTCB;//当前任务指针__asmvoidos_start(void);//汇编声明函数SVC任务启动uint32_ttest0;voidtest_task(void){while(1){Task_Switch();//触发任务切换的PendSV中断test;}}uint32_ttest20;voidtest_task2(void){while(1){Task_Switch();//触发任务切换的PendSV中断test2;}}编写供main函数调用的OS启动函数主要用于初始化就绪任务链表静态创建并初始化双任务控制块与私有栈指定首个运行任务。voidos_statrt()//操作系统启动{List_Init(test_list);//初始化挂载任务的链表用于后续任务调度//初始化静态任务并传入对应的任务和TCBTask_Creat_Static(test_task,test,128,0,test_stack,test_task_tcb,10);Task_Creat_Static(test_task2,test2,128,0,test_stack2,test_task_tcb2,10);CurrentTCBtest_task_tcb;os_start();}编写SVC中断实现首个任务启动//SVC0 中断__asmvoidos_start(void){SVC0nop bx lr}uint8_ti0;//SVC中断 __asm告诉编译器这是汇编函数__asmvoidSVC_Handler(void){externCurrentTCB;PRESERVE8 ldr r3,CurrentTCB/* r3pxCurrentTCB指向当前任务的TCB指针所在地址 */ldr r1,[r3]/* r1*r3pxCurrentTCB将r1指向当前任务的TCB*/ldr r0,[r1]//r0*r1pxTopOfStack获取任务第一个元素的值ro也就是任务栈顶地址ldmia r0!,{r4-r11}msr psp,r0/* psp pxTopOfStack */isb/*指令同步隔离确保之前的指令都已执行完毕*/mov r0,#0/* r0 清0*/msr basepri,r0 orr r14,#0xdbx r14 nop}定位任务栈顶通过CurrentTCB指针获取当前任务控制块进而读取Top_Of_Stack成员定位任务初始化时伪造的异常返回栈帧手动恢复寄存器使用ldmia指令从栈中弹出R4-R11软件保存的高寄存器随后更新PSP指向硬件自动保存区的起始位置配置运行环境清零basepri开放所有中断修改LR值为0xFFFFFFFD使用PSP返回线程模式触发异常返回执行bx r14触发硬件自动恢复机制从PSP栈中弹出xPSR、PC等寄存器最终PC指向任务入口地址首任务开始执行。编写PendSV中断触发函数通过向NVIC中断控制寄存器(0xE000ED04)的PendSV挂起位(第28位)写1触发PendSV异常请求。//触发PendSV中断#defineNVIC_INIT_CTRL_REG(*((volatileuint32_t*)0xe000ed04))#defineNVIC_PENDSVSET_BIT(1UL28UL)voidTask_Switch(void){NVIC_INIT_CTRL_REGNVIC_PENDSVSET_BIT;__dsb(0xf);__isb(0xf);}实现简易任务调度器主要用于在双任务场景下通过判断CurrentTCB当前指向完成两个固定任务控制块之间的切换决策更新全局当前任务指针以指定下一次PendSV中断恢复的目标任务上下文。voidtask_switch_context(void){if(CurrentTCBtest_task_tcb2){CurrentTCBtest_task_tcb;}elseif(CurrentTCBtest_task_tcb){CurrentTCBtest_task_tcb2;}else{}}编写PendSV中断实现任务间上下文切换//任务调度中断处理函数__asmvoidPendSV_Handler(void){externtask_switch_context;externCurrentTCB;/*保存当前任务的现场*///xPSR,PC,R14,R12,R3,R2,R1,R0这些将自动保存入任务栈PRESERVE8 mrs r0,psp//R0 当前任务的进程栈指针(PSP)isb//指令同步屏障确保前面的指令执行完毕ldr r2,CurrentTCB//r3pxCurrentTCB当前运行任务的TCB指针的地址ldr r3,[r2]//r2*r3pxCurrentTCBstmdb r0!,{r4-r11}//将R4-R11保存到栈中递减前存储多寄存器str r0,[r3]//将新的栈顶保存到TCBtest_task_tcb.Top_Of_Stack R0PUSH{r14}//r14压栈bl task_switch_context//执行任务切换//ldr r3 CurrentTCB; //修改R3指针变量地址指向 CurrentTCB地址mov r0,#80msr basepri,r0;//临界区保护dsb isb POP{r14}//r14出栈ldr r2,CurrentTCB//r2 CurrentTCB tcbx的栈顶指针ldr r3,[r2]ldr r1,[r3]//R1 更新R1,r1*r3pxCurrentTCBldmia r1!,{r4-r11}//基于tcb2的栈顶指针手动恢复栈中弹出R4-R11的寄存器msr psp,r1//恢复PSP test_task_tcb2-Top_Of_Stack新任务的栈顶指向硬件保存区的栈顶isb bx r14//跳转到LR指向的地址触发异常返回nop//利用内核硬件自动从PSP栈中弹出 xPSR, PC, LR, R12, R3, R2, R1, R0nop}保存当前任务上下文读取PSP获取当前任务栈顶使用stmdb指令将R4-R11压栈软件保存的高寄存器并将更新后的栈顶地址回写至当前TCB的Top_Of_Stack成员调用调度器决策压栈保护LR寄存器后通过bl指令跳转至C函数task_switch_context完成下一个任务的选择随后恢复LR确保异常返回地址正确配置临界区与恢复新任务设置basepri屏蔽低优先级中断重新加载CurrentTCB获取新任务指针使用ldmia从栈中恢复R4-R11更新PSP指向硬件保存区触发异常返回执行bx r14触发硬件自动恢复机制从PSP栈弹出xPSR、PC等寄存器PC指向新任务入口完成上下文切换。