FreeRTOS内部机制(一)

1.任务创建

在这里插入图片描述

1.1参数解析

  • 任务创建出来需要做什么?需要提供函数
  • 任务随时会被切换,寄存器保存在哪?需要提供栈,可以静态分配(数组),也可以动态分配
  • 怎么记录这些信息:栈在哪里?需要有一个任务结构体
1.1.1任务结构体

在这里插入图片描述

TCB_t用来表示一个任务,它的重要成员如下:

  • pxTopOfStack:执行栈里的最后一个元素
  • xStateListItem:通过它把当前任务放入某个状态链表(Ready, Delayed, Suspended)
  • xEventListItem:比如任务在等待队列A,则通过xEventListItem把自己放入队列A的链表
  • uxPriority:任务的原始优先级
  • pxStack:栈的起始位置
  • pxEndOfStack:栈顶,栈的最高的、有效地址
  • uxBasePriority:任务的当前优先级

提问:在TCB_t里怎么没看到函数指针?

答:初始化任务时,把函数指针存入了栈里(PC)

1.2任务创建的过程

xTaskCreate
		// 1. 分配任务结构体
	    TCB_t * pxNewTCB;
	    pxNewTCB = ( TCB_t * ) pvPortMalloc( sizeof( TCB_t ) );
                                                    
		// 2. 分配栈
		pxNewTCB->pxStack = ( StackType_t * ) pvPortMallocStack( ... );
		
		// 3. 初始化任务栈,即构造TCB_t的内容
		prvInitialiseNewTask
			// 3.1 初始化优先级、队列等等
			pxNewTCB->uxPriority = uxPriority;
			vListInitialiseItem( &( pxNewTCB->xStateListItem ) );
			vListInitialiseItem( &( pxNewTCB->xEventListItem ) );
			
			// 3.2 初始化栈
			pxNewTCB->pxTopOfStack = pxPortInitialiseStack(...)
		
		// 4. 放入就绪链表
		prvAddNewTaskToReadyList( pxNewTCB );

2.任务的调度机制

2.1使用链表来管理任务

有很多任务都想运行,优先级各不相同,怎么管理它们?

在这里插入图片描述

每个优先级,都有一个就绪链表:pxReadyTasksLists[优先级]

任务被创建时,要使用prvAddNewTaskToReadyList()来把它放入对应的就绪链表,调用过程为:

xTaskCreate
    ->prvAddNewTaskToReadyList
    	->vListInsertEnd( &( pxReadyTasksLists[ ( pxTCB )->uxPriority ] ), &( ( pxTCB )->xStateListItem ) );

2.2 使用链表来理解调度机制

2.2.1 第1个运行任务是谁?

最高优先级的ready list里最后一个创建的任务:

在这里插入图片描述

2.2.2 抢占

最高优先级的ready list里的第1个任务永远可以即刻执行:

在这里插入图片描述

2.2.3 使用链表和Tick来理解时间片轮转

问:谁来执行任务调度?

答:由 内核调度器(Kernel Scheduler) 通过 硬件中断软件协作 共同实现的

1.时间驱动者:SysTick 中断

  • 机制:
    • Cortex-M 内核自带的 SysTick 定时器 每隔一个固定的时间(通常是 1ms,即 configTICK_RATE_HZ)产生一次中断
    • 中断服务函数 xPortSysTickHandler() 被调用

在这里插入图片描述

  • 工作内容:
    1. 更新系统滴答计数 (xTickCount++)
    2. 检查是否有延时的任务到期了(将它们从延时列表移回就绪列表)
    3. 如果开启了时间片轮转 (configUSE_TIME_SLICING),且当前有多个同优先级的任务,它会标记需要进行上下文切换
    4. 如果需要切换,它会触发 PendSV 中断

2.事件驱动者:PendSV 中断

  • 为什么需要它?
    • 为了保证实时性,FreeRTOS 不希望在中断(如 SysTick 或外设中断)还没处理完时就急着切换任务
    • 因此,当调度器决定“该换人了”,它不会立即切换,而是挂起一个 PendSV (Pendable Request for System Service) 异常
  • 工作机制:
    • PendSV 的优先级被设置为全系统最低
    • 只有当所有其他高优先级的硬件中断(如串口、定时器、SysTick)都处理完毕后,CPU 才会去处理 PendSV
    • PendSV_Handler 被执行:
      1. 保存当前任务的上下文(R4-R11 等)到当前任务的栈中
      2. 调用 vTaskSwitchContext() 选择新任务
      3. 从新任务的栈中恢复上下文
      4. 返回,CPU 开始执行新任务的代码

3.核心执行者:调度器内核代码

这是调度的“大脑”。它包含在 tasks.c 文件中,主要职责是决定**“下一个运行谁”**。

  • 关键函数:vTaskSwitchContext()
    • 这个函数会调用我们之前讨论过的宏 taskSELECT_HIGHEST_PRIORITY_TASK()
    • 它扫描就绪列表,找到优先级最高的任务,并更新全局指针 pxCurrentTCB 指向该任务。
  • 触发时机:当系统检测到需要切换任务时(如时间片到期、高优先级任务就绪),就会调用这个函数

2.3任务状态的切换

2.3.1状态转换图

在这里插入图片描述

2.3.2核心:链表

怎么理解?

答:FreeRTOS 并不通过复杂的“状态机”变量来记录每个任务在哪里,而是通过将任务控制块(TCB)在不同的链表之间“剪贴”和“移动”,来直观地表示和执行任务状态的改变

在这里插入图片描述

typedef struct tskTaskControlBlock {
    volatile StackType_t *pxTopOfStack; /* 栈顶指针 */
    /* ... 其他信息 ... */
    ListItem_t xStateListItem;          /* 【关键】用于挂在“状态链表”中的节点 */
    ListItem_t xEventListItem;          /* 【关键】用于挂在“事件链表”中的节点 */
    UBaseType_t uxPriority;             /* 优先级 */
    /* ... */
} TCB_t;
  • 重点:每个 TCB 内部嵌入了 ListItem_t(列表项)
  • 作用:这就好比每个人(TCB)手里都拿着一个钩子(ListItem),可以随时把自己挂到不同的队伍(链表)里
Logo

openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。

更多推荐