本文简介
5 任务的定义和切换
下面是两个任务的切换代码
// 1. 初始化任务就绪列表
prvInitialiseTaskLists();
// 2. 创建任务
task1_handle = xTaskCreateStatic(
task1_entry, // 任务的函数名
"task1", // 名称
TASK1_STACK_SIZE,// 栈大小
NULL, // 任务参数指针
task1_stack, // 栈的起始地址
&task1_tcb // 任务控制块
)
task2_handle = xTaskCreateStatic(
task2_entry, // 任务的函数名
"task2", // 名称
TASK2_STACK_SIZE,// 栈大小
NULL, // 任务参数指针
task2_stack, // 栈的起始地址
&task2_tcb // 任务控制块
)
// 3. 把任务加入就绪列表
vListInsertEnd(&(pxReadyTasksLists[1]),
&(task1_tcb->xStateListItem))
vListInsertEnd(&(pxReadyTasksLists[2]),
&(task1_tcb->xStateListItem))
// 4. 启动调度器,开始任务调度
vTaskStartScheduler();
while(1);
5.1 就绪列表
- 就绪列表就是一个全局数组
List_t pxReadyTasksLists[configMAX_PRIORITIES]
- 初始化
- 对数组中的每一个链表进行初始化
5.1 什么是任务
- 一个不会返回的功能块就是一个任务
void task(void *args) { while(1) { /* 任务代码 */ } }
- 任务是一个独立的函数,无限循环不返回
5.2 创建任务
- 任务创建函数
TaskHandle_t xTaskCreateStatic( TaskFunction_t pxTaskCode, const char *pcName, const uint32_t ulStackDepth, void *const pvParameters, StackType_t *const puxStackBuffer, TCB_t * const pxTaskBuffer )
- 任务创建函数进行的三要素的统一
- 任务的栈
- 任务的主题
- 任务的控制块TCB
- 任务创建函数 xTaskCreateStatic()完成的工作
- 把任务栈和任务控制块联系起来
TCB->pxStack = puxStackBuffer
- 把任务名拷贝到TCB中
TCB->pcTaskName[i] = pcName
- 初始化TCB中的任务节点
TCB->xStateListItem
- 设置节点的拥有者为任务TCB
- 初始化任务中函数的栈
pxPortInitialiseStack(pxTopOfStack)
- 返回任务句柄,它指向TCB
task_handle = &TCB
- 把任务栈和任务控制块联系起来
- 裸机所有的变量放在栈中
- 栈是连续的内存空间
- 栈在启动文件或链接脚本中指定
- 有C的
__main
函数初始化
- 多任务系统
- 每个任务分配一个栈
- 创建任务
- 动态创建任务
- 静态创建任务
FreeRTOSConfig.h
configSUPPORT_STATIC_ALLOCATION = 1
- 每个任务有一个任务控制块TCB
typedef struct tskTaskControlBlock { /* 栈顶 */ volaitle StackType_t *pxTopOfStack; /* 任务节点 */ ListItem_t xStateListItem; /* 栈的起始位置 */ StackType_t *pxStack; /* 任务名称 */ char pcTaskName[configMAX_TASK_NAME_LEN]; } tskTCB; typedef tskTCB TCB_t;
- 调度系统通过TCB来调度任务的执行、删除
5.3 插入就绪链表
把节点插入对应优先级的链表中
vListInsertEnd( &pxReadyTasksLists[1],
&task1_tcb->xStateListItem)
5.4 调度器
调度器是OS的核心,实现了任务的切换,函数是vTaskStartScheduler()
vTaskStartScheduler()
函数- 设置第一个启动的任务
pxCurrentTCB = &task1_tcb;
- 调用调度器
xPortStartScheduler()
- 设置第一个启动的任务
xPortStartScheduler()
- 把
PendSVC
和SysTick
的延后级设为最高 - 启动第一个任务
prvStartFristTask()
- 把
prvStartFristTask()
- 设置MSP主栈指针
- `msp = *(int *(0xE000ED08))
- 打开所有中断和异常
cpsie i/f
- 通过SVC指令调用中断服务程序0
svc 0
- 设置MSP主栈指针
- 系统开始调用中断服务程序0,即
SVC_Handler
#define vPortSVCHandler SVC_Handler
vPortSVCHandler()
- 为第一个任务装备运行环境
- 即用task1的栈顶填充CPU寄存器
r0 = &task1_tcb
- TCB的第一个成员就栈顶指针pxTopOfStack
- 这时
r0 = pxTopOfStack
一个刚刚创建尚未运行的任务栈空间分布
- 手动准备第一个任务函数的运行环境
- 把r0向上的8个字 -> [r4:r11]
- psp = r0,更新psp栈顶指针
- 开启中断
- basepri = 0
- 设置后面使用进程栈指针psp
-
r14 = 0xD - r14(LR)寄存器
-
- 函数返回
bx r14
- cortex自动把psp指向的栈内容恢复到寄存器中
- psp指向新的的栈顶
- cortex自动把psp指向的栈内容恢复到寄存器中
- 为第一个任务装备运行环境
5.5 任务的切换
- 任务的切换通过
taskYIELD()
完成- 将PendSV位 = 1
PendSV
的处理函数是xPortPendSVHandler
- 保存上一个函数的上文
- 自动保存的部分
- 进入函数前,cortex自动把xPSR, PC, R14, R13, R3-R0保存在当前函数的栈中
psp已经移动到图中位置
- 进入函数前,cortex自动把xPSR, PC, R14, R13, R3-R0保存在当前函数的栈中
- 手动保存的部分
r2 = pxCurrentTCB
- r2保存上一个任务的栈顶指针
r0 = psp
- 把CPU的[r4: r11]保存到r0开始的8个字中
*pxCurrentTCB = r0
- 把r0保存到上一个任务的栈顶
- 自动保存的部分
- 关闭中断(进入临界状态)
- 调用
vTaskSwitchContext()
修改要切换的任务pxCurrentTCB = &task2_tcb
- 开中断
- 切换到要执行的任务
r0 = pxCurrentTCB
- 这时,pxCurrentTCB已经更新
- 更新函数运行环境
- 手动
- 把要运行的任务的任务栈加载到[r4:r11]中
- 更新psp
bx r14
退出SV异常- 这时,
r14 = 0xffff_fffd
,使用psp
- 这时,
- 自动
- 跳出异常后,cortex自动把psp后面的内容加载到cpu的[r0:r3], r14(LR), r15(PC), xPSR中
- 由r15(PC)引导系统跳转到新任务
- 手动
- 保存上一个函数的上文
6. 阻塞延时
- 任务调用阻塞延时后,任务放弃CPU使用,进入阻塞状态知道延时结束
- TCB中加入xTicksDealy
- vTaskDelay(xTicksToDelay)
- 只需要获取当前TCB
- TCB.xTicksToDelay = xTicksToDelay
- taskYIELD()
- 任务切换
- 流程
- 调用taskYIELD()产生PendSV中断
- PendSV中调用vTaskSwitchContext()
- 找到优先级最高的任务,更新pxCurrentTCB
- 思路
- 当前任务 = 空闲任务
- 查看task1/2的延时时间是否结束
- 到期更新pxCurrentTCB
- 否则继续执行空闲任务
- 查看task1/2的延时时间是否结束
- 当前任务 = task1/2
- 查看下一个taskx
- 到期更新pxCurrentTCB
- 查看当前任务延时时间 != 0
- 是,进入空闲任务
- 否,直接退出
- 查看下一个taskx
- 当前任务 = 空闲任务
- 流程
- SysTick中断服务函数
- 系统每次tick产生中断,执行SysTick_Handler函数
#define xPortSysTickHandler SysTick_Handler
- OS中最小的时间单位就是SysTick中断周期
- 这个函数
- 关中断
- 更新系统时基
- 给全局
xTickCount++
- 遍历所有的TCB,让
TCB.xTicksToDelay--
- 给全局
- 开中断
- 系统每次tick产生中断,执行SysTick_Handler函数
- SysTick的初始化函数vPortSetupTimerInterrupt()
- 直接向寄存器NVIC_SYSTICK_CTRL_REG中写入数据
- 在xPortStartScheduler()中调用