FreeRTOS协程
FreeRTOS的协程,实际上是线程并发出来的。从协程控制块中没有栈空间就能够知道,每个线程并发出来的协程共用一个栈空间。
/* 协程控制块 */
typedef struct corCoRoutineControlBlock
{crCOROUTINE_CODE pxCoRoutineFunction; /* 协程函数指针 */ListItem_t xGenericListItem; /* 状态列表项 */ListItem_t xEventListItem; /* 事件列表项 */UBaseType_t uxPriority; /* 协程优先级 */UBaseType_t uxIndex; /* 协程ID,区别不同协程调用相同函数 */uint16_t uxState; /* 协程状态 */
}CRCB_t;
创建协程
/* 创建协程 */
BaseType_t xCoRoutineCreate(crCOROUTINE_CODE pxCoRoutineCode, UBaseType_t uxPriority, UBaseType_t uxIndex)
{BaseType_t xReturn;CRCB_t *pxCoRoutine;/* 为协程控制块申请内存空间 */pxCoRoutine = (CRCB_t *)pvPortMalloc(sizeof(CRCB_t));if(pxCoRoutine){/* 当前协程为空 */if(pxCurrentCoRoutine == NULL){/* 将该协程设为当前协程 */pxCurrentCoRoutine = pxCoRoutine;/* 初始化协程列表 */prvInitialiseCoRoutineLists();}/* 初始化协程优先级 */if(uxPriority >= configMAX_CO_ROUTINE_PRIORITIES){uxPriority = configMAX_CO_ROUTINE_PRIORITIES - 1;}/* 协程状态 */pxCoRoutine->uxState = corINITIAL_STATE;/* 协程优先级 */pxCoRoutine->uxPriority = uxPriority;/* 协程ID */pxCoRoutine->uxIndex = uxIndex;/* 协程函数指针 */pxCoRoutine->pxCoRoutineFunction = pxCoRoutineCode;/* 初始化协程状态列表项 */vListInitialiseItem(&(pxCoRoutine->xGenericListItem));/* 初始化协程事件列表项 */vListInitialiseItem(&(pxCoRoutine->xEventListItem));/* 将协程状态列表项所属协程设为该协程 */listSET_LIST_ITEM_OWNER(&(pxCoRoutine->xGenericListItem), pxCoRoutine);/* 将协程事件列表项所属协程设为该协程 */listSET_LIST_ITEM_OWNER(&(pxCoRoutine->xEventListItem), pxCoRoutine);/* 设置协程事件列表项值为协程优先级 */listSET_LIST_ITEM_VALUE(&(pxCoRoutine->xEventListItem), ((TickType_t)configMAX_CO_ROUTINE_PRIORITIES - (TickType_t)uxPriority));/* 将协程挂接到就绪协程列表中 */prvAddCoRoutineToReadyQueue(pxCoRoutine);/* 返回成功 */xReturn = pdPASS;}/* 内存不足 */else{xReturn = errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY;}return xReturn;
}
协程调度
从代码中可以看出,调度协程本质上就是查找超时的协程将其转为就绪,然后调用协程。
也就是说vCoRoutineSchedule函数应该被任务循环调用。
/* 协程调度器 */
void vCoRoutineSchedule(void)
{/* 检查挂起时进入就绪的协程列表,并将协程转移到就绪列表 */prvCheckPendingReadyList();/* 检查延时列表,如果有协程超时则挂接到就绪列表 */prvCheckDelayedList();/* 检查所有就绪列表,是否有协程就绪 */while(listLIST_IS_EMPTY(&(pxReadyCoRoutineLists[uxTopCoRoutineReadyPriority]))){/* 没有协程就绪直接退出 */if(uxTopCoRoutineReadyPriority == 0){return;}--uxTopCoRoutineReadyPriority;}/* 取出最高优先级的就绪协程 */listGET_OWNER_OF_NEXT_ENTRY(pxCurrentCoRoutine, &(pxReadyCoRoutineLists[uxTopCoRoutineReadyPriority]));/* 运行协程 */(pxCurrentCoRoutine->pxCoRoutineFunction)(pxCurrentCoRoutine, pxCurrentCoRoutine->uxIndex);return;
}
协程调用方式
协程调用是有一定格式的,主要有三个宏:crSTART、crEND和crDELAY
void vACoRoutine(CoRoutineHandle_t xHandle, UBaseType_t uxIndex)
{static int32_t ulAVariable;static const TickType_t xDelayTime = 200 / portTICK_PERIOD_MS;crSTART(xHandle);for(;;){crDELAY(xHandle, xDelayTime);/* 应用程序 */}crEND();
}
将宏展开,可以看出每次调用协程会先运行应用程序,然后将协程挂接到延时列表,最后退出协程
void vACoRoutine(CoRoutineHandle_t xHandle, UBaseType_t uxIndex)
{static int32_t ulAVariable;static const TickType_t xDelayTime = 200 / portTICK_PERIOD_MS;switch(((CRCB_t *)(xHandle))->uxState) {case 0:;for(;;){/* 延时时间大于0 */if((xDelayTime) > 0){/* 将协程挂接到延时列表 */vCoRoutineAddToDelayedList((xDelayTime), NULL);}/* 设置协程状态 */((CRCB_t *)(xHandle))->uxState = (__LINE__ * 2); /* 退出协程,延时结束之后直接进入应用程序 */return;/* 应用程序,运行完之后因为for循环的存在,下一轮又开始延时 */case (__LINE__ * 2):;/* 应用程序 */}};
}
协程间通信
同一任务并发出来的协程,直接使用全局变量即可,不存在共享数据的完整性问题。但是不同任务并发出来的协程之间,如果需要通信,则需要考虑共享数据的完整性问题。FreeRTOS为协程提供了专门的队列通信API,只能用于协程之间通信,不能用于线程和协程之间通信。
发送队列消息,是一个宏定义
/* 发送队列消息 */
#define crQUEUE_SEND(xHandle, pxQueue, pvItemToQueue, xTicksToWait, pxResult) \
{ \*(pxResult) = xQueueCRSend((pxQueue), (pvItemToQueue), (xTicksToWait)); \if(*(pxResult) == errQUEUE_BLOCKED) \{ \crSET_STATE0((xHandle)); \*pxResult = xQueueCRSend((pxQueue), (pvItemToQueue), 0); \} \if(*pxResult == errQUEUE_YIELD) \{ \crSET_STATE1((xHandle)); \*pxResult = pdPASS; \} \
}
将宏定义完全展开
switch(((CRCB_t *)(xHandle))->uxState)
{case 0:;for(;;){
/这一段是展开//* 发送队列消息 */*(pxResult) = xQueueCRSend((pxQueue), (pvItemToQueue), (xTicksToWait));/* 阻塞 */if(*(pxResult) == errQUEUE_BLOCKED){/* 设置协程状态 */((CRCB_t *)(xHandle))->uxState = (__LINE__ * 2); /* 退出协程,阻塞结束之后,直接进入(__LINE__ * 2) */return;case (__LINE__ * 2):;/* 退出阻塞,意味着发送成功或者超时 */*pxResult = xQueueCRSend((pxQueue), (pvItemToQueue), 0);}/* 请求切换协程,也意味着发送成功 */if(*pxResult == errQUEUE_YIELD){/* 设置协程状态 */((CRCB_t *)(xHandle))->uxState = ((__LINE__ * 2)+1);/* 退出协程,下次直接进入((__LINE__ * 2)+1),然后进入应用程序 */return; case ((__LINE__ * 2)+1):;*pxResult = pdPASS;};
//* 应用程序 */}
};
真正用于发送队列消息的函数为xQueueCRSend
/* 协程发送队列消息 */
BaseType_t xQueueCRSend(QueueHandle_t xQueue, const void *pvItemToQueue, TickType_t xTicksToWait)
{BaseType_t xReturn;Queue_t *const pxQueue = xQueue;/* 禁止中断 */portDISABLE_INTERRUPTS();{/* 判断队列是否已满,已满 */if(prvIsQueueFull(pxQueue) != pdFALSE){/* 等待时间大于0 */if(xTicksToWait > (TickType_t)0){/* 将协程挂接到延时列表中 */vCoRoutineAddToDelayedList(xTicksToWait, &(pxQueue->xTasksWaitingToSend));/* 恢复中断 */portENABLE_INTERRUPTS();/* 返回阻塞 */return errQUEUE_BLOCKED;}/* 等待时间不大于0 */else{/* 恢复中断 */portENABLE_INTERRUPTS();/* 返回错误 */return errQUEUE_FULL;}}}/* 恢复中断 */portENABLE_INTERRUPTS();/* 运行到这儿说明解除阻塞或者队列中有位置 *//* 禁止中断 */portDISABLE_INTERRUPTS();{/* 队列项数小于队列长度 */if(pxQueue->uxMessagesWaiting < pxQueue->uxLength){/* 将数据拷贝到队列中 */prvCopyDataToQueue(pxQueue, pvItemToQueue, queueSEND_TO_BACK);/* 返回成功 */xReturn = pdPASS;/* 判断是否存在因等待消息而阻塞的协程,有 */if(listLIST_IS_EMPTY(&(pxQueue->xTasksWaitingToReceive)) == pdFALSE){/* 将协程从事件列表中移除 */if(xCoRoutineRemoveFromEventList(&(pxQueue->xTasksWaitingToReceive)) != pdFALSE){/* 请求切换协程 */xReturn = errQUEUE_YIELD;}else{mtCOVERAGE_TEST_MARKER();}}else{mtCOVERAGE_TEST_MARKER();}}/* 队列已满 */else{/* 返回错误 */xReturn = errQUEUE_FULL;}}/* 恢复中断 */portENABLE_INTERRUPTS();return xReturn;
}
接收队列消息
/* 接收队列消息 */
#define crQUEUE_RECEIVE(xHandle, pxQueue, pvBuffer, xTicksToWait, pxResult) \
{ \*(pxResult) = xQueueCRReceive((pxQueue), (pvBuffer), (xTicksToWait)); \if(*(pxResult) == errQUEUE_BLOCKED) \{ \crSET_STATE0((xHandle)); \*(pxResult) = xQueueCRReceive((pxQueue), (pvBuffer), 0); \} \if(*(pxResult) == errQUEUE_YIELD) \{ \crSET_STATE1((xHandle)); \*(pxResult) = pdPASS; \} \
}
将宏定义完全展开
switch(((CRCB_t *)(xHandle))->uxState)
{case 0:;for(;;){/* 接收队列消息 */*(pxResult) = xQueueCRReceive((pxQueue), (pvBuffer), (xTicksToWait));/* 阻塞 */if(*(pxResult) == errQUEUE_BLOCKED){/* 设置协程状态 */((CRCB_t *)(xHandle))->uxState = (__LINE__ * 2);/* 退出协程,解除阻塞之后直接进入(__LINE__ * 2) */ return; case (__LINE__ * 2):;/* 运行到这里说明接收到消息或超时 */*(pxResult) = xQueueCRReceive((pxQueue), (pvBuffer), 0);}/* 请求切换协程,也意味着接收到消息 */if(*(pxResult) == errQUEUE_YIELD){/* 设置协程状态 */((CRCB_t *)(xHandle))->uxState = ((__LINE__ * 2)+1);/* 退出协程,下一次直接接入((__LINE__ * 2)+1),然后运行应用程序 */return; case ((__LINE__ * 2)+1):;/* 接收到消息 */*(pxResult) = pdPASS;}; /* 应用程序 */}
};
真正用于接收队列消息的函数为xQueueCRReceive
/* 协程接收队列消息 */
BaseType_t xQueueCRReceive(QueueHandle_t xQueue, void *pvBuffer, TickType_t xTicksToWait)
{BaseType_t xReturn;Queue_t *const pxQueue = xQueue;/* 禁止中断 */portDISABLE_INTERRUPTS();{/* 队列项数为0 */if(pxQueue->uxMessagesWaiting == (UBaseType_t)0){/* 等待时间大于0 */if(xTicksToWait > (TickType_t)0){/* 将协程加入协程延时列表 */vCoRoutineAddToDelayedList(xTicksToWait, &(pxQueue->xTasksWaitingToReceive));/* 恢复中断 */portENABLE_INTERRUPTS();/* 返回阻塞 */return errQUEUE_BLOCKED;}/* 等待时间不大于0 */else{/* 恢复中断 */portENABLE_INTERRUPTS();/* 返回错误 */return errQUEUE_FULL;}}else{mtCOVERAGE_TEST_MARKER();}}/* 恢复中断 */portENABLE_INTERRUPTS();/* 运行到这儿说明,解除阻塞或者队列中就有数据 *//* 禁止中断 */portDISABLE_INTERRUPTS();{/* 队列项数大于0 */if(pxQueue->uxMessagesWaiting > (UBaseType_t)0){/* 将指针偏移到新的队列项 */pxQueue->u.xQueue.pcReadFrom += pxQueue->uxItemSize;if(pxQueue->u.xQueue.pcReadFrom >= pxQueue->u.xQueue.pcTail){pxQueue->u.xQueue.pcReadFrom = pxQueue->pcHead;}else{mtCOVERAGE_TEST_MARKER();}/* 队列项数减一 */--(pxQueue->uxMessagesWaiting);/* 将队列项内容拷贝出来 */(void)memcpy((void *)pvBuffer, (void *)pxQueue->u.xQueue.pcReadFrom, (unsigned)pxQueue->uxItemSize);/* 返回成功 */xReturn = pdPASS;/* 判断是否有协程在等待发送 */if(listLIST_IS_EMPTY(&(pxQueue->xTasksWaitingToSend)) == pdFALSE){/* 将协程从事件列表中移除 */if(xCoRoutineRemoveFromEventList(&(pxQueue->xTasksWaitingToSend)) != pdFALSE){/* 请求切换协程 */xReturn = errQUEUE_YIELD;}else{mtCOVERAGE_TEST_MARKER();}}else{mtCOVERAGE_TEST_MARKER();}}/* 队列项为空 */else{/* 返回错误 */xReturn = pdFAIL;}}/* 恢复中断 */portENABLE_INTERRUPTS();return xReturn;
}
FreeRTOS还提供了带中断的队列消息接收/发送函数,原理大同小异,不再深入分析。
/* 带中断的发送队列消息 */
#define crQUEUE_SEND_FROM_ISR(pxQueue, pvItemToQueue, xCoRoutinePreviouslyWoken) xQueueCRSendFromISR((pxQueue), (pvItemToQueue), (xCoRoutinePreviouslyWoken))
/* 带中断的接收队列消息 */
#define crQUEUE_RECEIVE_FROM_ISR(pxQueue, pvBuffer, pxCoRoutineWoken) xQueueCRReceiveFromISR((pxQueue), (pvBuffer), (pxCoRoutineWoken))
FreeRTOS协程相关推荐
- FreeRTOS基础以及UIP之协程--C语言剑走偏锋
在FreeRTOS中和UIP中,都使用到了一种C语言实现的多任务计数,专业的定义叫做协程(coroutine),顾名思义,这是一种协作的例程, 跟具有操作系统概念的线程不一样,协程是在用户空间利用程序 ...
- Kotlin协程使用,协程使用注意事项,协程中的await方法使用|不使用suspend使用协程
参见 码云 协程使用方法一 (Dispatchers调度器模式) 指定不同线程.同线程会挂起并阻塞(挂起是不影响主线程执行,阻塞是同样的IO线程会阻塞) withContext(Dispatchers ...
- 连接池和协程池为何能提升并发能力?
你有没有发现,"内存池"和"进程池"都带有"池"字?其实,这两种技术都属于"池化技术".它通常是由系统预先分配一批资源并 ...
- Go 知识点(14) — Go 多协程(单个协程触发panic会导致其它所有协程挂掉,每个协程只能捕获到自己的 panic 不能捕获其它协程)
在多协程并发环境下,我们常常会碰到以下两个问题.假设我们现在有 2 个协程,我们叫它们协程 A 和 B . [问题1]如果协程 A 发生了 panic ,协程 B 是否会因为协程 A 的 panic ...
- Go 知识点(11) — goroutine 泄露、设置子协程退出条件
1. 问题现象 如果在开发过程中不考虑 goroutine 在什么时候能退出和控制 goroutine 生命期,就会造成 goroutine 失控或者泄露的情况 ,看示例代码: func consum ...
- Go 知识点(10) — 子协程能否使用主协程变量
我们来看这样一个问题,在主协程中创建的变量,在子协程中能否使用?来看下面代码: func main() {s := "hello"fmt.Printf("s outsid ...
- Go 知识点(01)— 主协程与子协程执行顺序
下面代码的输出结果是什么? package mainimport ("fmt" )func main() {for i := 0; i < 10; i++ {go func( ...
- 协程和任务 异步IO 重点
20210815 https://mp.weixin.qq.com/s/XeHaWhKztnCOIXb_2GSitQ https://mp.weixin.qq.com/s/lnox3pbpzJ2kWl ...
- 通俗易懂的Go协程的引入及GMP模型简介
本文根据Golang深入理解GPM模型加之自己的理解整理而来 Go协程的引入及GMP模型 一.协程的由来 1. 单进程操作系统 2. 多线程/多进程操作系统 3. 引入协程 二.golang对协程的处 ...
最新文章
- 用DFS 解决全排列问题的思想详解
- JSP中两种include的区别
- 229. Majority Element II
- Leetcode--925. 长按键入
- MHA manage节点安装报错解决
- 办公室同事全是女性是一种什么体验?
- 搜索二维矩阵II(分治法和蛇行)
- 不使用库函数将字符串转换为数字
- 2022: Python下载安装教程
- TCP/IP协议学习总结
- 年薪60w的程序员与年薪6w的极品程序员,差距怎么这么大呢?
- 管中窥豹SPDK RBD bdev模块
- 如何产生JIC文件(sof+ELF=jic)
- 种草软文怎么写?分享一些超实用的种草软文写作技巧。
- 软件测试人员的一般职业规划是如何的?
- 用matlab对相机进行标定获取相机内参
- HTML 5 em strong dfn code samp kbd var cite 标签
- 布斯乘法 Mips实现 - Booth Algorithm
- dbus 嵌入式linux,MeeGo操作系统DBus调试工具
- ESP8266_04-------------串口的使用
热门文章
- Qt文档阅读笔记-Button QML Type官方解析及实例
- Qt工作笔记-QTreeWidget求总结点数以及此树中最多孩子的个数(非递归)
- MySQL入门之select、from、join、where子句及基本匹配符
- php 微信支付md5签名,微信支付回调验证签名处理
- ssm 转发请求_springmvc(重定向和请求转发、数据的接收和回显)
- 鼠标在滑块上滚轮控制_高速直线运动导轨—滚轮直线导轨
- ffmpeg 丢帧 灰屏_音视频常见问题分析和解决:HLS切片丢帧引起的视频卡顿问题排查...
- 罗技 连点 脚本_罗技推出多款《英雄联盟》联名外设 看了就忍不住想要
- 密码编码学之数字签名
- ajax不能访问本地php文件,php – 使用htaccess拒绝ajax文件访问