对FreeRTOS的使用过程进行精简后会发现,它的整个过程其实非常简单,首先创建任务,其次登记任务,根据任务所登记的位置不断开始执行任务
下面进行详细的解释。
创建任务,这个过程中会为任务分配一个任务堆栈,这里边主要存放一些任务的相关信息,如任务的优先级、任务所在的列表等等。如下图这个过程主要分为两部分,其一是填补满一个控制块中的各项内容,其二将控制块中的列表项放在就绪列表中,
管理任务,这个过程主要是对于控制块中列表指针的处理,即不断将任务控制块中的列表项指针放在不同的列表中。这个就是任务调度的过程。
- 创建任务
Freertos中实现任务创建的函数有两个:静态方法创建任务,动态函数创建任务,这两个函数的具体实现过程在前面的文章中已经有过详细的描述,这里仅仅从函数的使用层面来叙述一下函数的各个参数的用途,以及函数的返回值的说明。
1.1静态方法创建任务
首先描述静态方法创建任务,调用的函数如下
TaskHandle_t xTaskCreateStatic( TaskFunction_t pxTaskCode, //任务函数入口
const char * const pcName, //任务名
const uint32_t ulStackDepth, //任务栈深度
void * const pvParameters, //任务函数的参数
UBaseType_t uxPriority, //任务的优先级
StackType_t * const puxStackBuffer, //任务的栈地址
StaticTask_t * const pxTaskBuffer )//任务的控制块地址
这个函数其本质是在对任务的控制块pxTaskBuffer中的各项元素进行赋值。下表为简化过的任务控制块结构
成员 | 解释 | 数据类型 | 结构 |
pxTopOfStack | 栈顶指针 | StackType_t * | |
xStateListItem; | 任务节点 | ListItem_t |
/*检测列表项的完整性*/
/*指针,指向下一个列表项*/
/*指针,指向前一个列表项*/
/*记录列表项的拥有者,任务控制块中的,记录这个列表项是哪个任务的*/
/*说明该任务的这个列表现在挂在哪(是就绪列表or挂起列表)*/
/*检测列表项的完整性*/ |
pxStack | 任务栈起始地址 | StackType_t * | |
pcTaskName[configMAX_TASK_NAME_LEN] | 任务名称 | char数组 |
如上图,函数xTaskCreateStatic()需要任务函数、任务名、堆栈深度等七个参数,这个函数的实现过程为
将任务的栈地址放入任务控制块的pxStack
调用prvInitialiseNewTask()函数,并带入参数pxTaskCode, pcName, ulStackDepth, pvParameters, uxPriority, &xReturn, pxNewTCB, NULL。该函数会将控制块中的各项参数填满
调用prvAddNewTaskToReadyList( ),参数为刚刚处理过的任务控制块。将新建的任务添加到就序列表中。
任务创建完毕
1.2 动态方法创建任务
- 任务调度
在Cortex-M3架构中,FreeRTOS为了任务启动和任务切换使用了三个异常:SVC、PendSV、SysTick:
SVC(系统服务调用,也称为系统调用)用于任务启动
PendSV(可挂起系统调用)用于完成任务切换
SysTick 用于产生系统节拍时钟,提供一个时间片。
这里需要注意一点,系统提供的定时器使用的是系统时钟,也就是说delay_ms(100)中的100是指100个时钟节拍。
如图中所示,某FreeRTOS系统有任务1至任务3共3个任务,其中任务一、任务二优先级为1,任务三优先级为2,那么,这些如下图所示就是这些任务在就绪任务列表中的存储方式
在调度过程中,系统在ListAready[1]中不断循环执行任务一二,这个时候如果任务一二都进入阻塞,系统会去遍历ListAready[2]、此时当数组在ListAready[2]中找到一个可执行任务任务3就会执行这个任务,这个时候如果任务三也阻塞了,系统就会继续遍历ListAready[3]...;而不论任务三有没有阻塞,此时如果任务一和任务二由阻塞状态转为就绪状态,那么在一个时间片结束后,系统就会去执行任务一二。
- 主函数
硬件初始化
创建开始任务
打开调度器
执行初始任务
进入临界区
创建任务1、2、3、......、n
删除初始任务
离开临界区
开始运行多任务系统