链表和调度
← 任务(线程)的定义 | ← FreeRTOS内核实现与应用开发实战指南
任务的插入
双向链表,就绪任务按优先级挂到就绪链表 pxReadyTasksLists[] 上,调度器从链表里挑最高优先级任务运行(调度看下面的)
就绪链表 List_t
└─ xListEnd(哨兵) ⇄ xStateListItem(任务A)
⇄ xStateListItem(任务B)
│ pvOwner 指向
▼
TCB(任务B)
├─ pxTopOfStack → 栈顶(保存的 16 个寄存器现场)
└─ pxStack → 栈底(任务栈起始地址)
pvOwner是从链表节点找回 TCB 的桥梁:链表里存的是xStateListItem节点,调度器通过pvOwner拿到任务本体,再取pxTopOfStack恢复现场。- 一个任务有两个链表项:
xStateListItem管「就绪/阻塞」状态,xEventListItem管「等队列/信号量」事件,让任务能同时挂在两个链表上。 pxTopOfStack(栈顶)和pxStack(栈底)一高一低框出整个任务栈,切换任务时从pxTopOfStack恢复现场。
就绪任务的调度
任务切换在pendsv中断中
-
在 1.延时函数, 2.systick硬件中断, 3.while末尾 调用
taskYIELD() -
taskYIELD() :
PendSV的悬起位置PendSV设置成最低优先级,因为不能打断其他的中断 -
PendSV:调用
xPortPendSVHandler -
xPortPendSVHandler : 现在的任务即将结束,把cpu通用寄存器组和特殊寄存器的值进栈中(硬件自动+软件手动)
调用
vTaskSwitchContext(),从就绪链表中找出下一个最高优先级的任务,更新全局指针pxCurrentTCB指向新任务。通过
pxCurrentTCB获取栈顶指针,恢复通用寄存器组和特殊寄存器,更新到PSP实现无感切换
信号任务的调度
信号任务的调度在信号量/队列的 API 中
-
在 1. 信号获取超时阻塞 (
xQueueReceive/xSemaphoreTake), 2. 信号发送唤醒等待任务 (xQueueSend/xSemaphoreGive/xTaskNotifyGive), 3. 中断中发送信号 (xQueueSendFromISR/xSemaphoreGiveFromISR) 中处理信号等待与就绪链表 -
信号等待链表(
xTasksWaitingToReceive/xTasksWaitingToSend) : 每个队列/信号量内部自带两个事件链表,严格按照任务优先级从高到低排序(同优先级按 FIFO)。表头永远是等待该信号中优先级最高的任务 -
获取信号(Take / Receive 阻塞) :
- 检查资源计数值:若无资源且设置了超时时间,调用
vTaskPlaceOnEventList() - 将当前任务的
xEventListItem(事件节点)插入信号量的xTasksWaitingToReceive(等待接收链表) - 同时将当前任务的
xStateListItem(状态节点)插入系统的pxDelayedTaskList(延时链表,按超时时间戳排序),实现双重挂载 - 任务移出就绪链表,在 API 末尾调用
portYIELD_WITHIN_API()悬起PendSV,主动让出 CPU 进入阻塞
- 检查资源计数值:若无资源且设置了超时时间,调用
-
释放/发送信号(Give / Send 唤醒) :
- 写入数据或增加计数值后,检查
xTasksWaitingToReceive是否为空 - 若有任务在等,调用
xTaskRemoveFromEventList():直接从等待链表表头取出最高优先级的任务 - 将该任务同时从信号等待链表和系统延时链表中拔除,调用
prvAddTaskToReadyList()重新插入对应优先级的就绪链表 - 比对优先级:若唤醒的任务优先级高于当前正在运行的任务,调用
queueYIELD_IF_USING_PREEMPTION()悬起PendSV请求立即抢占
- 写入数据或增加计数值后,检查
延时任务的调度
1. 信号等待与接收机制(Take / Receive)
当任务尝试获取信号量或队列消息时:
- 资源充足 :直接减少资源计数值或取出数据,当前任务无需阻塞,保持在就绪链表继续运行。
- 资源不足(需阻塞等待) :
- 将任务的事件节点(
xEventListItem)按优先级降序插入对应信号量/队列的 等待接收链表(xTasksWaitingToReceive) 。 - 将任务的状态节点(
xStateListItem)按超时时间戳升序插入系统的 延时链表(pxDelayedTaskList) ,实现双重挂载。 - 任务移出就绪链表,在函数末尾调用
portYIELD_WITHIN_API()悬起PendSV,主动让出 CPU。
2. 信号释放与发送机制(Give / Send)
当外部中断或另一个任务释放信号量或发送数据时:
- 检查等待链表 :
- 有任务在等待 :直接从信号量的等待接收链表(
xTasksWaitingToReceive)表头取出优先级最高的任务。 - 无人等待 :增加信号量资源计数或将数据存入队列缓冲区。
- 唤醒与状态恢复 :
- 将被选中的任务从等待接收链表和系统的延时链表中同时拔出(解除阻塞并取消超时闹钟)。
- 调用
prvAddTaskToReadyList()将该任务重新插入对应优先级的 就绪链表 。 - 抢占判断 :
- 比对唤醒任务与当前运行任务的优先级。
- 若唤醒任务优先级更高,调用
portYIELD_WITHIN_API()(或在中断中使用portYIELD_FROM_ISR)悬起PendSV触发立即抢占。