sparrow系列十二
skaiuijing
前言
先讲讲RTOS。
RTOS有两种大类型,分别是事件触发系统和时间触发系统。
事件触发系统由中断驱动,通常采用中断线程化等技术,适用于响应速度要求高的场景,例如传感器数据采集,但是当大量事情一次性发生时,计算机会面临大问题。
(一般的中断是CPU放下一切全力去做一件事情,中断线程化就是把事情放线程里,等中断触发这个线程,然后让这个线程跟其他线程并行执行)
时间触发系统的任务调度基于定时器中断,适用于周期性任务和确定性要求高的场景,如控制系统。它的内部有一个时钟,每隔一定时间间隔就会触发一次时间中断,每次时钟中断时,调度器决定是否需要切换任务。
不过现在的RTOS往往会结合这两种类型,比如FreeRTOS就属于时间触发系统和时间触发系统的结合。
在第十节的最后,我们得到了这些架构图,这就是Sparrow的蓝图:
sparrow的运行
现在让我们来看看Sparrow的设计,因为它并不是一个完整的RTOS内核,现在还是时间触发系统的部分占主要成分:
当Sparrow运行起来后,调度器负责选择并且切换任务,结合笔者前面的arm架构的文章可知:
1.调度器的组成有SVC、Systick、PendSV三个中断,它们都属于arm cortex m3架构的硬件资源。
2.SVC:负责当MCU复位后启动第一个任务,此时属于特权级线程模式,确保os的启动成功。
- Systick: 定时触发,触发Systick中断,Systick中断会根据当前时钟检查是否需要把某些任务加入就绪列表,在那之后,会触发PendSV中断。
- PendSV中断:保存当前任务状态,然后选择最高优先级的任务进行切换。它是整个调度器的精髓所在。
任务对象
我们得到的任务对象的蓝图如下,对于arm架构和rtos架构大家可能有点陌生,所以笔者啰嗦一点,任务对象让我们直接开始写代码,在代码中结合框图理解:
Sparrow是动态创建任务,对于任务栈和任务控制块(TCB_t),我们都要分配内存。xTaskCreat是用户与RTOS的接口,我们可以得到TCB_t的各项参数并赋值,现在还没有就绪列表,所以笔者注释了几行代码。
(usStackDepth - (uint32_t)1)是为了获取栈顶的地址,我们分配内存会得到内存的第一个地址,所以加上栈的大小后要减1才是栈顶。举例:申请500,起始地址为1,那就是1–500这部分空间,1+500 -1得到的值才是500这个栈顶。
& (~…是字节对齐。
xTaskCreat完成了:
1.获得用户设置的参数,例如优先级,栈的大小等等,然后初始化任务控制块。
2.为任务栈和任务控制块开辟内存
3.初始化任务栈,获得任务栈的栈顶参数
1 | void xTaskCreate( TaskFunction_t pxTaskCode, |
pxPortInitialiseStack函数负责初始化任务栈,任务栈是保存寄存器的地方,让我们先看看arm cm3手册:
笔者在stack_register结构体中已经添加了这些寄存器,让我们边写代码,边听笔者慢慢讲解::
在32位架构下,一个指针大小为一个字,即32位,一个寄存器大小也为一个字。
pxTopOfStack-=16,因为一开始它是指向栈顶的,这是为了容纳stack_register结构体,把这个地址赋值给stack_register结构体,现在结构体的起始地址就是结构体中的第一个寄存器。
然后设置xPSR寄存器:xPSR的24位被置1,表示这是Thumb指令状态,实际上cm3架构只支持Thumb指令状态。
设置PC寄存器:pxCode是任务函数的地址,在arm架构中,分为arm指令集状态和Thumb指令集状态,你可以使用这两种指令集,区别是,Thumb指令集寄存器最低位是0,cm3下都是Thumb指令集,所以,为了预防未定义问题,必须把任务的地址最低位进行清0。
设置LR寄存器:LR寄存器是用于存储任务调用返回地址的专用寄存器,当一个函数被调用时,返回地址会被保存到 LR 寄存器中,以便在函数执行完毕后能够正确返回到调用该函数的下一条指令。但是,rtos中通常任务是不会返回的,所以这里的pvParameters其实是一个笔者用来调试的参数,观察栈有没有被破坏。读者可以把它改成自己的调试函数的地址。
设置r0:self也是一个笔者用来调试的参数,当任务执行后,r0的值会被马上更改,主要是用来判断任务栈是否创建成功。
(*self)->self_stack = Stack:设置任务对象的栈对象,这一操作是为了方便调试时找到一个线程的栈地址。
1 | uint32_t * pxPortInitialiseStack( uint32_t * pxTopOfStack, |
总结
笔者先给出Sparrow的框图,根据框图一步步完成了任务的创建以及初始化部分。任务对象的创建已经完成了,下一节笔者将会带领大家完成调度器对象的创建,同时使用SVC中断启动第一个任务!