链表和调度

← 任务(线程)的定义 | ← 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. 在 1.延时函数, 2.systick硬件中断, 3.while末尾 调用 taskYIELD()

  2. taskYIELD() :PendSV的悬起位置

    PendSV设置成最低优先级,因为不能打断其他的中断

  3. PendSV:调用 xPortPendSVHandler

  4. xPortPendSVHandler : 现在的任务即将结束,把cpu通用寄存器组特殊寄存器的值进栈中(硬件自动+软件手动)

    调用 vTaskSwitchContext(),从就绪链表中找出下一个最高优先级的任务,更新全局指针 pxCurrentTCB 指向新任务。

    通过 pxCurrentTCB获取栈顶指针,恢复通用寄存器组和特殊寄存器,更新到PSP实现无感切换

  5. FreeRTOS-Kernel port.c — xPortPendSVHandler 原码

信号任务的调度

信号任务的调度在信号量/队列的 API 中

  1. 在 1. 信号获取超时阻塞xQueueReceive / xSemaphoreTake), 2. 信号发送唤醒等待任务xQueueSend / xSemaphoreGive / xTaskNotifyGive), 3. 中断中发送信号xQueueSendFromISR / xSemaphoreGiveFromISR) 中处理信号等待与就绪链表

  2. 信号等待链表(xTasksWaitingToReceive / xTasksWaitingToSend : 每个队列/信号量内部自带两个事件链表,严格按照任务优先级从高到低排序(同优先级按 FIFO)。表头永远是等待该信号中优先级最高的任务

  3. 获取信号(Take / Receive 阻塞)

    • 检查资源计数值:若无资源且设置了超时时间,调用 vTaskPlaceOnEventList()
    • 将当前任务的 xEventListItem(事件节点)插入信号量的 xTasksWaitingToReceive(等待接收链表)
    • 同时将当前任务的 xStateListItem(状态节点)插入系统的 pxDelayedTaskList(延时链表,按超时时间戳排序),实现双重挂载
    • 任务移出就绪链表,在 API 末尾调用 portYIELD_WITHIN_API() 悬起 PendSV,主动让出 CPU 进入阻塞
  4. 释放/发送信号(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 触发立即抢占。