今天看了一下PT1.4的源代码,其实没几句话,用宏架构了子程序运行的框架,给子程序编号记录位置,用switch回到上次的程序位置。
就这样简单几句话却勾起了很多研究,网上见到这样一则帖子http://www.ourdev.cn/bbs/bbs_content.jsp?bbs_sn=4077658&bbs_page_no=11&bbs_id=1000
↓↓ 轻量级多线程protothread及类C++编程方法的代码(VC测试下测试通过,附源码)。 | 回复数:5,点击数:300 |
【楼主位】 smset 积分:27 派别: 等级:------ 来自: | 轻量级多线程protothread及类C++编程方法的代码 , 用纯C达到类似多线程和C++编程的效果。 这是我学习单片机编程中的一点测试,希望和大家交流一下。 1)把protothread移植到vc里面,方便调试。 2)自己实现了一个用纯C语言达到类似C++编程语法的原型。具有C++编程的风格,但又有良好的可移植性。 本实验在VC测试下测试通过,附源码。 //vc source code-------------- // 1.cpp : Defines the entry point for the console application. // //#include <stdio.h> //#include <stdlib.h> #include <iostream> #include "stdafx.h" #include "pt_type.h" #include "pt_config.h" #include "pt.h" #include "pt_ex.h" #include "pt_timer.h" #include "pt-sem.h" #include "lc.h" #include <windows.h> extern void SIGNAL(); PT_TMR_Create(tmr1); PT_TMR_Create(tmr2); PT_TMR_Create(tmr3); PT_TMR_Create(tmr4); PT_TSK_Create(protothread1); PT_TSK_Create(protothread2); PT_TSK_Create(protothread3); PT_TSK_Create(protothread4); //-------------------------------------------------------------------- #define INIT_MyClass(x) {x.init=MyClass_init; x.me=&x; x.init(&x);} struct MyClass; struct MyClass { int id; struct MyClass *me; void (*init)(struct MyClass *me); void (*disp)(struct MyClass *me); }; void MyClass_disp(struct MyClass* me){ char buf[20]; sprintf(buf,"I am Object%i \r\n",me->id); printf(buf); } void MyClass_init(struct MyClass *me){ me->disp= MyClass_disp; } //----------------------------------------------------------- struct MyClass o1,o2,o3,o4; void MyTicket() { int j,k,a,b; //putchar('\a'); Sleep(1); SIGNAL(); } unsigned char protothread1(struct pt *pt) { PT_BEGIN(pt); while(1) { PT_TimerSet(&tmr1, 3000); PT_WAIT_UNTIL(pt, PT_TimerExpired(&tmr1)); // printf("Hello World 1!\n"); o1.disp(&o1); } PT_END(pt); } unsigned char protothread2(struct pt *pt) { PT_BEGIN(pt); while(1) { PT_TimerSet(&tmr2, 2000); PT_WAIT_UNTIL(pt, PT_TimerExpired(&tmr2)); // printf("Hello World 2!\n"); o2.disp(&o2); } PT_END(pt); } unsigned char protothread3(struct pt *pt) { PT_BEGIN(pt); while(1) { PT_TimerSet(&tmr3, 200); PT_WAIT_UNTIL(pt, PT_TimerExpired(&tmr3)); // printf("Hello World 3!\n"); o3.disp(&o3); } PT_END(pt); } unsigned char protothread4(struct pt *pt) { PT_BEGIN(pt); while(1) { PT_TimerSet(&tmr4, 400); PT_WAIT_UNTIL(pt, PT_TimerExpired(&tmr4)); // printf("Hello World 4!\n"); o4.disp(&o4); } PT_END(pt); } int main(int argc, char* argv[]) { printf("Hello World!\n"); int i; INIT_MyClass(o1); INIT_MyClass(o2); INIT_MyClass(o3); INIT_MyClass(o4); o1.id=1; o2.id=2; o3.id=3; o4.id=4; PT_INIT(&PT_TCB(protothread1)); PT_INIT(&PT_TCB(protothread2)); PT_INIT(&PT_TCB(protothread3)); PT_INIT(&PT_TCB(protothread4)); while(1) { if (i<2) MyTicket(); else{ i=0; protothread1(&PT_TCB(protothread1)); protothread2(&PT_TCB(protothread2)); protothread3(&PT_TCB(protothread3)); protothread4(&PT_TCB(protothread4)); } i++; } return 0; } //---- 运行效果: (原文件名:1.GIF) //源代码包: 点击此处下载 本贴被 smset 编辑过,最后修改时间:2010-06-04,13:59:02. | ||
2010-06-04,13:45:06 |
|
【1楼】 dr2001 积分:1068 派别: 等级:------ 来自: | 如果LZ想介绍ProtoThread的话,最好详细介绍一下这么干的目的,实现方法,好处和坏处。 在Contiki的一个PDF文档有,不过翻译工作会比较辛苦。 而且,这个范例代码没有非常明显的表现出来Yield Point。 简单来说,ProtoThread和FreeRTOS等的CoRoutine是类似的实现方法,实际应用上,限制很明显。 | ||
2010-06-04,14:09:50 |
|
【2楼】 eworker 积分:1310 派别: 等级:------ 来自: | 回复【1楼】dr2001 ----------------------------------------------------------------------- 能具体说一下“CoRoutine”具体有哪些限制? | ||
2010-06-04,14:22:54 |
|
【3楼】 minux 啊啊? 积分:891 派别: 等级:------ 来自:Plan 9 | coroutine和真正的thread的区别挺多,但是也不好说是限制,须知Contiki的使用环境 是传感器网络节点,一般来说,大都是内存极其受限,相比之下,CPU资源是可以不那么 受限的(或者这么说,CPU工作时间长些仅仅是影响节点的寿命,而RAM不够用则节点根本 不可能工作起来)。 protothread就是为了解决这个多线程会消耗过多的RAM的问题而用增加CPU处理工作来做交换。 每个函数(protothread)在yield的时候都完全返回,也就是相当于多个“线程”共用同一个 堆栈。但是这和event-based的系统(比如tinyos)的区别在于,这个系统编程的时候你还是 可以认为是顺序执行的(yield之后如果继续执行的话,不是从函数开头而是从上次yield的 地方继续执行),event-based的系统则必须人为将系统拆分成许多event(简单的理解,你可 以认为就是把程序全写成状态机的形式,很多情况来说,这对人来说不是一个很好驾驭的 模型,比如我们会很习惯做了这个时候干什么,条件没成熟就继续做什么;可是要弄成状 态机形式,你就必须自己分成几个状态,弄出状态转移图,而用这种类似thread的protothread 则不需要显式地体现出来你的状态机)。 至于protothread(coroutine和它本质一样)的限制,取决于实现,如果使用可移植的switch来实现, 那么限制超多,不能用switch了;如果使用GNU的computed goto来实现,则没有这个限制; 但是不管是什么实现,都需要注意,如果你希望函数的auto变量保存上次的值的话,必须声明成 static,这才是这类在C语言中实现coroutine类机制的本质限制。 这些东西,说到最后就是一个continuation的概念,只不过在函数式编程社区以外都不怎么用这个 概念而已。 上面关于protothread的说得可能过于泛泛,如果想知道详情,还是应该读这篇论文: Protothreads: Simplifying Event-Driven Programming of Memory-Constrained Embedded Systems Adam Dunkels, Oliver Schmidt, Thiemo Voigt, Muneeb Ali | ||
2010-06-06,01:41:22 |
|
【4楼】 dr2001 积分:1068 派别: 等级:------ 来自: | minux解答的很详细啊。盛赞一下。 补充一点点: Protothread或者叫Coroutine,主要就是提供continuation point。即函数从yield返回后,下次调用会接着yeild的下一句执行。目的之一是简化代码结构,增强可读性。当然,Iterator这样的东西也可以比较方便的实现。 coroutine可以是有自己独立堆栈的(Thread的协调多任务用法),也可以是共享堆栈的。共享堆栈就是如freertos/coroutine,protothread这样的实现。好处是节约内存;最大劣势是共享堆栈,因此所有栈自动变量在yield会丢失。 解决办法要么是用static/全局变量,不在栈上保存,但是可能丢失重入特性(是否丢看实现手段);要么用malloc之类的使用堆,但存在内存泄漏(典型为Yield之后Reset),内存碎片的问题(malloc自身,决定于其算法)。 实现手段上,符合标准,可移植的,是基于switch的实现。为了使coroutine可用并且好用,至少仰赖于以下C标准的规定,或者C标准中没有说,但是经讨论,是合法描述的情形: - 宏展开后,宏中的代码都在一个物理行上。 - switch的case可以和for/if之类的语句嵌套。即switch(bbb) { for(xx; xx; xx) { case 1: ... ; case 2: yyy} }是合法的。 第二条是switch实现的核心,如果有变化,就over。 标准switch实现的坏处: - switch必须谨慎使用:嵌入的switch中间不能带yield,(return,exit看实现)之类的返回点。这个可以用if/else避免。 - switch靠后的continuation point的调用一次额外开销比较大,要一个一个比较过去。 使用嵌入汇编,GNU的label,computed goto等实现,限制要少,效率也高。问题一个是可移植;另外一个是编译器优化是否导致问题,嵌入式汇编这个很明显,GNU的我没用过,没体会。 本贴被 dr2001 编辑过,最后修改时间:2010-06-06,10:07:39. | ||
2010-06-06,10:06:50 |
|
文章评论(0条评论)
登录后参与讨论