完善资料让更多小伙伴认识你,还能领取20积分哦, 立即完善>
|
|
相关推荐
1个回答
|
|
单片机裸机开发中常用到一个大循环, 即while(1), 程序中常常有需要延时的情况, 延时函数通常都是让CPU原地等待, 这样CPU无法进行其他操作, 会阻塞其他的工作, 利用率不高.
如果在需要延时的时候, 当前程序让出CPU, 让别的程序运行, 那么CPU利用率会提高, 而且其他程序的响应也会快一些. 图中上边是传统的串行的执行方式, 程序B必须等待程序A执行完, 才轮到它, 如果在程序A delay_ms的时候让CPU去执行程序B, 等延时完了再回到程序A, 这样在宏观上看两个程序就像在同时运行一样, 叫做并发. 比如公司排队打卡的时候, 有的人手指出汗, 打卡机暂时识别不了, 如果他原地等待, 会阻塞后边的人都不能打卡, 大家都希望他能让出打卡机到旁边等, 先让别人打卡, 后面再回去打卡. 图中下边是两个程序并发的示意图, 在程序A执行do_something后遇到了延时, 此时没有必要让CPU原地等待, 可以让出CPU去执行程序B(程序B执行的那段当然不会巧合的等于程序A的延时时间), 同样的程序B也要让出CPU才能回到程序A. CPU切换程序的过程也是需要时间的, 但是整体上看还是提升了CPU的利用率, 可以看到两个程序总的执行时间减少了. Protothread 那么如何实现这种工作方式呢? 操作系统是通过控制CPU中的PC寄存器, 也就是当前运行到的地方, 来控制切换, 但是太难了, 刚开始学操作系统就去看汇编和寄存器, 不仅枯燥还打击信心. 而Protothread则是完全使用C语言实现的状态机, 理解起来较容易. contiki就是基于Protothread的操作系统(也有说不算操作系统的). Protothread简介 他的作者Adam Dunkels,他是uip,lwip,contiki的作者. 一个程序实例 #include "pt.h" struct pt pt; PT_THREAD(example(struct pt *pt)) { PT_BEGIN(pt); while(1) { PT_WAIT_UNTIL(pt, 事件); } PT_END(pt); } struct pt pt; 定义了一个存放行号的变量, lc_t只是一个短整形的类型. struct pt { lc_t lc; }; typedef unsigned short lc_t; PT_THREAD这个宏只是定义了一个带返回char型的函数 #define PT_THREAD(name_args) char name_args PT_BEGIN里面还有一个LC_RESUME宏, 本质上就是一个switch语句的开头, 但是这个switch选择的变量是当前执行到的行号s, #define PT_BEGIN(pt) { char PT_YIELD_FLAG = 1; LC_RESUME((pt)->lc) #define LC_RESUME(s) switch(s) { case 0: PT_WAIT_UNTIL宏里面还有一个LC_SET宏, 它在当前行放置一个case, 这样就跟前面的switch组合起来了. 然后判断condition是否满足, 不满足就return. 下一次进这个函数的时候, 直接从PT_BEGIN的地方, 跳转到这里. #define PT_WAIT_UNTIL(pt, condition) do { LC_SET((pt)->lc); /*会在当前行放置一个case:*/ if(!(condition)) { return PT_WAITING; } } while(0) #define LC_SET(s) s = __LINE__; case __LINE__: switch光有开头不行, 还要有后面的右大括号作为结尾, 也就是PT_END的作用之一, PT_END里面有一个LC_END, 它会在当前行放一个右大括号, 这样switch语句就完整了 #define PT_END(pt) LC_END((pt)->lc); PT_YIELD_FLAG = 0; LC_INIT((pt)->lc); return PT_ENDED; } #define LC_END(s) }/*没别的作用, 就是放一个右大括号*/ 上面有点抽象, 画个图清楚一些 这样, 本来连贯的程序就被打散成了几个片段(return之后程序A就结束了, 轮到程序B运行), 程序A和程序B就可以穿插了. 示例如下所示 #include "pt.h" #include PT_THREAD(protothread1(struct pt *pt)) { PT_BEGIN(pt); while(1) { printf("Protothread 1 runningn"); PT_YIELD(pt); // 要让出CPU, 否则就在这无限循环退不出去了 } PT_END(pt); } PT_THREAD(protothread2(struct pt *pt)) { PT_BEGIN(pt); while(1) { printf("Protothread 2 runningn"); PT_YIELD(pt); // 要让出CPU, 否则就在这无限循环退不出去了 } PT_END(pt); } static struct pt pt1, pt2; int main(void) { // 初始化pt1 和pt2, 就是初始化行号为0 PT_INIT(&pt1); PT_INIT(&pt2); // 在一个大循环中轮流调用两个线程 while(1) { protothread1(&pt1); protothread2(&pt2); } } 需要注意的是两个线程中如果使用局部变量, 会因为return而丢失, 因此如果需要切换回来的时候还使用该变量, 那么需要定义成static. 利用gcc扩展标签变量实现 前面的方案是利用switch实现跳转, 但是由于switch被封装成宏隐藏起来了, 如果不注意又写了switch则会出错. gcc提供了一种标签变量, 也就是用于存放标签的变量, /** hideinitializer */ typedef void * lc_t; #define LC_INIT(s) s = NULL #define LC_RESUME(s) do { if(s != NULL) { goto *s; /*跳转到之前保存的标签处*/ } } while(0) #define LC_CONCAT2(s1, s2) s1##s2 #define LC_CONCAT(s1, s2) LC_CONCAT2(s1, s2) #define LC_SET(s) do { LC_CONCAT(LC_LABEL, __LINE__): /*将LC_LABEL和行号粘贴起来作为一个标签*/ /*例如, LC_LABEL10*/ (s) = &&LC_CONCAT(LC_LABEL, __LINE__); /*获取这个标签的值, 保存到s中*/ } while(0) #define LC_END(s) 这样, 函数再次进入时, 可以根据上一次保存的标签, 直接通过goto跳转过去. 简单的宏展开效果 if(s != NULL) { goto *s; /*跳转之前保存的标签处*/ } // ... 一些代码 ... LC_LABEL10: s = &&LC_LABEL10; // ... 一些代码 ... LC_LABEL25: s = &&LC_LABEL25; |
|
|
|
只有小组成员才能发言,加入小组>>
3311 浏览 9 评论
2994 浏览 16 评论
3493 浏览 1 评论
9058 浏览 16 评论
4087 浏览 18 评论
1178浏览 3评论
605浏览 2评论
const uint16_t Tab[10]={0}; const uint16_t *p; p = Tab;//报错是怎么回事?
599浏览 2评论
用NUC131单片机UART3作为打印口,但printf没有输出东西是什么原因?
2335浏览 2评论
NUC980DK61YC启动随机性出现Err-DDR是为什么?
1896浏览 2评论
小黑屋| 手机版| Archiver| 电子发烧友 ( 湘ICP备2023018690号 )
GMT+8, 2024-12-23 00:39 , Processed in 1.307708 second(s), Total 79, Slave 59 queries .
Powered by 电子发烧友网
© 2015 bbs.elecfans.com
关注我们的微信
下载发烧友APP
电子发烧友观察
版权所有 © 湖南华秋数字科技有限公司
电子发烧友 (电路图) 湘公网安备 43011202000918 号 电信与信息服务业务经营许可证:合字B2-20210191 工商网监 湘ICP备2023018690号