程序小白
认证:优质创作者
作者动态
小小调度器——开局default,优雅总藏于细节之中
04-02 09:23
小小调度器——调度器核心的纯C实现
04-01 14:09
小小调度器——原来C代码还能这么写,原作者没少挨打吧!
03-27 15:42
解析由系统库引发的hardfault血案(RT-Thread+ESP32)
2023-07-17 16:04
TINA瞬时现象仿真分析——运放缓冲器振铃
2023-02-28 11:38

小小调度器——原来C代码还能这么写,原作者没少挨打吧!


开局分割线(理解了以后的一番感概):一个很偶然的机会接触到了一个全部用C语言写的任务调度框架,刚开始觉得有点奇怪,因为当你用它创建任务的时候,不能用switch 不能用return,局部变量还得变成static,想想写代码就有点别扭,深入学习了一下,的确很有意思,对RTOS的机制实现的理解,忽而有种推门而入的感觉。


好多童鞋刚入门单片机变成,还在裸跑阶段的时候,对调度器三个字可能一头雾水,这玩意儿到底干嘛用的,举个例子,写个led点灯的函数:

int func(void)
{
	led1_on;			//实现led1灯亮
	delayms(500);	    //延时500ms,阻塞的方式
	led1_off;			//实现led1灯灭
}

这里引入了一个阻塞的概念,对应的还有一个词叫做非阻塞,关键就是延时500ms,那么执行到这个函数的时候,程序不会继续执行,需要等待500ms:

      非阻塞的方式,程序一直等在这,等到500ms实现到了,继续执行关灯。

      阻塞的方式,程序会记录下当前函数的运行位置,然后跳走去执行别的函数,当500ms时间到时,再从别的位置调到该函数led1_off这一条继续执行,而不是从函数头部位置开始执行。

      裸机也能解决这个问题,但是要引入不少复杂的语法操作,这仅仅是一个函数的操作,一个程序不可能只有一个点灯的任务,还会有多个任务,当我们把裸机执行的操作抽象出来解决大部分问题的时候,小小调度器就出现了(他并不是很复杂的东东,其实最初的它只是为了解决一个阻塞式的delayms)。


unsigned short task0()
{
  _SS  //宏定义
  while(1)
  {
    WaitX(50); //宏定义
    LED0=!LED0;
  }
  _EE //宏定义
}

要实现阻塞,执行WaitX(50);时跳出task0任务,当50个ticket到了以后,进入函数从LED0=!LED0开始执行,最关关关键(重要的事儿重复三遍)的就是这三个宏:

//任务头
#define _SS static U8 _lc=0; switch(_lc){default:

//等待X个时钟周期
#define WaitX(tickets)  do { _lc=(__LINE__%255)+1; return (tickets) ;case (__LINE__%255)+1:;} while(0);

//任务尾
#define _EE ;}; _lc=0; return TICKET_MAX;

单看每一行都无法读懂,不要担心,只要记住三行注释就可以了,接下来把他带进去展开后我们再来看这个函数:

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;
}

这看起来,总算像是一个完整的函数了,但是看到switch条件判断被打散分布在程序里,第一感觉还是难以接受,default里面居然套上了case!!!,我们自己写代码如果这么写,估计会被领导打,但是站在编译器的角度来看,还是符合规则的,就是能完整执行。

接下来看他的执行流程,假如任务被调用,则从头开始执行,_lc为0,所以执行default,这里把(__LINE__%255)+1当作一个常数,例如用100代替,那么接着执行return 50;函数执行结束了,这个50会返回给一个很特殊的定时器任务,执行50个tickets以后,再次主动调用task0,从case 100开始执行,led灯翻转,然后回到while(1) 执行return50 跳走,整儿流程就是间隔50个tickets 翻转LED输出。

最后不得不感叹虽然有点乱,但是的确神奇的完成了想要的工作,好像是程序里面写了两个bug,然后来了个负负得正,居然跑起来了~!!!

声明:本内容为作者独立观点,不代表电子星球立场。未经允许不得转载。授权事宜与稿件投诉,请联系:editor@netbroad.com
觉得内容不错的朋友,别忘了一键三连哦!
赞 3
收藏 4
关注 115
成为作者 赚取收益
全部留言
0/200
成为第一个和作者交流的人吧