任务创建
1 | BaseType_t xTaskCreate( TaskFunction_t pxTaskCode, //指向任务函数的入口。任务永远不会返回(位于死循环内) |
任务结构体的定义:
1 | typedef struct tskTaskControlBlock |
这个函数就是创建TCB结构体, 并进行初始化
1 | { |
- pxTopOfStack指向当前堆栈栈顶,随着进栈出栈,pxTopOfStack指向的位置是会变化的;
- pxStack指向当前堆栈的起始位置,一经分配后,堆栈起始位置就固定了,不会被改变了。
- 那么为什么需要pxStack变量呢,这是因为随着任务的运行,堆栈可能会溢出,在堆栈向下增长的系统中,这个变量可用于检查堆栈是否溢出;
- 如果在堆栈向上增长的系统中,要想确定堆栈是否溢出,还需要另外一个变量pxEndOfStack来辅助诊断是否堆栈溢出.
嵌入式系统上大多都是向上增长的, 因此需要pxEndOfStack
来检测是否溢出
prvInitialiseNewTask
初始化TCB
1 | static void prvInitialiseNewTask( TaskFunction_t pxTaskCode, |
pxTopOfStack的处理
这个函数关注点在 pxTopOfStack
段的处理, 估计跟rtthread的处理是相似的,
pxTopOfStack 类似于 rtthread的每个thread结构体的sp成员, 是当前任务栈的游标, sp向上几十个字节压入了必要的寄存器信息(a0 ra 等)以及status cause epc 等协处理器的信息,
在往上就是当前任务entry 执行了多少函数后, 当前函数栈sp递减到的位置.
1 | StackType_t *pxPortInitialiseStack( StackType_t *pxTopOfStack, TaskFunction_t pxCode, void *pvParameters ) |
二是注意
事件列表项
xEventListItem. xItemValue等于configMAX_PRIORITIES减去任务A的优先级(为1),即5-1=4。
在这里xItemValue不是直接保存任务优先级,而是保存优先级的补数,这意味着xItemValue的值越大,对应的任务优先级越小。
使用宏listGET_OWNER_OF_HEAD_ENTRY获得列表中的第一个列表项的xItemValue值总是最小(因为是升序排列的),也就是优先级最高的任务!
进入临界区
调用taskENTER_CRITICAL()进入临界区,这是一个宏定义,最终进入临界区的代码由移植层提供。
1 | void vTaskEnterCritical( void ) |
与离开临界区呼应, pxCurrentTCB->uxCriticalNesting--
, uxCriticalNesting减到0后,启用中断.
1 | void vTaskExitCritical( void ) |
注意这里的启用/关闭中断仅仅是把timer的systick 中断禁用了, 全局中断并没有关
1 |
prvAddNewTaskToReadyList
1 | static void prvAddNewTaskToReadyList( TCB_t *pxNewTCB ) |
为第一次运行做必要的初始化
如果这是第一个任务(uxCurrentNumberOfTasks等于1),则调用函数prvInitialiseTaskLists()初始化任务列表。
FreeRTOS使用列表来跟踪任务,在tasks.c中,定义了静态类型的列表变量:
1 | static void prvInitialiseTaskLists( void ) |
全局当前任务指针变动
1 | PRIVILEGED_DATA TCB_t * volatile pxCurrentTCB= NULL; |
这个变量用来指向当前正在运行的任务TCB
,我们需要多了解一下这个变量。FreeRTOS的核心是确保处于优先级最高的就绪任务获得CPU运行权。
任务切换就是找到优先级最高的就绪任务,而找出的这个最高优先级任务的TCB,就被赋给变量pxCurrentTCB。
任务调度
调度器启动过程分析
任务创建完成后,静态变量指针pxCurrentTCB指向优先级最高的就绪任务。但此时任务并不能运行,因为接下来还有关键的一步:启动FreeRTOS调度器。
调度器是FreeRTOS操作系统的核心,主要负责任务切换,即找出最高优先级的就绪任务,并使之获得CPU运行权。调度器并非自动运行的,需要人为启动它。
vTaskStartScheduler()用于启动调度器,它会创建一个空闲任务、初始化一些静态变量,最主要的,它会初始化系统节拍定时器并设置好相应的中断,然后启动第一个任务。
mips portRESTORE_CONTEXT
接下来看一下 portRESTORE_CONTEXT
怎么调度到函数entry处执行的
1 | .macro portRESTORE_CONTEXT |