[Kinetis] 【争做大明星】+ 赢取Freedom,我发个近呼裸奔的操作系统

[复制链接]
 楼主| holts 发表于 2013-12-31 13:30 | 显示全部楼层 |阅读模式
本帖最后由 holts 于 2014-1-1 13:13 编辑

pt-1.4.zip (19.48 KB, 下载次数: 3)

资源占用少,近呼裸奔的 protothread 系统, 介绍给大家


protothread 的核心是:

1)每个任务的本质都是一个基于状态机的函数,多任务并行实际上是不断执行各个基于状态机的函数


2)对于任务函数:自动实现状态机,无需编程者主动去设计状态机的各个状态,巧妙的应用了c 编译器的特点,行号 __LINE__ 就用作状态,每个代码行都自带了一个隐含状态,用行号作为状态,就无需人为再设计状态了 。

因此任务函数本质上是状态机,但是又被精妙的自动实现了,无需编程人员以状态机的方式思考,极大的提高了程序代码的自然度。

在许多系统资源非常紧张的单片机应用中,使用实时操作系统进行任务调度来实现实时多任务系统时,由操作系统带来的系统开销往往是不可接受的。通过升级硬件来改善系统资源紧张,意味着成本的增加,降低产品的竞争力。

Protothread的特点
  Protothread是专为资源有限的系统设计的一种耗费资源特别少并且不使用堆栈的线程模型,其特点是:
  ◆ 以纯C语言实现,无硬件依赖性;
  ◆ 极少的资源需求,每个Protothread仅需要2个额外的字节;
  ◆ 可以用于有操作系统或无操作系统的场合;
  ◆ 支持阻塞操作且没有栈的切换。
  使用Protothread实现多任务的最主要的好处在于它的轻量级。每个Protothread不需要拥有自已的堆栈,所有的Protothread 共享同一个堆栈空间,这一点对于RAM资源有限的系统尤为有利。相对于操作系统下的多任务而言,每个任务都有自已的堆栈空间,这将消耗大量的RAM资源,而每个Protothread仅使用一个整型值保存当前状态。


  1. /**
  2. * This is a very small example that shows how to use
  3. * protothreads. The program consists of two protothreads that wait
  4. * for each other to toggle a variable.
  5. */

  6. /* We must always include pt.h in our protothreads code. */
  7. #include "pt.h"

  8. #include <stdio.h> /* For printf(). */

  9. /* Two flags that the two protothread functions use. */
  10. static int protothread1_flag, protothread2_flag;

  11. /**
  12. * The first protothread function. A protothread function must always
  13. * return an integer, but must never explicitly return - returning is
  14. * performed inside the protothread statements.
  15. *
  16. * The protothread function is driven by the main loop further down in
  17. * the code.
  18. */
  19. static int
  20. protothread1(struct pt *pt)
  21. {
  22.   /* A protothread function must begin with PT_BEGIN() which takes a
  23.      pointer to a struct pt. */
  24.   PT_BEGIN(pt);

  25.   /* We loop forever here. */
  26.   while(1) {
  27.     /* Wait until the other protothread has set its flag. */
  28.     PT_WAIT_UNTIL(pt, protothread2_flag != 0);
  29.     printf("Protothread 1 running\n");

  30.     /* We then reset the other protothread's flag, and set our own
  31.        flag so that the other protothread can run. */
  32.     protothread2_flag = 0;
  33.     protothread1_flag = 1;

  34.     /* And we loop. */
  35.   }

  36.   /* All protothread functions must end with PT_END() which takes a
  37.      pointer to a struct pt. */
  38.   PT_END(pt);
  39. }

  40. /**
  41. * The second protothread function. This is almost the same as the
  42. * first one.
  43. */
  44. static int
  45. protothread2(struct pt *pt)
  46. {
  47.   PT_BEGIN(pt);

  48.   while(1) {
  49.     /* Let the other protothread run. */
  50.     protothread2_flag = 1;

  51.     /* Wait until the other protothread has set its flag. */
  52.     PT_WAIT_UNTIL(pt, protothread1_flag != 0);
  53.     printf("Protothread 2 running\n");
  54.    
  55.     /* We then reset the other protothread's flag. */
  56.     protothread1_flag = 0;

  57.     /* And we loop. */
  58.   }
  59.   PT_END(pt);
  60. }

  61. /**
  62. * Finally, we have the main loop. Here is where the protothreads are
  63. * initialized and scheduled. First, however, we define the
  64. * protothread state variables pt1 and pt2, which hold the state of
  65. * the two protothreads.
  66. */
  67. static struct pt pt1, pt2;
  68. int
  69. main(void)
  70. {
  71.   /* Initialize the protothread state variables with PT_INIT(). */
  72.   PT_INIT(&pt1);
  73.   PT_INIT(&pt2);
  74.   
  75.   /*
  76.    * Then we schedule the two protothreads by repeatedly calling their
  77.    * protothread functions and passing a pointer to the protothread
  78.    * state variables as arguments.
  79.    */
  80.   while(1) {
  81.     protothread1(&pt1);
  82.     protothread2(&pt2);
  83.   }
  84. }


