搜索
bottom↓
回复: 74

[交流][微知识]支持状态“跃迁”的超轻量级调度器

  [复制链接]

出0入296汤圆

发表于 2012-11-30 00:05:42 | 显示全部楼层 |阅读模式
本帖最后由 Gorgon_Meducer 于 2015-2-10 14:02 编辑

TO BE CONTINUE...


  1. //! \brief task control block
  2. typedef struct __task task_t;

  3. //! \brief task prototype
  4. typedef void* task_routine_t(task_t *ptTask);

  5. //! \name task control block structure
  6. //! @{
  7. struct __task {
  8.     task_routine_t *fnRoutine;    //!< task routine
  9.     bool bLocked;
  10.     void *pArg;                    //!< task argument
  11. };
  12. //! @}
复制代码

  1. //! \brief declare a task pool
  2. #ifndef TASK_POOL_SIZE
  3. #define TASK_POOL_SIZE    4
  4. #endif
  5. static task_t s_tTaskPool[TASK_POOL_SIZE] = {0};
复制代码

  1. bool new_task(task_routine_t *fnRoutine, void *pArg)
  2. {
  3.     uint8_t n;
  4.     bool bResult = false;
  5.     if (NULL == fnRoutine) {
  6.         return bResult;
  7.     }

  8.     //! search for free task. As shared resource involved, atom access is required.
  9.     SAFE_ATOM_CODE(
  10.         for (n = 0; n < UBOUND(s_tTaskPool); n++) {
  11.             if (NULL == s_tTaskPool[n].fnRoutine) {
  12.                 s_tTaskPool[n].fnRoutine = fnRoutine;
  13.                 s_tTaskPool[n].pArg = pArg;
  14.                 s_tTaskPool[n].bLocked = false;
  15.                 bResult = true;
  16.                 break;
  17.             }
  18.         }
  19.     )

  20.     return bResult;
  21. }
复制代码

  1. bool scheduler(void)
  2. {
  3.     static uint8_t s_tTaskCounter = 0;
  4.     static uint8_t s_tFreeCounter = 0;
  5.     task_t *ptTask;

  6.     //! access shared resource, atom access is required
  7.     SAFE_ATOM_CODE(
  8.         ptTask = &s_tTaskPool[s_tTaskCounter++];
  9.         if (s_tTaskCounter >= UBOUND(s_tTaskPool)) {
  10.             s_tTaskCounter = 0;
  11.         }

  12.         if (NULL != ptTask->fnRoutine) {
  13.             s_tFreeCounter = 0;
  14.             if (!ptTask->bLocked) {
  15.                 ptTask->bLocked = true;
  16.             } else {
  17.                 ptTask = NULL;
  18.             }
  19.         } else {
  20.             s_tFreeCounter++;
  21.             if (UBOUND(s_tTaskPool) <= s_tFreeCounter) {
  22.                 s_tFreeCounter = UBOUND(s_tTaskPool);
  23.                 EXIT_SAFE_ATOM_CODE();
  24.                 return false;
  25.             }
  26.             ptTask = NULL;
  27.         }
  28.     )
  29.    
  30.     if (NULL != ptTask) {
  31.         ptTask->fnRoutine = (task_routine_t *)ptTask->fnRoutine(ptTask);
  32.         SAFE_ATOM_CODE(
  33.             ptTask->bLocked = false;
  34.         )
  35.     }
  36.    
  37.     return true;
  38. }
复制代码


  1. static void idle_task(void)
  2. {
  3.     sleep();
  4. }

  5. int main(void)
  6. {
  7.     ...
  8.     while(1) {
  9.         if (!scheduler()) {
  10.             //! return false means system is idle, run idle to enter sleep mode.
  11.             idle_task();
  12.         }
  13.     }

  14.     return 0;
  15. }

  16. //! the most amazing part is here
  17. /*! \note this is a simple demonstration for the fact that the scheduler can works well in
  18. *! real multi-task system from a simple front/back-end system to multi-task OS environment.
  19. *! Is it useful? *^_^* Try to think the multi-core and multi-thread programmng model.
  20. */
  21. ISR(TIM0_COMPA_vect)
  22. {
  23.     //! 1ms compare match interrupt service routine

  24.     //! we can call scheduler both at super loop and ISR simultaneously. this is useful!
  25.     scheduler();
  26. }
  27. ...
  28. ISR(ADC_vect)
  29. {

  30.     ...
  31.     scheduler();
  32. }
  33. ...
复制代码

阿莫论坛20周年了!感谢大家的支持与爱护!!

知道什么是神吗?其实神本来也是人,只不过神做了人做不到的事情 所以才成了神。 (头文字D, 杜汶泽)

出0入296汤圆

 楼主| 发表于 2012-11-30 00:06:06 | 显示全部楼层
占位,非沙发

出0入0汤圆

发表于 2012-11-30 00:22:58 | 显示全部楼层
凌晨帮顶支持。

出0入0汤圆

发表于 2012-11-30 00:50:30 | 显示全部楼层
占位学习~

出0入0汤圆

发表于 2012-11-30 01:09:58 来自手机 | 显示全部楼层
手机占位

出0入0汤圆

发表于 2012-11-30 01:10:44 | 显示全部楼层
来晚了,沙发、板凳、地板、地下室都没了……

出0入0汤圆

