从系统的角度看,任务是竞争系统资源的最小运行单元。
任务可以使用或等待CPU、使用内存空间等系统资源,并独立于其它任务运行。
项目地址
在其他RTOS中,任务一般是由:任务堆栈、任务控制块和任务函数三部分组成。
任务堆栈:上下文切换的时候用来保存任务的工作环境,就是STM32的内部寄存器值。
任务控制块:任务控制块用来记录任务的各个属性。
任务函数:由用户编写的任务处理代码(一般无返回值,单个void *参数,不会返回)
void task1Entry(void *param){
for(;;){
}
}
在本文中,任务控制块只定义了栈指针
typedef uint32_t stack_task;
typedef struct _losTask{
stack_task *stack;
}losTask;
结构体的首地址跟第一个元素的地址一致,我们可以通过该任务栈保存参数,例如寄存器的值。
我们知道一个函数的执行内核会从高地址到低地址分配栈,堆,数据区,代码区等
栈stack(函数内部定义的局部变量和函数形参)它们在进入函数时自动分配地址,退出函数时自动收回。
堆区(heap)— 一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收。
一些RTOS的任务栈如下图所示(来源mculover666):
在高地址保存程序状态寄存器(xPSR),R15(函数入口参数)等。
那么我们要做的就是把这些寄存器压入任务栈中,这里把一些没有用到的寄存器设置成对应寄存器编号的值,代码如下
void los_task_init(losTask * task,void (*taskEntry)(void *),void *param,stack_task *stack){
//初始化任务栈
//传入末端,先减后操作.
*(--stack) = (unsigned long)(1<<24); //xPSR
*(--stack) = (unsigned long)taskEntry; //PC寄存器
*(--stack) = (unsigned long)0x14; //R14(LR)寄存器
*(--stack) = (unsigned long)0x12; //R12
*(--stack) = (unsigned long)0x13; //R13
*(--stack) = (unsigned long)0x2; //R2
*(--stack) = (unsigned long)0x1; //R1
*(--stack) = (unsigned long)param; //R0(程序入口参数)
//其他寄存器.
*(--stack) = (unsigned long)0x11; //R11
*(--stack) = (unsigned long)0x10; //R10
*(--stack) = (unsigned long)0x9; //R9
*(--stack) = (unsigned long)0x8; //R8
*(--stack) = (unsigned long)0x7;
*(--stack) = (unsigned long)0x6;
*(--stack) = (unsigned long)0x5;
*(--stack) = (unsigned long)0x4;
task->stack = stack;
}
T标志位:该位反映处理器的运行状态。当该位为1时,程序运行于THUMB状态(arm执行16位指令的状态,即16位状态),否则运行于ARM状态。
这里只有两个任务,简单起见使用固定的方式,或者采用求余的方式实现。
void los_task_sched(){
if (currentTask == taskTable[0]){
nextTask = taskTable[1];
}else{
nextTask = taskTable[0];
}
los_task_switch();
}
在los_task_switch中触发pendsv异常即可。
该任务入口函数里面是死循环,把task1Flag取反延时在调用任务调度函数执行下一个任务。
//定义任务
losTask task1;
//定义任务栈
stack_task task1Env[1024];
//任务初始化
los_task_init(&task1,task1Entry,(void *)0x11111111,&task1Env[1024]);
void task1Entry(void *param){
for(;;){
task1Flag = !task1Flag;
delay(100);
los_task_sched();
}
};
int main(){
//任务初始化
los_task_init(&task1,task1Entry,(void *)0x11111111,&task1Env[1024]);
los_task_init(&task2,task2Entry,(void *)0x22222222,&task2Env[1024]);
//初始化任务数组
taskTable[0] = &task1;
taskTable[1] = &task2;
//赋值nextTask
nextTask = taskTable[0];
los_task_run(); //执行第一个任务
return 0;
}
__asm void PendSV_Handler(void){
IMPORT currentTask
IMPORT nextTask
//判断PSP寄存器是否为0
MRS R0,PSP
//不需要保存状态直接恢复
CBZ R0,PendSVHandler_nosave
//保存另外一些寄存器
STMDB R0!,{R4-R11}
LDR R1,=currentTask
LDR R1,[R1]
STR R0,[R1]
PendSVHandler_nosave
LDR R0,=currentTask
LDR R1,=nextTask
LDR R2,[R1]
STR R2,[R0]
//取出堆栈地址
LDR R0,[R2]
LDMIA R0!,{R4-R11}
//切换
MSR PSP,R0
ORR LR,LR,#0x04
BX LR //退出堆栈
}
寄存器R4-R11的值,我们在初始化的时候手动入栈了,如图所示,出栈成功。
当该任务被调度执行时,CPU会自动将任务栈中最前面的8个寄存器值加载到CPU寄存器中,完成「下文环境切换」。
Cotex-M内核双堆栈指针MSP和PSP
RTOS内功修炼记(一)—— 任务到底应该怎么写?
01课堂-从0到1实现RTOS