DIY简单的RTOS(二)任务切换

从系统的角度看,任务是竞争系统资源的最小运行单元。
任务可以使用或等待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

猜你喜欢

转载自blog.csdn.net/qq_40318498/article/details/107553032