一. FreeRTOS任务调度流程
- RTOS调度流程中主要关注以下几个点:创建任务、开启任务调度、任务调度;下面将以FReeRTOS在Cortex-m4的实现为例,展示一下RTOS任务调度的大体流程。
二. 创建任务
- 创建任务之前,为了防止被其他中断或异常打断,通常会开辟一个临界段。在Cortex-m4中,是通过操作basepri寄存器,来实现临界段的功能的。
- 此处采用静态方式创建任务,即任务栈空间需要用户提供。因此创建任务时主要提供的参数是任务主体函数、任务名字、任务栈地址及空间大小、任务优先级以及任务控制块。任务控制块即为描述任务信息的一个结构体,如下
typedef struct tskTaskControlBlock
{
volatile StackType_t *pxTopOfStack; /* 栈顶指针,作为TCB的第一个成员 */
ListItem_t xStateListItem; /* 任务节点 */
StackType_t *pxStack; /* 任务栈起始地址 */
char pcTaskName[configMAX_TASK_NAME_LEN ]; /* 任务名称 */
TickType_t xTicksToDelay; /* 阻塞延时时间 , 单位为SysTick的中断周期 */
UBaseType_t uxPriority; /* 任务优先级 */
/*..............................*/
}tskTCB;
- 创建任务主要是做以下几件事:
- 根据任务栈的基地址以及栈大小得到栈顶地址(向下生长),并且将栈顶地址进行8字节对齐(一般32位内核CPU只要4字节对齐,因为考虑到后续兼容浮点运算);
- 保存任务名字至TCB控制块,初始化TCB的任务优先级;
- 初始化TCB任务控制块所在的节点,并将该节点的拥有者设置为TCB;
- 初始化任务堆栈,使得xPSR寄存器24位置1(使用Thumb指令),同时规定好PC、LR寄存器等的值(将PC寄存器设为任务主体函数地址),最后返回指向空闲栈的栈顶指针;
- 将任务按照优先级添加至对应的任务就绪列表,同时将优先级位号图对应位置1(若第一次创建任务,则会初始化任务就绪、任务阻塞延时列表);
- 任务创建成功后,任务栈及任务就绪列表如下
三. 开启任务调度
- 开启任务调度器前,会首先去创建一个优先级最低的任务——空闲任务,当没有其他任务运行时,才会运行该任务。
- 初始化时基计数器,该计数器用来控制任务运行节拍。
- 接着初始化sysTick(之所以选择这个定时器,是为了方便Cortex-m4内核的处理器间移植),选择合适的定时周期。
- 配置PendSV异常。
- 调用SVC异常,在异常处理函数中启动第一个任务
- 获取当前任务控制块的栈顶指针
- 将任务控制块的栈中”r4-r11”寄存器的值,加载到CPU的r4-r11寄存器
- 将任务控制块的栈顶指针更新至psp
- 设置r14/LR寄存器,使其异常返回时使用psp指针、thumb指令、用户模式
- bx r14异常返回,此时CPU自动将psp中剩下内容加载到CPU寄存器:xPSR,PC(任务入口地址),R14,R12,R3,R2,R1,R0(任务的形参)
- 此时,将会跳转到任务函数去执行
四. 任务调度
- 当前运行的任务被调度成其他任务,主要有两种情况:一是将自己挂起进入阻塞状态,主动让出CPU使用权;二是当sysTick一个节拍来临后,有更高优先级任务而被剥夺CPU使用权。
- 使用vTaskDelay阻塞延时函数 —— 阻塞任务,让出CPU使用权
- 将任务移出就绪列表,若该优先级就绪列表没有其他任务,则将优先级位号图对应的位清零
- 根据传入的延时时间,计算任务解锁时间,并作为节点排序值,插入到阻塞延时列表(若时间溢出,插入到溢出阻塞延时列表;否则插入普通阻塞延时列表)
- 更新最小的任务阻塞延时到期时间
- 调用PendSV异常
- sysTick中断服务函数 —— 找出就绪任务,并进行任务切换
- 更新系统时基计数器的值
- 若时基计数器的值溢出,则交换阻塞延时列表,设置下一个任务解锁时间。若该列表没有其他任务,则设为最大0xFFFFFFFF;否则设为下一个任务到期的时间。
- 如果有任务到期,则将其从阻塞列表移除,加入就绪列表。直到列表为空(设置下一个任务解锁时间为最大) 或还有没到期的任务(设置下一个任务解锁时间为此时间)。
- 调用PendSV异常
- PendSV异常服务函数 —— 任务切换(对比与SVC,多了上文保存和上下文切换的过程)
- 上文保存:将CPU的r4-r11寄存器保存到当前任务的任务栈中
- 上下文切换:根据前导零计数指令clz配合优先级位号图,得到当前最高优先级的就绪任务;将当前任务块指针pxCurrentTCB指向该优先级就绪列表第一个节点的任务控制块。
- 下文切换:将切换后任务的”r4-r11”寄存器保存到CPU的r4-r11寄存器中;同时更新psp指针。
- 异常返回,此时CPU自动将psp中剩下内容加载到CPU寄存器,:xPSR,PC(任务入口地址),R14,R12,R3,R2,R1,R0(任务的形参)
- 此时将会跳转到当前任务控制块对应的任务函数去执行。
五. 参考资料
- FreeRTOS源码
- 野火《FreeRTOS 内核实现与应用开发实战指南》
- 《ARM体系结构与编程》
以上是我在学习过程中的总结,不当之处请在评论区指出。