<?xml:namespace prefix = st1 ns = "urn:schemas-microsoft-com:office:smarttags" /> 2009-5-13  
声明:本篇文章基本思想参考了http://www.openrtos.cn/modules/article/view.article.php?a19
       队列( queue )是 FreeRTOS 内核中的一个重要的数据结构,信号量、互斥锁等结构均是在队列的基础上实现的。队列的结构体定义如下: <?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />

* Definition of the queue used by the scheduler.

* Items are queued by copy, not reference.

typedef struct QueueDefinition

{

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

       signed portCHAR *pcHead;                       

 

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

       signed portCHAR *pcTail;                         

 

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

       signed portCHAR *pcWriteTo;                          

 

       /*< Points to the last place that a queued item was read from. */

       signed portCHAR *pcReadFrom;               

 

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

       xList xTasksWaitingToSend;                      

 

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

       xList xTasksWaitingToReceive;                  

 

       /*< The number of items currently in the queue. */

       volatile unsigned portBASE_TYPE uxMessagesWaiting;

 

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

       unsigned portBASE_TYPE uxLength;         

 

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

       unsigned portBASE_TYPE uxItemSize;       

 

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

       signed portBASE_TYPE xRxLock;                    

 

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

       signed portBASE_TYPE xTxLock;                    

 

} xQUEUE ;

 

队列的创建:

创建函数的实现,这里就不列出来了,只给个原型。功能很简单分配内存【主要是给队列指针,队列中 x item 】,并初始化队列结构体成员。
xQueueHandle xQueueCreate( unsigned portBASE_TYPE uxQueueLength, unsigned portBASE_TYPE uxItemSize )

不过需要特别注意下面这一句
/* Create the list of pointers to queue items.  The queue is one byte

longer than asked for to make wrap checking easier/faster. */

xQueueSizeInBytes = ( size_t ) ( uxQueueLength * uxItemSize ) + ( size_t ) 1;

 

队列的发送:

FreeRTOS 有三个一般的队列发送函数,xQueueSend(), xQueueSendToFront()    xQueueSendToBack() ,此三个函数不可以在中断中使用,它们都是通过宏的方式实现,其中xQueueSend()xQueueSendToBack()的定义是一样的,只是为与早期的版本兼容而保留下来。 此三个宏定义如下:

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

 

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

 

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

 

三个宏在底层均是通过xQueueGenericSend函数来实现,通过传递给此函数的最后一个参数来区别三个宏的实现,下面分析xQueueGenericSend函数的实现方式

1)  循环处理,直到发送出去,或者时间超时

2)  等待时间的处理

3)  Item 入队列

4)  接收task的调度,移出、移入list

 

if( xTicksToWait > ( portTickType ) 0 )开始几乎都是对等待时间的处理

这其中的vTaskSetTimeOutState( &xTimeOut );比较有意思

void vTaskSetTimeOutState( xTimeOutType * const pxTimeOut )

{

    pxTimeOut->xOverflowCount = xNumOfOverflows;

    pxTimeOut->xTimeOnEntering = xTickCount;

}

xTickCount 系统的滴答数,xNumOfOverflows系统滴答溢出值,这两个是系统的值。等待时间的变化就是通过xTickCount来计算的。同时,如果队列满的话,就把当前tskTCBxEventListItem放入队列的等待发送链表中,并resumealltask开始调度。

接下来的itemtask调度处理比较繁琐,可以这样概括:队列中的xTasksWaitingToReceive元素,是一个list,存放所有希望从该队列接收的task,并按优先级从高到低排列。那么当需要发送一个queueItem时,就是先将该item入队列,再将优先级高的等待接收task放到合适的链表中【根据诸多条件,scheduler的状态:suspendresumequeue是否满等,放入合适的链表中,readylistpendinglist等】。详细情况请参考queue.c文件。

 

队列的接收:

与队列的发送对应,一般的队列接收函数也是由宏来实现,底层用一个函数来实现,队列接收函数有下面两个:

#define xQueuePeek( xQueue, pvBuffer, xTicksToWait ) xQueueGenericReceive( xQueue, pvBuffer, xTicksToWait, pdTRUE )

#define xQueueReceive( xQueue, pvBuffer, xTicksToWait ) xQueueGenericReceive( xQueue, pvBuffer, xTicksToWait, pdFALSE )

