( void ) memcpy( ( void * ) pxQueue->pcWriteTo, pvItemToQueue, ( size_t ) pxQueue->uxItemSize );
pxQueue->pcWriteTo += pxQueue->uxItemSize;
if( pxQueue->pcWriteTo >= pxQueue->pcTail )
{
pxQueue->pcWriteTo = pxQueue->pcHead;
}
( void ) memcpy( ( void * ) pxQueue->u.pcReadFrom, pvItemToQueue, ( size_t ) pxQueue->uxItemSize );
pxQueue->u.pcReadFrom -= pxQueue->uxItemSize;
if( pxQueue->u.pcReadFrom < pxQueue->pcHead )
{
pxQueue->u.pcReadFrom = ( pxQueue->pcTail - pxQueue->uxItemSize );
}
pxQueue->u.pcReadFrom += pxQueue->uxItemSize;
if( pxQueue->u.pcReadFrom >= pxQueue->pcTail ) /*lint !e946 MISRA exception justified as use of the relational operator is the cleanest solutions. */
{
pxQueue->u.pcReadFrom = pxQueue->pcHead;
}
( void ) memcpy( ( void * ) pvBuffer, ( void * ) pxQueue->u.pcReadFrom, ( size_t ) pxQueue->uxItemSize );
适用于任务中的消息发送函数有三个版本:
xQueueSend() 等同于 xQueueSendToBack(),作用都是将消息插入队列尾部
xQueueSendToFront() 作用是将消息插入队列头部
适用于中断中的消息发送函数也有三个版本(只能用于中断中执行,是不带阻塞机制的):
BaseType_t xQueueSend(QueueHandle_t xQueue,
const void * pvItemToQueue,
TickType_t xTicksToWait);
参数说明
宏定义
#define xQueueSend( xQueue, pvItemToQueue, xTicksToWait ) xQueueGenericSend( ( xQueue ), ( pvItemToQueue ), ( xTicksToWait ), queueSEND_TO_BACK )
该宏是为了向后兼容没有包含 xQueueSendToFront() 和 xQueueSendToBack() 这两个宏的 FreeRTOS 版本。
static void Send_Task(void* parameter)
{
BaseType_t xReturn = pdPASS; /* 定义一个创建信息返回值,默认为 pdPASS */
uint32_t send_data1 = 1;
uint32_t send_data2 = 2;
while (1) {
if (Key_Scan(KEY1_GPIO_PORT, KEY1_GPIO_PIN) == KEY_ON) {
/* K1 被按下 */
printf("发送消息 send_data1!\n");
xReturn = xQueueSend(Test_Queue, /* 消息队列的句柄 */
&send_data1, /* 发送的消息内容 */
0); /* 等待时间 0 */
if (pdPASS == xReturn)
printf("消息 send_data1 发送成功!\n\n");
}
if (Key_Scan(KEY2_GPIO_PORT, KEY2_GPIO_PIN) == KEY_ON) {
/* K2 被按下 */
printf("发送消息 send_data2!\n");
xReturn = xQueueSend(Test_Queue, /* 消息队列的句柄 */
&send_data2, /* 发送的消息内容 */
0); /* 等待时间 0 */
if (pdPASS == xReturn)
printf("消息 send_data2 发送成功!\n\n");
}
vTaskDelay(20); /* 延时 20 个 tick */
}
}
#define xQueueSendToBack( xQueue, pvItemToQueue, xTicksToWait ) xQueueGenericSend( ( xQueue ), ( pvItemToQueue ), ( xTicksToWait ), queueSEND_TO_BACK )
xQueueSendFromISR() 与 xQueueSendToBackFromISR() 是等同的。
BaseType_t xQueueSendFromISR(QueueHandle_t xQueue,
const void *pvItemToQueue,
BaseType_t *pxHigherPriorityTaskWoken);
#define xQueueSendToFrontFromISR(xQueue, pvItemToQueue, pxHigherPriorityTaskWoken) \
xQueueGenericSendFromISR((xQueue), (pvItemToQueue), (pxHigherPriorityTaskWoken), queueSEND_TO_FRONT)
#define xQueueSendToBackFromISR(xQueue, pvItemToQueue, pxHigherPriorityTaskWoken) \
xQueueGenericSendFromISR((xQueue), (pvItemToQueue), (pxHigherPriorityTaskWoken), queueSEND_TO_BACK)
使用 pxHigherPriorityTaskWoken 来确定是否需要上下文切换:
void vBufferISR(void)
{
char cIn;
BaseType_t xHigherPriorityTaskWoken;
/* 在 ISR 开始的时候,我们并没有唤醒任务 */
xHigherPriorityTaskWoken = pdFALSE;
/* 直到缓冲区为空 */
do {
/* 从缓冲区获取一个字节的数据 */
cIn = portINPUT_BYTE(RX_REGISTER_ADDRESS);
/* 发送这个数据 */
xQueueSendFromISR(xRxQueue, &cIn, &xHigherPriorityTaskWoken);
} while (portINPUT_BYTE(BUFFER_COUNT));
/* 这时候 buffer 已经为空,如果需要则进行上下文切换 */
if (xHigherPriorityTaskWoken) {
/* 上下文切换,这是一个宏,不同的处理器,具体的方法不一样 */
taskYIELD_FROM_ISR();
}
}
用于向队列队首发送一个消息,该消息会被优先读取。
BaseType_t xQueueSendToFront( QueueHandle_t xQueue,
const void * pvItemToQueue,
TickType_t xTicksToWait );
#define xQueueSendToFront(xQueue, pvItemToQueue, xTicksToWait) \
xQueueGenericSend((xQueue), (pvItemToQueue), \
(xTicksToWait), queueSEND_TO_FRONT)
用于在中断中向队首发送信息。
BaseType_t xQueueSendToFrontFromISR(QueueHandle_t xQueue,
const void *pvItemToQueue,
BaseType_t *pxHigherPriorityTaskWoken);
#define xQueueSendToFrontFromISR(xQueue, pvItemToQueue, pxHigherPriorityTaskWoken) \
xQueueGenericSendFromISR((xQueue), (pvItemToQueue), \
(pxHigherPriorityTaskWoken), queueSEND_TO_FRONT)
上述的任务中的消息发送函数 API 经过宏定义展开后实际上就是传入不同参数的 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;
configASSERT( pxQueue );
configASSERT( !( ( pvItemToQueue == NULL ) && ( pxQueue->uxItemSize != ( UBaseType_t ) 0U ) ) );
configASSERT( !( ( xCopyPosition == queueOVERWRITE ) && ( pxQueue->uxLength != 1 ) ) );
#if ( ( INCLUDE_xTaskGetSchedulerState == 1 ) || ( configUSE_TIMERS == 1 ) )
{
configASSERT( !( ( xTaskGetSchedulerState() == taskSCHEDULER_SUSPENDED ) && ( xTicksToWait != 0 ) ) );
}
#endif
/* This function relaxes the coding standard somewhat to allow return
statements within the function itself. This is done in the interest
of execution time efficiency. */
for( ;; )
{
taskENTER_CRITICAL();
{
/* Is there room on the queue now? The running task must be the
highest priority task wanting to access the queue. If the head item
in the queue is to be overwritten then it does not matter if the
queue is full. *///消息空间未满或允许覆盖写入
if( ( pxQueue->uxMessagesWaiting < pxQueue->uxLength ) || ( xCopyPosition == queueOVERWRITE ) )
{
traceQUEUE_SEND( pxQueue );
xYieldRequired = prvCopyDataToQueue( pxQueue, pvItemToQueue, xCopyPosition );
//此处省略队列集相关代码
taskEXIT_CRITICAL();
return pdPASS;
}
else//消息空间已满且不允许覆盖写入
{
if( xTicksToWait == ( TickType_t ) 0 )
{
/* The queue was full and no block time is specified (or
the block time has expired) so leave now. */
//消息空间已满且不允许覆盖写入,且阻塞时间设置为0 或 阻塞时间已过
taskEXIT_CRITICAL();
/* Return to the original privilege level before exiting
the function. */
traceQUEUE_SEND_FAILED( pxQueue );
return errQUEUE_FULL;
}
else if( xEntryTimeSet == pdFALSE )
{
/* The queue was full and a block time was specified so
configure the timeout structure. */
//消息空间已满且不允许覆盖写入,并且阻塞时间大于0所以需要配置超时结构
vTaskSetTimeOutState( &xTimeOut );
xEntryTimeSet = pdTRUE;
}
else
{
/* Entry time was already set. */
//已经设置了进入时间
mtCOVERAGE_TEST_MARKER();
}
}
}
taskEXIT_CRITICAL();
/* Interrupts and other tasks can send to and receive from the queue
now the critical section has been exited. */
//挂起调度器 并 锁定队列的等待发送任务列表和等待接收任务列表:
//这两个操作都是为了防止其它任务或者中断操作 xTasksWaitingToReceive 列表和 xTasksWaitingToSend 列表而引起优先级翻转
//问:为什么可能会优先级翻转?
//答:设想当前要进入阻塞的任务B 的优先级比已经在阻塞中的任务A 的优先级高。
//正常情况下,当任务B 也进入阻塞后,此时两个任务都在阻塞中(比如都在阻塞等待入队),
//那么当可以入队时应该是优先级高的B 线解除阻塞并入队;
//但是可能会发生这样一种情况,在设置任务B 进入阻塞的过程中,
//其它任务或者中断操作了 xTasksWaitingToReceive 列表和 xTasksWaitingToSend 列表导致可以入队了,
//正确的逻辑应该是解除优先级较高的任务B 的阻塞而任务A 继续阻塞等待入队,
//但是此时由于任务B 还在设置进入阻塞的过程中,所以阻塞列表中只有任务A,导致只能解除
//任务A 的阻塞。挂起调度器来禁止其它任务对两个列表的操作 和 锁定中断对两个列表的操作可以确保
//在任务B 成功进入阻塞后在通过优先级高低来解除任务的阻塞,防止优先级较低的任务A 先于任务B 解除阻塞。
vTaskSuspendAll(); //暂停任务切换,防止在运行其它任务时该任务修改队列的等待接收和等待发送列表
prvLockQueue( pxQueue ); //锁定队列,防止此时有中断触发修改队列的等待接收和等待发送列表
/* Update the timeout state to see if it has expired yet. */
if( xTaskCheckForTimeOut( &xTimeOut, &xTicksToWait ) == pdFALSE )//延时未到期且还未插进去
{
if( prvIsQueueFull( pxQueue ) != pdFALSE )//如果队列还是满,就把未到期且未成功插入(等待插入)的任务放入 xTasksWaitingToSend 列表中
{
traceBLOCKING_ON_QUEUE_SEND( pxQueue );
vTaskPlaceOnEventList( &( pxQueue->xTasksWaitingToSend ), xTicksToWait );//等待发送的任务放进等待发送列表中
/* Unlocking the queue means queue events can effect the
event list. It is possible that interrupts occurring now
remove this task from the event list again - but as the
scheduler is suspended the task will go onto the pending
ready last instead of the actual ready list. */
prvUnlockQueue( pxQueue );//任务插入完成,解锁中断对两个列表的操作,但此时调度器还是挂起的状态
/* Resuming the scheduler will move tasks from the pending
ready list into the ready list - so it is feasible that this
task is already in a ready list before it yields - in which
case the yield will not cause a context switch unless there
is also a higher priority task in the pending ready list. */
//恢复任务调度器,根据返回值确定是否要进行任务切换
//如果挂起的就绪列表中有优先级较高的任务,恢复后调度器后才需要进行任务切换
if( xTaskResumeAll() == pdFALSE )
{
portYIELD_WITHIN_API();
}
}
else//延时未到期但是队列未满时,解除对队列列表的锁定从而可以再次尝试插入队列
{
/* Try again. */
prvUnlockQueue( pxQueue );
( void ) xTaskResumeAll();
}
}
else//延时到期了还没有插进去,返回由于队列满插入失败
{
/* The timeout has expired. */
prvUnlockQueue( pxQueue );
( void ) xTaskResumeAll();
traceQUEUE_SEND_FAILED( pxQueue );
return errQUEUE_FULL;
}
}
}
/*-----------------------------------------------------------*/
下面将对 xQueueGenericSend() 中调用到的函数进行详细的说明:
if( ( pxQueue->uxMessagesWaiting < pxQueue->uxLength ) || ( xCopyPosition == queueOVERWRITE ) )
{
traceQUEUE_SEND( pxQueue );
xYieldRequired = prvCopyDataToQueue( pxQueue, pvItemToQueue, xCopyPosition );
}
else if( xPosition == queueSEND_TO_BACK )
{
( void ) memcpy( ( void * ) pxQueue->pcWriteTo, pvItemToQueue, ( size_t ) pxQueue->uxItemSize );
pxQueue->pcWriteTo += pxQueue->uxItemSize;
if( pxQueue->pcWriteTo >= pxQueue->pcTail )
{
pxQueue->pcWriteTo = pxQueue->pcHead;
}
}
( void ) memcpy( ( void * ) pxQueue->u.pcReadFrom, pvItemToQueue, ( size_t ) pxQueue->uxItemSize );
pxQueue->u.pcReadFrom -= pxQueue->uxItemSize;
if( pxQueue->u.pcReadFrom < pxQueue->pcHead )
{
pxQueue->u.pcReadFrom = ( pxQueue->pcTail - pxQueue->uxItemSize );
}
static BaseType_t prvCopyDataToQueue( Queue_t * const pxQueue, const void *pvItemToQueue, const BaseType_t xPosition )
{
BaseType_t xReturn = pdFALSE;
UBaseType_t uxMessagesWaiting;
/* This function is called from a critical section. */
uxMessagesWaiting = pxQueue->uxMessagesWaiting;
if( pxQueue->uxItemSize == ( UBaseType_t ) 0 )
{
#if ( configUSE_MUTEXES == 1 )
{
if( pxQueue->uxQueueType == queueQUEUE_IS_MUTEX )
{
/* The mutex is no longer being held. */
xReturn = xTaskPriorityDisinherit( ( void * ) pxQueue->pxMutexHolder );
pxQueue->pxMutexHolder = NULL;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
#endif /* configUSE_MUTEXES */
}
else if( xPosition == queueSEND_TO_BACK )
{
( void ) memcpy( ( void * ) pxQueue->pcWriteTo, pvItemToQueue, ( size_t ) pxQueue->uxItemSize ); /*lint !e961 !e418 MISRA exception as the casts are only redundant for some ports, plus previous logic ensures a null pointer can only be passed to memcpy() if the copy size is 0. */
pxQueue->pcWriteTo += pxQueue->uxItemSize;
if( pxQueue->pcWriteTo >= pxQueue->pcTail ) /*lint !e946 MISRA exception justified as comparison of pointers is the cleanest solution. */
{
pxQueue->pcWriteTo = pxQueue->pcHead;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
( void ) memcpy( ( void * ) pxQueue->u.pcReadFrom, pvItemToQueue, ( size_t ) pxQueue->uxItemSize ); /*lint !e961 MISRA exception as the casts are only redundant for some ports. */
pxQueue->u.pcReadFrom -= pxQueue->uxItemSize;
if( pxQueue->u.pcReadFrom < pxQueue->pcHead ) /*lint !e946 MISRA exception justified as comparison of pointers is the cleanest solution. */
{
pxQueue->u.pcReadFrom = ( pxQueue->pcTail - pxQueue->uxItemSize );
}
else
{
mtCOVERAGE_TEST_MARKER();
}
if( xPosition == queueOVERWRITE )
{
if( uxMessagesWaiting > ( UBaseType_t ) 0 )
{
/* An item is not being added but overwritten, so subtract
one from the recorded number of items in the queue so when
one is added again below the number of recorded items remains
correct. */
--uxMessagesWaiting;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
pxQueue->uxMessagesWaiting = uxMessagesWaiting + 1;
return xReturn;
}
/*-----------------------------------------------------------*/
vTaskSetTimeOutState( &xTimeOut );
xEntryTimeSet = pdTRUE;
void vTaskSetTimeOutState( TimeOut_t * const pxTimeOut )
{
configASSERT( pxTimeOut );
pxTimeOut->xOverflowCount = xNumOfOverflows; //溢出次数
pxTimeOut->xTimeOnEntering = xTickCount; //进入时间 == 当前时间
}
//挂起调度器 并 锁定队列的等待发送任务列表和等待接收任务列表:
//这两个操作都是为了防止其它任务或者中断操作 xTasksWaitingToReceive 列表 xTasksWaitingToSend 列表而引起优先级翻转
vTaskSuspendAll(); //暂停任务切换,防止在运行其它任务时该任务修改队列的等待接收和等待发送列表
prvLockQueue( pxQueue ); //锁定队列,防止此时有中断触发修改队列的等待接收和等待发送列表
对于挂起调度器和锁上队列的意义,笔者思考了很久,如下:
挂起调度器 并 锁定队列的等待发送任务列表和等待接收任务列表:这两个操作都是为了防止其它任务或者中断操作 xTasksWaitingToReceive 列表和 xTasksWaitingToSend 列表而引起优先级翻转。
当要使任务进入阻塞时,假设此任务(任务B)的优先级比已经在阻塞中的任务(任务A)优先级高。
正常情况下,两个任务都应该在阻塞列表中等待操作,当可以进行队列操作时,应该优先解除优先级高的任务B的阻塞并进行操作。
但是在某种情况下,可能发生在设置任务B进入阻塞的过程中,有其他任务或者中断对等待接收和等待发送列表进行了操作,使得可以进行队列操作。
为了防止这种情况发生,代码中使用了两个控制操作:
通过挂起调度器和锁定队列,可以确保在任务B成功进入阻塞状态后,根据优先级高低来解除任务阻塞,防止优先级较低的任务A先于任务B解除阻塞,从而避免了优先级翻转的问题。
这样的保护措施可以确保在任务切换及队列操作过程中,保持任务的正确优先级顺序和避免优先级翻转对系统的影响。
挂起调度器的代码在前面的文章中我们已经介绍过,这里不再赘述。
【学习日记】【FreeRTOS】调度器函数实现详解
此处介绍队列上锁代码:
/*
* Macro to mark a queue as locked. Locking a queue prevents an ISR from
* accessing the queue event lists.
*/
#define prvLockQueue( pxQueue ) \
taskENTER_CRITICAL(); \
{ \
if( ( pxQueue )->cRxLock == queueUNLOCKED ) \
{ \
( pxQueue )->cRxLock = queueLOCKED_UNMODIFIED; \
} \
if( ( pxQueue )->cTxLock == queueUNLOCKED ) \
{ \
( pxQueue )->cTxLock = queueLOCKED_UNMODIFIED; \
} \
} \
taskEXIT_CRITICAL()
/*-----------------------------------------------------------*/
传入超时状态结构和设置的超时时长进行检查:
if( xTaskCheckForTimeOut( &xTimeOut, &xTicksToWait ) == pdFALSE )
{
//...
}
BaseType_t xTaskCheckForTimeOut( TimeOut_t * const pxTimeOut, TickType_t * const pxTicksToWait )
{
BaseType_t xReturn;
configASSERT( pxTimeOut );
configASSERT( pxTicksToWait );
taskENTER_CRITICAL();
{
/* Minor optimisation. The tick count cannot change in this block. */
const TickType_t xConstTickCount = xTickCount;
#if( INCLUDE_xTaskAbortDelay == 1 )
if( pxCurrentTCB->ucDelayAborted != pdFALSE )
{
/* The delay was aborted, which is not the same as a time out,
but has the same result. */
pxCurrentTCB->ucDelayAborted = pdFALSE;
xReturn = pdTRUE;
}
else
#endif
#if ( INCLUDE_vTaskSuspend == 1 )
if( *pxTicksToWait == portMAX_DELAY )
{
/* If INCLUDE_vTaskSuspend is set to 1 and the block time
specified is the maximum block time then the task should block
indefinitely, and therefore never time out. */
xReturn = pdFALSE;
}
else
#endif
if( ( xNumOfOverflows != pxTimeOut->xOverflowCount ) && ( xConstTickCount >= pxTimeOut->xTimeOnEntering ) ) /*lint !e525 Indentation preferred as is to make code within pre-processor directives clearer. */
{
/* The tick count is greater than the time at which
vTaskSetTimeout() was called, but has also overflowed since
vTaskSetTimeOut() was called. It must have wrapped all the way
around and gone past again. This passed since vTaskSetTimeout()
was called. */
//现在的时间比进入延时的时间大 且 现在的时间计时溢出过
//意味着时基计数已经跑过一轮了,延时肯定到期了
xReturn = pdTRUE; //延时肯定到期了
}
else if( ( ( TickType_t ) ( xConstTickCount - pxTimeOut->xTimeOnEntering ) ) < *pxTicksToWait ) /*lint !e961 Explicit casting is only redundant with some compilers, whereas others require it to prevent integer conversion errors. */
{//当前时间减进入时间 < 需要等待的时间,意味着延时还未到期
/* Not a genuine timeout. Adjust parameters for time remaining. */
//现在需要等待的时间 = 原来需要等待的时间 - 已经等待的时间
*pxTicksToWait -= ( xConstTickCount - pxTimeOut->xTimeOnEntering );
vTaskSetTimeOutState( pxTimeOut );
xReturn = pdFALSE; //延时没有到期
}
else
{
xReturn = pdTRUE;
}
}
taskEXIT_CRITICAL();
return xReturn;
}
用于将延时未到期且暂时无法入队的任务插入任务等待列表:
vTaskPlaceOnEventList( &( pxQueue->xTasksWaitingToSend ), xTicksToWait );//等待发送的任务放进等待发送列表中
void vTaskPlaceOnEventList( List_t * const pxEventList, const TickType_t xTicksToWait )
{
configASSERT( pxEventList );
/* THIS FUNCTION MUST BE CALLED WITH EITHER INTERRUPTS DISABLED OR THE
SCHEDULER SUSPENDED AND THE QUEUE BEING ACCESSED LOCKED. */
/* Place the event list item of the TCB in the appropriate event list.
This is placed in the list in priority order so the highest priority task
is the first to be woken by the event. The queue that contains the event
list is locked, preventing simultaneous access from interrupts. */
vListInsert( pxEventList, &( pxCurrentTCB->xEventListItem ) );
prvAddCurrentTaskToDelayedList( xTicksToWait, pdTRUE );
}
/*-----------------------------------------------------------*/
/* Constants used with the cRxLock and cTxLock structure members. */
#define queueUNLOCKED ( ( int8_t ) -1 )
#define queueLOCKED_UNMODIFIED ( ( int8_t ) 0 )
static void prvUnlockQueue( Queue_t * const pxQueue )
{
/* THIS FUNCTION MUST BE CALLED WITH THE SCHEDULER SUSPENDED. */
/* The lock counts contains the number of extra data items placed or
removed from the queue while the queue was locked. When a queue is
locked items can be added or removed, but the event lists cannot be
updated. */
taskENTER_CRITICAL();
{
int8_t cTxLock = pxQueue->cTxLock;
/* See if data was added to the queue while it was locked. */
while( cTxLock > queueLOCKED_UNMODIFIED )
{
/* Data was posted while the queue was locked. Are any tasks
blocked waiting for data to become available? */
#if ( configUSE_QUEUE_SETS == 1 )
{
if( pxQueue->pxQueueSetContainer != NULL )
{
if( prvNotifyQueueSetContainer( pxQueue, queueSEND_TO_BACK ) != pdFALSE )
{
/* The queue is a member of a queue set, and posting to
the queue set caused a higher priority task to unblock.
A context switch is required. */
vTaskMissedYield();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
/* Tasks that are removed from the event list will get
added to the pending ready list as the scheduler is still
suspended. */
if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToReceive ) ) == pdFALSE )
{
if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToReceive ) ) != pdFALSE )
{
/* The task waiting has a higher priority so record that a
context switch is required. */
vTaskMissedYield();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
break;
}
}
}
#else /* configUSE_QUEUE_SETS */
{
/* Tasks that are removed from the event list will get added to
the pending ready list as the scheduler is still suspended. */
if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToReceive ) ) == pdFALSE )
{
if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToReceive ) ) != pdFALSE )
{
/* The task waiting has a higher priority so record that
a context switch is required. */
vTaskMissedYield();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
break;
}
}
#endif /* configUSE_QUEUE_SETS */
--cTxLock;
}
pxQueue->cTxLock = queueUNLOCKED;
}
taskEXIT_CRITICAL();
/* Do the same for the Rx lock. */
taskENTER_CRITICAL();
{
int8_t cRxLock = pxQueue->cRxLock;
while( cRxLock > queueLOCKED_UNMODIFIED )
{
if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToSend ) ) == pdFALSE )
{
if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToSend ) ) != pdFALSE )
{
vTaskMissedYield();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
--cRxLock;
}
else
{
break;
}
}
pxQueue->cRxLock = queueUNLOCKED;
}
taskEXIT_CRITICAL();
}
/*-----------------------------------------------------------*/
这段代码是 FreeRTOS 中的 prvUnlockQueue 函数的实现。以下是函数执行的主要步骤:
可以看到,忽略锁的计数器操作(锁的计数器主要用于对队列的并发访问),3 和 6 说明只要调用这个函数,队列的等待发送任务列表和等待接收任务列表都会解锁。
在FreeRTOS中,队列的cRxLock
大于0的情况是在调用接收数据的API(比如xQueueReceive()
函数)时。这是由于队列需要在多任务环境中保持线程安全性。当一个任务正在从队列接收数据时,会通过增加cRxLock
的计数来锁定队列,防止其他任务同时进行接收操作。
具体来说,以下情况会使得cRxLock
大于0:
cRxLock
值增加到 1。cRxLock
值增加到 2。cRxLock
值减少到 1。cRxLock
值减少到 0。这个锁定机制确保了队列在多任务环境中的正确操作,避免了竞态条件的发生。
上述的任务中的消息发送函数 API 经过宏定义展开后实际上就是传入不同参数的 xQueueGenericSendFromISR(),下面将对这个函数进行详细的介绍。
BaseType_t xQueueGenericSendFromISR( QueueHandle_t xQueue, const void * const pvItemToQueue, BaseType_t * const pxHigherPriorityTaskWoken, const BaseType_t xCopyPosition )
{
BaseType_t xReturn;
UBaseType_t uxSavedInterruptStatus;
Queue_t * const pxQueue = ( Queue_t * ) xQueue;
configASSERT( pxQueue );
configASSERT( !( ( pvItemToQueue == NULL ) && ( pxQueue->uxItemSize != ( UBaseType_t ) 0U ) ) );
configASSERT( !( ( xCopyPosition == queueOVERWRITE ) && ( pxQueue->uxLength != 1 ) ) );
/* RTOS ports that support interrupt nesting have the concept of a maximum
system call (or maximum API call) interrupt priority. Interrupts that are
above the maximum system call priority are kept permanently enabled, even
when the RTOS kernel is in a critical section, but cannot make any calls to
FreeRTOS API functions. If configASSERT() is defined in FreeRTOSConfig.h
then portASSERT_IF_INTERRUPT_PRIORITY_INVALID() will result in an assertion
failure if a FreeRTOS API function is called from an interrupt that has been
assigned a priority above the configured maximum system call priority.
Only FreeRTOS functions that end in FromISR can be called from interrupts
that have been assigned a priority at or (logically) below the maximum
system call interrupt priority. FreeRTOS maintains a separate interrupt
safe API to ensure interrupt entry is as fast and as simple as possible.
More information (albeit Cortex-M specific) is provided on the following
link: http://www.freertos.org/RTOS-Cortex-M3-M4.html */
portASSERT_IF_INTERRUPT_PRIORITY_INVALID();
/* Similar to xQueueGenericSend, except without blocking if there is no room
in the queue. Also don't directly wake a task that was blocked on a queue
read, instead return a flag to say whether a context switch is required or
not (i.e. has a task with a higher priority than us been woken by this
post). */
uxSavedInterruptStatus = portSET_INTERRUPT_MASK_FROM_ISR();
{
if( ( pxQueue->uxMessagesWaiting < pxQueue->uxLength ) || ( xCopyPosition == queueOVERWRITE ) )
{
const int8_t cTxLock = pxQueue->cTxLock;
traceQUEUE_SEND_FROM_ISR( pxQueue );
/* Semaphores use xQueueGiveFromISR(), so pxQueue will not be a
semaphore or mutex. That means prvCopyDataToQueue() cannot result
in a task disinheriting a priority and prvCopyDataToQueue() can be
called here even though the disinherit function does not check if
the scheduler is suspended before accessing the ready lists. */
( void ) prvCopyDataToQueue( pxQueue, pvItemToQueue, xCopyPosition );
/* The event list is not altered if the queue is locked. This will
be done when the queue is unlocked later. */
if( cTxLock == queueUNLOCKED )
{
#if ( configUSE_QUEUE_SETS == 1 )
{
if( pxQueue->pxQueueSetContainer != NULL )
{
if( prvNotifyQueueSetContainer( pxQueue, xCopyPosition ) != pdFALSE )
{
/* The queue is a member of a queue set, and posting
to the queue set caused a higher priority task to
unblock. A context switch is required. */
if( pxHigherPriorityTaskWoken != NULL )
{
*pxHigherPriorityTaskWoken = pdTRUE;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToReceive ) ) == pdFALSE )
{
if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToReceive ) ) != pdFALSE )
{
/* The task waiting has a higher priority so
record that a context switch is required. */
if( pxHigherPriorityTaskWoken != NULL )
{
*pxHigherPriorityTaskWoken = pdTRUE;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
}
#else /* configUSE_QUEUE_SETS */
{
if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToReceive ) ) == pdFALSE )
{
if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToReceive ) ) != pdFALSE )
{
/* The task waiting has a higher priority so record that a
context switch is required. */
if( pxHigherPriorityTaskWoken != NULL )
{
*pxHigherPriorityTaskWoken = pdTRUE;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
#endif /* configUSE_QUEUE_SETS */
}
else
{
/* Increment the lock count so the task that unlocks the queue
knows that data was posted while it was locked. */
pxQueue->cTxLock = ( int8_t ) ( cTxLock + 1 );
}
xReturn = pdPASS;
}
else
{
traceQUEUE_SEND_FROM_ISR_FAILED( pxQueue );
xReturn = errQUEUE_FULL;
}
}
portCLEAR_INTERRUPT_MASK_FROM_ISR( uxSavedInterruptStatus );
return xReturn;
}
/*-----------------------------------------------------------*/
BaseType_t xQueueReceive(QueueHandle_t xQueue,
void *pvBuffer,
TickType_t xTicksToWait);
#define xQueueReceive( xQueue, pvBuffer, xTicksToWait ) \
xQueueGenericReceive( ( xQueue ), ( pvBuffer ), \
( xTicksToWait ), pdFALSE )
static void Receive_Task(void* parameter)
{
BaseType_t xReturn = pdTRUE; /* 定义一个创建信息返回值,默认为pdPASS */
uint32_t r_queue; /* 定义一个接收消息的变量 */
while (1) {
xReturn = xQueueReceive(Test_Queue, /* 消息队列的句柄 */
&r_queue, /* 接收的消息内容 */
portMAX_DELAY); /* 等待时间 一直等 */
if (pdTRUE == xReturn)
printf("本次接收到的数据是:%d\n\n", r_queue);
else
printf("数据接收出错,错误代码: 0x%lx\n", xReturn);
}
}
xQueuePeek()函数接收消息完毕不会删除消息队列中的消息。
#define xQueuePeek( xQueue, pvBuffer, xTicksToWait ) \
xQueueGenericReceive( ( xQueue ), ( pvBuffer ), \
( xTicksToWait ), pdTRUE )
QueueHandle_t xQueue;
/* 创建一个队列,并往队列里面发送一些数据 */
void vAFunction(void *pvParameters)
{
char cValueToPost;
const TickType_t xTicksToWait = (TickType_t)0xff;
/* 创建一个可以容纳10个字符的队列 */
xQueue = xQueueCreate(10, sizeof(char));
if (xQueue == 0) {
/* 队列创建失败 */
}
/* ... 任务其他代码 */
/* 往队列里面发送两个字符
如果队列满了则等待xTicksToWait个系统节拍周期*/
cValueToPost = 'a';
xQueueSend(xQueue, (void *)&cValueToPost, xTicksToWait);
cValueToPost = 'b';
xQueueSend(xQueue, (void *)&cValueToPost, xTicksToWait);
/* 继续往队列里面发送字符
当队列满的时候该任务将被阻塞 */
cValueToPost = 'c';
xQueueSend(xQueue, (void *)&cValueToPost, xTicksToWait);
}
/* 中断服务程序:输出所有从队列中接收到的字符 */
void vISR_Routine(void)
{
BaseType_t xTaskWokenByReceive = pdFALSE;
char cRxedChar;
while (xQueueReceiveFromISR(xQueue,
(void *)&cRxedChar,
&xTaskWokenByReceive)) {
/* 接收到一个字符,然后输出这个字符 */
vOutputCharacter(cRxedChar);
/* 如果从队列移除一个字符串后唤醒了向此队列投递字符的任务,
那么参数xTaskWokenByReceive将会设置成pdTRUE,这个循环无论重复多少次,
仅会有一个任务被唤醒 */
}
if (xTaskWokenByReceive != pdFALSE) {
/* 我们应该进行一次上下文切换,当ISR返回的时候则执行另外一个任务 */
/* 这是一个上下文切换的宏,不同的处理器,具体处理的方式不一样 */
taskYIELD();
}
}
这段代码是FreeRTOS中的xQueueGenericReceive函数的实现。以下是函数执行的主要步骤:
总之,这个函数的主要功能是从队列中接收数据,并在数据不可用时进行阻塞等待,直到有数据可用或超时。它还处理了互斥队列和任务优先级的协调。
BaseType_t xQueueGenericReceive( QueueHandle_t xQueue, void * const pvBuffer, TickType_t xTicksToWait, const BaseType_t xJustPeeking )
{
BaseType_t xEntryTimeSet = pdFALSE;
TimeOut_t xTimeOut;
int8_t *pcOriginalReadPosition;
Queue_t * const pxQueue = ( Queue_t * ) xQueue;
configASSERT( pxQueue );
configASSERT( !( ( pvBuffer == NULL ) && ( pxQueue->uxItemSize != ( UBaseType_t ) 0U ) ) );
#if ( ( INCLUDE_xTaskGetSchedulerState == 1 ) || ( configUSE_TIMERS == 1 ) )
{
configASSERT( !( ( xTaskGetSchedulerState() == taskSCHEDULER_SUSPENDED ) && ( xTicksToWait != 0 ) ) );
}
#endif
/* This function relaxes the coding standard somewhat to allow return
statements within the function itself. This is done in the interest
of execution time efficiency. */
for( ;; )
{
taskENTER_CRITICAL();
{
const UBaseType_t uxMessagesWaiting = pxQueue->uxMessagesWaiting;
/* Is there data in the queue now? To be running the calling task
must be the highest priority task wanting to access the queue. */
if( uxMessagesWaiting > ( UBaseType_t ) 0 )
{
/* Remember the read position in case the queue is only being
peeked. */
pcOriginalReadPosition = pxQueue->u.pcReadFrom;
prvCopyDataFromQueue( pxQueue, pvBuffer );
if( xJustPeeking == pdFALSE )
{
traceQUEUE_RECEIVE( pxQueue );
/* Actually removing data, not just peeking. */
pxQueue->uxMessagesWaiting = uxMessagesWaiting - 1;
#if ( configUSE_MUTEXES == 1 )
{
if( pxQueue->uxQueueType == queueQUEUE_IS_MUTEX )
{
/* Record the information required to implement
priority inheritance should it become necessary. */
pxQueue->pxMutexHolder = ( int8_t * ) pvTaskIncrementMutexHeldCount(); /*lint !e961 Cast is not redundant as TaskHandle_t is a typedef. */
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
#endif /* configUSE_MUTEXES */
if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToSend ) ) == pdFALSE )
{
if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToSend ) ) != pdFALSE )
{
queueYIELD_IF_USING_PREEMPTION();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
traceQUEUE_PEEK( pxQueue );
/* The data is not being removed, so reset the read
pointer. */
pxQueue->u.pcReadFrom = pcOriginalReadPosition;
/* The data is being left in the queue, so see if there are
any other tasks waiting for the data. */
if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToReceive ) ) == pdFALSE )
{
if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToReceive ) ) != pdFALSE )
{
/* The task waiting has a higher priority than this task. */
queueYIELD_IF_USING_PREEMPTION();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
taskEXIT_CRITICAL();
return pdPASS;
}
else
{
if( xTicksToWait == ( TickType_t ) 0 )
{
/* The queue was empty and no block time is specified (or
the block time has expired) so leave now. */
taskEXIT_CRITICAL();
traceQUEUE_RECEIVE_FAILED( pxQueue );
return errQUEUE_EMPTY;
}
else if( xEntryTimeSet == pdFALSE )
{
/* The queue was empty and a block time was specified so
configure the timeout structure. */
vTaskSetTimeOutState( &xTimeOut );
xEntryTimeSet = pdTRUE;
}
else
{
/* Entry time was already set. */
mtCOVERAGE_TEST_MARKER();
}
}
}
taskEXIT_CRITICAL();
/* Interrupts and other tasks can send to and receive from the queue
now the critical section has been exited. */
vTaskSuspendAll();
prvLockQueue( pxQueue );
/* Update the timeout state to see if it has expired yet. */
if( xTaskCheckForTimeOut( &xTimeOut, &xTicksToWait ) == pdFALSE )
{
if( prvIsQueueEmpty( pxQueue ) != pdFALSE )
{
traceBLOCKING_ON_QUEUE_RECEIVE( pxQueue );
#if ( configUSE_MUTEXES == 1 )
{
if( pxQueue->uxQueueType == queueQUEUE_IS_MUTEX )
{
taskENTER_CRITICAL();
{
vTaskPriorityInherit( ( void * ) pxQueue->pxMutexHolder );
}
taskEXIT_CRITICAL();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
#endif
vTaskPlaceOnEventList( &( pxQueue->xTasksWaitingToReceive ), xTicksToWait );
prvUnlockQueue( pxQueue );
if( xTaskResumeAll() == pdFALSE )
{
portYIELD_WITHIN_API();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
/* Try again. */
prvUnlockQueue( pxQueue );
( void ) xTaskResumeAll();
}
}
else
{
prvUnlockQueue( pxQueue );
( void ) xTaskResumeAll();
if( prvIsQueueEmpty( pxQueue ) != pdFALSE )
{
traceQUEUE_RECEIVE_FAILED( pxQueue );
return errQUEUE_EMPTY;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
}
}
/*-----------------------------------------------------------*/
无论是发送或者是接收消息都是以拷贝的方式进行,如果消息过于庞大,可以将消息的地址作为消息进行发送、接收。
如果您觉得本文写得不错,可以点个赞激励一下作者!
如果您发现本文的问题,欢迎在评论区或者私信共同探讨!
共勉!