发表于 2012-11-30 01:54:29 | 显示全部楼层
有意思。
主循环一条线,中断一条线,把任务分配到两条线上,任务可以在两条线上分别推进,并且可以抢占。
扩大了中断的概念,把中断当CPU用。但抢占也会使问题变复杂。
能不能举一个合适的具体应用,跟这个思想严丝合缝配合的,让大伙看看这玲珑思想的前凸后翘?

出0入0汤圆

发表于 2012-11-30 07:53:46 | 显示全部楼层
先拷贝下来研究

出0入0汤圆

发表于 2012-11-30 08:04:24 来自手机 | 显示全部楼层
看不懂哦,楼主授课下!

出0入0汤圆

发表于 2012-11-30 08:34:41 | 显示全部楼层
把代码一行行解释下,谢谢

出0入0汤圆

发表于 2012-11-30 08:59:45 | 显示全部楼层
stdio 发表于 2012-11-30 01:54
有意思。
主循环一条线,中断一条线,把任务分配到两条线上,任务可以在两条线上分别推进,并且可以抢占。
...

task中建立task

出0入0汤圆

发表于 2012-11-30 09:18:52 | 显示全部楼层
占位学习,如果任务间的关系很复杂的话,用这种方式会不会造成问题。

出0入0汤圆

发表于 2012-11-30 09:50:46 | 显示全部楼层
scheduler里,静态的s_tTaskCounter一路++上去,没意外要溢出吧。。。

- 本来多个任务之间是协调调度的,很多锁并不需要;如果多处调用调度器,则可能出现抢占,则需要考虑锁。
- Demo代码中,堆栈需求上升,现在最差堆栈需求可能是最大+次大两个任务的和,外加中断等开销。
- 调度器完成的前提是任务跳转序列结束,如果任务在跳转序列中出现某种等待,是Busy Waiting的状态。

没太看出来应用场景是什么。

出0入296汤圆

 楼主| 发表于 2012-11-30 10:17:48 | 显示全部楼层
本帖最后由 Gorgon_Meducer 于 2012-11-30 10:35 编辑
dr2001 发表于 2012-11-30 09:50
scheduler里,静态的s_tTaskCounter一路++上去,没意外要溢出吧。。。

- 本来多个任务之间是协调调度的, ...


哈哈哈,终于有人认真看代码了。是的,我删除了一部分代码,就想看看有没有人真的认真看……
关于栈的需求,因为实际遵守的是状态机规范,所以只是由原本的1深度增加到2深度(前后台系统)。

如果仔细看,你会发现这个调度器其实和前面一个帖子的状态机本质结构是一样的,是的,这是一个状态机
调度器,之所以加入跃迁的功能是为了提升系统的反追踪性(Anti-reverse-engineering: Runtime tracking);
这是用途之一而且对开发者透明——如果遵守了“任务平面多元化原则”的话——这个一两句话解释不清楚,
但也不是什么复杂的原则。
用途之二就是用来做多任务环境的培训模型:一般情况下,大家编写调度器代码会默认被调度函数彼此是不可
打断的,也就是互有原子性的,因而在开发过程中没有保护共享资源的概念。事实上,状态机可以在多个不同
的任务平面上执行,开发代码的时候应该将每一个任务都视作拥有独立的线程,而线程之间是会相互打断的,
这就和普通的多线程环境开发是一样的。为了培训目的,在不使用操作系统的情况下模拟出这种可能,我引入
了这个模型,这样,任务之间就没有了原本天然的“互为原子性”的特征,在普通裸机环境下也可以看到复杂
多线程环境下会遇到的种种问题,从而能开展进一步的培训。

最后说一个很现实的应用场景:
已知我们有一个全状态机开发的系统,你写好了所有的驱动、GUI、各种服务,所有的Task都是none-block的
到目前为止一切都很和谐。突然,因为伟大的上帝有一个需求,需要加入一个存在阻塞代码且执行时间很长的
任务,比如T9输入法,比如某个用户知识产权的.a文件,里面有一个封装到牙齿,且霸道的独占CPU(block-code)
的任务,现在,你又想用原有的系统,感谢你的勤奋,你已经挖掘了这块CPU的几乎每一个汗毛的潜力——当然
为了降低BOM成本——这可能也是客户需求之一,或者打一开始客户就选了一个极具挑战的MCU,然后你还答应
了。在这种情况下,跑操作系统是不可能了……咋办?很简单:
1、让T9这类上帝任务跑在主循环里面。
2、允许的情况下,在所有事件出发相关且不开嵌套的中断处理程序里面调用scheduler。
3、为了保证日常任务,你可能还要开一个定时器来跑scheduler。
4、理论上,你可以让定时器工作的很疯狂……反正只要能保证上帝任务的最低时效性要求就OK了……

经过以上的改造,很好,系统工作了~你也成功地发挥了系统的潜能,榨干了MCU的每一滴血,还模拟了2个任务
的OS环境。

出0入0汤圆

发表于 2012-11-30 11:54:56 | 显示全部楼层
Gorgon_Meducer 发表于 2012-11-30 10:17
哈哈哈,终于有人认真看代码了。是的,我删除了一部分代码,就想看看有没有人真的认真看……
关于栈的需 ...

把中断控制器视为具有一堆缺陷和不足(Stack,Context管理,IPC,etc)的调度器,很多问题都很显然;前后台本身就是个2优先级抢占式调度。。。

最后一个例子实际上就是还有天然的优先级没有利用……如果原始资源利用的到位,那就没啥可玩的了。

出0入0汤圆

发表于 2012-11-30 13:26:38 | 显示全部楼层
记号...堆栈很难算清要多少吧!!!

