在FreeRTOS中和UIP中,都使用到了一种C语言实现的多任务计数,专业的定义叫做协程(coroutine),顾名思义,这是一种协作的例程, 跟具有操作系统概念的线程不一样,协程是在用户空间利用程序语言的语法语义就能实现逻辑上类似多任务的编程技巧。

意思就是说协程不需要每次调用的时候都为任务准备一次空间,我们知道像ucos这种操作系统,它内置的多任务是需要在中断过程中切换堆栈的,开销较大,而协程的功能就是在尽量降低开销的情况下,实现能够保存函数上下文快速切换的办法,用操作系统的概念来说,一千个一万个协程对应的其实还是一个任务,也可以这样人物,对应的就是一个很长的函数,函数中途会返回,但是返回之后再次进入函数的时候,会从上次我们返回的地方继续执行.

还有蛮多理论上的东西,比如消费者-创造者模型等等,就不空谈了,直接上代码

int function(void)
{
static int i, state = ; //注意这是静态变量
switch (state)
{
case : //这里是开始入口
for (i = ; i < ; i++)
{
state = ; //现在设置静态变量为1了
return i;
case :; //到这里选择会被跳出
}
}
}

这段代码要看懂需要费点功夫,注意这里面有两个静态变量,静态变量在编译的时候就已经固定好了,存放在堆中的,并不会被销毁.

首先第一次调用这个函数,state被设置成1,函数返回0重要的是接下来,static变量已经被设置了,不会在此设置为0,那么直接匹配到case1,case1没东西,可是case1在循环体内,下一次循环的时候state又被设置1,此时因为i也是static变量,所以这时候i返回的是1,再接着调用会依次返回0-9,直到i=10,在这个程序就不会返回东西了.

所以你看,我们没有定义外部的变量,但是这个函数每次进行切换的时候都能保存之前的上下文,造成的开销就是两个字节的静态变量,这就是协程啦,协程上下文切换不需要堆栈的参与.,而第一次的state = 0,相当于任务启动信号(这段代码着实变态!!!)

既然已经这样了不妨再来一下,每次用0 1 2 3 4 写起来也麻烦,让宏定义参与进来不是更好

int function(void)
{
static int i, state = ;
switch (state)
{
case : /* start of function */
for (i = ; i < ; i++)
{
state = __LINE__ + ; //__LINE__ 标识当前处于第几行
return i;
case __LINE__:; //上面的那个__LINE__+2其实就等于现在的__LINE__,因为代码又增加了两行
//所以这里的代码结构不能变哦
}
}
}

这样我们就可以在原来的基础上再用宏把代码提炼一下

#define Begin() static int state=0; switch(state) { case 0:
#define Yield(x)
do { state=__LINE__; return x; case __LINE__:; } while ()
#define End() }
int function(void)
{
static int i;
Begin();
for (i = ; i < ; i++)
Yield(i);
End();
}

展开和上面是一样一样的

实际上我们利用了 switch-case 的分支跳转特性,以及预编译的 __LINE__ 宏,实现了一种隐式状态机,最终实现了“yield 语义

但是, 这就使得代码不具备可重入性和多线程应用,因为static是不可重入的,所以使用协程和多线程要注意,不能再两个任务中同时使用一个协程

行,说到这里基本说明白了协程,接着我们分析分析uip的协程源码,uip使用的协程我们一般叫做Protothreads,包括lc.h lc_switch.h lc_addrlabels.h pt.h

首先看他的数据结构

struct pt {
lc_t lc;
};
typedef unsigned short lc_t;

一个short型数据,长度是编译器默认长度, 实际上它就是协程的上下文结构体,用以保存状态变量,

#define LC_INIT(s) s = 0;
#define LC_RESUME(s) switch(s) { case 0:
#define LC_SET(s) s = __LINE__; case __LINE__:
#define LC_END(s) }

四句协程原语,和之前我们自己提炼的类似,只不过他把state换成个s

