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协程相关推荐

  1. FreeRTOS基础以及UIP之协程--C语言剑走偏锋

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

  2. Kotlin协程使用,协程使用注意事项,协程中的await方法使用|不使用suspend使用协程

    参见 码云 协程使用方法一 (Dispatchers调度器模式) 指定不同线程.同线程会挂起并阻塞(挂起是不影响主线程执行,阻塞是同样的IO线程会阻塞) withContext(Dispatchers ...

  3. 连接池和协程池为何能提升并发能力?

    你有没有发现,"内存池"和"进程池"都带有"池"字?其实,这两种技术都属于"池化技术".它通常是由系统预先分配一批资源并 ...

  4. Go 知识点(14) — Go 多协程(单个协程触发panic会导致其它所有协程挂掉,每个协程只能捕获到自己的 panic 不能捕获其它协程)

    在多协程并发环境下,我们常常会碰到以下两个问题.假设我们现在有 2 个协程,我们叫它们协程 A 和 B . [问题1]如果协程 A 发生了 panic ,协程 B 是否会因为协程 A 的 panic ...

  5. Go 知识点(11) — goroutine 泄露、设置子协程退出条件

    1. 问题现象 如果在开发过程中不考虑 goroutine 在什么时候能退出和控制 goroutine 生命期,就会造成 goroutine 失控或者泄露的情况 ,看示例代码: func consum ...

  6. Go 知识点(10) — 子协程能否使用主协程变量

    我们来看这样一个问题,在主协程中创建的变量,在子协程中能否使用?来看下面代码: func main() {s := "hello"fmt.Printf("s outsid ...

  7. Go 知识点(01)— 主协程与子协程执行顺序

    下面代码的输出结果是什么? package mainimport ("fmt" )func main() {for i := 0; i < 10; i++ {go func( ...

  8. 协程和任务 异步IO 重点

    20210815 https://mp.weixin.qq.com/s/XeHaWhKztnCOIXb_2GSitQ https://mp.weixin.qq.com/s/lnox3pbpzJ2kWl ...

  9. 通俗易懂的Go协程的引入及GMP模型简介

    本文根据Golang深入理解GPM模型加之自己的理解整理而来 Go协程的引入及GMP模型 一.协程的由来 1. 单进程操作系统 2. 多线程/多进程操作系统 3. 引入协程 二.golang对协程的处 ...

最新文章

  1. 用DFS 解决全排列问题的思想详解
  2. JSP中两种include的区别
  3. 229. Majority Element II
  4. Leetcode--925. 长按键入
  5. MHA manage节点安装报错解决
  6. 办公室同事全是女性是一种什么体验?
  7. 搜索二维矩阵II(分治法和蛇行)
  8. 不使用库函数将字符串转换为数字
  9. 2022: Python下载安装教程
  10. TCP/IP协议学习总结
  11. 年薪60w的程序员与年薪6w的极品程序员,差距怎么这么大呢?
  12. 管中窥豹SPDK RBD bdev模块
  13. 如何产生JIC文件(sof+ELF=jic)
  14. 种草软文怎么写?分享一些超实用的种草软文写作技巧。
  15. 软件测试人员的一般职业规划是如何的?
  16. 用matlab对相机进行标定获取相机内参
  17. HTML 5 em strong dfn code samp kbd var cite 标签
  18. 布斯乘法 Mips实现 - Booth Algorithm
  19. dbus 嵌入式linux,MeeGo操作系统DBus调试工具
  20. ESP8266_04-------------串口的使用

热门文章

  1. Qt文档阅读笔记-Button QML Type官方解析及实例
  2. Qt工作笔记-QTreeWidget求总结点数以及此树中最多孩子的个数(非递归)
  3. MySQL入门之select、from、join、where子句及基本匹配符
  4. php 微信支付md5签名,微信支付回调验证签名处理
  5. ssm 转发请求_springmvc(重定向和请求转发、数据的接收和回显)
  6. 鼠标在滑块上滚轮控制_高速直线运动导轨—滚轮直线导轨
  7. ffmpeg 丢帧 灰屏_音视频常见问题分析和解决:HLS切片丢帧引起的视频卡顿问题排查...
  8. 罗技 连点 脚本_罗技推出多款《英雄联盟》联名外设 看了就忍不住想要
  9. 密码编码学之数字签名
  10. ajax不能访问本地php文件,php – 使用htaccess拒绝ajax文件访问