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))