学习FreeRTOS(五) - 任务通信

d. 任务之间或者任务和中断之间如何通信

在裸机编程的时候,任务之间或者任务和中断之间,一般通过全局变量来传递消息。而在freeRTOS系统下,通过queue(队列)的机制来完成消息传递。个人认为,queue实际上也是全局变量,不过,他的功能更加强大, 更加通用,可以实现消息大小配置,异步读写,对消息的保护机制(比如说任务和中断同时对消息写入),以及支持不同的功能(mutex, semaphore)等。我们来学习一下吧。

消息的定义,创建

/*

 * Definition of the queue used by the scheduler.

 * Items are queued by copy, not reference.  See the following link for the

 * rationale: http://www.freertos.org/Embedded-RTOS-Queues.html

 */

typedef struct QueueDefinition

{

       int8_t *pcHead;                               /*< Points to the beginning of the queue storage area. */

       int8_t *pcTail;                               /*< Points to the byte at the end of the queue storage area.  Once more byte is allocated than necessary to store the queue items, this is used as a marker. */

       int8_t *pcWriteTo;                      /*< Points to the free next place in the storage area. */

       union                                          /* Use of a union is an exception to the coding standard to ensure two mutually exclusive structure members don't appear simultaneously (wasting RAM). */

       {

             int8_t *pcReadFrom;              /*< Points to the last place that a queued item was read from when the structure is used as a queue. */

             UBaseType_t uxRecursiveCallCount;/*< Maintains a count of the number of times a recursive mutex has been recursively 'taken' when the structure is used as a mutex. */

       } u;

       List_t xTasksWaitingToSend;            /*< List of tasks that are blocked waiting to post onto this queue.  Stored in priority order. */

       List_t xTasksWaitingToReceive;   /*< List of tasks that are blocked waiting to read from this queue.  Stored in priority order. */

       volatile UBaseType_t uxMessagesWaiting;/*< The number of items currently in the queue. */

       UBaseType_t uxLength;                   /*< The length of the queue defined as the number of items it will hold, not the number of bytes. */

       UBaseType_t uxItemSize;                 /*< The size of each items that the queue will hold. */

       volatile int8_t cRxLock;         /*< Stores the number of items received from the queue (removed from the queue) while the queue was locked.  Set to queueUNLOCKED when the queue is not locked. */

       volatile int8_t cTxLock;         /*< Stores the number of items transmitted to the queue (added to the queue) while the queue was locked.  Set to queueUNLOCKED when the queue is not locked. */

       #if( ( configSUPPORT_STATIC_ALLOCATION == 1 ) && ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) )

             uint8_t ucStaticallyAllocated;   /*< Set to pdTRUE if the memory used by the queue was statically allocated to ensure no attempt is made to free the memory. */

       #endif

       #if ( configUSE_QUEUE_SETS == 1 )

             struct QueueDefinition *pxQueueSetContainer;

       #endif

       #if ( configUSE_TRACE_FACILITY == 1 )

             UBaseType_t uxQueueNumber;

             uint8_t ucQueueType;

       #endif

} xQUEUE;

1.pcHead:指向队列消息存储区起始位置,即第一个消息空间。

2. pcWriteTo :指向队列消息存储区下一个可用消息空间。(一般就是从pcWriteTo 位置入队消息即尾插)

3.一个联合体变量: 当用于队列时使用xQueue结构体变量,当用于信号量 时使用xSemaphore结构体变量,详情看下图。

pcTail:队列存储区域的结束地址,与pcHead一样一个指向开始地址一个指向结束地址,他们只是一个一头一尾的标识,在入队出队的时候他们并不会改变。

pcReadFrom:最后一次读取队列的位置。

uxRecursiveCallCount: 当用作递归互斥量的时候,用来记录递归互斥量被调用的次数(????)

4.xTasksWaitingToSend :发送消息阻塞列表,看英文意思也知道:等待发送的任务,也就是队列已满,任务想要发送消息到队列(入队),如果设定了阻塞时间,任务就会挂入该列表,表示任务已阻塞,任务会按照优先级进行排序(后面解除阻塞就是按照任务的优先级:当队列不为满了,xTasksWaitingToSend 列表中优先级高的就会先被唤醒)