出0入0汤圆

发表于 2012-11-30 14:07:44 | 显示全部楼层
我是来顶的

出0入0汤圆

发表于 2012-11-30 14:08:39 | 显示全部楼层
最近也在研究ucgui和ucos,楼主有什么推荐的书吗?

出0入296汤圆

 楼主| 发表于 2012-11-30 15:29:16 | 显示全部楼层
zhonggp 发表于 2012-11-30 13:26
记号...堆栈很难算清要多少吧!!!

这个好算~

出0入296汤圆

 楼主| 发表于 2012-11-30 15:31:13 | 显示全部楼层
dr2001 发表于 2012-11-30 11:54
把中断控制器视为具有一堆缺陷和不足(Stack,Context管理,IPC,etc)的调度器,很多问题都很显然;前后 ...

前后台只有抢占,没有调度……现在只是加了个调度而已
至于中断,我并不认为是缺陷,我认为中断是最优雅,最简单的。调度系统只要遵守状态机开发原则,就不会破坏这种优雅。

出0入0汤圆

发表于 2012-11-30 16:18:27 | 显示全部楼层
Gorgon_Meducer 发表于 2012-11-30 15:29
这个好算~

我感觉他可能存在中断嵌套对吧.那这样就要很清楚知道每个任务的运行时间是实际需要的栈空间对吗?

出0入296汤圆

 楼主| 发表于 2012-11-30 22:10:47 | 显示全部楼层
zhonggp 发表于 2012-11-30 16:18
我感觉他可能存在中断嵌套对吧.那这样就要很清楚知道每个任务的运行时间是实际需要的栈空间对吗? ...

严格来说前后台系统是二阶任务平面系统,是不允许中断嵌套的。

出0入0汤圆

发表于 2012-11-30 23:08:08 | 显示全部楼层
是啊,中断嵌套就会出现中断深度的问题.

出0入0汤圆

发表于 2012-12-9 17:58:24 | 显示全部楼层
我来试着解释下这段代码,我认为整个调度器核心在这里,请楼主看我的解释是否到位。

  1. bool scheduler(void)
  2. {
  3.     static uint8_t s_tTaskCounter = 0;  // 用于任务选择的变量
  4.     static uint8_t s_tFreeCounter = 0;  // 任务数组里“空白”任务的个数
  5.     task_t *ptTask;

  6.     //! access shared resource, atom access is required
  7.     SAFE_ATOM_CODE(
  8.         // 根据s_tTaskCounter来选择要调度的任务
  9.         ptTask = s_tTaskPool[s_tTaskCounter++];
  10.         // 在所有任务中循环
  11.         if (s_tTaskCounter >= UBOUND(s_tTaskPool)) {
  12.             s_tTaskCounter = 0;
  13.         }

  14.         if (NULL != ptTask->fnRoutine) { // 选择出来的任务其“状态机动作”还没有执行完成
  15.             s_tFreeCounter = 0;          // 为什么在这里要清0?
  16.             if (!ptTask->bLocked) {      // 任务没有上锁,则设置锁标志;
  17.                 ptTask->bLocked = true;
  18.             } else {
  19.                 ptTask = NULL;          // 如果是在main中正运行的任务,其锁标志被置位,被中断抢占(中断中调用scheduler)时,使得不会重复执行main中正在运行的任务
  20.             }
  21.         } else {
  22.             // 运行到这里,说明选出来的任务“状态机动作”执行到了结束状态
  23.             s_tFreeCounter++;
  24.             // 如果“空白”任务个数与所有支持的任务数相等,说明没有任务需要执行了,返回false,结束运行
  25.             if (UBOUND(s_tTaskPool) == s_tFreeCounter) {
  26.                 EXIT_SAFE_ATOM_CODE();
  27.                 return false;
  28.             }
  29.             // 清除任务数组中的值。
  30.             ptTask = NULL;
  31.         }
  32.     )
  33.    
  34.     // 如果是一个“有效”的任务,运行它
  35.     if (NULL != ptTask) {
  36.         ptTask->fnRoutine = ptTask->fnRoutine(ptTask);  // 每一个任务都是状态机任务,ptTask->fnRoutine中保存的是下一个状态要执行的动作
  37.         SAFE_ATOM_CODE(
  38.             // 任务当前状态动作执行完毕,清除锁标志。这个锁标志是不是就是 running的意思?
  39.             ptTask->bLocked = false;
  40.         )
  41.     }
  42.     // 返回true,表示下次要继续调用我,以调度下一个任务。
  43.     return true;
  44. }
复制代码

出0入296汤圆

 楼主| 发表于 2012-12-9 18:12:17 来自手机 | 显示全部楼层
解释正确,只是你没有理解到s_tFreeCounter=0这句话的重要意义:我用的是数组模拟的等效堆,函数指针是否为空决定了任务控制块是否释放。调度代码实际上是直接在堆中寻找有效任务的。

出0入296汤圆

 楼主| 发表于 2012-12-9 18:13:49 来自手机 | 显示全部楼层
解释正确,只是你没有理解到s_tFreeCounter=0这句话的重要意义:我用的是数组模拟的等效堆,函数指针是否为空决定了任务控制块是否释放。调度代码实际上是直接在堆中寻找有效任务的。

出0入0汤圆

发表于 2012-12-9 18:18:16 | 显示全部楼层
当且仅当连续的FreeTask等于总Task数量时,才是所有任务全部完成。

