通过前几章的学习,我们学会了如何为RTOS设计一个合理的内存管理算法。现在,是时候学习设计RTOS内核了。
关于RTOS内核的文章也有很多,但都有一点先射箭再化靶子的意味。要么是代码连篇解释却寥寥无几,要么是要先怎么样再怎么样的说教式教程。并不是说这样的教程不好,而是他们缺乏读者普遍需要的东西,也就是更关键的思想方法!
为此,笔者决定:从我们的需求与应用出发,通过从结果思考过程的方式,使用面向对象的思想,逐步构建一个RTOS的内核。
程序 = 数据结构 + 算法
至于为什么要使用面向对象的思想,是因为面向对象思想本身和程序 = 数据结构 + 算法思想就是相通的,我们可以通过类和对象来表现数据结构,通过方法实现算法,从对象与对象的交互关系来构建,从而实现更加健壮的程序。
另外再借用linus的一句话:“Bad programmers worry about the code. Good programmers worry about data structures and their relationships.”
我们需要实现什么?
如果读者有过使用RTOS的经历,那么请你思考:RTOS实现了什么?带来了怎样的便利?
笔者先提出一点:多线程与优先级带来的实时性
RTOS将应用程序划分为多个独立的任务,也就是多线程。多线程允许同时执行多个任务,提高系统的处理能力和效率。例如,在嵌入式系统中,一个线程可以处理传感器数据,另一个线程可以更新用户界面。
实时性的需求,要求RTOS必须在指定的时间内完成关键任务。
我们创建一个又一个的任务,知道这是一个又一个的线程,它们可以并行执行。设置优先级时,我们知道优先级高的任务会优先执行,从而满足实时性。当我们想让任务同时且更有主次地执行时,我们第一个想到的就是使用RTOS。那么,强大的实时性与同时执行的任务,这就是我们想要实现的结果!但是,我们该如何去实现它呢?
如何实现实时性?
请读者想一想,我们创建任务设置优先级时,往往希望某些任务被优先执行,这是实时性实现的关键,也就是说,会有一个调度器来选择高优先级的任务,因此,我们得到了两个对象:任务和调度器。
调度器对象
通过上图我们可以推断,调度器会选择就绪列表中优先级高的任务。同时,就绪列表经常会发生变化,当优先级最高的任务发生变化,那么调度器还要切换任务,因此,我们得到了下图:
切换任务
切换任务时,我们肯定不希望先前任务的状态丢失,因此需要保存任务状态。线程(任务)切换如下:
1.保存之前运行的线程的上下文
2.选择优先级高的任务
3.调用准备运行的线程的上下文
因此有了下图:
保存任务状态,这部分就涉及到和任务对象的交互了。
任务对象
为了方便管理任务,比如设置优先级啥的,我们肯定需要一个任务控制块,也方便我们把任务挂载到就绪列表中。同时,我们要保存当前状态,也就是说我们需要内存,那么这段内存我们给它命名为栈。
任务控制块
一个任务需要记录任务栈的信息,也就是pxTopOfStack(栈顶)、pxStack(栈起始地址)、self_stack(栈对象)这三个结构体。为了实时性,我们还需要优先级。
把图进一步展开:
现在,我们关键的数据结构已经出来了,请读者写下这些代码,pxCurrentTCB就是当前执行的优先级最高的任务了:
sparrow.c
Class(TCB_t)
{
volatile uint32_t * pxTopOfStack;
unsigned long uxPriority;
uint32_t * pxStack;
Stack_register *self_stack;
};
typedef TCB_t *TaskHandle_t;
__attribute__( ( used ) ) TCB_t * volatile pxCurrentTCB = NULL;
typedef void (* TaskFunction_t)( void * );
栈对象
对于栈对象,我们要保存先前的任务状态,方便下一次任务执行时取出当前任务状态到CPU中,那么任务状态是CPU中的哪些信息呢?答案是寄存器:
以及XPSR,它是非常重要的特殊寄存器:
寄存器包括两部分寄存器,一部分是发生中断时硬件自动帮我们保存的寄存器,另一部分是需要我们手动保存的寄存器。
因此继续展开我们的图:
因此让我们写下代码:
Class(Stack_register)
{
//automatic stacking
uint32_t r4;
uint32_t r5;
uint32_t r6;
uint32_t r7;
uint32_t r8;
uint32_t r9;
uint32_t r10;
uint32_t r11;
//manual stacking
uint32_t r0;
uint32_t r1;
uint32_t r2;
uint32_t r3;
uint32_t r12;
uint32_t LR;
uint32_t PC;
uint32_t xPSR;
};