5.xTasksWaitingToReceive:等待消息阻塞列表,看英文意思也知道:等待接收的任务,也就是队列已空,任务想要从队列中读取消息(出队),如果设定了阻塞时间,任务就会挂入该列表,表示任务已阻塞,任务会按照优先级进行排序(后面解除阻塞就是按照任务的优先级:当队列不为空了,xTasksWaitingToReceive列表中优先级高的就会先被唤醒)

其实这两条阻塞列表就是队列的核心之一,当然这里只是讲了个大概,更多细节看源码分析。

6.uxMessagesWaiting:用于记录当前消息队列的消息个数,如果消息

队列被用于信号量的时候,这个值就表示有效信号量个数。

7.uxLength:表示队列的长度,表示一共能存放多少消息。

8.uxItemSize:表示单个消息的大小(单位为字节)。

9.cRxLock:队列上锁后,从队列接收(从队列中删除)的出队项目数。 如果队列没有上锁,设置为queueUNLOCKED

10.cTxLock:队列上锁后,传输到队列(添加到队列)的入队项目数。 如果队列没有上锁,设置为queueUNLOCKED

创建一个queue

QueueHandle_t xQueueGenericCreate( const UBaseType_t uxQueueLength, const UBaseType_t uxItemSize, const uint8_t ucQueueType )

       {

       Queue_t *pxNewQueue;

       size_t xQueueSizeInBytes;

       uint8_t *pucQueueStorage;

             configASSERT( uxQueueLength > ( UBaseType_t ) 0 );

             if( uxItemSize == ( UBaseType_t ) 0 )

             {

                    /* There is not going to be a queue storage area. */

                    xQueueSizeInBytes = ( size_t ) 0;

             }

             else

             {

                    /* Allocate enough space to hold the maximum number of items that

                    can be in the queue at any time. */

                    xQueueSizeInBytes = ( size_t ) ( uxQueueLength * uxItemSize ); /*lint !e961 MISRA exception as the casts are only redundant for some ports. */

             }

             pxNewQueue = ( Queue_t * ) pvPortMalloc( sizeof( Queue_t ) + xQueueSizeInBytes );

             if( pxNewQueue != NULL )

             {

                    /* Jump past the queue structure to find the location of the queue

                    storage area. */

                    pucQueueStorage = ( ( uint8_t * ) pxNewQueue ) + sizeof( Queue_t );

                    #if( configSUPPORT_STATIC_ALLOCATION == 1 )

                    {

                           /* Queues can be created either statically or dynamically, so

                           note this task was created dynamically in case it is later

                           deleted. */

                           pxNewQueue->ucStaticallyAllocated = pdFALSE;

                    }

                    #endif /* configSUPPORT_STATIC_ALLOCATION */

                    prvInitialiseNewQueue( uxQueueLength, uxItemSize, pucQueueStorage, ucQueueType, pxNewQueue );

             }

             else

             {

                    traceQUEUE_CREATE_FAILED( ucQueueType );

             }

             return pxNewQueue;

       }

分配内存 (uxQueueLength * uxItemSize)

初始化queue

static void prvInitialiseNewQueue( const UBaseType_t uxQueueLength, const UBaseType_t uxItemSize, uint8_t *pucQueueStorage, const uint8_t ucQueueType, Queue_t *pxNewQueue )

{

       /* Remove compiler warnings about unused parameters should

       configUSE_TRACE_FACILITY not be set to 1. */

       ( void ) ucQueueType;

       if( uxItemSize == ( UBaseType_t ) 0 )

       {

             /* No RAM was allocated for the queue storage area, but PC head cannot

             be set to NULL because NULL is used as a key to say the queue is used as

             a mutex.  Therefore just set pcHead to point to the queue as a benign

             value that is known to be within the memory map. */

             pxNewQueue->pcHead = ( int8_t * ) pxNewQueue;

       }

       else

       {

             /* Set the head to the start of the queue storage area. */

             pxNewQueue->pcHead = ( int8_t * ) pucQueueStorage;

       }

       /* Initialise the queue members as described where the queue type is

       defined. */

       pxNewQueue->uxLength = uxQueueLength;

       pxNewQueue->uxItemSize = uxItemSize;

       ( void ) xQueueGenericReset( pxNewQueue, pdTRUE );

       #if ( configUSE_TRACE_FACILITY == 1 )

       {

             pxNewQueue->ucQueueType = ucQueueType;

       }

       #endif /* configUSE_TRACE_FACILITY */

       #if( configUSE_QUEUE_SETS == 1 )

       {

             pxNewQueue->pxQueueSetContainer = NULL;

       }

       #endif /* configUSE_QUEUE_SETS */

       traceQUEUE_CREATE( pxNewQueue );

}