先对将要执行的任务加锁,然后在调用,这样使得调度器以及其调用的任务看起来”可重入“。

出0入296汤圆

 楼主| 发表于 2012-12-9 18:19:55 | 显示全部楼层
dr2001 发表于 2012-12-9 18:18
当且仅当连续的FreeTask等于总Task数量时,才是所有任务全部完成。

先对将要执行的任务加锁,然后在调用, ...

Bingo~多谢解读

出0入0汤圆

发表于 2012-12-9 18:58:51 | 显示全部楼层

  1.       | Step | Task1   | Task2   | Task3   | Task4 | Task5 | Task6 | Task7 | Task8 |
  2.       |------+---------+---------+---------+-------+-------+-------+-------+-------|
  3.       | 1    | Running | Wait    | Wait    | Wait  | Wait  | Wait  | Wait  | Wait  |
  4.       | 2    | Wait    | Running | Wait    | Wait  | Wait  | Wait  | Wait  | Wait  |
  5.       | 3    | Wait    | Stop    | Running | Wait  | Wait  | Wait  | Wait  | Wait  |
  6.       |      | ...     | ...     | ...     | ...   | ...   | ...   | ...   | ...   |
  7.       | N    | Running | Stop    | Wait    | Wait  | Wait  | Wait  | Wait  | Wait  |
  8.       | N+1  | Wait    | Stop    | Wait    | Wait  | Wait  | Wait  | Wait  | Wait  |
  9.       | N+2  | Wait    | NULL    | Running | Wait  | Wait  | Wait  | Wait  | Wait  |
  10.       |      |         |         |         |       |       |       |       |       |
复制代码
* 调度过程,如图所示
  1. 第N步时,在运行Task1,由于以前的调度过程中没有遇到NULL任务,所以s_tFreeCounter = 0;
  2. 第N+1步,取出Task2,由于Task2的“状态机动作”已经完成,它的fnRoutine为NULL,调度器遇到一个NULL任务,将s_tFreeCounter加1,s_tFreeCounter, 并且释放Task2位置任务控制块;
  3. 第N+2步,取出Task3,由于Task3是一个就绪任务,所以if(NULL != ptTask->fnRouting)成立,代码将s_tFreeCounter清0了,那么第N+1步中的加1操作被取消了,并且没有起到统计NULL任务个数的作用。

综上,s_tFreeCounter = 0那句话该如何理解?还是写错了?

出0入0汤圆

发表于 2012-12-9 19:02:50 | 显示全部楼层
调度器的实时性不太好,在通信应用中 不太方便处理,因为
如果调度间隔是1ms,一次通信有时必须要在1ms处理完,而且每个毫秒周期得发送一个字节的数据,这样 调度器不可能每个毫秒的周期都执行通信任务,而不做其它的事了,而想要把要发送的数据放入缓冲区,而mcu又没有太大的缓冲区,一般的只有两字节的缓冲区。

没 有办法,我好象是用了中断加调度器的混合方法。自己都不知道是怎么实现的了。

出0入0汤圆

发表于 2012-12-9 19:05:29 | 显示全部楼层
从遇到的第一个NULL开始,NULL的个数是总Task数那么多个。

出0入296汤圆

 楼主| 发表于 2012-12-9 19:07:21 | 显示全部楼层
zhwm3064 发表于 2012-12-9 19:02
调度器的实时性不太好,在通信应用中 不太方便处理,因为
如果调度间隔是1ms,一次通信有时必须要在1ms处理 ...

为什么调度间隔是一毫秒?如果用全状态结构,理论上不会有有实时性问题。不是说混合调度不是解决方案,而是在那之前,为什么你会判定一个固定的调度周期是1ms,为什么又判定实时性不好。这是我感兴趣的部分。

出0入0汤圆

发表于 2012-12-9 19:10:45 | 显示全部楼层
本帖最后由 ifree64 于 2012-12-9 19:17 编辑
dr2001 发表于 2012-12-9 19:05
从遇到的第一个NULL开始,NULL的个数是总Task数那么多个。


怎么理解?

再想了一下,freeCounter表示的是从遇到的第一个NULL开始,连续的NULL任务的个数。
所以,在遇到一个不是NULL的任务时,就将它清零了。

所有任务执行完毕是,连续NULL任务个数与总任务个数相等。

出0入296汤圆

 楼主| 发表于 2012-12-9 19:11:49 | 显示全部楼层
本帖最后由 Gorgon_Meducer 于 2012-12-9 19:16 编辑
ifree64 发表于 2012-12-9 18:58
* 调度过程,如图所示
  1. 第N步时,在运行Task1,由于以前的调度过程中没有遇到NULL任务,所以s_tFreeCou ...


如果3的情况发生了,就不能去运行idle任务,因为idle表明系统没有ready状态的任务。在idle中,系统很可能是会sleep的,如果这种事情发生了,就会导致有遗留任务的情况下,系统判定为idle。s_tFreeCount在发现任何一个有效任务的情况下都会复位,因为只有真正的idle状态,这个计数器才有可能自加到堆大小。另外,这个系统没有wait状态,只有ready和stop状态,而stop实际上就是NULL,你如果想重新开始就必须重新注册任务。要实现wait,比如基于某个critical section的阻塞,就需要建立一个critical section的抽象数据类型,其本质原理是一个阻塞的任务队列。当进入临街区的时候,入队,当临界区施放的时候,出队并自动注册任务。

出0入0汤圆