FSL_TICS_A 发表于 2013-12-31 16:44 | 显示全部楼层
虽然不是很了解,但还是觉得挺学习价值,麻烦楼主多多解释工作原理!!
Imakey 发表于 2013-12-31 16:45 | 显示全部楼层
有空研究研究
 楼主| holts 发表于 2013-12-31 20:56 | 显示全部楼层
FSL_TICS_A 发表于 2013-12-31 16:44
虽然不是很了解,但还是觉得挺学习价值,麻烦楼主多多解释工作原理!!

这个轻量级操作系统作者是 Adam Dunkels,网上很多介绍, 初次看到就被其独特的思路吸引, 之后在我的一个小项目中应用,感觉效果不错。
hu_uuu 发表于 2013-12-31 22:57 | 显示全部楼层
不明觉历
 楼主| holts 发表于 2014-1-1 13:22 | 显示全部楼层
本帖最后由 holts 于 2014-1-1 13:31 编辑

   
◆ protothread是专为资源有限的系统设计的一种耗费资源特别少并且不使用堆栈的线程模型,相比于嵌入式操作系统,其有如下优点:

      1. 以纯C语言实现,无硬件依靠性; 因此不存在移植的困难。

      2. 极少的资源需求,每个Protothread仅需要2个额外的字节;

      3. 支持阻塞操纵且没有栈的切换。

      ◆它的缺陷在于:

      1. 函数中不具备可重入型,不能使用局部变量;

      2. 按顺序判断各任务条件是否满足,因此无优先级抢占;

      3. 任务中的各条件也是按顺序判断的,因此要求任务中的条件必须是依次出现的。

      ◆ protothread的阻塞机制: 在每个条件判断前,先将当前地址保存到某个变量中,再判断条件是否成立,若条件成立,则往下

          运行;若条件不成立,则返回。



