新闻  |   论坛  |   博客  |   在线研讨会
Protothreads 简介
bpshui | 2013-03-23 20:18:32    阅读:6630   发布文章

Protothreads极轻量级系统用于资源紧张的单片机。
Ptotothreads的作者是Adam Dunkels <adam@sics.se>,很感谢作者给我们提供这样一个好的系统,我认为合适的才是最好的。

作者描述Protothreads的特性是:
1 没有专用的机器代码,纯C实现;
2 不使用容易犯错的跳转指令;
3 极小的内存占用;
4 当不当做操作系统来用都行;
5 所提供的阻断等待不需要堆栈或者full multi-threading。
作者提供了几个例子,以帮助我们理解。

先看一下作者提供的5个头文件,这5个头文件就是Protothreads的全部文件。
lc-addrlabels.h :以字符串方式实现Protothreads系统,占用的ram可能会多些。
lc-switch.h :以标准C的switch结构实现Protothreads系统(默认)
lc.h :选择lc-addrlabels.h或lc-switch.h两种实现Protothreads系统
pt.h :在实际应用中一般只包含此文件就行了
pt-sem.h :附加的信号量操作的支持,不需要的话则不必包含他

虽然我不熟悉其他的操作系统,但是,单看一眼其他系统的源代码,就能看到有不少压栈和出栈的指令。我认为,对于小小单片机来说,不太合适,压栈和出栈的指令明显占用了时间,更占用了大量的本来不宽裕的ram。并且,似乎用于ram紧张的单片机,还会留有隐患。

对于我来说,使用“系统”只是为了让我的程序思路更清晰,不然一堆变量,信号量和信号量之间互相制约,有没有层次感,时间一长,思路断了,只能重新编写。

Protothreads提供给我们另外一种思路:我们的程序经过了Protothreads的包装,而实际上Protothreads的“线程”就是普通的函数。

所有的“线程”都是(也允许不是)一个死循环,不多占用堆栈,不能被抢占,所以中途必须“退出”,不然怎么会有其他“线程”被调度的机会?退出有两种结果:下一次被调度后从头运行或从退出的位置继续向后运行。Protothreads使用C语言中的“switch”结构或goto语句来实现。既然能够从上一次退出的位置继续向后运行,那么就需要一个变量来记录这个“位置”信息,这个变量就是线程唯一占用的ram变量。作者提供的数据类型是短整型或字符串类型。我认为这就是Protothreads的精髓,也就是说Protothreads
线程不是被其他程序“中断”的,而是自己主动退出(有点雷锋精神),但退出之前记录当前位置,以便下次从此位置继续运行(真是矛盾的统一体)。

Protothreads为我们提供方便的同时也有一些需要我们改变编程习惯的地方,比如,我们不能在正常线程里面放一个类似“while(1);”的程序,除非我们确实需要这样做--期待看门狗复位。但是,Protothreads提供给我们一个理解Protothreads的机会,源代码完全开放,实现“多任务”的方式方式又是如此的简单明了。我们完全可以根据自己的需要修改库文件。并且以我们对Protothreads的理解,作出我们“心中有数”的程序。

上面描述过作者提供的5个头文件,而我只想把Protothreads用在ram和速度都紧张的单片机(如avr的m16)上,所以,我去掉了两个头文件,只留下了三个:
lc.h :以标准C的switch结构实现Protothreads系统
pt.h :Protothreads的接口,必须包含此头文件
pt-sem.h :附加的信号量操作的支持,不需要的话则不必包含他

在pt.h中包含所有实现Protothreads的宏和变量结构的定义:可以阅读一下作者提供的帮助文档,已经非常详细了,结合作者提供的例子,很快就能理解了,几乎都是字面意思,我保证在你真正了解C语言的前提下,不用半天就能够理解Ptotothreads。我只说一下我对PT_YIELD(pt)的理解:放弃当前执行后续代码的机会,退出,但是下一次调度到本线程,将从此处继续运行。有了这个宏就给了其他线程被调度的机会,如果一个线程中包含PT_WAIT_UNTIL(pt)或PT_WAIT_WHERE(pt)之类的宏,则没有必要再调用PT_YIELD(pt)了。另外,作者为了实现PT_YIELD(pt),而定义了一个局部变量PT_YIELD_FLAG,我认为没有必要,所以我对pt.h
相关地方做了修改,已经把局部变量PT_YIELD_FLAG去掉了。