发表于 2012-12-9 19:14:59 | 显示全部楼层
zhwm3064 发表于 2012-12-9 19:02
调度器的实时性不太好,在通信应用中 不太方便处理,因为
如果调度间隔是1ms,一次通信有时必须要在1ms处理 ...

对这个问题有兴趣。

协调多任务调度,轮询算法,确实有最长等待时间。最坏情形是每个任务最长运行片段(这个话题下,就是每个状态函数)之和。
但是这个可以通过把每个任务拆分,(这里就是一个状态的处理拆成一系列的状态),从而达到每个片段运行时间少于某个值的要求。

实现你的要求自然是可以,当然,协调式调度实现出来的代码肯定不会比抢占式实现出来的自然,好看。
我认为。

出0入0汤圆

发表于 2012-12-9 19:19:29 | 显示全部楼层
本帖最后由 ifree64 于 2012-12-9 19:33 编辑
Gorgon_Meducer 发表于 2012-12-9 19:11
……s_tFreeCount在发现任何一个有效任务的情况下都会复位,因为只有真正的idle状态,这个计数器才有可能自加到堆大小。另外,这个系统没有wait状态,只有ready和stop状态,而stop实际上就是NULL,你如果想重新开始就必须重新注册任务。……


看来我把术语用错了,我用wait表达等待着去运行,也就是就绪(ready)的意思。我图表中用stop只是表示状态机函数为NULL,但pTask任务控制块不是NULL。

>>>因为只有真正的idle状态,这个计数器才有可能自加到堆大小。
这个问题我想明白了,你的freeCounter这个变量,纪录的是连续的NULL任务个数,只有连续NULL任务等于任务总数才代表达到idle状态。

出0入296汤圆

 楼主| 发表于 2012-12-9 19:20:05 | 显示全部楼层
dr2001 发表于 2012-12-9 19:14
对这个问题有兴趣。

