Small RTOS51 学习笔记(6)如何切换任务(上)
个人笔记,主要内容均来自原文
文章目录
- CPU 可以执行多个任务的原因
- CPU 怎样运行才能执行多个任务
- 何时进行任务切换
- Small RTOS51 任务切换时的程序框图
- 数组 OSTsakStackBotton[] 和 Small RTOS51 的堆栈结构
- 变量 OSFastSwap
- 常数数组 OSMapTb[]
CPU 可以执行多个任务的原因
原文从汇编指令的角度描述了 CPU 可以同时执行多个任务的原因,我不太理解原理,所以只把结论记录一下,以下结论摘自原文。
对于一段程序,如果能保证以下条件,程序的执行结果是一致的:
- CPU 执行了同样的指令序列,不管指令是从哪里来的,中间是否插入了别的指令;
- 所有指令执行时所需的关键数据一样,不管数据存储的位置。
所有的多任务操作系统都利用了这样这样一个事实:让任务轮流执行,又让每一个任务满足以上两点。当速度足够快时,人们就认为多个任务在同时运行时,而且人们看到每个任务都运行正确(只是速度变慢了)。
CPU 怎样运行才能执行多个任务
当一个任务从某个地址开始存放代码,另一个任务就不可以在那里存放不同的代码,也就是说各个任务所占用的代码空间不可以重叠。另外各个任务所占用的数据空间也不可以重叠。
不过我们一般使用高级语言编写程序,即使需要使用汇编,也是用宏汇编,极少使用小汇编,这样,编译器或汇编器就会产生与位置无关的代码,让连接器来确定代码和数据存放的位置。也就是说,无论代码和数据放到任何位置,程序都能正确执行。这样的话,就满足了上一章的条件一。
同样,连接器也将各个任务的数据分配到不同的空间,所以上一章的条件二也已满足。但连接器不会为每一个任务分配独立的堆栈,如果此时多个任务交叉运行,就会破坏这些任务的堆栈数据。所以操作系统要为每个任务分配独立的堆栈,并在任务切换时改变堆栈指针。
另外,各个任务会共享寄存器,所以需要在任务切换时对每个任务的寄存器值进行保存和恢复,这些寄存器值存放在上面提到的各个任务的独立堆栈中。
到这里,CPU 就可以执行多个任务了。
何时进行任务切换
对于协作式操作系统(如 Windows 3.1 和 一些免费的 RTOS 等),任务切换是在任务主动放弃 CPU 时进行的;对于基于时间片轮询调度算法的操作系统(如 RTX51Tiny),任务切换除了发生在任务主动放弃 CPU 时外,还会发生在任务的时间片使用完时;对于基于有优先级的抢占式的操作系统,有两种方法能中断当前任务而运行另一个任务:一种是任务自己放弃 CPU,另一种是中断服务程序让更高优先级任务就绪,出中断后进行任务切换。
有一些操作系统既有优先级调度算法,又有时间片轮询调度算法。Small RTOS51 是完全基于有优先级的抢占式的操作系统。
Small RTOS51 任务切换时的程序框图
图片摘自原文:
数组 OSTsakStackBotton[] 和 Small RTOS51 的堆栈结构
不知道为什么是 Tsak,原文和源码都是 Tsak,难道不是 Task ?
Small RTOS51 把所有的内部自由 RAM 空间分配给当前任务。为了顺利进行堆栈变化,Small RTOS51定义了一个数组 OSTsakStackBotton[] 保存所有任务的顶端和低端位置,它的定义如下:
uint8 idata * data OSTsakStackBotton[OS_MAX_TASKS + 2];/* 任务堆栈底部位置 */
在 CPU 初始化函数中,将每个任务的 OSTsakStackBotton[] 赋值为系统堆栈的首地址,后续如何操作这里暂不研究。
/*********************************************************************************************************
** 函数名称: OSCPUInit
** 功能描述: Small RTOS 与系统相关的初始化
** 输 入: 无
** 输 出 : 无
** 全局变量: OSTaskID,OSTsakStackBotton,SP
** 调用模块: LoadCtx
**
** 作 者: 陈明计
** 日 期: 2002年2月22日
**-------------------------------------------------------------------------------------------------------
** 修改人:
** 日 期:
**-------------------------------------------------------------------------------------------------------
********************************************************************************************************/
void OSCPUInit(void) small
{
uint8 i;
for (i = 0; i < OS_MAX_TASKS + 1; i++)
{
OSTsakStackBotton[i] = STACK;
}
OSTsakStackBotton[OS_MAX_TASKS + 1] = (uint8 idata *)(IDATA_RAM_SIZE % 256);
}
变量 OSFastSwap
#if OS_MAX_TASKS < 9
uint8 data OSFastSwap[1]; /* 任务是否可以快速切换 */
#else
uint8 data OSFastSwap[2];
#endif
任务快速切换的标志,当 OSFastSWap[] 中某一位为 0 时,表示这一位对应的任务是通过中断被强制放弃 CPU 时间的,之后它获得 CPU 时间而继续运行时,必须从该任务的堆栈中恢复所有寄存器;当 OSFastSWap[] 中某一位为 1 时,表示这一位对应的任务是通过调用内核函数主动放弃 CPU 时间的,之后它获得 CPU 时间而继续运行时,只需切换 PC 指针,不需要恢复寄存器。
原理:Keil C51 编译器在编译 C 语言程序时,如果一个函数调用了一个外部函数,当这个函数返回时,R0~R7、ACC、B、PSW、DPH 和 DPL 等寄存器会发生变化。因此不需要提前保存寄存器的值。
常数数组 OSMapTb[]
该数组在前面的笔记中也有所提及,主要用来对标志的置位和复位。数组里的值对应的是任务ID在某些数据中对应的偏移量。
uint8 const OSMapTbl[] = {0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x00};
下面是内核代码中关于 OSMapTb[] 的例子(这个数组的值其实就是对 0x1 进行了适当的偏移)
if ((OSTaskCreated[0] & OSMapTbl[TaskID]) != 0)
{
return FALSE;
}
OSTaskCreated[0] |= OSMapTbl[TaskID];
OSTaskRuning[0] |= OSMapTbl[TaskID];