但是吧,这里的原语有一个漏洞, 无法在 LC_RESUME 和 LC_END (或者包含它们的组件)之间的代码中使用 switch-case语句,因为这会引起外围的 switch 跳转错误, 为 此,protothreads 又实现了基于 GNU C 的调度“原语”。在 GNU C 下还有一种语法糖叫做标签指针,就是在一个 label 前面加 &&(不是地址的地址,是 GNU 自定义的符号),可以用 void 指针类型保存,然后 goto 跳转

typedef void * lc_t;

#define LC_INIT(s) s = NULL

#define LC_RESUME(s)                            \
do { \
if(s != NULL) { \
goto *s; \
} \
} while() #define LC_SET(s) \
do { ({ __label__ resume; resume: (s) = &&resume; }); }while() #define LC_END(s)

__label__这个就是label

现在准备条件都做好了, Protothreads真正的实现是在pt.h文件中,有着如下接口

#define PT_WAITING 0    //设定等待
#define PT_EXITED 1 //退出
#define PT_ENDED 2 //结束
#define PT_YIELDED 3 //阻塞 /* 初始化一个协程,也即初始化状态变量 */
#define PT_INIT(pt) LC_INIT((pt)->lc) /* 声明一个函数,返回值为 char 即退出码,表示函数体内使用了 proto thread,(个人觉得有些多此一举) */
#define PT_THREAD(name_args) char name_args /* 协程入口点, PT_YIELD_FLAG=0表示出让,=1表示不出让,放在 switch 语句前面,下次调用的时候可以跳转到上次出让点继续执行 */
#define PT_BEGIN(pt) { char PT_YIELD_FLAG = 1; LC_RESUME((pt)->lc) /* 协程退出点,至此一个协程算是终止了,清空所有上下文和标志 */
#define PT_END(pt) LC_END((pt)->lc); /*PT_YIELD_FLAG = 0;*/ \
PT_INIT(pt); return PT_ENDED; } /* 协程阻塞点(blocking),本质上等同于 PT_YIELD_UNTIL,只不过退出码是 PT_WAITING,用来模拟信号量同步 */
#define PT_WAIT_UNTIL(pt, condition) \
do { \
LC_SET((pt)->lc); \
if(!(condition)) { \
return PT_WAITING; \
} \
} while() /* 同 PT_WAIT_UNTIL 条件反转 */
#define PT_WAIT_WHILE(pt, cond) PT_WAIT_UNTIL((pt), !(cond)) //协程等待
#define PT_WAIT_THREAD(pt, thread) PT_WAIT_WHILE((pt), PT_SCHEDULE(thread)) /* 用于协程嵌套调度,child 是子协程的上下文句柄 */
#define PT_SPAWN(pt, child, thread) \
do { \
PT_INIT((child)); \
PT_WAIT_THREAD((pt), (thread)); \
} while() //协程重启
#define PT_RESTART(pt) \
do { \
PT_INIT(pt); \
return PT_WAITING; \
} while() //协程退出
#define PT_EXIT(pt) \
do { \
PT_INIT(pt); \
return PT_EXITED; \
} while() //协程调度
#define PT_SCHEDULE(f) ((f) == PT_WAITING) /* 协程出让点,如果此时协程状态变量 lc 已经变为 __LINE__ 跳转过来的,那么 PT_YIELD_FLAG = 1,表示从出让点继续执行。 */
#define PT_YIELD(pt) \
do { \
PT_YIELD_FLAG = ; \
LC_SET((pt)->lc); \
if(PT_YIELD_FLAG == ) { \
return PT_YIELDED; \
} \
} while() /* 附加出让条件 */
#define PT_YIELD_UNTIL(pt, cond) \
do { \
PT_YIELD_FLAG = ; \
LC_SET((pt)->lc); \
if((PT_YIELD_FLAG == ) || !(cond)) { \
return PT_YIELDED; \
} \
} while()

通过这些宏定义就可以完善的处理协程了,而且我们还可以在上面扩展,例如我们想添加一个信号量控制,那这样