Reset queue里面的参数。

BaseType_t xQueueGenericReset( QueueHandle_t xQueue, BaseType_t xNewQueue )

{

Queue_t * const pxQueue = ( Queue_t * ) xQueue;

       configASSERT( pxQueue );

       taskENTER_CRITICAL();

       {

             pxQueue->pcTail = pxQueue->pcHead + ( pxQueue->uxLength * pxQueue->uxItemSize );

             pxQueue->uxMessagesWaiting = ( UBaseType_t ) 0U;

             pxQueue->pcWriteTo = pxQueue->pcHead;

             pxQueue->u.pcReadFrom = pxQueue->pcHead + ( ( pxQueue->uxLength - ( UBaseType_t ) 1U ) * pxQueue->uxItemSize );

             pxQueue->cRxLock = queueUNLOCKED;

             pxQueue->cTxLock = queueUNLOCKED;

             if( xNewQueue == pdFALSE )

             {

                    /* If there are tasks blocked waiting to read from the queue, then

                    the tasks will remain blocked as after this function exits the queue

                    will still be empty.  If there are tasks blocked waiting to write to

                    the queue, then one should be unblocked as after this function exits

                    it will be possible to write to it. */

                    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

             {

                    /* Ensure the event queues start in the correct state. */

                    vListInitialise( &( pxQueue->xTasksWaitingToSend ) );

                    vListInitialise( &( pxQueue->xTasksWaitingToReceive ) );

             }

       }

       taskEXIT_CRITICAL();

       /* A value is returned for calling semantic consistency with previous

       versions. */

       return pdPASS;

}

假设我们申请了4个队列项,每个队列项占用32字节存储空间(即uxLength=4uxItemSize=32),则队列内存分布如下图所示

学习FreeRTOS(五) - 任务通信_第1张图片

vListInitialise( &( pxQueue->xTasksWaitingToSend ) );

vListInitialise( &( pxQueue->xTasksWaitingToReceive ) );

创建两个阻塞列表, 当queue full的时候,把发消息的任务放到waiting to send 列表里面。当queue empty的时候,把收消息的任务放到waiting to receive 列表里面

消息发送与接收

freeRTOS提供了一些消息收发的API接口函数如下:

学习FreeRTOS(五) - 任务通信_第2张图片

这几个函数都会调用xQueueGenericSend(),只是它们写入的方式会不一样( head / tail/ overrite )。

#define xQueueSend( xQueue, pvItemToQueue, xTicksToWait ) xQueueGenericSend( ( xQueue ), ( pvItemToQueue ), ( xTicksToWait ), queueSEND_TO_BACK )

#define xQueueSendToFront( xQueue, pvItemToQueue, xTicksToWait ) xQueueGenericSend( ( xQueue ), ( pvItemToQueue ), ( xTicksToWait ), queueSEND_TO_FRONT )

#define xQueueOverwrite( xQueue, pvItemToQueue ) xQueueGenericSend( ( xQueue ), ( pvItemToQueue ), 0, queueOVERWRITE )

走进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( ;; )

       {

// 进入临界区,防止突然来的interrupt更改queue

             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. */

//如果queue没满

                    if( ( pxQueue->uxMessagesWaiting < pxQueue->uxLength ) || ( xCopyPosition == queueOVERWRITE ) )

                    {

                           traceQUEUE_SEND( pxQueue );

                           // 按照Position来拷贝数据

                           xYieldRequired = prvCopyDataToQueue( pxQueue, pvItemToQueue, xCopyPosition );

                           #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. */

                                               queueYIELD_IF_USING_PREEMPTION();

                                        }

                                        else

                                        {

                                               mtCOVERAGE_TEST_MARKER();

                                        }

                                 }

                                 else

                                  {

                                        /* If there was a task waiting for data to arrive on the

                                        queue then unblock it now. */

                                        if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToReceive ) ) == pdFALSE )

                                        {

                                             if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToReceive ) ) != pdFALSE )

                                               {

                                                     /* The unblocked task has a priority higher than

                                                     our own so yield immediately.  Yes it is ok to

                                                     do this from within the critical section - the

                                                     kernel takes care of that. */

                                                     queueYIELD_IF_USING_PREEMPTION();

                                               }

                                               else

                                               {

                                                      mtCOVERAGE_TEST_MARKER();

                                               }

                                        }

                                        else if( xYieldRequired != pdFALSE )

                                        {

                                               /* This path is a special case that will only get

                                               executed if the task was holding multiple mutexes

                                               and the mutexes were given back in an order that is

                                               different to that in which they were taken. */

                                               queueYIELD_IF_USING_PREEMPTION();

                                        }

                                        else

                                        {

                                               mtCOVERAGE_TEST_MARKER();

                                        }

                                 }

                           }

                           #else /* configUSE_QUEUE_SETS */

                           {

                                 /* If there was a task waiting for data to arrive on the

                                 queue then unblock it now. */

// 如果等待接收的列表不是空的话,                             if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToReceive ) ) == pdFALSE )

                                 {

// 找到列表里面的最高优先级的任务,移除,并添加到任务的ready list里面,然后任务切换                                    if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToReceive ) ) != pdFALSE )

                                        {

                                               /* The unblocked task has a priority higher than

                                               our own so yield immediately.  Yes it is ok to do

                                               this from within the critical section - the kernel

                                               takes care of that. */

                                               queueYIELD_IF_USING_PREEMPTION();

                                        }

                                        else

                                        {

                                               mtCOVERAGE_TEST_MARKER();

                                        }

                                 }

                                  else if( xYieldRequired != pdFALSE )

                                 {

                                        /* This path is a special case that will only get

                                        executed if the task was holding multiple mutexes and

                                        the mutexes were given back in an order that is

                                        different to that in which they were taken. */

                                        queueYIELD_IF_USING_PREEMPTION();

                                 }

                                 else

                                 {

                                        mtCOVERAGE_TEST_MARKER();

                                 }

                           }

                           #endif /* configUSE_QUEUE_SETS */

                           taskEXIT_CRITICAL();

                           return pdPASS;

                    }

                    else

                    {

// 下面是如果queue满了,该怎么处理。

// 如果等待时间是0, 直接退出

                           if( xTicksToWait == ( TickType_t ) 0 )

                           {

                                 /* The queue was full and no block time is specified (or

                                 the block time has expired) so leave now. */

                                 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,记录时间状态       

                                 vTaskInternalSetTimeOutState( &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. */

// 挂起调度器, 只执行当前task, 不会发生上下文切换,中断除外

             vTaskSuspendAll();

             prvLockQueue( pxQueue );

// 先检查时间

             /* Update the timeout state to see if it has expired yet. */

             if( xTaskCheckForTimeOut( &xTimeOut, &xTicksToWait ) == pdFALSE )

             {

// 如果阻塞时间没到,queue满了的话,

                    if( prvIsQueueFull( pxQueue ) != pdFALSE )

                    {

                           traceBLOCKING_ON_QUEUE_SEND( pxQueue );

// 将任务添加到队列的 xTasksWaitingToSend 列表中和延时列表中,并且将任务从就绪列表中移除     

                    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;

             }

       }

}

我们看看上锁操作:

学习FreeRTOS(五) - 任务通信_第3张图片

第一个问题:在什么地方需要给队列上锁?

从上面的代码我们可以看出,是在当任务进入阻塞期间,在这个时间段需要给队列上锁,而且我们还发现不仅队列上锁了调度器也被挂起了。

第二个问题:为什么要在任务进入阻塞期间需要挂起调度器且队列上锁?

因为在将任务设置为阻塞的过程中(是这个过程中),系统不希望有其它任务和中断操作这个队列的 xTasksWaitingToReceive 列表和 xTasksWaitingToSend 列表,而什么情况下会去操作这两个列表,只有其他更高优先级的任务或者中断来读写队列。

第三个问题:为什么在这个过程中不能有其他任务或者中断来操作这两个列表?

因为可能引起其它任务解除阻塞,这可能会发生优先级翻转。比如任务 A 的优先级低于当前任务,但是在当前任务进入阻塞的过程中,任务 A 却因为其它原因解除阻塞了,此时系统切换到任务A,而当前任务的手上的活还没完全干完,就被优先级反转了,这是要绝对禁止的。

最重要的一个问题:讲了这么多,给队列上锁有何作用?

简单来讲就是去避免中断在当前任务进入阻塞期间来操作这个队列的 xTasksWaitingToReceive 列表或者 xTasksWaitingToSend 列表。

因为挂起调度器只能禁止任务切换,也就是说可以避免其他任务来操作队列。但挂起调度器并不会禁止中断的发生,中断服务函数中仍然可以操作队列事件列表,可能会解除任务阻塞、就可能会进行任务切换,这是不允许的。所以给队列上锁就是防止中断来操作事件列。

这里注意一个问题我们要防止的是中断操作事件列表(xTasksWaitingToReceive ,xTasksWaitingToSend ),并不是说给队列上锁可以阻止中断的发生,其他在队列上锁期间中断服务函数仍然可以调用中断级的出队入队函数,来让消息入队或者出队,但是因为队列锁住则不能去操作xTasksWaitingToReceive ,xTasksWaitingToSend 这两个事件列表(防止让其他任务解除阻塞)。

原文链接:FreeRTOS-消息队列详解_freertos消息队列溢出_rivencode的博客-CSDN博客

#define prvLockQueue( pxQueue )                                                 \

       taskENTER_CRITICAL();                                                          \

       {                                                                                             \

             if( ( pxQueue )->cRxLock == queueUNLOCKED )                \

             {                                                                                      \

                    ( pxQueue )->cRxLock = queueLOCKED_UNMODIFIED;      \

             }                                                                                      \

             if( ( pxQueue )->cTxLock == queueUNLOCKED )                \

             {                                                                                      \

                    ( pxQueue )->cTxLock = queueLOCKED_UNMODIFIED;      \

             }                                                                                      \

       }                                                                                             \

       taskEXIT_CRITICAL()

队列解锁prvUnlockQueue()函数主要是判断在队列上锁期间是否有中断在读写队列,因为在队列上锁期间,中断来读写队列,只会拷贝消息(消息的出队或入队),并不会去操作xTasksWaitingToReceive ,xTasksWaitingToSend 这两个事件列表,只会记录(cRxLock或cTxLock加1),比如cTxLock+1表示队列上锁时,传输到队列(添加到队列)的入队项目数,则在队列解锁时则需要进行cTxLock次判断是否有任务在等待读取队列如果有则需要唤醒该任务。

其实队列锁,就相当于在队列上锁期间,保护xTasksWaitingToReceive ,xTasksWaitingToSend 这两个事件列表不被访问,在队列解锁之后补回来就行。

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

}

有点头疼,总结一下:

Task0 往queue里面发消息:

a) 如果queue 没满, 把消息抄送到queue里面。判断有没有list xTasksWaitingToReceive是不是空:

       -> 空的,直接退出send函数

       -> 不是空的,说明有任务在等待接收queue里面的消息, 如果该task的优先级大于当前优先级,任务切换,不大于,则直接退出

b) 如果queue满了,如果阻塞时间为0, 直接退出,

              -> 如果不是0, 记录当前时间, 挂起调度器,锁住queue, 接着判断有没有过阻塞时间, 过了,直接return;

                                  -> 没过, 把当前任务插入到delayed 列表里面,同时放入到queue的xTasksWaitingToSend, 接着任务切换。

很难理解,不过我们可以简单的想象成一个任务发送,一个任务接收,而非多个任务发送,多个任务接收这种涉及各种任务优先级的调度, 那么我们就容易理解消息的发送和接收了。

附上一个xQueueGenericSendFromISR() 的源码,对于理解queue lock有很大帮助。

中断中不能阻塞,不能立马切换任

pxHigherPriorityTaskWoken output, 判断需不需要任务切换

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;

}

任务接收和发送类似,简单介绍一下几个api函数吧。

学习FreeRTOS(五) - 任务通信_第4张图片

你可能感兴趣的:(linux,嵌入式硬件,arm)