底软
模型
今天穿插一篇底软文章,“【硬件篇】VCU软件接口原理”尚未更新完,预计会在10篇以上,大家可以继续保持关注。本文介绍一种事件驱动模型“protothreads”,由瑞典SICS的Adam Dunkels开发,也是Contiki OS中的源代码,此模型适合应用于资源受限的嵌入式系统中,当然我也曾用于某项目的开发中,受益匪浅,独乐乐不如众乐乐,今天分享给大家,所讲细节不一定面面俱到,不足之处还请指教。
protothreads简介
任务栈:
protothreads是一种轻量级的线程模型,基于此模型可以实现类似于windows 中线程的编程风格,编程思路也倾向去线程。在OS中,每个线程都有独立的任务栈,然而protothreads的每个线程共享同一个任务栈,从而减少 RAM的 占用。
上下文切换:
在protothreads编程模型中,任务的本质是函数,函数与函数之间是协同工作关系,因而也叫“协程(coroutine)”;上下文的切换也有所不同,在OS中,上下文切换由OS来管理,但在 protothreads模型中,则是通过 yield 方式来保存现场和转移运行权。
任务阻塞:
protothreads 虽然提供了在各自线程内的条件阻塞机制,但对于在该线程内调用的其它函数,则无法阻塞其运行。所以,如果要在线程内调用占用时间较多的函数,为保证各个线程的实时性要求,需要将这类函数进一步划分为更小的函数,分步执行。protothreads 的阻塞实质就是函数返回,且仅能在程序员指定的位置阻塞。
代码分析
protothreads 包含5个头文件,是的,你没有看错,是头文件,意思就是没有任何C文件,整个模型是通过宏实现的,下表简单描述了这5个头文件的主要内容。
lc-addrlabels.h | 使用GCC扩展语法实现的协程基础 |
---|---|
lc-switch.h | 使用switch语句实现的协程基础 |
lc.h | 用于选择GCC语法还是switch语句实现 |
pt.h | 基于lc.h实现协程API |
pt-sem.h | 协程间通信(信号量)的实现 |
主要API被包含在pt.h中,下面介绍一些常用的API:
代码:#define PT_INIT(pt) LC_INIT((pt)->lc)
解释:初始化一个协程,其实就是初始化状态变量(pt)->lc,类似于MBD模型开发中的各个StateFlow状态;
代码:
#define PT_BEGIN(pt) \
{ char PT_YIELD_FLAG = 1; LC_RESUME((pt)->lc)
解释:协程的入口,PT_YIELD_FLAG =1,表示不出让运行权,PT_YIELD_FLAG =0表示出让运行权,LC_RESUME就是跳转到上一次出让运行权的位置继续运行,本质就是switch 跳转到相应的case。
代码:
#define PT_END(pt) \
LC_END((pt)->lc); PT_YIELD_FLAG = 0; \
PT_INIT(pt); return PT_ENDED; }
解释:协程的退出口,到此就算一个协程的结束,内容就是清除标志和上下文状态。
代码:
#define PT_WAIT_UNTIL(pt, condition) \
do { \
LC_SET((pt)->lc); \
if(!(condition)) { \
return PT_WAITING; \
} \
} while(0)
解释:协程条件阻塞,阻塞的本质是出让CPU运行权,首先记录下当前的状态(LC_SET((pt)->lc)),以便下一次恢复,保存现场后再判断条件,如果condition为真,则继续往下运行,如果condition为假,直接return返回函数状态PT_WAITING,出让运行权,保持阻塞状态。所以根据字面上的意思可以这样理解:直到condition条件成立才往下执行,否则继续等待。
代码:
#define PT_WAIT_WHILE(pt,condition) \
PT_WAIT_UNTIL((pt), !(condition))
解释:类似PT_WAIT_UNTIL,只是condition的条件取反而已。根据字面上的意思可以这样理解:当condition条件成立时继续等待,否则往下执行。
代码:
#define PT_EXIT(pt) \
do { \
PT_INIT(pt); \
return PT_EXITED; \
} while(0)
解释:任务后面的代码不执行,初始化状态变量,然后直接退出重新执行。
代码:
#define PT_YIELD(pt) \
do { \
PT_YIELD_FLAG = 0; \
LC_SET((pt)->lc); \
if(PT_YIELD_FLAG == 0) { \
return PT_YIELDED; \
} \
} while(0)
解释:协程运行权无条件出让1次,出让前,先记录下当前的状态(LC_SET((pt)->lc)),以便下一次恢复,保存现场后,立即出让运行权,本质就是函数return。
代码:
#define PT_YIELD_UNTIL(pt,condition) \
do { \
PT_YIELD_FLAG = 0; \
LC_SET((pt)->lc); \
if((PT_YIELD_FLAG == 0) || !(condition)) { \
return PT_YIELDED; \
} \
} while(0)
解释:比PT_YIELD(pt)多了一个条件, 协程运行权首先无条件出让1次,这是通过PT_YIELD_FLAG来实现的,第2次运行时,再判断条件condition,如果条件为真,往下执行,如果条件为假,则立即出让运行权继续阻塞。
实例
上面介绍了常用API,下面我们看一个实例:
上面的代码就不多解释了,注释已经写得够详细了吧,唯一需要解释的是这个宏“PT_WAIT_MS”,这是我通过PT_WAIT_UNITLL扩展出来的API,并结合系统Tick实现了延时功能,如果大家使用得比较熟悉之后,也可以利用基础的API去扩展自己的功能API。
大家不难发现,相对于使用状态机去实现以上同样的功能,使用protothreads能够节省很多行代码,而且易阅读,编程思路和效率也得到提高。