FreeRTOS任务状态机切换流程图
vTaskStartScheduler函数
通过configSUPPORT_STATIC_ALLOCATION这个宏,来决定是创建静态创建Idle任务,还是动态创建任务;静态创建就是TCB和栈空间使用DATA数据段预先分配好的内存,动态创建就是使用malloc分配堆上的内存
1:在vTaskStartScheduler中创建IDLE任务,是确保系统必须至少有一个任务可调度;
2:xPortStartScheduler
- 根据业务需要,配置PMU;
- 启动systick开始计数;后续同优先级任务通过tick中断进行任务的时间片调度;
- 启动第一个任务,从NV中获取栈地址,并配置MSP寄存器,开启全局中断,并调用SV中断去启动第一个任务;注意:只有第一个任务启动的时候,才需要调用SV中断来启动;
xTaskCreate函数
1:先申请TCB和栈空间,如下代码:
2:prvInitialiseNewTask是初始化一个新的任务
- 设置栈顶地址,如果栈配置的是向下增长的,设置栈顶指向的地址= 栈的基地址 + 栈的深度pxTopOfStack = pxNewTCB->pxStack + ( ulStackDepth - ( uint32_t ) 1 );
- 配置任务名称和初始化任务链表
- 初始化化栈的空间。用于保存R0~R14寄存器值和函数的入口地址;不同平台稍微有些不一样;
Cortex-M中,有两个栈寄存器:PSP和MSP。程序运行时,可以选择使用PSP或MSP作为栈的指针。
缺省情况下,Cortex-M3使用的是单栈模型,即上电后程序工作在特权级线程模式下,此时使用的MSP寄存器作为栈指针寄存器。之后,无论异常还是中断,都使用MSP指向的栈。
3:prvAddNewTaskToReadyList将任务加入就绪任务链表中;
将创建的任务加入到就绪任务链表中,如果当前任务的优先级小于新创建的任务优先级,则执行一次任务抢占;
xPortPendSVHandler(cortex-m3/m4 不带浮点)
保存当前任务端点,将MCU寄存器存入当前任务栈内存中
vTaskSwitchContex切换上下文,恢复要执行任务的上下文
在vTaskSwitchContext中,调用taskSELECT_HIGHEST_PRIORITY_TASK,找到下一个要执行的任务;根据任务优先级,先从高到底查找;
prvSVCHandler
恢复第一个任务的上下文
获取pxCurrentTCB的地址,并从中获取栈顶指针,执行出栈操作,恢复MCU的寄存器,并开始执行任务;
xPortSysTickHandler
portSET_INTERRUPT_MASK_FROM_ISR这个函数屏蔽除NMI之外的所有中断;
xTaskIncrementTick函数作用:
操作系统的运行是由系统节拍时钟驱动的。
系统每次节拍中断服务程序中主要任务由函数 xTaskIncrementTick 完成。
在任务调度器没有挂起的情况下( xTaskIncrementTick != pdFALSE ),该函数主要完成 :
- 判断节拍计数器xTickCount 是否溢出, 溢出轮换延时函数队列
- 判断是否有阻塞任务超时,取出插入就绪链表
- 同优先级任务时间片轮
而当任务调度器被挂起时, 该函数累加挂起时间计数器 uxPendedTicks, 调用用户钩子函数, 此时,正在运行的任务不会被切换, 一直运行。
当恢复调度时, 系统会先重复调用 xTaskIncrementTick 补偿 (uxPendedTicks次)。
不管, 系统调度器是否挂起, 每次节拍中断都会调用用户的钩子函数 vApplicationTickHook 。 由于函数是中断中调用,不要在里面处理太复杂的事情!
vTaskSuspend
1:从readylist或delaylist中移除
2:插入到xSuspendedTaskList链表中
vListInsertEnd( &xSuspendedTaskList, &( pxTCB->xStateListItem ) );
3:如果当前任务就是要挂起的任务,有两种情况:
- 调度器在运行状态,调用portYIELD_WITHIN_API,触发pendSV,进行一次任务调度;
- 调度器停止工作状态,由于调度器未运行,但pxCurrentTCB指向的任务刚刚被挂起,必须调整pxCurrentTCB以指向其他任务,这其中又存在两种情况,一种是所有任务都未就绪,则pxCurrentTCB设置未NULL,否则调用vTaskSwitchContext,找个一个就绪任务,设置pxCurrentTCB
vTaskResume
所有挂起的任务,不再参与任务调度,必须调用vTaskResume,将任务再重新放入到就绪链表中,并判断当前任务和resume的任务优先级,如果resume的任务优先级大于当前任务优先级,则执行一次任务抢占;
vTaskDelay
1、将当前任务放入到延迟任务链表中;
2、当前任务已经处于sleep状态了,通过portYIELD_WITHIN_API,触发pendSV需要重新做一次任务调度了