xQueuePeek xQueueReceive的区别在于前者只接收而不删除条目,后者在成功接收后会删除原来的条目,两者均通过xQueueGenericReceive函数来实现,不能在中断中调用。

原型如下:

signed portBASE_TYPE xQueueGenericReceive( xQueueHandle pxQueue, void * const pvBuffer, portTickType xTicksToWait, portBASE_TYPE xJustPeeking )

这个函数的实现流程和发送的差不多,只不过是一个相反的过程。

(1)       处理等待时间,如果队列为空,则将当前tskTCBxEventListItem放入队列的等待接收链表中,并resumealltask开始调度。

(2)       调用prvCopyDataFromQueue从队列拷贝出queueuItem

(3)       如果队列中还有等待发送的task存在,也就是xTasksWaitingToSend不为空,那么将优先级高的等待发送task放到合适的链表中,准备调度。

 

队列在中断中接收与发送的实现

与标准的队列函数相比,中断中的队列收发相对要简洁,运行需要的时间相对来说要短。在中断服务程序中接收与发送队列,不需要进行临界段的处理,同时这些函数会直接返回而不会等待。在中断中收发队列不会导致任务的切换,但是会返回一个标志来提醒用户进行任务切换。

与标准的队列函数相似,中断中的队列操作也是由两个底层的函数实现,具体的操作则由宏来定义:
#define xQueueSendToFrontFromISR( pxQueue, pvItemToQueue, pxHigherPriorityTaskWoken ) xQueueGenericSendFromISR( pxQueue, pvItemToQueue, pxHigherPriorityTaskWoken, queueSEND_TO_FRONT )

#define xQueueSendToBackFromISR( pxQueue, pvItemToQueue, pxHigherPriorityTaskWoken ) xQueueGenericSendFromISR( pxQueue, pvItemToQueue, pxHigherPriorityTaskWoken, queueSEND_TO_BACK )

#define xQueueSendFromISR( pxQueue, pvItemToQueue, pxHigherPriorityTaskWoken ) xQueueGenericSendFromISR( pxQueue, pvItemToQueue, pxHigherPriorityTaskWoken, queueSEND_TO_BACK )

 

xQueueGenericSendFromISR 为所有中断中队列发送的实现函数
signed portBASE_TYPE xQueueGenericSendFromISR( xQueueHandle pxQueue, const void * const pvItemToQueue, signed portBASE_TYPE *pxHigherPriorityTaskWoken, portBASE_TYPE xCopyPosition )

{
signed portBASE_TYPE xReturn;
unsigned portBASE_TYPE uxSavedInterruptStatus;

    /* Similar to xQueueGenericSend, except we don't block if there is no room
    in the queue.  Also we don't directly wake a task that was blocked on a
    queue read, instead we 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 )   
        {
            traceQUEUE_SEND_FROM_ISR( pxQueue );
            prvCopyDataToQueue( pxQueue, pvItemToQueue, xCopyPosition );


            /* If the queue is locked we do not alter the event list.  This will
            be done when the queue is unlocked later. */

            /* 没有上锁,则把任务从等待队列中移除*/
            if( pxQueue->xTxLock == queueUNLOCKED )   
            {
                if( !listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToReceive ) ) )
                {
                    if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToReceive ) ) != pdFALSE )    /*
说明移除的任务有更高的优先级*/
                    {
                        /* The task waiting has a higher priority so record that

a context    switch is required. */

/* 返回任务切换标志,使得中断服务程序需要进行任务切换*/
                        *pxHigherPriorityTaskWoken = pdTRUE;   
                    }
                }
            }
            else
            {
                /* Increment the lock count so the task that unlocks the queue
                knows that data was posted while it was locked. */
                /*
因为队列被上锁了,不能进行任务移除操作,增加xTxLock的值
                
当进行与上锁操作所对应的解锁时,会根据此值多次的移除任务*/

                ++( pxQueue->xTxLock );
            }

            xReturn = pdPASS;
        }
        else    /*
队列满,不等待直接返回*/
        {
            traceQUEUE_SEND_FROM_ISR_FAILED( pxQueue );
            xReturn = errQUEUE_FULL;
        }
    }
    portCLEAR_INTERRUPT_MASK_FROM_ISR( uxSavedInterruptStatus );

    return xReturn;
}

 