struct pt_sem {   unsigned int count; };
#define PT_SEM_INIT(s, c) (s)->count = c
#define PT_SEM_WAIT(pt, s) \
do { \
PT_WAIT_UNTIL(pt, (s)->count > ); \
--(s)->count; \
} while()
#define PT_SEM_SIGNAL(pt, s) ++(s)->count

就可以了

现在我们可以看看UIP利用协程实现的DHCP了,直接在源码里面说吧

static
PT_THREAD(handle_dhcp(void))//这是一个函数,同时也表明这是一个协程
{
PT_BEGIN(&s.pt);//协程启动 /* try_again:*/
s.state = STATE_SENDING;
s.ticks = CLOCK_SECOND; do {
send_discover();
timer_set(&s.timer, s.ticks);
//等待一个事件
PT_WAIT_UNTIL(&s.pt, uip_newdata() || timer_expired(&s.timer)); if(uip_newdata() && parse_msg() == DHCPOFFER) {
s.state = STATE_OFFER_RECEIVED;
break;
} if(s.ticks < CLOCK_SECOND * ) {
s.ticks *= ;
}
} while(s.state != STATE_OFFER_RECEIVED); s.ticks = CLOCK_SECOND; do {
send_request();
timer_set(&s.timer, s.ticks);
//再次等待一个事件
PT_WAIT_UNTIL(&s.pt, uip_newdata() || timer_expired(&s.timer)); if(uip_newdata() && parse_msg() == DHCPACK) {
s.state = STATE_CONFIG_RECEIVED;
break;
} if(s.ticks <= CLOCK_SECOND * ) {
s.ticks += CLOCK_SECOND;
} else {
//协程重启
PT_RESTART(&s.pt);
}
} while(s.state != STATE_CONFIG_RECEIVED); #if 0
printf("Got IP address %d.%d.%d.%d\n",
uip_ipaddr1(s.ipaddr), uip_ipaddr2(s.ipaddr),
uip_ipaddr3(s.ipaddr), uip_ipaddr4(s.ipaddr));
printf("Got netmask %d.%d.%d.%d\n",
uip_ipaddr1(s.netmask), uip_ipaddr2(s.netmask),
uip_ipaddr3(s.netmask), uip_ipaddr4(s.netmask));
printf("Got DNS server %d.%d.%d.%d\n",
uip_ipaddr1(s.dnsaddr), uip_ipaddr2(s.dnsaddr),
uip_ipaddr3(s.dnsaddr), uip_ipaddr4(s.dnsaddr));
printf("Got default router %d.%d.%d.%d\n",
uip_ipaddr1(s.default_router), uip_ipaddr2(s.default_router),
uip_ipaddr3(s.default_router), uip_ipaddr4(s.default_router));
printf("Lease expires in %ld seconds\n",
ntohs(s.lease_time[])*65536ul + ntohs(s.lease_time[]));
#endif dhcpc_configured(&s); /* timer_stop(&s.timer);*/ /*
* PT_END restarts the thread so we do this instead. Eventually we
* should reacquire expired leases here.
*/
while() {
PT_YIELD(&s.pt);//协程出让
} PT_END(&s.pt);//最后完成
}
/*---------------------------------------------------------------------------*/
void
dhcpc_init(const void *mac_addr, int mac_len)
{
uip_ipaddr_t addr; s.mac_addr = mac_addr;
s.mac_len = mac_len; s.state = STATE_INITIAL;
uip_ipaddr(addr, ,,,);
s.conn = uip_udp_new(&addr, HTONS(DHCPC_SERVER_PORT));
if(s.conn != NULL) {
uip_udp_bind(s.conn, HTONS(DHCPC_CLIENT_PORT));
}
//初始化协程
PT_INIT(&s.pt);
}

其实上面这段代码是有BUG的,在两个do_while的循环中都没有进行标志位的清空, 导致程序误判以为是dhcp已经接收到下一个数据了.另外没有dhcp租约机制没有写进去.这一点我自己改好了,如下

