Small RTOS 是在 Keil C51 上面 编译的。
看如下代码:
#include "config.h"
void main(void)
{
TMOD = (TMOD & 0XF0) | 0X01;
TL0 = 0x0;
TH0 = 0x0;
TR0 = 1;
ET0 = 1;
OSStart();
}
Keil C51 编译器 在 程序进入main函数之前,会做如下的工作,
进入 main函数之后 ,
进入 OSStart() 函数:
代码如下:
void OSStart(void)
{
uint8 idata *cp;
uint8 i;
uint8 tmp ;
cp = STACK;
OSTsakStackBotton[0] = STACK;
OSTsakStackBotton[OS_MAX_TASKS + 1] = (uint8 idata *)(IDATA_RAM_SIZE % 256);
/* 初始化优先级最高的任务堆栈,使返回地址为任务开始地址 */
/*SP PCL 然后 SP+1 PCH*/
tmp = cp ;
*cp++ = ((uint16)(TaskFuction[0])) % 256; //低8位字节
tmp = cp ;
*cp = ((uint16)(TaskFuction[0])) / 256; //高8位字节
/* 初始化优先级最低的任务堆栈 */
cp = (uint8 idata *)(IDATA_RAM_SIZE - 1) ;
tmp = cp ;
*cp-- = 0;
tmp = cp ;
*cp-- = ((uint16)(OSIdle)) / 256;
tmp = cp ;
OSTsakStackBotton[OS_MAX_TASKS] = cp;
*cp-- = ((uint16)(OSIdle)) % 256;
tmp = cp ;
/* 初始化其它优先级的任务堆栈 */
for(i = OS_MAX_TASKS - 1; i > 0; i--)
{
*cp-- = 0;
tmp = cp ;
*cp-- = ((uint16)(TaskFuction[i])) / 256;
tmp = cp ;
OSTsakStackBotton[i] = cp;
*cp-- = ((uint16)(TaskFuction[i])) % 256;
tmp = cp ;
}
/* 允许中断 */
Os_Enter_Sum = 1;
OS_EXIT_CRITICAL();
/* 函数返回优先级最高的任务 */
}
其中的
cp = STACK;
STACK 是经过C51 初始化之后,sp堆栈指针指向的RAM地址。
这样 cp指针 也指向堆栈指针指向的RAM地址了。
Keil C51 在编译的时候,会将全局变量,局部变量等都分配到RAM的某一个地址上去。
之后,RAM剩余的地址,才会指定给sp指针,作为栈使用。
/* 初始化优先级最高的任务堆栈,使返回地址为任务开始地址 */
/*SP PCL 然后 SP+1 PCH*/
tmp = cp ;
*cp++ = ((uint16)(TaskFuction[0])) % 256; //低8位字节
tmp = cp ;
*cp = ((uint16)(TaskFuction[0])) / 256; //高8位字节
这一部分代码的作用是 将 void TaskA(void) 函数的地址,放入堆栈中,
首先将地址的低8位字节放入sp指向的地址,
然后sp+1 ,
将地址ide高8位字节放入sp指向的地址。
这样做的目的是 OSStart 返回之后,会执行 RET 汇编指令。
RET指令的作用如下:
这样OSStart 会首先跳转到 TaskA 函数中去。
查看一下 :test.m51
OSTsakStackBotton 的地址:
D:0010H PUBLIC OSTsakStackBotton
I:0021H PUBLIC STACK
#define IDATA_RAM_SIZE 0x100
cp = (uint8 idata *)(IDATA_RAM_SIZE - 1) ;
cp指针指向了RAM的0xFF地址
函数指向完毕 跳转到TaskA 去执行 :
void TaskA(void)
{
while (1)
{
OSWait(K_TMO,5);
}
}
进入 OSWait(K_TMO,5);
uint8 data OSTaskID = 0;
OSWaitTick[OSTaskID] = ticks; //ticks = 5
case K_TMO: /* 等待超时,即延时一段时间 */
OS_ENTER_CRITICAL();
while (OSWaitTick[OSTaskID] != 0) /* 判断超时时间是否到 */
{
OSClearSignal(OSTaskID); /* 任务进入等待状态 */
OSSched(); /* 运行下一个任务 */
}
OS_EXIT_CRITICAL();
return TMO_EVENT;
uint8 OSTaskRuning = 0xff; // 初始值
uint8 const OSMapTbl[] = {0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x00};
OSTaskRuning &= ~OSMapTbl[TaskId]; //将对应的bit位清零
变为:0xfe了
堆栈
复位之后:
运行到main函数起始位置的时候,(看PC指针)
OSStart 函数 最后一步,将要退出函数的时候:此时 SP堆栈指针设置的是指向22H地址
仔细看上图其中:
OSTsakStackBotton
地址0010H :
TaskA
地址 0548H
TaskB
地址 0552H
TaskC
地址 055CH
OSIdle
地址 0003H
0010H 位置值是 21 F7 FA FD
0x21 对应的是 TaskA 05 48H 存放的地址
0xF7 对应的是 TaskB 地址 0552H
0xFA对应的是 TaskC 地址 055CH
0xFD对应的是 OSIdle 地址 0003H
SP 等于22H的原因是:
子程序返回的时候
SP 指向22H:0x05
这样PCH = 05,然后 SP = SP-1 = 21H
SP 指向21H:0x48
PCL= 0x48,然后 SP = SP-1 = 20H
这样OSStart 返回之后,PC指向地址0548H这个程序段对应的函数,SP=20H
单步执行以下,程序切换到TaskA 中,
TaskA中,运行之后,进入OSWait(K_TMO,5);
OSSched();
进入 OSCtxSw
C:0530H PUBLIC OSCTXSW
TaskA – 21: TaskA 起始堆栈
TaskB – F7: TaskB起始堆栈,并且在起始堆栈中设置taskB的函数指针位置。并且重新设置SP 堆栈指针的指向地址。
TaskC – FA:
Idle – FD:
OSCtxSw 跳转到 LoadCtx 之前:
TaskA – 21: TaskA 起始堆栈
TaskB – 28: TaskB起始堆栈,并且在起始堆栈中设置taskB的函数指针位置。并且重新设置SP 堆栈指针的指向地址。
TaskC – FA:
Idle – FD:
单步执行下一步:
进入 TaskB
因为每个任务 都是通过 OSWait(K_TMO,5); 进入休息状态,等待时间到了,才能够继续运行。
如果时间不够, 此时 进入了 OSIdle 任务中。等待中断时间到达。
os_core.c 的
OSTickISR---->OSTimeTick---->OSIntSendSignal-----> OSTaskRuning |= OSMapTbl[TaskId];
此时,如果 定时时间到达,对应的 OSTaskRuning 任务位 变为 1。
OSTickISR 在结束的时候,会调用 OSIntExit(); 函数。
OSIntExit 中会根据 优先级的顺序,查看 OSTaskRuning 那个位 变为1 了,
如果那个位变为1了,OSNextTaskID 变为对应的任务。
os_core.c 最后 的OSTickISR函数
需要打断点的地方
OSTickISR -----> os_cpu_c.c 最后几行
OSTimeTick(); //一个断点
OSIntExit(); //一个断点
OSIdle ------> os_cpu_c.c
OSStart ------> os_cpu_c.c
变量的状态变化和依赖关系
OSTaskRuning 根据任务的状态 和中断 变化
OSNextTaskID 根据OSTaskRuning 的变化而变化
OSTaskID 根据OSNextTaskID 的变化而变化。
假设有3个任务,TaskA,TaskB,TaskC,优先级 TaskA 大于 TaskB 大于 TaskC
刚开始 OSTaskRuning = 0xFF,所有的任务,都等待执行,
任务A 首先 执行,然后任务A 休眠5s
任务切换,任务B 得到执行,然后任务B 休眠10s
任务切换,任务C 得到执行,然后任务C 休眠15s
这个时候,所有任务都没有事情作,都处于休眠状态,所以进入OSIdle()任务中。
经过5s之后,中断程序,激活任务A,这个时候,OSTaskRuning 发生变化。
OSNextTaskID 发生变化。OSTaskID 根据OSNextTaskID 变化而变化。
任务A,进行运行模式。
OSTsakStackBotton[ ] 保存的是,任务A,B,C 被切换到其他任务之前的那个断点时候的,堆栈的地址。
任务的堆栈,是该任务被中断的那个断点上的 程序PC 断点。
具体参看《Small RTOS OSStart 函数解析 以及 内存堆栈 变化 (二)》
程序:
uint8 idata * data OSTsakStackBotton[OS_MAX_TASKS + 2];/* 任务堆栈底部位置 */
extern uint8 data OSTaskID,OSNextTaskID;
//OSNextTaskID 是要切换去的任务
//OSTaskID 是当前要保存的任务
//0---TaskA -- 21: TaskA 起始堆栈
//1---TaskB -- F7: TaskB起始堆栈,并且在起始堆栈中设置taskB的函数指针位置。并且重新设置SP 堆栈指针的指向地址。
//2---TaskC -- FA:
//3---Idle -- FD:
//0---TaskA -- 21: TaskA 起始堆栈
//1---TaskB -- 28: TaskB起始堆栈,并且在起始堆栈中设置taskB的函数指针位置。并且重新设置SP 堆栈指针的指向地址。
//2---TaskC -- FA:
//3---Idle -- FD:
//OSTaskID = 0
//OSNextTaskID = 1
void C_OSCtxSw(void)
{
uint8 idata * cp1,idata *cp2 ;
uint8 i,temp ,SaveSP ;
SaveSP = SP ;
/*当前堆栈指针位置0x27*/
/*SP = 0x27*/
/*cp1 = 0x28*/
cp1 = (uint8 idata *)SP + 1 ;
/*TaskC的地址FA*/
temp = (uint8) OSTsakStackBotton[OSNextTaskID +1 ];
/*TaskB的地址F7*/
cp2 = OSTsakStackBotton[OSTaskID+1];
if(OSNextTaskID > OSTaskID){
/*预加载任务 堆栈内容,复制到当前堆栈中*/
/*cp2 是预加载任务的起始地址*/
/*temp是预加载任务的结束地址*/
/*cp1是当前堆栈指向的RAM的相邻地址*/
while(cp2 != (uint8 idata *) temp){
*cp1++ = *cp2++ ;
}
//重新设置堆栈指针 指向位置
SP = (uint8)cp1 -1 ;
/*TaskB的地址F7*/
/*SaveSP 是 0x26*/
temp = OSTsakStackBotton[OSTaskID + 1] - (uint8 idata *)SaveSP -1 ;
//i = 1; i<2;i++
//OSTsakStackBotton[1] = OSTsakStackBotton[1] - temp
///*TaskB的地址*/ = F7 - (0xF7-0x27-1) = 0x27 +0x01
//更新预加载任务的堆栈内容存放地址
for(i = OSTaskID +1 ; i < OSNextTaskID +1 ; i++){
OSTsakStackBotton[i] -= temp ;
}
//更新预加载任务为当前任务
OSTaskID = OSNextTaskID ;
goto aaa ;
}
if(OSNextTaskID < OSTaskID){
cp2-- ;
cp1-- ;
while(cp2 != (uint8 idata *)temp ){
*cp2-- = *cp1-- ;
}
SP = (uint8) OSTsakStackBotton[OSNextTaskID + 1 ] -1 ;
temp = OSTsakStackBotton[OSTaskID + 1] - (uint8 idata *)SaveSP -1 ;
for(i = OSNextTaskID +1 ; i < OSTaskID +1 ; i++ ){
OSTsakStackBotton[i] += temp ;
}
OSTaskID = OSNextTaskID ;
goto aaa ;
}
SP = SaveSP ;
aaa:
LoadCtx();
}