协调多任务调度,轮询算法,确实有最长等待时间。最坏情形是每个任务最长运行片段( ...

观点一致。但如果写成状态机,真不好说在某些层面上究竟是原始形势还是状态代码在逻辑上更清晰了。
另外,我喜欢叫做合作式调度或者协作式调度,这是cooperation的意义。协调式似乎有点偏差。

出0入0汤圆

发表于 2012-12-9 19:39:22 | 显示全部楼层
Gorgon_Meducer 发表于 2012-12-9 19:07
为什么调度间隔是一毫秒?如果用全状态结构,理论上不会有有实时性问题。不是说混合调度不是解决方案,而 ...

我使用的是1毫秒调度器,你的调试器 如果 没有这种时间间隔,那应该很好,和实时性系统差不多 了。
查看了我的通信程序,确实是用了中断的方法:
////////采样装数////////////////////
void caiyangzhuangshu(void)//装数
{
(*shoufa2[shoufa2pin].zTask)();
UCSR0B = 0b00101000;
/*              ||||||||发送数据位8 当是9位数据帧时 写UDR0之前先写此 这是发送的第9位数据 一般不用
            |||||||接收数据位8 当是9位数据帧时 读UDR0之前先读此
            ||||||位2 与下面的寄存器位0合用 控制字符的长度
            |||||位3 发送使能 =1时发送 发送时外管脚是串口,完成后外管脚可以作别用
            ||||接收使能 同上
            |||位5 数据寄存器空中断使能
            ||位6 发送结束中断使能
            |位7 接收结束断使能
*/
}

出0入296汤圆

 楼主| 发表于 2012-12-9 19:41:14 | 显示全部楼层
ifree64 发表于 2012-12-9 19:19
看来我把术语用错了,我用wait表达等待着去运行,也就是就绪(ready)的意思。我图表中用stop只是表示状 ...

不支持状态函数是NULL但控制块不是NULL的情况吧?求详解

出0入296汤圆

 楼主| 发表于 2012-12-9 19:43:57 | 显示全部楼层
zhwm3064 发表于 2012-12-9 19:39
我使用的是1毫秒调度器,你的调试器 如果 没有这种时间间隔,那应该很好,和实时性系统差不多 了。
查看 ...


那这实际上是一个中断驱动的合作式调度器。你能抢占主循环里面的任务,但实际上调度器的任务仍然是合作式的。这正好是我帖子里面介绍过的一个情况,就是那个T9输入法的例子。

出0入0汤圆

发表于 2012-12-9 19:50:14 | 显示全部楼层
Gorgon_Meducer 发表于 2012-12-9 19:41
不支持状态函数是NULL但控制块不是NULL的情况吧?求详解

在我前面的图中,第2步Task2运行,假设函数返回的是NULL,那么Task2的状态机函数就是NULL,但sch调度已经运行完了,所以Task2的任务控制块为非NULL;
在余下的第3、4、5、6、7、8、1等步骤中,都没有再将Task2的任务控制块取出来,所以这段时间Task2的状态机函数是NULL,但任务控制块是非NULL。
直到再一次将Task2的任务控制块取出来,由于状态机函数是NULL,所以sch代码将Task2的任务控制块清空为NULL了。

出0入296汤圆

 楼主| 发表于 2012-12-9 19:58:38 | 显示全部楼层
ifree64 发表于 2012-12-9 19:50
在我前面的图中,第2步Task2运行,假设函数返回的是NULL,那么Task2的状态机函数就是NULL,但sch调度已经 ...

只要返回NULL,控制块就已经是被释放了,ptTask是临时变量,带不到下一轮的。

出0入0汤圆

发表于 2012-12-9 20:07:18 | 显示全部楼层
Gorgon_Meducer 发表于 2012-12-9 19:58
只要返回NULL,控制块就已经是被释放了,ptTask是临时变量,带不到下一轮的。 ...

  1.           for (n = 0; n < UBOUND(s_tTaskPool); n++) {
  2.             if (NULL == s_tTaskPool[n].fnRoutine) {
  3.                 s_tTaskPool[n].fnRoutine = fnRoutine;
  4.                 s_tTaskPool[n].pArg = pArg;
  5.                 s_tTaskPool[n].bLocked = false;
  6.                 bResult = true;
  7.                 break;
  8.             }
复制代码
哦,没注意看你添加新任务的代码直接是用函数指针是否为NULL来判断任务是否结束的。我先前理解成任务控制块整体置空去了。

出0入296汤圆

 楼主| 发表于 2012-12-9 22:02:47 | 显示全部楼层
ifree64 发表于 2012-12-9 20:07
哦,没注意看你添加新任务的代码直接是用函数指针是否为NULL来判断任务是否结束的。我先前理解成任务控制 ...

这是从轻量级角度去考虑的。从效率角度,也许用链表会好一点。

出0入0汤圆

发表于 2012-12-9 22:44:28 | 显示全部楼层
对于调度这块现在还不理解
帮顶一下先
在学习中

出0入0汤圆

发表于 2013-3-16 08:53:15 | 显示全部楼层
本帖最后由 qingaixww 于 2013-3-16 17:23 编辑

傻孩子大哥,这个就是完整的调度器程序吗?本源程序的头文件是不是要自己写一个呢?看了好久这些大师的讨论和源程序,还是不太明白本调试器如何使用,求详解。谢谢!
这里还有一个疑问:ptTask = s_tTaskPool[s_tTaskCounter++];    这个语句中,ptTask是结构体指针,s_tTaskPool[s_tTaskCounter++]是结构体数组的一个元素,这样赋值不会报错吗?应该是将元素的地址赋值给指针吧?
第二个疑问:ptTask->fnRoutine = ptTask->fnRoutine(ptTask);  这个语句中,函数运行后返回的是task_t结构体,而这个ptTask->fnRoutine是函数指针,这样赋值不会报错吗?

出0入296汤圆

 楼主| 发表于 2013-3-16 23:06:29 | 显示全部楼层
qingaixww 发表于 2013-3-16 08:53
傻孩子大哥,这个就是完整的调度器程序吗?本源程序的头文件是不是要自己写一个呢?看了好久这些大师的讨论 ...

你问的问题都问在点子上了。关于这一点,我来解释下:
1、是不是我粗心写错了呢?很认真地给你说,是的,是我写错了。
2、我之前发现这个问题了么?很认真的回答你,写完没多久就有人给我说过了
3、为什么知道了还不改呢?因为只有认真看我帖子的人才会发现。我这不是第一次
     这么说了:我虽然也很喜欢大家跟贴喊牛,但我来不是为了获得虚荣,也不是单
     纯为了什么传道授业(这不是我的职责),我是来交流的。如果没人认真看我代
     码,喊不喊牛,顶不顶对来说,都是心有不甘的。就像一个人穿了一身新衣服,
     问你今天觉得我怎么样,你回答说,气色不错。

目前为止,这只是一个交流思想的伪代码,从我一个已有的调度器修改而来。
ptTask = s_tTaskPool[s_tTaskCounter++];
你说的是对的,正确的应该是:
ptTask = &s_tTaskPool[s_tTaskCounter++];

对于第二个问题,实际上要修改两个地方,首先

  1. //! \brief task prototype
  2. typedef task_t task_routine_t(task_t *ptTask);
复制代码
修改为

  1. //! \brief task prototype
  2. typedef void* task_routine_t(task_t *ptTask);
复制代码
然后主程序部分

  1.     if (NULL != ptTask) {
  2.         ptTask->fnRoutine = ptTask->fnRoutine(ptTask);
  3.         SAFE_ATOM_CODE(
  4.             ptTask->bLocked = false;
  5.         )
  6.     }
复制代码
修改为

  1.     if (NULL != ptTask) {
  2.         ptTask->fnRoutine = (task_routine_t *)ptTask->fnRoutine(ptTask);
  3.         SAFE_ATOM_CODE(
  4.             ptTask->bLocked = false;
  5.         )
  6.     }
复制代码
谢谢你。欢迎交流。

出0入0汤圆

发表于 2013-3-17 11:10:20 | 显示全部楼层
Gorgon_Meducer 发表于 2013-3-16 23:06
你问的问题都问在点子上了。关于这一点,我来解释下:
1、是不是我粗心写错了呢?很认真地给你说,是的, ...

谢谢你认真的解答了我的疑问。这些已经困惑我有几天了,一直在等你的解答。我之前写程序的模式就是超级循环+中断,没有接触过软件框架这一块,最近一直在看你的专栏,虽然有些看得很吃力,但是很感兴趣。自己还没有能力能写出软件框架,所以想借用你现有的调试器,体会这些在软件开发中的奥妙,感受带给我的乐趣。我现在正在学习状态机的程序思想和行为,看到你即将出的新书对这一块会有很深入的讲解,真是兴高,盼着你的新书能快点面市,便也能马上拜读。在这里斗胆一问:可否将此调度器的完整代码发一份与我学习?若有不便也无妨,因为这样做难免有窃取他人劳动成果之嫌!

出0入296汤圆

 楼主| 发表于 2013-3-17 13:58:10 | 显示全部楼层
qingaixww 发表于 2013-3-17 11:10
谢谢你认真的解答了我的疑问。这些已经困惑我有几天了,一直在等你的解答。我之前写程序的模式就是超级循 ...

如果要论学习的话,你可以先看这个帖子
http://www.amobbs.com/thread-5507175-1-1.html
看完以后如果觉得还需要代码,那再说吧。

出0入0汤圆

发表于 2013-3-17 15:06:09 | 显示全部楼层
Gorgon_Meducer 发表于 2013-3-17 13:58
如果要论学习的话,你可以先看这个帖子
http://www.amobbs.com/thread-5507175-1-1.html
看完以后如果觉 ...

好的,我先看看,谢谢!

出0入0汤圆

发表于 2013-4-26 01:00:06 | 显示全部楼层
马了个克的,学习调度系统

出0入0汤圆

发表于 2013-10-29 23:24:42 | 显示全部楼层
学习了,下周移植到m0玩玩

出0入0汤圆

发表于 2013-12-12 20:11:29 | 显示全部楼层
请教下
ptTask->fnRoutine = (task_routine_t *)ptTask->fnRoutine(ptTask);
的意义是什么?完全理解不了24楼的意思,谢谢

出0入296汤圆

 楼主| 发表于 2013-12-12 23:19:10 | 显示全部楼层
rossih 发表于 2013-12-12 20:11
请教下
ptTask->fnRoutine = (task_routine_t *)ptTask->fnRoutine(ptTask);
的意义是什么?完全理解不了2 ...

简单说就是每个被执行的任务函数,或者说状态函数都要指定自己的后续,以此来实现状态转移。

出0入0汤圆

发表于 2013-12-13 08:56:18 | 显示全部楼层
本帖最后由 rossih 于 2013-12-13 09:20 编辑
Gorgon_Meducer 发表于 2013-12-12 23:19
简单说就是每个被执行的任务函数,或者说状态函数都要指定自己的后续,以此来实现状态转移。 ...


噢,就是一个函数是一个状态,那等号右边的函数的参数呢?

出0入296汤圆

 楼主| 发表于 2013-12-13 10:00:05 | 显示全部楼层
rossih 发表于 2013-12-13 08:56
噢,就是一个函数是一个状态,那等号右边的函数的参数呢?


右边是“函数指针”,也就是说它只是一个指针变量,不是执行函数

出0入0汤圆

发表于 2013-12-13 12:05:16 | 显示全部楼层
http://www.amobbs.com/thread-5507175-1-1.html    这个帖子 第一楼 就有我要的答案

出0入296汤圆

 楼主| 发表于 2013-12-13 14:41:10 | 显示全部楼层
rossih 发表于 2013-12-13 12:05
http://www.amobbs.com/thread-5507175-1-1.html    这个帖子 第一楼 就有我要的答案

见鬼了……那个帖子没有一楼……

出0入0汤圆

发表于 2014-3-26 17:08:41 | 显示全部楼层
马克                           

出0入0汤圆

发表于 2014-9-19 14:13:01 | 显示全部楼层
下载学习

出0入0汤圆

发表于 2014-9-19 14:21:14 | 显示全部楼层
MARK                                      

出0入0汤圆

发表于 2014-9-19 14:28:16 | 显示全部楼层
要努力发贴了

出0入0汤圆

发表于 2015-2-10 10:42:26 | 显示全部楼层
本帖最后由 lihaimeng@163 于 2015-2-10 10:45 编辑

傻孩子,问一下下面这段代码
s_tFreeCounter++;
            // 如果“空白”任务个数与所有支持的任务数相等,说明没有任务需要执行了,返回false,结束运行
            if (UBOUND(s_tTaskPool) == s_tFreeCounter) {
                EXIT_SAFE_ATOM_CODE();
                return false;
            }

如果s_tFreeCounter统计的空闲任务 等于 总任务数,这时候触发了idle_task(); 这个时候如果调度器运行一次,恰好当前的指向的任务为NULL,
那么s_tFreeCounter++后,是不是就大于UBOUND(s_tTaskPool) 了,那么调度器返回的就是TRUE了,是这样的吗?需要清零吗?

出0入296汤圆

 楼主| 发表于 2015-2-10 14:03:21 | 显示全部楼层
lihaimeng@163 发表于 2015-2-10 10:42
傻孩子,问一下下面这段代码
s_tFreeCounter++;
            // 如果“空白”任务个数与所有支持的任务数 ...

谢谢你认真读了代码,你发现的问题是存在的,代码应该修改为:

  1.          if (NULL != ptTask->fnRoutine) {
  2.             s_tFreeCounter = 0;
  3.             if (!ptTask->bLocked) {
  4.                 ptTask->bLocked = true;
  5.             } else {
  6.                 ptTask = NULL;
  7.             }
  8.         } else {
  9.             s_tFreeCounter++;
  10.             if (UBOUND(s_tTaskPool) <= s_tFreeCounter) {
  11.                 s_tFreeCounter = UBOUND(s_tTaskPool);
  12.                 EXIT_SAFE_ATOM_CODE();
  13.                 return false;
  14.             }
  15.             ptTask = NULL;
  16.         }
复制代码

再次谢谢你,你是个很仔细思考的人。

出0入0汤圆

发表于 2015-2-10 15:36:24 | 显示全部楼层
有个疑问:
这个task不是传统os的结构吧?如果这样写调度器只能在有中断是才能切换task对么?
我说的传统os的task:
void task(.....){
  while(true)
  {
   .....
  }
}

出0入0汤圆

发表于 2015-2-10 16:44:23 | 显示全部楼层
Gorgon_Meducer 发表于 2012-12-9 18:12
解释正确,只是你没有理解到s_tFreeCounter=0这句话的重要意义:我用的是数组模拟的等效堆,函数指针是否为 ...

s_tFreeCounter的真正用意没有看明白,烦请楼主能在解释一下。

出0入296汤圆

 楼主| 发表于 2015-2-11 10:25:41 | 显示全部楼层
worldsing 发表于 2015-2-10 15:36
有个疑问:
这个task不是传统os的结构吧?如果这样写调度器只能在有中断是才能切换task对么?
我说的传统os ...

是的,不是传统结构,因为这是合作式调度器,task肯定不能有死循环的。

出0入296汤圆

 楼主| 发表于 2015-2-11 10:26:22 | 显示全部楼层
worldsing 发表于 2015-2-10 16:44
s_tFreeCounter的真正用意没有看明白,烦请楼主能在解释一下。

用来确认当前是否没有就绪的任务了。如果当前没有可以运行的任务,就去执行idle任务。

出0入0汤圆

发表于 2015-9-7 18:40:10 | 显示全部楼层
居然还有没被我发现的东西!谢谢老师哈!

出0入0汤圆

发表于 2015-11-11 19:27:23 | 显示全部楼层
mark

出0入0汤圆

发表于 2017-1-13 08:54:02 | 显示全部楼层
求解:
            if (!ptTask->bLocked) {      // 任务没有上锁,则设置锁标志;
                ptTask->bLocked = true;
            } else {
                ptTask = NULL;          // 如果是在main中正运行的任务,其锁标志被置位,被中断抢占(中断中调用scheduler)时,使得不会重复执行main中正在运行的任务
            }

中 ptTask = NULL;          // 如果是在main中正运行的任务,其锁标志被置位,被中断抢占(中断中调用scheduler)时,使得不会重复执行main中正在运行的任务
是不是应该理解为: 如果是在main中正运行的任务,其锁标志被置位,被中断抢占(中断中调用scheduler)时,放弃中断任务??

出0入296汤圆

 楼主| 发表于 2017-2-12 15:41:27 | 显示全部楼层
EE_Duan 发表于 2017-1-13 08:54
求解:
            if (!ptTask->bLocked) {      // 任务没有上锁,则设置锁标志;
                ptTas ...

只有中断可以打断超级循环,所以当中断发现任务已经被锁了以后,就只好放弃了.

出0入0汤圆

发表于 2017-2-16 09:30:02 | 显示全部楼层
Gorgon_Meducer 发表于 2017-2-12 15:41
只有中断可以打断超级循环,所以当中断发现任务已经被锁了以后,就只好放弃了. ...

得大师解惑. 不胜感激.
新疑问如下:
1. 正常运行的程序, 中断任务是不能放弃处理的(如边沿触发).
2. 如果任务不能抢占, 超级循环里的调度和中断的调度功能上有什么区别? 中断任务的优先级在哪里体现?
3. 是否可以不需要原子保护? 超级循环调度时,需要能被中断,不需要保护. 中断中调度时(优先级高), 不支持中断嵌套, 不需要保护
4. 放弃条件应该是超级循环里的函数与中断中调度的函数同名?                       
//对将要执行的任务加锁
                        if (!ptTask->bLocked)
                                {
                                        ptTask->bLocked = true;
                                }
                                //如果是在main中正运行的任务,其锁标志被置位,被中断抢占(中断中调用scheduler)时,使得不会重复执行main中正在运行的任务
                                else
                                {
                                        //清除任务数组中的值. 释放当前任务块资源
                                        ptTask = NULL;
                                }

出0入0汤圆

发表于 2017-2-16 12:03:43 | 显示全部楼层
任务的耦合性是不是太高了.感觉就是多个函数轮流执行.

出0入296汤圆

 楼主| 发表于 2017-2-19 14:41:48 | 显示全部楼层
apple_eat 发表于 2017-2-16 12:03
任务的耦合性是不是太高了.感觉就是多个函数轮流执行.

请解释下你这里所说的“任务之间的耦合”是什么意思?
回帖提示: 反政府言论将被立即封锁ID 在按“提交”前,请自问一下:我这样表达会给举报吗,会给自己惹麻烦吗? 另外:尽量不要使用Mark、顶等没有意义的回复。不得大量使用大字体和彩色字。【本论坛不允许直接上传手机拍摄图片,浪费大家下载带宽和论坛服务器空间,请压缩后(图片小于1兆)才上传。压缩方法可以在微信里面发给自己(不要勾选“原图),然后下载,就能得到压缩后的图片】。另外,手机版只能上传图片,要上传附件需要切换到电脑版(不需要使用电脑,手机上切换到电脑版就行,页面底部)。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

手机版|Archiver|amobbs.com 阿莫电子技术论坛 ( 粤ICP备2022115958号, 版权所有:东莞阿莫电子贸易商行 创办于2004年 (公安交互式论坛备案:44190002001997 ) )

GMT+8, 2024-7-14 09:31

© Since 2004 www.amobbs.com, 原www.ourdev.cn, 原www.ouravr.com

快速回复 返回顶部 返回列表