一个轻量级操作系统最核心的地方就在于任务的执行与切换,像FreeRTOS和ucOSⅢ在任务启动与切换方面都差不多,本文主要从枝干而省去了所有细枝末节让你最快的了解操作系统的任务创建与切换。 以正点原子FreeRTOS移植实验代码为例,该代码主要移植了FreeRTOS操作系统,并创建了4个任务,开始任务、LED闪烁1、LED闪烁2、浮点运算。 1.先从.s启动文件说起 启动文件里面有三个中断是操作系统必要的: 即SVC_Handler,PendSV_Handler,SysTick_Handler。其中:SVC是用于触发一个PendSV异常来进行一个上下文切换,具体切换过程在PendSV_Handler里完成,SysTick_Handler则是为操作系统提供一个时基,进行一系列操作比如延时时切换任务等。 简单点来说: SVC_Handler是用于启动第一个任务的中断; PendSV_Handler是用于每次任务切换中断; SysTick_Handler是一个定时器,比如一个任务运行3s,这3s就是用这个定时器来计时得到的。 至于为什么这些操作要放在这几个中断内进行,这是因为CM3内核有两种模式:用户和特权,模式不同,权限不同,有些操作需要在特权模式下进行。 具体的代码分析放在后面,现在只了解存在这三个中断。(想了解的可以搜索:CM3的两种模式,两种权限,以及两个指针MSP与PSP,中断与异常) 接下来直接进入到main函数 在main里面关于操作系统主要存在于三个函数内:delay_init,xTaskCreate,vTaskStartScheduler 其中delay_init中主要开启了SysTick中断:
然后xTaskCreate是创建函数任务,里面主要操作是为任务申请堆栈空间 其中分为两种情况:堆栈向上和向下生长两种情况,不做具体讨论。 在为任务申请完空间之后,还有很重要的两个操作: 填充任务控制块信息和把新创建的任务加入就绪列表。 其中填充任务控制块中比较重要的是: 1获取栈顶地址pxTopOfStack protSTACK_GROWTH<0即栈为向下生长,栈顶在高地址,然后需要8字节对齐。 可以看出来pxTopOfStack = 为任务申请的堆栈(数组)首地址+堆栈(数组)大小-1 2填充堆栈区域 上面的函数即为填充堆栈:
至此任务初始化完成。简单来说,任务创建过程就是:
即开始第一个任务。
该函数主要两个作用:
该函数主要内容: 8字节对齐 加载pxCurrentTCB(任务控制块,存储每个任务的相关信息)的地址到R3 加载pxCurrentTCB的地址里面的内容到R1 因为pxCurrentTCB的地址里面的内容还是一个地址,所以又加载这个地址里面的内容到R0 R0=pxCurrentTCB首地址内容即栈顶地址,pxCurrentTCB是一个结构体,结构体第一个成员为任务控制块栈顶地址
所以现在R0=栈顶地址,然后以R0为基址,向高地址++,把地址内的内容依次加载到R4、R5、R6、R7、R8、R9、R10、R11、R14等寄存器 然后把R0此时的地址赋给psp 然后R0=0 basepri=R0 然后用BX指令,把以psp为基址,向高地址++,里面的的内容依次加载到R0、R1、R2、R3、R12、R14、R15、xPSR寄存器内 从这里就可以理解了初始化任务堆栈时,为什么要按顺序填充内存,因为取的时候是按顺序取的。 然后任务的切换是在滴答定时器中断内进行的:
可以看到里面就一个函数:
函数里面最重要的是通过设置相应寄存器触发了PendSV中断。 PendSV里面的内容如下:
主要内容:
其实上面两条,1和任务刚初始化时做的一样,只不过任务初始化时是初始化为0,而现在是把当前寄存器内值存起来,保存的位置都一样。2和启动第一个任务一样,把任务堆栈内的内容一一对应加载到对应寄存器内。 至此,操作系统最基本的任务流程就完成了。 总结一下 一. 开启系统滴答定时器 二. 为任务分配运行空间,把任务启动时每个寄存器里面的值按规定好的顺序存在任务分配的运行空间里 三. 触发SVC,开始第一个任务,即把第一个任务运行空间里面的存的寄存器的值按顺序加载到寄存器里 四. 系统滴答定时器中断触发,把现在寄存器内的所有值存进当前任务的运行空间里,加载下个要运行的任务运行空间里面的值到寄存器里。