protothread基本源码及注释:

  1.         
  2. #ifndef PC_H
  3. #define PC_H

  4. typedef unsigned int INT16U;

  5. struct pt
  6. {
  7.   INT16U lc;  
  8. };

  9. #define PT_THREAD_WAITING   0
  10. #define PT_THREAD_EXITED    1

  11. //初始化任务变量,只在初始化函数中执行一次就行
  12. #define PT_INIT(pt)     (pt)->lc = 0

  13. //启动任务处理,放在函数开始处
  14. #define PT_BEGIN(pt)    switch((pt)->lc) { case 0:

  15. // 等待某个条件成立,若条件不成立则直接退出本函数,下一次进入本函数就直接跳到这个地方判断  
  16. // __LINE__ 编译器内置宏,代表当前行号,比如:若当前行号为8,则 s = __LINE__; case __LINE__: 展开为 s = 8; case 8:
  17. #define PT_WAIT_UNTIL(pt,condition)   (pt)->lc = __LINE__;   case __LINE__: /
  18.                                       if(!(condition))  return            

  19. // 结束任务,放在函数的最后
  20. #define PT_END(pt)      }

  21. // 等待某个条件不成立      
  22. #define PT_WAIT_WHILE(pt,cond)    PT_WAIT_UNTIL((pt),!(cond))

  23. // 等待某个子任务执行完成
  24. #define PT_WAIT_THREAD(pt,thread)   PT_WAIT_UNTIL((pt),(thread))  

  25. // 新建一个子任务,并等待其执行完退出
  26. #define PT_SPAWN(pt,thread) /
  27.   PT_INIT((pt));            /
  28.   PT_WAIT_THREAD((pt),(thread))
  29.   
  30. // 重新启动某任务执行
  31. #define PT_RESTART(pt)  PT_INIT(pt); return

  32. // 任务后面的部分不执行,直接退出
  33. #define PT_EXIT(pt)     (pt)->lc = PT_THREAD_EXITED;return

  34. #endif


     应用实例:

  1.       
  2. static struct pt pt1,pt2;

  3. static int protothread1_flag,protothread2_flag;
  4. // ========================================
  5. // 线程1
  6. // ========================================
  7. static void protothread1(struct pt *pt)
  8. {
  9.   PT_BEGIN(pt); // 开始时调用
  10.   while(1)
  11.   {
  12.     // 应用代码
  13.     protothread1_flag = 1;
  14.     PT_WAIT_UNTIL(pt,protothread2_flag != 0); // 等待protothread2_flag 标志置位
  15.     protothread2_flag = 0;

  16.   }  
  17.   PT_END(pt);  // 结束时调用
  18. }
  19. // ========================================
  20. // 线程2
  21. // ========================================
  22. static void protothread2(struct pt *pt)
  23. {
  24.   PT_BEGIN(pt);
  25.   while(1)
  26.   {
  27.     // 应用代码
  28.     protothread2_flag = 1;
  29.     PT_WAIT_UNTIL(pt,protothread1_flag != 0); // 等待protothread1_flag 标志置位

  30.     protothread1_flag = 0;
  31.   }
  32.   PT_END(pt);
  33. }
  34. // ========================================
  35. // 主函数
  36. // ========================================
  37. void main(void)
  38. {
  39.   PT_INIT(&pt1);  // 初始化
  40.   PT_INIT(&pt2);
  41.   
  42.   while(1)
  43.   {
  44.     protothread1(&pt1);
  45.     protothread2(&pt2);
  46.   }
  47. }




        线程1,2的展开式:


  1. 01.// ========================================   
  2. 02.// 线程1   
  3. 03.// ========================================   
  4. 04.static void protothread1(struct pt *pt)  
  5. 05.{  
  6. 06.  // PT_BEGIN(pt);展开   
  7. 07.  switch(pt->lc)  
  8. 08.  {  
  9. 09.    case 0:  
  10. 10.    ;     
  11. 11.      
  12. 12.    while(1)  
  13. 13.    {  
  14. 14.      protothread1_flag = 1;  
  15. 15.        
  16. 16.      // PT_WAIT_UNTIL(pt,protothread2_flag != 0);展开   
  17. 17.      // 条件判断部分,条件不成立,则调度   
  18. 18.      pt->lc = 26;   // 假定当前为26行   
  19. 19.      case 26:  
  20. 20.      if(protothread2_flag == 0)  
  21. 21.        return;      // 若protothread2_flag未发生,返回   
  22. 22.  
  23. 23.      protothread2_flag = 0;  
  24. 24.    }  
  25. 25.   
  26. 26.  // PT_END(pt); 对应switch   
  27. 27.  }  
  28. 28.}  
  29. 29.  
  30. 30.// ========================================   
  31. 31.// 线程2   
  32. 32.// ========================================   
  33. 33.static void protothread2(struct pt *pt)  
  34. 34.{  
  35. 35.  switch(pt->lc)  
  36. 36.  {  
  37. 37.    case 0:  
  38. 38.    ;            
  39. 39.    while(1)  
  40. 40.    {  
  41. 41.      protothread2_flag = 1;  
  42. 42.      pt->lc = 44;  
  43. 43.      case 44:  
  44. 44.      if(protothread1_flag == 0)  
  45. 45.        return;  
  46. 46.        
  47. 47.      myFunc2();  
  48. 48.      protothread1_flag = 0;  
  49. 49.    }  
  50. 50.  }  
  51. 51.  
  52. 52.}  



motodefy 发表于 2014-1-1 14:44 | 显示全部楼层
这个给力
 楼主| holts 发表于 2014-1-1 15:26 | 显示全部楼层
本帖最后由 holts 于 2014-1-1 15:39 编辑

        图解Protothreads  以下引用 原文在朱工的博客   http://blog.chinaaet.com/fy_zhu


Protothreads语句不多,但宏套宏,初读起来有点令人费解。我就借文[1]中的两张图来图解Protothreads。

2.jpg