作者提供的库没有提供线程的自挂起和恢复挂起功能,这个动作可以使用作者提供的PT_YIELD_UNTIL(pt,cond)宏,来实现,或利用一下信号量的操作,使用pt-sen.h头文件所包含的宏来实现。但以我对Protothreads的理解,把pt.h做了一点修改,提供了与PT_YIELD(pt)相似的方法,实现了线程的“自己”挂起和“他人”恢复。既然看懂了系统的源程序,那么我们的思路还可以再扩展一下,函数当中什么地方只运行一次(PT_BEGIN(pt)后面),什么地方每次调度都能运行(PT_BEGIN(pt)前面),为什么作者说局部变量不能乱用等等。我们都能从系统的源程序里面找到答案。

AVR GCC对C支持的很好。Protothreads可以很好的用在avrgcc上。实际效果也很好,程序看起来更清晰了,多个线程配合的也很好。我对操作系统了解不多,对其他的操作系统也没有深入接触过,有些名词会有错误。但我认为,Ptotothreads极容易掌握,对于像avr的m16之类的单片机,Protothreads是很好的选择。

这篇文章是基于作者的Protothreads-1.4版写的,作者还在不断的更新中,作者网站是:http://www.sics.se/~adam/pt/

原文地址:http://blog.csdn.net/t_larry/archive/2008/10/18/30

原来在用RTOS时感觉特别方便的就是任务函数中可以调用系统提供的延时,等待等函数,切换到其它任务上执行,这样写程序又简单而且又能充分利用硬件资源,但这样做在切换任务时浪费了很多的系统时间及占用很多RAM,而且对于8位或16位小内存的 单片机也不太适合。最近几天在看开源网络协议UIP1.0,居然在DHCP应用实例中看到了一种以很简单的方法实现类似多任务功能的编程方法,只需占用只个字节的RAM资源,而且非常的容易使用。
    要实现这种这种编程方法,只需下载到UIP1.0的源代码,找到其中的三个文件分别是pt.h,lc.h,lc-switch.h,在自己项目中把pt.h包含进去就行。
   在DHCP中有完整的应用函数static PT_THREAD(handle_dhcp(void)),这个函数年看起来有点怪,但其实将其宏展开就是static char handle_dhcp(void),返回char类型静态函数,其实就是向外部返回任务执行状态。pt.h中提供很多功能的宏定义:
PT_INIT(pt)   初始化任务变量,只在初始化函数中执行一次就行
PT_BEGIN(pt)   启动任务处理,放在函数开始处
PT_END(pt)   结束任务,放在函数的最后
PT_WAIT_UNTIL(pt, condition) 等待某个条件(条件可以为时钟或其它变量,IO等)成立,否则直接退出本函数,下一次进入本     函数就直接跳到这个地方判断
PT_WAIT_WHILE(pt, cond)  和上面一个一样,只是条件取反了
PT_WAIT_THREAD(pt, thread) 等待一个子任务执行完成
PT_SPAWN(pt, child, thread) 新建一个子任务,并等待其执行完退出
PT_RESTART(pt)   重新启动某个任务执行
PT_EXIT(pt)   任务后面的部分不执行,直接退出重新执行
PT_YIELD(pt)   锁死任务
PT_YIELD_UNTIL(pt, cond) 锁死任务并在等待条件成立,恢复执行
在pt中一共定义四种线程状态,在任务函数退出到上一级函数时返回其状态
PT_WAITING  等待
PT_EXITED  退出
PT_ENDED  结束
PT_YIELDED  锁死
 

我会继续关注Protothreads并在项目中使用它.

*博客内容为网友个人发布,仅代表博主个人观点,如有侵权请联系工作人员删除。

参与讨论
登录后参与讨论
电子设计应用群(181263624)
推荐文章
最近访客