> 文章列表 > 小小OS整理

小小OS整理

小小OS整理

一、task0

关键基石:
1)“_ _ LINE _ _ ”返回当前这行代码对应行数。注意返回值是一个整数类型。
2)在while(1)里面使用return,创造不会霸占 CPU 的任务。

unsigned short task0()
{_SSwhile(1){WaitX(50);//记忆行号,返回50.至于这个 50,就是告诉调度器,延时 500 毫秒后,再次进入 task0 任务函数执行的意思。这个是由调度框架来保证的,后面会描述到。LED0=!LED0;}_EE
}

将原本是一行的语句展开成多行

unsigned short task0()
{static unsigned char _lc=0; switch(_lc){default://注意,其实这里用 switch(_lc){case 0: 也是一样的。while(1)//死循环{do { _lc=(__LINE__%255)+1; //记忆当前行号return 50 ; //函数返回,级任务0退出,任务主动释放 CPU。50是WaitX(50)的参数50。case (__LINE__%255)+1://当 500 毫秒过去后,会再次进入 task0 任务函数,执行task0的switch(_lc),那么 switch 会去找 case101 的代码。即此行。;} while(0);//只执行一次的循环LED0=!LED0;} ;}; _lc=0;return 65535;
}

二、task1

while(1){
WaitX(50);
LED0=!LED0;
}

三、调度框架

1、如何记录让出CUP的任务,进行到的位置

小小调度器 V1.1 , 采用的是 switch case 跳转语法,详见task0。

2、变量和寄存器的值

在任务里,如果需要使用额外的变量,应首先考虑使用静态局部变量,这样可以保证任务每次进入时,这些变量的值没被释放,还是保持和上次退出时一样。(如果确保需要用到的变量,其使用过程中任务不会释放 CPU,也可以使用局部变量)

3、如何禁止任务长期霸占 CPU

如果要使得每个任务都只执行一小段,就让出 CPU 运行权,只有两种方式,一种是任务主动释放 CPU,一种是任务不主动释放,由中断主动打断 CPU 的运行,强制将 CPU 交给另一个任务。第一种方式就称为协作式多任务,第二种方式就称为抢占式多任务。小小调度器 V1.1 版本,实际上是采用了两种机制,一种是协作式多任务,一种是抢占式多任务。因此,属于混合式任务调度模式。

我们可以从代码中看到,共有三个任务,task0,task1,task2;
其中,task1 和 task2 在 main 函数里循环运行。 而在定时器中断运行着 task0 任务。

void INTT0(void) interrupt 1 using 1
{
TL0=0Xff; //10ms 重装
TH0=0XDB;//b7;
UpdateTimers();
RunTask(task0,0);//任务具有精确按时获得执行的权限,要求:task0 每次执行消耗时间<0.5 个 ticket
}

因此,对于 task1 和 task2 来说,它们两个任务是平级的,需要协作释放 CPU,如果 task1 不主动释放 CPU,task2 就无法运行,反之如果 task2 不主动释放 CPU,task1 也无法行。
但是 task0 则具有更高的优先级,即便 task1 或 task2 不主动释放 CPU, task0 任务仍然在中断里可以定时得到执行。

实际上,大部分情况下,使用协作式多任务已经能够满足一般项目的需要。并且纯协作式多任务有一个好处,就是不需要考虑变量的互斥机制。当引入抢占式多任务后,就需要认真考虑哪些变量有可能被中断里面的任务给修改。

4、延时

小小调度器 V1.1 有一个名为 timers 的数组:
volatile unsigned short timers[MAXTASKS];
这里的 MAXTASKS 就是顶级任务的个数,最大为 255 个。

我们知道每个任务函数,在开头只有一个简单的 switch 跳转,这个 switch 就像 goto 一样,只负责跳转到相应行。也就是说只要进入了函数,任务就会往后面的代码执行。那么等待延迟的任务,就需要任务函数以外的机制来实现,而不是让任务函数自己去判断。也就是说当一个任务内部使用 WaitX 进行 500 毫秒,那么就应该调度器框架保证在 500 毫秒后才再次进入任务函数。

因此我们可以看 RunTask 宏:

#define RunTask(TaskName,TaskID) do { if (timers[TaskID]==0)
timers[TaskID]=TaskName(); } while(0);

这里就是有一个判断:只有 timers[TaskID] 等于 0 时 ,才进入任务函数。然后,当任务函数返回延时值后,又写入 timers[TaskID]。
也就是说,如果 task0 任务使用 WaitX(50);等待 500 毫秒,那么 timers[0]就会变成 50,然后调度器必须保证在 500 毫秒时,准确的让 timers[0]又变为 0,好让 RunTask 再次能够进入task0 任务函数。因此,我们设计了一个 10 毫秒中断,在这个 10 毫秒中断里,每次中断让 timers[0]的值减1。当 500 毫秒过后,刚好就使得 timers[0]的值变为 0.

void InitT0()
{
TMOD = 0x21;
IE |= 0x82; // 12t
TL0=0Xff;
TH0=0XDB;
TR0 = 1;
}
void INTT0(void) interrupt 1 using 1
{
TL0=0Xff; //10ms 重装
TH0=0XDB;//b7;
UpdateTimers();
RunTask(task0,0);//任务具有精确按时获得执行的权限,要求:task0 每次执行消耗时间<0.5 个 ticket
}

5、终止

展开其中的 UpdateTimers()宏:

do{unsigned char i; for(i=MAXTASKS;i>0 ;i--){if((timers[i-1]!=0)&&(timers[i-1]!=65535))   timers[i-1]--;}
} while(0);

就看得出来,当每个任务的 timer 变量不为 0,或者不为 65535 时,timer 变量值都在减少。当任务的 timer 变量不为 0 时,变量值减少,这个很容易理解,因为 0 已经是最小值了。但是还有一个条件:当任务的 timer 变量为 65535 时,也不发生变化。这不就会导致这个任务永远不会再进入了吗?因为一直是 65535。让我们再次看一下任务结尾出的_EE 宏,就能明白了:

#define _EE ;}; _lc=0; return 65535;

当任务函数执行到最后时,返回 65535,那么这个任务再也不会被执行了。这个就恰好体现了任务的 timer 变量为 65535 的意义了:任务对用的 timers[TASKID]值为 65535 意味着任务终止。

6、终止的任务经过过设定时间后,再次被调用

那么,当一个任务作为子任务,这一次流程执行完毕,下一次又被父任务调用,该如何处理呢?我们来看调用子任务的宏 CallSub:

#define    CallSub(SubTaskName) do{unsigned char currdt;_lc=(__LINE__%255)+1;   return 0;      //记录当前行号, 然后返回 0,延时0mscase (__LINE__%255)+1: currdt=SubTaskName();  //延时0ms后执行子任务SubTaskName的代码if(currdt!=255) {return currdt;}
}while(0);