中断中队列接收的实现与上面发送的实现相对应,由函数 xQueueReceiveFromISR 来完成

signed portBASE_TYPE xQueueReceiveFromISR( xQueueHandle pxQueue, void * const pvBuffer, signed portBASE_TYPE *pxTaskWoken )

{

signed portBASE_TYPE xReturn;

unsigned portBASE_TYPE uxSavedInterruptStatus;

 

    uxSavedInterruptStatus = portSET_INTERRUPT_MASK_FROM_ISR();

    {

        /* We cannot block from an ISR, so check there is data available. */

        if( pxQueue->uxMessagesWaiting > ( unsigned portBASE_TYPE ) 0 )

        {

            traceQUEUE_RECEIVE_FROM_ISR( pxQueue );

 

            prvCopyDataFromQueue( pxQueue, pvBuffer ); // 从队列拷贝出数据

            --( pxQueue->uxMessagesWaiting );

 

            /* If the queue is locked we will not modify the event list.  Instead

            we update the lock count so the task that unlocks the queue will know

            that an ISR has removed data while the queue was locked. */

            if( pxQueue->xRxLock == queueUNLOCKED )

            {

                if( !listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToSend ) ) )

                {

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

                    {

                        /* The task waiting has a higher priority than us so

                        force a context switch. */

                        *pxTaskWoken = pdTRUE;

                    }

                }

            }

            else

            {

                /* Increment the lock count so the task that unlocks the queue

                knows that data was removed while it was locked. */

                ++( pxQueue->xRxLock );

            }

 

            xReturn = pdPASS;

        }

        else

        {

            xReturn = pdFAIL;

            traceQUEUE_RECEIVE_FROM_ISR_FAILED( pxQueue );

        }

    }

    portCLEAR_INTERRUPT_MASK_FROM_ISR( uxSavedInterruptStatus );

 

    return xReturn;

}

 

信号量的实现:

 

FreeRTOS 的信号量分为二值信号量、计数信号量与互斥锁三种,均是在队列( Queue )的基础上实现。

 

二值信号量的实现:

freertos\source\include\semhpr.h

#define vSemaphoreCreateBinary( xSemaphore )                                  \

{                                                                             \

     xSemaphore =                                                             \

xQueueCreate((unsigned portBASE_TYPE )1,semSEMAPHORE_QUEUE_ITEM_LENGTH );\

if( xSemaphore != NULL )                                                 \

     {                                                                        \

           xSemaphoreGive( xSemaphore );                                      \

 }                                                                        \

}                                                                             \

数信号量的实现:

与二值信号量类似,所不同的是有两个参数,uxCountValue为计数信号量的最大值,uxInitialCount为初始值,创建返回的是信号量的句柄,函数CreateCountingSemaphore的实现如下:【freertos\source\queue.c
xQueueHandle xQueueCreateCountingSemaphore( unsigned portBASE_TYPE uxCountValue, unsigned portBASE_TYPE uxInitialCount )
{
    xQueueHandle pxHandle;
    

/* 对队列进行初始化操作后,队列的长度为信号量的最大值,uxMessagesWaiting初始  

化为信号量的初始值*/
    pxHandle = xQueueCreate( ( unsigned portBASE_TYPE ) uxCountValue, queueSEMAPHORE_QUEUE_ITEM_LENGTH );

     if( pxHandle != NULL )
     {
         pxHandle->uxMessagesWaiting = uxInitialCount;
         traceCREATE_COUNTING_SEMAPHORE();
     }
     else
     {
          traceCREATE_COUNTING_SEMAPHORE_FAILED();
     }
     return pxHandle;
}

 

互斥锁的实现:

跟上面的两种信号量不一样,它通过 pxMutexHolder 来指向其所有者的tcb,它的长度为1,同时使用 uxRecursiveCallCount 来记录其所有者获取此互斥锁的次数,互斥锁还有一个特性就是具有优先级继承机制,当前任务请求获取互斥锁时,如果互斥锁已经被另一个任务获取,当前任务会把已获取互斥锁的任务优先级提升到与自己一致,用来减少优先级翻转情况的出现。互斥锁的创建如下:
几个相关的宏定义:
#define pxMutexHolder                pcTail
#define uxQueueType                  pcHead
#define uxRecursiveCallCount         pcReadFrom
#define queueQUEUE_IS_MUTEX          NULL


