FreeRTOS内核实现与应用开发实战指南
P238 通用消息队列发送函数 xQueueGenericSend()(任务)
上面看到的那些在任务中发送消息的函数都是 xQueueGenericSend()展开的宏定义,真
正起作用的就是 xQueueGenericSend()函数,根据指定的参数不一样,发送消息的结果就不
一样,下面一起看看任务级的通用消息队列发送函数的实现过程,具体见代码清单 17-18。
代码清单 17-18 xQueueGenericSend()函数源码(已删减)
BaseType_t xQueueGenericSend(QueueHandle_t xQueue, const void *const pvItemToQueue, TickType_t xTicksToWait, const BaseType_t xCopyPosition)
{
BaseType_t xEntryTimeSet = pdFALSE, xYieldRequired;
TimeOut_t xTimeOut;
Queue_t *const pxQueue = (Queue_t *)xQueue;
/* 已删除一些断言操作 */
for (;;)
{
taskENTER_CRITICAL();/*进入临界段*/
{
/* 队列未满 */
if ((pxQueue->uxMessagesWaiting < pxQueue->uxLength) || (xCopyPosition == queueOVERWRITE))
{
traceQUEUE_SEND(pxQueue);
xYieldRequired =
prvCopyDataToQueue(pxQueue, pvItemToQueue, xCopyPosition);
/* 已删除使用队列集部分代码 */
/* 如果有任务在等待获取此消息队列 */
if (listLIST_IS_EMPTY(&(pxQueue->xTasksWaitingToReceive)) == pdFALSE)
{
/* 将任务从阻塞中恢复 */
if (xTaskRemoveFromEventList(&(pxQueue->xTasksWaitingToReceive)) != pdFALSE)
{
/* 如果恢复的任务优先级比当前运行任务优先级还高,
* 那么需要进行一次任务切换 */
queueYIELD_IF_USING_PREEMPTION();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else if (xYieldRequired != pdFALSE)
{
/* 如果没有等待的任务,拷贝成功也需要任务切换 */
queueYIELD_IF_USING_PREEMPTION();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
taskEXIT_CRITICAL();
return pdPASS;
}
/* 队列已满 */
else
{
if (xTicksToWait == (TickType_t)0)
{
/* 如果用户不指定阻塞超时时间,退出 */
taskEXIT_CRITICAL();
traceQUEUE_SEND_FAILED(pxQueue);
return errQUEUE_FULL;
}
else if (xEntryTimeSet == pdFALSE)
{
/* 初始化阻塞超时结构体变量,初始化进入
* 阻塞的时间 xTickCount 和溢出次数 xNumOfOverflows */
vTaskSetTimeOutState(&xTimeOut);
xEntryTimeSet = pdTRUE;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
}
taskEXIT_CRITICAL();
/* 挂起调度器 */
vTaskSuspendAll();
/* 队列上锁 */
prvLockQueue(pxQueue);
/* 检查超时时间是否已经过去了 */
if (xTaskCheckForTimeOut(&xTimeOut, &xTicksToWait) == pdFALSE)
{
/* 如果队列还是满的 */
if (prvIsQueueFull(pxQueue) != pdFALSE)
{
traceBLOCKING_ON_QUEUE_SEND(pxQueue);
/* 将当前任务添加到队列的等待发送列表中
* 以及阻塞延时列表,延时时间为用户指定的超时时间 xTicksToWait */
vTaskPlaceOnEventList(&(pxQueue->xTasksWaitingToSend), xTicksToWait);
/* 队列解锁 */
prvUnlockQueue(pxQueue);
/* 恢复调度器 */
if (xTaskResumeAll() == pdFALSE)
{
portYIELD_WITHIN_API();
}
}
else
{
/* 队列有空闲消息空间,允许入队 */
prvUnlockQueue(pxQueue);
(void)xTaskResumeAll();
}
}
else
{
/* 超时时间已过,退出 */
prvUnlockQueue(pxQueue);
(void)xTaskResumeAll();
traceQUEUE_SEND_FAILED(pxQueue);
return errQUEUE_FULL;
}
}
}
/*-----------------------------------------------------------*/
/*
总结:
调用写队列函数时,
如果队列不满,发送信息,且对面没有读任务,立刻切换任务,返回
如果队列不满,发送信息,且对面有读任务,唤醒读任务,立刻切换任务,返回(若没有更高优先级的任务,读任务得到执行)
如果队列满,且指定阻塞时间为0,立刻返回。
如果队列满,且指定阻塞时间不为0,且时间未过,进入阻塞,等待被列表或定时器唤醒。
如果队列满,且指定阻塞时间不为0,且时间过了,立刻返回错误代码。
*/
/* 总结: 调用写队列函数时, 如果队列不满,发送信息,且对面没有读任务,立刻切换任务,返回 如果队列不满,发送信息,且对面有读任务,唤醒读任务,立刻切换任务,返回(若没有更高优先级的任务,读任务得到执行) 如果队列满,且指定阻塞时间为0,立刻返回。 如果队列满,且指定阻塞时间不为0,且时间未过,进入阻塞,等待被读队列函数或时间片中断(阻塞时间过了)唤醒。 如果队列满,且指定阻塞时间不为0,且时间过了,立刻返回错误代码。 */
P242 消息队列发送函数 xQueueGenericSendFromISR()(中断)
既然有任务中发送消息的函数,当然也需要有在中断中发送消息函数,其实这个函数跟 xQueueGenericSend() 函 数 很 像 , 只 不 过 是 执 行 的 上 下 文 环 境 是 不 一 样 的 ,xQueueGenericSendFromISR()函数只能用于中断中执行,是不带阻塞机制的,源码具体见 代码清单 17-19。
代码清单 17-19xQueueGenericSendFromISR()函数源码
BaseType_t xQueueGenericSendFromISR(QueueHandle_t xQueue,
const void *const pvItemToQueue,
BaseType_t *const xHigherPriorityTaskWoken,
const BaseType_t xCopyPosition)
{
BaseType_t xReturn;
UBaseType_t uxSavedInterruptStatus;
Queue_t *const pxQueue = (Queue_t *)xQueue;
uxSavedInterruptStatus = portSET_INTERRUPT_MASK_FROM_ISR();
{
/* 队列未满 */
if ((pxQueue->uxMessagesWaiting < pxQueue->uxLength) || (xCopyPosition == queueOVERWRITE))
{
const int8_t cTxLock = pxQueue->cTxLock;
traceQUEUE_SEND_FROM_ISR(pxQueue);
/* 完成消息拷贝 */
(void)prvCopyDataToQueue(pxQueue, pvItemToQueue, xCopyPosition);
/* 判断队列是否上锁 */
if (cTxLock == queueUNLOCKED)
{
{
/* 如果有任务在等待获取此消息队列 */
if (listLIST_IS_EMPTY(&(pxQueue->xTasksWaitingToReceive)) == pdFALSE)
{
/* 将任务从阻塞中恢复 */
if (xTaskRemoveFromEventList(&(pxQueue->xTasksWaitingToReceive)) != pdFALSE)
{
if (pxHigherPriorityTaskWoken != NULL)
{
/* 解除阻塞的任务优先级比当前任务高,记录上下文切换请求,
* 等返回中断服务程序后,就进行上下文切换 */
*pxHigherPriorityTaskWoken = pdTRUE;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
}
else
{
/* 队列上锁,记录上锁次数,等到任务解除队列锁时,
* 使用这个计录数就可以知道有多少数据入队 */
pxQueue->cTxLock = (int8_t)(cTxLock + 1);
}
xReturn = pdPASS;
}
else
{
/* 队列是满的,因为 API 执行的上下文环境是中断,
* 所以不能阻塞,直接返回队列已满错误代码 errQUEUE_FULL */
traceQUEUE_SEND_FROM_ISR_FAILED(pxQueue);
xReturn = errQUEUE_FULL;
}
}
portCLEAR_INTERRUPT_MASK_FROM_ISR(uxSavedInterruptStatus);
return xReturn;
}
1.如果队列未满或允许覆盖,完成拷贝。(锁不影响拷贝信息)
2.如果队列未上锁,检查是否有任务在等待队列中的数据。如果有,并且可以从等待列表中移除至少一个任务,则通过设置xHigherPriorityTaskWoken
为pdTRUE
来标记需要在ISR完成后进行任务切换。
3.如果队列上锁,则增加1次锁计数器以延迟唤醒逻辑,直到队列解锁
4.如果队列已满,因为 API 执行的上下文环境是中断,不能阻塞,立刻返回错误代码 errQUEUE_FULL。