对应Protothreads,在图中[ *]的地方,使用PT_WAIT_UNTIL(pt, condition) 替换, 得到如下图型:
3.jpg


        看看框图右边的

                                       
                                                n -> Rx                                       
                                
                                       
                                                -> EXIT                                       
                                
        是否就是 Protothreads的“ LC_SET(Rx) ; return ; ”?
        框图左侧各TASK入口的地方,是否就是Protothread的“ LC_RESUME(Rx)switch-case ”?

          不用多少文字,Protothreads的机制借图就已一目了然。


          图2、图3和Protothreads实质上是表示了用同一种方法解决问题的不同手段。Protothreads只是这种方法的C语言实现。提出过这种方法的,还可以找到[2]。不过,我们国人提出的这种方法,都还没有提升到系统的高度。


        实现协程多任务的无标号单步跳转方法

       
          Protothreads的switch方法编译后的目标代码是通过多步比较才找到case的入口。而Protothreads的addrlabels方法的高效就在于它是用“goto *lc”一步跳转到后续点LC(Local Continuation)。但是,主流的C编译器不支持这样的goto。


          正如当初达夫在引入后来被叫做“Duff’s Device”的Switch-Case机制时,被人称作诡异(trick)方法。正是Simon Tatham借用这种诡计才把Coroutine引入C编程,而Adam Dunkels受到Simon的启迪而创建了Protothreads。既然主流的C编译器也接受诡异的方法,而民众化的GNU GCC做了“Labels as Values”扩展,那么我们何不采取一些办法让主流的C编译器(包括8051的C编译器)也接受Protothreads的addrlabels方法,让协程重入时一步跳转到后续点LC。


          后面,就是我要表述的用C和汇编混合编程实现的“无标号单步跳转方法”。


          GOTO不用标号,似乎讲不通。其实一点就明。C编译器为了提高代码执行效率,允许与汇编语言混合编程,互相留有接口。这就是契机。


          任何CPU都有调用子程序的指令——不妨叫CALL吧。CALL指令为了执行子程序后返回,都要CPU先记住后面的指令地址,才跳到调用的子程序。所以,我们只要在后续点LC之前,用一条混合编程CALL指令,LC就已经在握了,而不必在LC写上标号,再去取标号。所以,不管编译器允许不允许取标号地址,CPU已经取得LC的地址,我们只有把它存到变量lc中去就行了。


          原理已定。下面就是具体的实施。因为不同的CPU有不同的CALL指令,而且不同的C编译器有不同的汇编语言书写格式,所以文档可能因CPU和编译器有所不同,但大同小异。


          程序代码部分见附件: 用C和汇编混合编程实现协程多任务方法(ARM).pdf


          首先说明一下:
          在这里,我们不再使用Protothread及缩写PT,而使用协程Coroutine这个词及缩写CR。因为Protothread有其特定的内涵。这里的扩展,已超出其内涵;拓展未必受欢迎。所以使用“协程”比较不会有争议。


          并且,我们不使用抢占式多任务常用的阻塞(block)一词,而使用YIELD(让出)一词,因为它更能体现协程多任务方法的协作思想。


          对于本方法,我们还有如下几点要说。


        一.关键点
          因程序文档中不见标号,初读起来有点难度,所以先把关键点拿出来说明一下。


        ARM Cortex-M 的Thumb指令:
        C语句:
        EXTERN void SUBROUTINE(void) ;
       
        SUBROUTINE() ;
        //<- LC is here (后续点LC就在这里)
        DoSomething …
        汇编语句:
        _ SUBROUTINE:
        ; lr 中就是LC的地址,把它保存到lc中就行了。
        ;(Thumb指令是LC的地址+1,返回跳转时BIT0要置1。所以不减不增,就保存地址+1)
       
        [有关8051指令部分,我们另外给出]。


        二.way_out()的作用
          我们把CR_START宏展开如下:
        retcode_temp=CR_LEAVESETTING;
        way_out();
        //quit_addr: <- way_out()的作用就是取得这个地址。
        if (retcode_temp != CR_LEAVESETTING) { return retcode_temp; }
        if (lc != 0) { resume_lc(lc); }




          way_out()的作用就是为了取得quit_addr这个出口地址,YIELD让出时,跳到这里,统一出口路径。
          这里,我们顺便讲一下:多入多出与单入单出。
          从协程本身的定义来讲,有多个出入口。在用汇编语言实现协程多任务方法时,编程者自己可以决定是否可以从协程中间某处跳出。而C语言的函数,从规范性来讲,是单入单出的。所以,用C来实现协程多任务方法时,必须将多入多出纳入单入单出的规范。因为调用C函数时,编译器经常会在入口处对寄存器和栈指针做一些处理,而在出口处恢复原状。
          CR_START宏就是将多入多出汇集为单入单出。单入,与Protothreads没有什么区别。单出,就是为汇编函数提供与C语言接口的统一出口。本来统一出口与CR_END宏放在一起比较直观。但多任务程序中,每一任务往往是一个while(1)或for( ; ; )的无限循环。循环后的CR_END宏会被聪明的编译器优化掉,而仅仅只起一个语法作用。因此只好把它放在入口的必经路上。就是说,还没进去,就留好了退路。在START中安置退路,不怕被人耻笑?哈哈,人人会用WINDOWS,大家为了关机,不是也要先点击START吗。听说当年WINDOWS刚出时,通用汽车公司的老总就笑话盖茨:哪有汽车停车要先打火的道理!


        三.为什么不用C语言内嵌汇编
          原则上,本方法也可以用C语言内嵌汇编的办法来实现。但是,有几个因素是要考虑的。首先,KEIL编译器不支持ARM Thumb指令内嵌汇编;而本方法主要是针对Cortex-M0、-M3等使用Thumb指令的小型MCU。另外,现在的编译器越来越聪明,有时在优化时要省掉CALL指令,直接跳到子程序。而用CALL指令取得后一指令地址,恰恰是“无标号”方法的核心。这样的优化,将使方法失灵。所以,我们趁着目前编译器的“手还不够长”,无法伸到与C语言文件独立的另一个汇编语言文件中去做优化,而保持用CALL指令去调用汇编子程序,这就能达到我们的目的。


        四.可移植性
          本方法将会受到一下指责:可移植性会受到影响。我认为,工程师的职责是在产品的设计运用中找到解决问题的高效的方法。如果有人喜欢低效的完美,那就自当别论。当然,我也喜欢两全其美的完美。
          