xQueueHandle xQueueCreateMutex( void )
{
    xQUEUE *pxNewQueue;
   

/* Allocate the new queue structure. */
    pxNewQueue = ( xQUEUE * ) pvPortMalloc( sizeof( xQUEUE ) );

    if( pxNewQueue != NULL )
    {
           
/* Information required for priority inheritance. */
            pxNewQueue->pxMutexHolder = NULL;
            pxNewQueue->uxQueueType = queueQUEUE_IS_MUTEX;

           
/* Queues used as a mutex no data is actually copied into or out
            of the queue. */

            pxNewQueue->pcWriteTo = NULL;
            pxNewQueue->pcReadFrom = NULL;

           
/* Each mutex has a length of 1 (like a binary semaphore) and
            an item size of 0 as nothing is actually copied into or out
            of the mutex. */

            pxNewQueue->uxMessagesWaiting = 0;
            pxNewQueue->uxLength = 1;
            pxNewQueue->uxItemSize = 0;
            pxNewQueue->xRxLock = queueUNLOCKED;
            pxNewQueue->xTxLock = queueUNLOCKED;

           
/* Ensure the event queues start with the correct state. */
            vListInitialise( &( pxNewQueue->xTasksWaitingToSend ) );
            vListInitialise( &( pxNewQueue->xTasksWaitingToReceive ) );

           
/* Start with the semaphore in the expected state. */
            xQueueGenericSend( pxNewQueue, NULL, 0, queueSEND_TO_BACK );
            traceCREATE_MUTEX( pxNewQueue );
    }
    else
    {
            traceCREATE_MUTEX_FAILED();
    }
    return pxNewQueue;
}

信号量的应用:

信号量是在队列的基础上实现的,而信号量的获取与释放也是通过队列对应的接收与发送函数来实现,这三个函数适用与上面介绍的三种信号量类型。其宏定义如下:

【详见:semphr.h
#define xSemaphoreTake( xSemaphore, xBlockTime ) 

       xQueueGenericReceive( ( xQueueHandle ) xSemaphore, NULL, xBlockTime, pdFALSE )

#define xSemaphoreGive( xSemaphore ) 

xQueueGenericSend( ( xQueueHandle ) xSemaphore, NULL, semGIVE_BLOCK_TIME,

queueSEND_TO_BACK )

#define xSemaphoreGiveFromISR( xSemaphore, pxHigherPriorityTaskWoken )

xQueueGenericSendFromISR( ( xQueueHandle ) xSemaphore, NULL, pxHigherPriorityTaskWoken, queueSEND_TO_BACK )

获取信号量可以导致任务挂起,而释放信号量是无论是否成功均会立刻返回。需要注意的是对于前面两个函数,当对互斥锁进行操作时会进行优先级继承,在获取互斥锁时通过调用 vTaskPriorityInherit 函数来提升互斥锁拥有者的优先级到当前任务的优先级,当释放互斥锁时也会对应的检查任务是否被提升过优先级,并进行恢复,通过调用 vTaskPriorityDisinherit 函数,这两个函数的实现如下:

 

/* 如果被提升优先级的任务在就绪列表中,则先从就绪列表中移出,提升优先级后再插入;如果此任务没有就绪运行,则简单的提升优先级*/
void vTaskPriorityInherit( xTaskHandle * const pxMutexHolder )
{
    tskTCB * const pxTCB = ( tskTCB * ) pxMutexHolder;
    if( pxTCB->uxPriority < pxCurrentTCB->uxPriority )
    {
       
/* Adjust the mutex holder state to account for its new priority. */
        listSET_LIST_ITEM_VALUE( &( pxTCB->xEventListItem ), configMAX_PRIORITIES

- ( portTickType ) pxCurrentTCB->uxPriority );

       
/* If the task being modified is in the ready state it will need to
           be moved in to a new list. */

        if( listIS_CONTAINED_WITHIN( &( pxReadyTasksLists[ pxTCB->uxPriority ] ),

&( pxTCB->xGenericListItem ) ) )
        {
                vListRemove( &( pxTCB->xGenericListItem ) );

               
/* Inherit the priority before being moved into the new list. */
                pxTCB->uxPriority = pxCurrentTCB->uxPriority;
                prvAddTaskToReadyQueue( pxTCB );
        }
        else
        {
               
/* Just inherit the priority. */
                pxTCB->uxPriority = pxCurrentTCB->uxPriority;
        }
    }
}

