自己实现一个freertos(2)任务调度 1——最基本的TCB
绪论
freertos的任务调度模块是整个内核最基础的模块,除了链表以外的几乎所有模块都依赖于任务调度模块存在,在这种情况下了解任务调度至关重要,但是任务调度模块中包含了大量的宏以及大量不相干的模块相互交织的部分,在这个基础上,将剥离所有不相干的东西构建一个最简任务调度。
从TCB开始(参考《操作系统设计与实现》
TCB设计思路
TCB是一个任务存在的根本证明,在tcb中保存了一个任务/线程中的所有信息,包括任务栈,任务优先级,任务的栈顶指针这些信息,如果没有tcb,所有的对任务调度的讨论都无从谈起。
我们先从最简单的任务的标志开始讨论。在rtos的任务实际上对应的是操作系统中的线程,也就是说实际上rtos的tcb和线程的tcb的基础功能是一致的。
维度 | RTOS 的 TCB(如 FreeRTOS tskTCB ) |
设计目标 | 支持硬实时 / 软实时场景,需快速调度和确定性响应,资源管理极简,优先保证实时性。 |
复杂度 | 轻量化,通常仅数十个成员(FreeRTOS tskTCB 约 20-30 个成员),聚焦核心调度需求。 |
资源管理 | 几乎不直接管理资源,资源(如内存、外设)需用户手动分配和释放,TCB 仅记录任务私有栈和 MPU 配置(可选)。 |
上下文存储 | 上下文存储极简,通常绑定物理内存的固定大小栈,依赖硬件特性(如 Cortex-M 的 PSP 栈指针)快速保存 / 恢复寄存器。 |
调度策略 | 调度策略简单直接:多为固定优先级抢占调度,部分支持时间片轮转,TCB 仅需记录优先级和基础调度标记。 |
内存开销 | 体积极小(FreeRTOS tskTCB 约 100-200 字节),支持静态分配(内存可预测),适合资源受限场景。 |
扩展功能 | 扩展功能极简,仅包含必要的调试信息(如任务名称)、运行时间统计(可选),无安全或复杂统计字段。 |
典型成员示例 | pxTopOfStack (栈顶指针)、uxPriority (优先级)、xStateListItem (状态列表项)、pcTaskName (任务名称)等。 |
freeRTOS的最简TCB
freeRTOS提供的TCB实际上也不是一个最简化版本,为了适配提供的其余的模块,在其中也插入了大量的条件编译语句,在一个只有任务调度功能的TCB中这些东西都是不必要的。有了上面的分析之后我们可以开始实现一个可用的TCB。
最简的TCB只要有以下的内容就可用开始使用了。
首先是栈顶指针,每个线程都需要独立的栈来运行,任务切换过程中一定会涉及到寄存器组内容的切换(用于指向不同的栈和以及切换运行不同的代码),为了实现最简的TCB,除了栈顶以外的所有寄存器的值我们都保存在栈中,TCB只保存栈顶指针即可。
其次是栈的起始位置,因为实际上所有的栈空间都是从芯片的内存空间中划分出来的,而且为了保证栈的相互独立需要知道栈的起始位置才能够做到防止在使用过程中出现栈越界。
接下去是两个链表节点(参见上一节自己实现一个freertos(1)链表)第一个将链表挂在对应的任务链表中,另一个将代码挂在事件链表上,用于事件到达的时候能够正确的选取到对应的任务执行(当然如果想要再省点空间可用考虑使用Linux的方案,直接两个指针塞在TCB中,然后通过gcc的扩展计算偏移值直接找到TCB的起始地址,但是移植性会变得非常差,不是很有必要)
然后是任务的优先级,任务的优先级保证了能够进行优先级调度来保证紧急的任务能够优先完成。
最后是任务名称用于标识这个任务本身(当然freertos直接保存任务的名称,但是在Linux中实际上是保存一个线程的id)
根据上面我们给出的分析实现一个最简单的TCB
typedef struct tskTaskControlBlock
{volatile uint32_t *pxTopOfStack; //指向栈顶的指针ListItem_t xStateListItem; //任务链表节点ListItem_t xEventListItem; //信号链表节点uint16_t uxPriority; //优先级uint32_t *pxStack; //栈起始地址char pcTaskName[ configMAX_TASK_NAME_LEN]; //任务名称(用作任务标识)
}tskTCB;
以上就是我们实现的一个最简单的TCB,有这个TCB我们就可用完成一个最简单的任务调度管理,包括时间片轮转调度,