FSL_TICS_A 发表于 2014-1-2 09:16 | 显示全部楼层
学习啊,谢谢楼主!!
 楼主| holts 发表于 2014-1-2 09:55 | 显示全部楼层
FSL_TICS_A 发表于 2014-1-2 09:16
学习啊,谢谢楼主!!

其实我更想看到的是有板子的兄弟,写几个实例贴上来
FSL_TICS_A 发表于 2014-1-2 10:11 | 显示全部楼层
holts 发表于 2014-1-2 09:55
其实我更想看到的是有板子的兄弟,写几个实例贴上来

楼主如果没板子的话,那就请参加第二波“争当大明星”活动啊,只要你上传资料就可以参与啊。
 楼主| holts 发表于 2014-1-2 10:25 | 显示全部楼层
FSL_TICS_A 发表于 2014-1-2 10:11
楼主如果没板子的话,那就请参加第二波“争当大明星”活动啊,只要你上传资料就可以参与啊。 ...

我被你搞晕了, 我这个贴子已经上传了资料,算不算已经参加了第二波“争当大明星”活动 ?  还需要做什么操作才能算参加 ?
FSL_TICS_A 发表于 2014-1-2 11:02 | 显示全部楼层
holts 发表于 2014-1-2 10:25
我被你搞晕了, 我这个贴子已经上传了资料,算不算已经参加了第二波“争当大明星”活动 ?  还需要做什么 ...

哦,忘了看你的标题啊,刚过新年没缓过来,请楼主谅解!!
 楼主| holts 发表于 2014-1-2 11:17 | 显示全部楼层
FSL_TICS_A 发表于 2014-1-2 11:02
哦,忘了看你的标题啊,刚过新年没缓过来,请楼主谅解!!

如此,心里踏实了:lol
 楼主| holts 发表于 2014-1-4 11:40 | 显示全部楼层
FSL_TICS_A 发表于 2014-1-2 11:33
那我就期待楼主分享更多资料,并预祝你会出现在2014年的第一次获奖名单中吧!! ...

谢谢,我只不过是搜集在一起,方便大家
您需要登录后才可以回帖 登录 | 注册

本版积分规则

个人签名:QQ854887889  出几块TFT屏(2.2寸,有资料,送转接板),现在15RMB到付

45

主题

769

帖子

4

粉丝
快速回复 在线客服 返回列表 返回顶部