/*
检查当前的优先级是否跟另一个副本相等,如果不等则表示被提升过优先级,就要恢复*/
void vTaskPriorityDisinherit( xTaskHandle * const pxMutexHolder )
{
    tskTCB * const pxTCB = ( tskTCB * ) pxMutexHolder;

    if( pxMutexHolder != NULL )
    {
        if( pxTCB->uxPriority != pxTCB->uxBasePriority )
        {
               
/* We must be the running task to be able to give the mutex back.
                Remove ourselves from the ready list we currently appear in. */

                vListRemove( &( pxTCB->xGenericListItem ) );

               
/* Disinherit the priority before adding ourselves into the new
                ready list. */

                pxTCB->uxPriority = pxTCB->uxBasePriority;

                listSET_LIST_ITEM_VALUE( &( pxTCB->xEventListItem ),

configMAX_PRIORITIES - ( portTickType ) pxTCB->uxPriority );

                prvAddTaskToReadyQueue( pxTCB );
        }
    }
}

 

互斥锁的应用:

对于互斥锁还有递归的获取与发送函数,为xQueueTakeMutexRecursivexQueueGiveMutexRecursive,分别对 uxRecursiveCallCount 进行加与减的操作,调用xQueueTakeMutexRecursive时如果任务还没有获取互斥锁,就会先进行获取操作,调用xQueueGiveMutexRecursive时如果 uxRecursiveCallCount 的值变为0,就会释放互斥锁。此两个函数的实现如下:
portBASE_TYPE xQueueTakeMutexRecursive( xQueueHandle pxMutex, portTickType xBlockTime )
{
    portBASE_TYPE xReturn;

   
/* Comments regarding mutual exclusion as per those within
    xQueueGiveMutexRecursive(). */


    traceTAKE_MUTEX_RECURSIVE( pxMutex );

    if( pxMutex->pxMutexHolder == xTaskGetCurrentTaskHandle() )
    {
        ( pxMutex->uxRecursiveCallCount )++;
        xReturn = pdPASS;
    }
    else
    {
        xReturn = xQueueGenericReceive( pxMutex, NULL, xBlockTime, pdFALSE );

       
/* pdPASS will only be returned if we successfully obtained the mutex,
          we may have blocked to reach here. */

        if( xReturn == pdPASS )
        {
            ( pxMutex->uxRecursiveCallCount )++;
        }
    }

    return xReturn;
}

 

portBASE_TYPE xQueueGiveMutexRecursive( xQueueHandle pxMutex )
{
    portBASE_TYPE xReturn;

   
/* If this is the task that holds the mutex then pxMutexHolder will not
     change outside of this task.  If this task does not hold the mutex then
     pxMutexHolder can never coincidentally equal the tasks handle, and as
     this is the only condition we are interested in it does not matter if
     pxMutexHolder is accessed simultaneously by another task.  Therefore no
     mutual exclusion is required to test the pxMutexHolder variable. */

    if( pxMutex->pxMutexHolder == xTaskGetCurrentTaskHandle() )
    {
        traceGIVE_MUTEX_RECURSIVE( pxMutex );

        /* uxRecursiveCallCount cannot be zero if pxMutexHolder is equal to
            the task handle, therefore no underflow check is required.  Also,
            uxRecursiveCallCount is only modified by the mutex holder, and as
            there can only be one, no mutual exclusion is required to modify the
            uxRecursiveCallCount member. */

        ( pxMutex->uxRecursiveCallCount )--;

       
/* Have we unwound the call count? */
        if( pxMutex->uxRecursiveCallCount == 0 )
        {
           
/* Return the mutex.  This will automatically unblock any other
                task that might be waiting to access the mutex. */

            xQueueGenericSend( pxMutex, NULL, queueMUTEX_GIVE_BLOCK_TIME, queueSEND_TO_BACK );

        }
        xReturn = pdPASS;
    }
    else
    {
       
/* We cannot give the mutex because we are not the holder. */
        xReturn = pdFAIL;
        traceGIVE_MUTEX_RECURSIVE_FAILED( pxMutex );
    }

    return xReturn;
}