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)