接着昨晚的说吧,可能会有有些跳跃,但是会循着前面的框架是不变的。
先说说任务吧,一般呢,在嵌入式操作系统里面,可以叫任务,也可以叫线程,我在底层习惯叫任务,在上位机编程时习惯叫线程。而嵌入式系统多任务编程,它的结构有点不一样,因为啊,每个任务都是一个死循环,在系统运行开始后,操作系统会接管CPU和硬件资源。
void main(void)
{
xxx_init();
yyy_init();
OS_Init();
OS_Creat_Task(Task_A, ***, ***, .....); //注册任务A;
OS_Creat_Task(Task_B, ***, ***, .....); //注册任务B;
OS_Creat_Task(Task_C, ***, ***, .....); //注册任务C;
OS_Start();
}
带系统的主函数结构就长那个样。
而那些任务函数呢?长什么样?
void Task_A(void) //想带参数也可以啊,这里不讲那个。
{
***_Init();
while(1) //此任务所做的事情,就在这这个循环里面了。
{
******();
}
}
任务函数,基本都长这样;在这里说说,为什么每个任务都是一个死循环呢?因为啊,任务的切换,必须是受系统管理的,如果系统没有实现调度切换,那么硬件自己是无法实现切换任务的。就这个理!还有一个是叫做什么程序的局限性什么的,记不清名称了。
而上面的那个OS_Creat()函数呢,就是把任务添加到系统的任务表里面,最后那个OS_Start()是开始系统调度,系统会把PC指针指向优先级最高的那个任务的函数所在的地址,CPU就接着那个地址开始执行那个任务。在这里呢,还要说一个很重要的东西:任务的独立性,也就是说,任务对任务之间是相对独立的、封闭的。一个时刻只有一个任务在占有CPU,那么如何让一个任务独立呢?我们来分析一下,一个任务就是一个函数,函数里面就是局部变量和计算,还有函数调用,函数调用其实就是在编译时,已经成为了PC指针的转变了。而前面讲的那个任务的切换要入栈,把公用寄存器保存了,那么局部变量呢?C语言中的局部变量是定义在栈中的,全局变量不牵涉到私有性。如何让任务独立呢?在这里,使用私有栈,C函数的运行是需要栈和公用寄存器的,对每个任务构造一个私有的栈,那么这个任务就独立了。
也就是说,系统的任务切换实际就是控制从一个任务的while(1)转移到另一个任务的while(1)里面,最快的方法就是转移PC指针,一步到位。可以对照前面那个硬件中断原理;在主函数的while(1)里面执行命令,发生中断(任务切换),转换到中断服务函数(进入新任务),在中断发生时,硬件自动将公用寄存器入栈,然后转换PC指针。出栈时,硬件自动将栈里面的数据还原回去。
没有系统时,也就只有一个任务,因此一个公共的栈就够了。在多任务时,就不行了,每个任务是不能去访问别的任务的栈的,会出乱子的。也就是前面说的,任务要独立。说到那个私有栈的事情,那么问题来了,如何为一个任务分配一个私有的栈呢?我们仔细想想,栈是如何访问的?栈是通过SP访问的,SP也就是栈指针。我们人为的为一个任务定义一个栈顶地址和栈的长度,在任务调度切换时,把分配的这个栈地址通过底层传递给SP。因为栈是不会跳跃的,只会一步一步的增长,或者退栈。
到了这一步,我想需要整理一下任务是如何切换的。
1、系统开始调度,取出任务就绪表里面优先级最高的任务。
2、将这个任务栈里面的SP、PC、RX等公用寄存器的值还原回去(假设这个任务之前已经被运行过;没运行过的话,只需要更改SP和PC就行了)
3、进入新任务运行。
4、任务运行时间到、重新进行任务调度。
5、取出新任务的数据,填入寄存器。
6、执行新任务。
7、循环到4.
任务之间结构就这样。但是程序里面还细化很多东西的,也就是每个任务的参数的管理。要记住,每个任务针对CPU在运行时都有一组数据,那么任务切换时,要保护好当前任务的运行环境,然后再切换,切换进入新任务之前,是需要将新任务之前离开的CPU运行环境恢复出来,任务才能恢复接着运行。
在系统里面,有一个叫做TCB的东西,这是什么东西呢? Task Contrl Block:任务控制块。在这里,为每个任务定义一个叫做任务控制块的结构体,这个结构体保存对应任务的运行环境,这个TCB里面有基本的任务栈地址、运行环境等参数。系统对任务的管理,实际上就是对任务控制块的管理。
进一步分析一下任务的切换:
1、从就绪表里面找出优先级最高的任务;
2、通过优先级,从TCB数组里面找出相应任务的任务控制块;
3、通过任务控制块,找到任务私有栈的地址;
4、通过任务私有栈,恢复任务的运行环境;
5、转移PC指针到新任务的运行处。
到这里,任务切换的进一步分析就结束了。
明天讲一下抢占式内核和任务里面的延时处理。
|