这个宏,只是记录了父任务的当前行号,然后返回 0;
意味着几乎无需等待,调度框架会致使,立即再次进入父任务。执行

currdt=SubTaskName();   if(currdt!=65535)  {return currdt;}

即执行子任务SubTaskName的代码。

当子任务结束返回是65535时,(按上几行的#define,是255,不是上面错就是这里错!) ,这个 65535 值并不写入父任务的 timers 数组对应的变量,而是直接执行父任务后面的代码:也就等于是父任务已经完成这次子任务的调用,继续执行后续代码。

当子任务返回的不是 65535 的值时,实际是子任务需要延时,我们可以看到父任务会返回这个值:return currdt; 这会导致父任务在等待一段时间后才会再次进入。这可以解释为:因为父任务在子任务未结束时,不能去执行子任务后的代码,因此把子任务需要等待的时间,转由让父任务来进行等待。这样就可以让子任务共用父任务的时间延迟变量,子任务无需独立的时间延迟变量,从而节省了 RAM 空间。

四、使用说明事项

这里把在论坛上发布小小调度器早期版本时,码的一段文字拷贝过来:
小小调度器任务函数的写法主要注意的,主要有三点:

  1. 任务函数内部变量,建议都用静态局部变量来定义。
  2. 任务函数内不能用 switch 语句。
  3. 任务函数内,不能用 return 语句。 因为 return 已经被赋予任务延时的特定意义。(这是返回型任务函数版本的一个强制要求)
    这三点,并不会明显造成写程序的不方便。
    ---------------------------
    从裸奔到使用 OS 操作系统或调度系统的代价主要有:
    硬件资源代价(对 RAM 和 ROM 的消耗),学习代价(学会其原理,并掌握其用法),移植代价
    (往不同 cpu 上移植的工作量),效率代价(使用调度系统后带来的额外 cpu 负担),商业代
    价(版权费用),稳定性代价(是否引入潜在不稳定因素,或者增大 bug 跟踪调试工作量)。
    从这几方面来讲,应用小小调度器的代价,都是非常小的。
  4. 硬件资源代价: 前面的优化版本已经说明问题。keil 下,本例程 ram 消耗 : 22 字节,rom
    消耗 126 字节.
  5. 学习代价: 小小调度器总共只有十多行代码,如果我们做一个简单的解释说明,理解起来
    其实是很快的。我相信学习时间比其他调度系统要短。
  6. 移植代价: 几乎没有什么移植工作量,对于各种 cpu,几乎是通吃。
  7. 效率代价: 我们一直在努力优化,相信调度效率已经不低了。比如任务切换时间,应该是
    可以做到 uS 级别,甚至亚 uS 级别。
  8. 商业代价: 小小调度器为免费使用,无需支付任何费用。
  9. 稳定性代价:小小调度器本质上仅仅是几个宏而已,未涉及任何对内部寄存器或堆栈的操
    作,避免了引入不稳定风险因素,所有操作都在可预见,可把控的前提下进行。
    除了以上三点需要注意的外,还容易出的错误有:
  10. WaitX 里面的值>=65535 了,因为 v1.1 版本的值最大只能是 65534。
  11. While,for 循环里,忘了写 WaitX,导致程序死循环。
  12. 任务函数重入,也就是说一个任务函数同时被几个顶级任务调用。这在 V1.1 里是不允许的。
  13. 妄图在普通函数里使用 WaitX。这个是无法通过编译的。
  14. 在 RunTaskA 调度的任务里使用 waitx(0),导致后续任务无法运行。

五、附录: 小小调度器 V1.1.c

/****小小调度器开始**********************************************/
#define MAXTASKS 3
volatile unsigned short timers[MAXTASKS];
#define _SS static unsigned char _lc=0; switch(_lc){default: 
#define _EE ;}; _lc=0; return 65535;#define WaitX(tickets)  do { _lc=(__LINE__%255)+1; return (tickets) ;case (__LINE__%255)+1:;} while(0);
#define WaitUntil(A)    do { while(!(A)) WaitX(1);} while(0);
#define WaitUtilT(A,TimeOut)  do { static unsigned short _count=(TimeOut); do { WaitX(1); _count--; } while((!(A))&&(_count>0));} while(0);#define RunTask(TaskName,TaskID)  do { if (timers[TaskID]==0) { unsigned short d=TaskName(); while(timers[TaskID]!=d) timers[TaskID]=d;} }  while(0); 
#define RunTaskA(TaskName,TaskID) do { if (timers[TaskID]==0) {unsigned short d=TaskName(); while(timers[TaskID]!=d) timers[TaskID]=d;   continue;} }while(0);   //前面的任务优先保证执行#define CallSub(SubTaskName) do {unsigned short currdt; _lc=(__LINE__%255)+1; return 0; case (__LINE__%255)+1:  currdt=SubTaskName(); if(currdt!=65535) return currdt;} while(0);
#define InitTasks() do {unsigned char i; for(i=MAXTASKS;i>0 ;i--) timers[i-1]=0; } while(0);
#define UpdateTimers() do{unsigned char i; for(i=MAXTASKS;i>0 ;i--){if((timers[i-1]!=0)&&(timers[i-1]!=65535)) timers[i-1]--;}} while(0);#define SEM unsigned int 
//初始化信号量
#define InitSem(sem) do{sem=0;}while(0);
//等待信号量
#define WaitSem(sem) do{ sem=1; WaitX(0); if (sem>0) return 1;} while(0);
//发送信号量
#define SendSem(sem)  do {sem=0;} while(0);/*****小小调度器结束*******************************************************/sbit LED1 = P2^1;
sbit LED2 = P2^2;sbit LED0 = P2^5;unsigned short task0(){
_SSwhile(1){WaitX(50);LED0=!LED0;   }
_EE
}unsigned short  task1(){
_SSwhile(1){WaitX(100);LED1=!LED1;   }
_EE
}unsigned short task2(){
_SSwhile(1){WaitX(100);LED2=!LED2;   }
_EE
}void InitT0()
{TMOD = 0x21;IE |= 0x82;  // 12tTL0=0Xff;TH0=0XDB;TR0 = 1;
}void INTT0(void) interrupt 1 using 1
{TL0=0Xff;    //10ms 重装TH0=0XDB;//b7;    UpdateTimers();
}void main()
{InitT0();InitTasks(); //初始化任务,实际上是给timers清零while(1){
//           RunTask(task0,0);RunTaskA(task1,1);//任务1具有比任务2高的运行权限                   RunTaskA(task2,2);//任务2具有低的运行权限                   }
}//如需要技术交流,电话或WX 13631645567