static
PT_THREAD(handle_dhcp(void))
{
PT_BEGIN(&s.pt); /* try_again:*/
s.state = STATE_SENDING;
s.ticks = CLOCK_SECOND; do
{
send_discover();
timer_set(&s.timer, s.ticks);
PT_WAIT_UNTIL(&s.pt, uip_newdata() || timer_expired(&s.timer)); if(uip_newdata() && parse_msg() == DHCPOFFER)
{
s.state = STATE_OFFER_RECEIVED;
break;
} if(s.ticks < CLOCK_SECOND * )
{
s.ticks *= ;
}
}
while(s.state != STATE_OFFER_RECEIVED); s.ticks = CLOCK_SECOND;
//连接的状态标志清零
uip_flags = ; request_pro:
do
{
send_request();
timer_set(&s.timer, s.ticks);
PT_WAIT_UNTIL(&s.pt, uip_newdata() || timer_expired(&s.timer)); if(uip_newdata() && parse_msg() == DHCPACK)
{
s.state = STATE_CONFIG_RECEIVED;
break;
} if(s.ticks <= CLOCK_SECOND * )
{
s.ticks += CLOCK_SECOND;
}
else
{
PT_RESTART(&s.pt);
}
}
while(s.state != STATE_CONFIG_RECEIVED); #if 1
printf("Got IP address %d.%d.%d.%d\r\n",
uip_ipaddr1(s.ipaddr), uip_ipaddr2(s.ipaddr),
uip_ipaddr3(s.ipaddr), uip_ipaddr4(s.ipaddr));
printf("Got netmask %d.%d.%d.%d\r\n",
uip_ipaddr1(s.netmask), uip_ipaddr2(s.netmask),
uip_ipaddr3(s.netmask), uip_ipaddr4(s.netmask));
printf("Got DNS server %d.%d.%d.%d\r\n",
uip_ipaddr1(s.dnsaddr), uip_ipaddr2(s.dnsaddr),
uip_ipaddr3(s.dnsaddr), uip_ipaddr4(s.dnsaddr));
printf("Got default router %d.%d.%d.%d\r\n",
uip_ipaddr1(s.default_router), uip_ipaddr2(s.default_router),
uip_ipaddr3(s.default_router), uip_ipaddr4(s.default_router));
printf("Lease expires in %ld seconds\r\n",
ntohs(s.lease_time[]) * 65536ul + ntohs(s.lease_time[]));
#endif dhcpc_configured(&s); /* timer_stop(&s.timer);*/ /*
* PT_END restarts the thread so we do this instead. Eventually we
* should reacquire expired leases here.
*/
/* 判断超时 租约到期重连*/
timer_set(&s.timer,(ntohs(s.lease_time[]) * 65536ul + ntohs(s.lease_time[]))*);
PT_WAIT_UNTIL(&s.pt, timer_expired(&s.timer));
/* 超时了 */
goto request_pro; while ()
{
PT_YIELD(&s.pt);
} PT_END(&s.pt);
}

