开局分割线:接着我们上一篇,其实小小调度器最不同于其它实时任务框架的点就在于他的核心是基于:_LINE_的应用(纯C的实现,并没有用到和cpu相关的寄存器操作,所以他是可以忽略硬件进行移植的,只要编译器支持),当然这并不是原作者的原创(真要是我怕你也不敢用),使用_LINE_行号记录程序运行位置,最早是有PT thread采用的,感兴趣的童鞋可以自行度娘,言归正传,先从LINE用法开始下刀:
ANSI C 规定了预定义宏,__LINE__:表示当前源代码的行号;
#define WaitX(tickets) do { _lc=(__LINE__%255)+1; return (tickets) ;case (__LINE__%255)+1:;} while(0);
int func(void)
{
while(1)
{
WaitX(50); //假定该行代码在文件中的行号是18
LED0=!LED0;
}
}
这里有两个需要注意的,define WaitX宏为什么写为一行,因为写一行__LINE__的值就是行号而且是相同的,虽然宏写一行看起来很难理解,但是这的确是必须的。
接下来解释下_lc=(__LINE__%255)+1; _lc类型为char,取值范围在0-255,而0为刚进入函数开始直接,所以需要避过该号,所以实际取值应该在1-255之间,__LINE__的实际值可能超过255,所以需对其取余操作,防止跳转冲突(该错误可在编译阶段查找出来,并不需要太过担心,一旦出现冲突,解决方案也很简单,再任务中多打一行空格即可错开行号冲突)。
到这里我们再来看WaitX(50);这个宏到底执行了什么,其实就以下三点:
1.记录该行号(因为后面程序要跳转到该行继续执行)。
2.跳走,返回值50(返回值是给到调度任务,调度任务计时到后,主动调用当前任务)
3.进入当前任务,从记录行号开始执行。
接下来,隆重介绍下即将登场的调度任务:本质上其就是一个定时器任务(框架任务,必不可少),运行环境为:中断运行,代码如下:
void INTT0(void) interrupt 1 using 1 //看这个样子明显是个中断函数
{
TL0=0Xff; //寄存器操作,10ms执行一次中断
TH0=0XDB; //寄存器操作,这不是重点
UpdateTimers(); //这里是重点,看着像函数,实际这也是个宏。
}
重点就是UpdateTimers()宏展开的实现:
//用最简单的方式:纯C实现调度器核心代码。
do{
unsigned char i;
for(i=MAXTASKS;i>0 ;i--)
{
if((timers[i-1]!=0)&&(timers[i-1]!=65535)) timers[i-1]--;
}
} while(0);
这段宏完成了定时器任务最主要的功能,主要是对timers数组进行--操作,timers数组里面记录的每个任务的延时时间(专业一点叫做阻塞时间),当一个任务被阻塞时,return返回值就会被写入对应的timers数组中。
每一个任务都有一个自己的timers,所以timers数组的最大值就是MAXTASKS(最大任务数)。一个任务有三中状态:
1. timers为0,这个时候的任务要么是就绪态(即将运行),要么就是运行态。
2. timers不为0,也不是最大值(65535),这个时候的任务为阻塞态(也就是延时时间还没到)。
3. timers为65535(最大值),这个时候的任务为停止态(也就是生命周期结束了,不再参与系统调度)。
有了这个调度框架(定时任务)再配合程序猿自己定义的任务函数,那么小小os就可以简简单单的跑起来了。