【基于cortex-M3 内核的任务管理,栈是向下增长的。
这里看代码有个原则:
1. 被 #if #endif 控制的,代码不看,这里的代码可以理解为独立的功能;
2. 被 #if #else #endif 控制的,一定要搞清楚走哪一个分支。
通常看代码的 if 也可以采用这种方式,可以快速理清主代码流程。】
1. 任务概念介绍
在裸机系统中,系统的主体就是main 函数里的死循环,在这个循环里,CPU按照顺序依次执行各种事情;
在多任务系统中,我们根据功能的不同,把整个系统分割成多个任务,每个任务都有自己的执行环境(运行栈,TCB),且业务上也是相对独立的。Freertos 就是一个实时且免费的多任务系统,调度的最小单位就是任务,这里可以理解为就是进程,没有线程的概念。任务的具体定义就是:独立且无法返回的函数,大概形式如下:
2. 任务创建
每个任务都运行在自己的环境中,这里的环境包括栈和TCB,所以任务创建的时候,需要知道任务栈的地址和大小(统称栈空间),还有TCB的地址(统称TCB空间,TCB的大小是固定的,等于 sizeof(TCB_t))。
任务创建分为动态创建和静态创建,所谓的动态创建,就是任务栈空间和TCB空间都是从Freertos 管理的堆空间申请来的,当任务被删除时,也可以从堆空间释放对应的空间;所谓的静态创建,就是任务栈空间和TCB空间都是写死的,也就是声明一个栈变量和TCB变量,由系统启动时,在内存中给定具体地址,但是有个问题,当任务被删除时,这里的栈空间和TCB空间就浪费了。所以,综上所述,再加上实际项目经验,一般使用动态创建的方式,下面说下动态创建流程。
2.1 任务创建大体流程
1)申请栈空间
2)申请TCB空间
3)TCB的栈指针(pxStack)指向栈空间
4)初始化TCB结构体的成员
5)初始化栈空间
6)初始化任务相关的链表,包括就绪链表,延时链表,挂起链表和删除链表
7)将任务插入到就绪链表中
2.2 任务创建流程图
3. 任务启动
3.1 任务启动大体流程
1)创建idle 任务
2)如果需要使用定时器的话,创建定时器任务
3)关闭 Freertos 管理的中断
4)设置 pendsv 和 svc 的中断优先级为最低
5)打开不受 Freertos管理的中断
6)触发svc 中断
7) 在svc 中断中,加载任务栈中的内容到CPU寄存器(只加载 r4 – r11),并且打开Freertos 管理的中断,与3)对应,最后设置中断退出进入用户模式,所以当中断退出后,系统会从任务栈空间加载剩余栈内容到寄存器(r0, r1, r2, r3, r12, r14, r15, xPSR)。
3.2 任务启动流程图
4. 任务切换
4.1 任务切换大体流程
触发场景:systick 定时器中断中触发、事件响应触发(比如任务B读取一个空队列,任务A给此队列发送了一个消息,此时需要执行一个任务切换,让任务B开始运行)。
怎么触发:通过触发pendsv 中断,在中断处理流程中完成任务切换。
具体流程:
1) 保存任务A的运行信息
2)找到下一个要运行的任务B
3)将任务B的栈信息加载到寄存器中
4.2 任务切换代码讲解
在调用“vTaskSwitchContext” 之前, 将 R3 和 R14 寄存器压入主堆栈,原因是:
R3 指向的是 pxCurrentTCB , 在调用函数过程中 R0 - R3 的值随时都可能改变,为了之后不在重新指向 pxCurrentTCB,所以保存下来
R14 的值是在离开任务后,将R14 的值赋值为0xFFFFFFFd, 表示从应用进入的中断,所以等到中断处理完成后,需要返回到应用中,而在 调用vTaskSwitchContext时,会修改R14的值(返回地址),所以将R14的值写入主堆栈,用来在退出中断后返回到应用。
5. 任务状态迁移
5.1 状态迁移
5.2 状态解释
【说明】除了运行态之外,其他3个均有一个链表和其对应,也就是说任务挂在哪个链表上,那么这个任务就是处于什么态。运行态就是任务正在使用CPU,不用链表维护,也可以说不属于这3个状态就是运行态,下面对上图做个解释。
1)任务创建之后,加入就绪列表,就是就绪态。
2)获得了CPU权限之后就是运行态。
3)当时间片到了后,需要切换下一个任务,此时任务又回到就绪态。
4)当运行态的任务需要读取一个空队列的时候,就会将任务挂到阻塞链表,就是阻塞态(这里只是举例,其他情况下也会发生阻塞的情况)。
5)当队列中有消息之后,将阻塞任务加入到就绪链表,变成就绪态(对应上面的举例)。
6)7)8)处于任何状态下的任务都可以直接进入到挂起态,进入挂起态这个动作不是系统调度的动作,是一个主动调用的过程。
9)挂起的任务要恢复,都是先恢复到就绪态,有一个特殊的挂起所有任务,并不是将任务全部挂到挂起链表,而是直接挂起调度器。
6. 任务相关API
函数名 | 功能描述 |
uxTaskPriorityGet() | 查询某个任务的优先级 |
vTaskPrioritySet() | 改变某个任务的优先级 |
uxTaskGetSystemState() | 获取系统中任务状态 |
vTaskGetInfo() | 获取某个任务信息 |
xTaskGetCurrentTaskHandle() | 获取当前正在运行任务的任务句柄 |
xTaskGetHandle() | 根据任务名字查找某个任务的句柄 |
xTaskGetIdleTaskHandle() | 获取空闲任务的句柄 |
uxTaskGetStackHighWaterMark() | 获取任务的堆栈的历史剩余最小值,也叫高水位线(可以用来判断栈深度是否合理) |
pcTaskGetName() | 获取某个任务的任务名字 |
xTaskGetTickCount() | 获取系统时间计数器值 |
xTaskGetTickCountFromISR() | 在中断服务函数中获取时间计数器值 |
xTaskGetSchedulerState() | 获取任务调度器的状态,开启或未开启 |
uxTaskGetNumberOfTask() | 获取当前系统中存在的任务数量 |
vTaskList() | 以表格的形式输出当前系统中所有任务的详细信息 |
vTaskGetRunTimeStats() | 获取每个任务的运行时间 |