FreeRTOS基础以及UIP之协程--C语言剑走偏锋的更多相关文章

  1. Lua基础之coroutine(协程)

    概括:1.创建协程2.coroutine的函数3.coroutine的基本流程4.yield对coroutine流程的干预5.resume, function()以及yield之间的参数传递和返回值传 ...

  2. python基础(16)-进程&线程&协程

    进程之multiprocessing模块 Process(进程) Process模块是一个创建进程的模块,借助这个模块,就可以完成进程的创建. 介绍 初始化参数 Process([group [, t ...

  3. 基础10 多进程、协程(multiprocessing、greenlet、gevent、gevent.monkey、select、selector)

    1.多进程实现方式(类似于多线程) import multiprocessing import time,threading def thread_run():#定义一个线程函数 print(&quo ...

  4. 代理池抓取基础版-(python协程)--抓取网站(西刺-后期会持续更新)

    # coding = utf- __autor__ = 'litao' import urllib.request import urllib.request import urllib.error ...

  5. Lua 协程coroutine

    协程和一般多线程的区别是,一般多线程由系统决定该哪个线程执行,是抢占式的,而协程是由每个线程自己决定自己什么时候不执行,并把执行权主动交给下一个线程. 协程是用户空间线程,操作系统其存在一无所知,所以 ...

  6. Golang源码探索(二) 协程的实现原理

    Golang最大的特色可以说是协程(goroutine)了, 协程让本来很复杂的异步编程变得简单, 让程序员不再需要面对回调地狱, 虽然现在引入了协程的语言越来越多, 但go中的协程仍然是实现的是最彻 ...

  7. Python并发编程之从生成器使用入门协程(七)

    大家好,并发编程 进入第七篇. 从今天开始,我们将开始进入Python的难点,那就是协程. 为了写明白协程的知识点,我查阅了网上的很多相关资料.发现很难有一个讲得系统,讲得全面的文章,导致我们在学习的 ...

  8. Golang源码探索(二) 协程的实现原理(转)

    Golang最大的特色可以说是协程(goroutine)了, 协程让本来很复杂的异步编程变得简单, 让程序员不再需要面对回调地狱,虽然现在引入了协程的语言越来越多, 但go中的协程仍然是实现的是最彻底 ...

  9. 基于汇编的 C/C++ 协程 - 切换上下文

    在前一篇文章<基于汇编的 C/C++ 协程 - 背景知识>中提到一个用于 C/C++ 的协程所需要实现的两大功能: 协程调度 上下文切换 其中调度,其实在技术实现上与其他的线程.进程调度没 ...

随机推荐

  1. HDU2050 由直线分割平面推广到折线分割平面

    直线分割平面问题: 加入已有n-1条直线,那么再增加一条直线,最多增加多少个平面? 为了使增加的平面尽可能的多,我们应该使新增加的直线与前n条直线相交,且不存在公共交点.那么我们可以将新增加的这条直线 ...

  2. 第三方 XListview 上拉加载、下拉刷新、分页加载和Gson解析

    注意:此Demo用的是第三方的Xlistview.jar,需要复制me文件夹到项目中,两个XML布局文件和一张图片 把下面的复制到String中 <string name="xlist ...

  3. [转]Android 如何对sqlite数据库进行增删改[insert、update和delete] 操作

    import android.content.ContentValues; import android.content.Context; import android.database.Cursor ...

  4. lucene中Field.Index,Field.Store的一些设置

    lucene在doc.add(new Field("content",curArt.getContent(),Field.Store.NO,Field.Index.TOKENIZE ...

  5. Node.js学习 - Modules

    创建模块 当前目录:hello.js, main.js // hello.js exports.world = function() { // exports 对象把 world 作为模块的访问接口 ...

  6. jq之简单的表单验证

    <body> <form method="post" action=""> <div class="int"& ...

  7. Entity Framework 学习初级篇2--ObjectContext、ObjectQuery、ObjectStateEntry、ObjectStateManager类的介绍

    本节,简单的介绍EF中的ObjectContext.ObjectQuery.ObjectStateEntry.ObjectStateManager这个几个比较重要的类,它们都位于System.Data ...

  8. 基于ATmgea8单片机设计的加热控制系统(转)

    源:http://blog.163.com/zhaojun_xf/blog/static/3005058020085102562729/ 1 引言 温度是工业生产中主要的被控参数之一,与之相关的各种温 ...

  9. 我也谈javascript闭包的原理理解

    参考原文:http://www.oschina.net/question/28_41112 前言:还是一篇入门文章.Javascript中有几个非常重要的语言特性——对象.原型继承.闭包.其中闭包 对 ...

  10. js中如何获取时间

    var myDate = new Date();myDate.getYear();        //获取当前年份(2位)myDate.getFullYear();    //获取完整的年份(4位,1 ...