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

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

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

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

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

首先第一次调用这个函数,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 = 0;   switch (state) {     case 0: /* start of function */    for (i = 0; i < 10; i++) {       state = __LINE__ + 2; //__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 (0)
#define End() }
int function(void)
{   static int i;   Begin();   for (i = 0; i < 10; 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(0)#define LC_SET(s)                               \do { ({ __label__ resume; resume: (s) = &&resume; }); }while(0)#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(0)/* 同 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(0)//协程重启
#define PT_RESTART(pt)                \do {                        \PT_INIT(pt);                \return PT_WAITING;            \} while(0)//协程退出
#define PT_EXIT(pt)                \do {                        \PT_INIT(pt);                \return PT_EXITED;            \} while(0)//协程调度
#define PT_SCHEDULE(f) ((f) == PT_WAITING)/* 协程出让点,如果此时协程状态变量 lc 已经变为 __LINE__ 跳转过来的,那么 PT_YIELD_FLAG = 1,表示从出让点继续执行。 */
#define PT_YIELD(pt)                \do {                        \PT_YIELD_FLAG = 0;                \LC_SET((pt)->lc);                \if(PT_YIELD_FLAG == 0) {            \return PT_YIELDED;            \}                        \} while(0)/* 附加出让条件 */
#define PT_YIELD_UNTIL(pt, cond)        \do {                        \PT_YIELD_FLAG = 0;                \LC_SET((pt)->lc);                \if((PT_YIELD_FLAG == 0) || !(cond)) {    \return PT_YIELDED;            \}                        \} while(0)

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

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 > 0);    \    --(s)->count;       \   } while(0)   #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 * 60) {s.ticks *= 2;}} 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 * 10) {s.ticks += CLOCK_SECOND;} else {//协程重启PT_RESTART(&s.pt);}} while(s.state != STATE_CONFIG_RECEIVED);#if 0printf("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[0])*65536ul + ntohs(s.lease_time[1]));
#endifdhcpc_configured(&s);/*  timer_stop(&s.timer);*//** PT_END restarts the thread so we do this instead. Eventually we* should reacquire expired leases here.*/while(1) {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, 255,255,255,255);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 * 60){s.ticks *= 2;}}while(s.state != STATE_OFFER_RECEIVED);s.ticks = CLOCK_SECOND;//连接的状态标志清零uip_flags = 0;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 * 10){s.ticks += CLOCK_SECOND;}else{PT_RESTART(&s.pt);}}while(s.state != STATE_CONFIG_RECEIVED);#if 1printf("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[0]) * 65536ul + ntohs(s.lease_time[1]));#endifdhcpc_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[0]) * 65536ul + ntohs(s.lease_time[1]))*50);PT_WAIT_UNTIL(&s.pt, timer_expired(&s.timer));/* 超时了 */goto request_pro;while (1){PT_YIELD(&s.pt);}PT_END(&s.pt);}

转载于:https://www.cnblogs.com/dengxiaojun/p/4385357.html

FreeRTOS基础以及UIP之协程--C语言剑走偏锋相关推荐

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

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

  2. Lua基础之coroutine(协程)

    为什么80%的码农都做不了架构师?>>>    概括:1.创建协程2.coroutine的函数3.coroutine的基本流程4.yield对coroutine流程的干预5.resu ...

  3. 【项目介绍】协程——C语言实现的用户态非抢占式轻量级线程

    文章目录 项目介绍 开发语言 开发环境 项目简介 项目特点 适用场景 发布链接 使用介绍 上下文环境 宏 协程状态 协程与调度器结构体 接口 示范用例 使用协程实现一个TCP服务器 项目介绍 开发语言 ...

  4. android room 线程,Android协程——RoomCoroutines-Go语言中文社区

    在Room2.1版本中提供了对协程的支持.Dao层的方法可以被suspend标记来确保他们在主线程中被执行.接下来,我们就来看看如何使用并为它写一个简单的单元测试. 为你的数据库加点suspendin ...

  5. 深入理解python.md_从python角度,理解进程,线程,协程.md-Go语言中文社区

    写在前面 文中有较多的内容为转载,尽量指出转载来源. 1 进程(process) 定义:进程是正在运行程序的实例. 如chrome 进程的三种状态: 就绪态 执行态 阻塞态 进程是基于计算机系统的异常 ...

  6. 协程 c语言,协程-C语言实现

    最近在学习lua的过程中发现lua居然有个东西叫协程(协同coroutine),虽然以前就听过这个概念,但没有结合实践的一些理解. 开始今天的文章前,首先需要学习下面几篇文章. Segment Fau ...

  7. Kotlin 学习笔记(四)—— 协程的基础知识,面试官的最爱了~

    又是一个月没见了,坚持永远是世上最难的事情,但,往往难事才会有更大的收获.与君共勉~ 前段时间一直在学习 Compose,所以导致 Kotlin 笔记系列搁置了好久.一方面是因为 Compose 的学 ...

  8. Go中线程和协程的区别

    1. 协程是什么 ? 在go语言中,协程被认为是轻量级的线程, 和线程不同的是,操作系统内核 感知不到协程的存在, 协程的管理依赖于Go语言运行时自身提供的调度器 同时Go语言中的协程是从属于某一个线 ...

  9. php携程语比,PHP 协程

    理解生成器 参考官方文档:Generators 生成器让我们快速.简单地实现一个迭代器,而不需要创建一个实现了Iterator接口的类后,再实例化出一个对象. 一个生成器长什么样?如下 1 2 3 4 ...

最新文章

  1. Vue.js-Day07【项目实战(附带 完整项目源码)-day02:学习能力、字体图标(使用步骤)、在vue.js中使用jQuery】
  2. idea application context not configured for this file的问题的解决
  3. idea for mac 控制台 mvn command not found
  4. cJONS序列化工具解读二(数据解析)
  5. java参数后面跟三个点是什么意思
  6. 邓迎春绘画201702作品10
  7. linux ubuntu mysql 安装_1.MySQL的安装(linux Ubuntu环境下)
  8. Spring mvc 参数类型转换
  9. 第 2 章 设计模式七大原则
  10. linux 安装scons
  11. 8种最差的预测建模技术,你认同吗?
  12. python3读取多行数据合并_python3 数据规整化:清理、转换、合并、重塑(一)
  13. 正则html标签sublime,sublimetext 使用正则表达式匹配中文
  14. 《HarmonyOS开发 - 小凌派-RK2206开发笔记》第3章 应用开发
  15. java给word表格追加行_Java 使用Spire.Cloud.Word给Word文档添加表格
  16. python抢票脚本github_GitHub标星超12K,抢票神器大更新,支持候补!
  17. 每天过得很焦虑怎么办?尤其是职场焦虑。
  18. 高德地图API画圆形、高的地图删除圆形打点
  19. python万年历节气_用Python精确计算100年内二十四节气日期
  20. 详解Po.et 技术栈

热门文章

  1. 【 全干货 】5 分钟带你看懂 Docker ! 1
  2. 基于CUDA的粒子系统的实现
  3. 1月全球Web服务器市场:Apache份额回升至41.64%
  4. [置顶]理解Linux系统负荷
  5. 蔬菜大棚成本_蔬菜大棚建设标准和成本
  6. jpg png webp_为在线图像删除PNG和JPG:使用WebP
  7. 什么是ci/cd_什么是CI / CD?
  8. 建立自己的唱机前置放大器
  9. 查期刊是否开源_新期刊HardwareX促进科学的开源硬件
  10. linux系统安装xhprof,LNMP部署laravel与xhprof安装使用