FreeRTOS分析

来源:互联网 发布:大数据的基本概念 编辑:程序博客网 时间:2024/05/16 08:45


freertos是一个轻量级的rtos,它目前实现了一个微内核,并且port到arm7, avr, pic18, coldfire等众多处理器上;目前已经在rtos的市场上占有不少的份额。它当然不是一个与vxworks之类的rtos竞争的操作系统,它的目标在 于低性能小RAM的处理器上。整个系统只有3个文件,外加上port的和处理器相关的两个文件,实现是很简洁的。
与ucosii不同,它是free的,ucosii不是free的,虽然它的代码是公开的。FreeRTOS提供的功能包括:任务管理、时间管理、信号量、消息队列、内存管理。FreeRTOS内核支持优先级调度算法,每个任务可根据重要程度的不同被赋予一定的优先级,CPU总是让处于就绪态的、 优先级最高的任务先运行。FreeRT0S内核同时支持轮换调度算法,系统允许不同的任务使用相同的优先级,在没有更高优先级任务就绪的情况下,同一优先级的任务共享CPU的使用时间。这一点是和ucosii不同的。
另外一点不同是freertos既可以配置为可抢占内核也可以配置为不可抢占内核。当FreeRTOS被设置为可剥夺型内核时,处于就绪态的高优先级任务能剥夺低优先级任务的CPU使用权,这样可保证系统满足实时性的要求;当FreeRTOS被设置为不可剥夺型内核时,处于就绪态的高优先级任务只有等当前运行任务主动释放CPU的使用权后才能获得运行,这 样可提高CPU的运行效率。
这篇文章是以freertos v5.0版本的代码为例子分析下它的任务管理方面的实现。时间关系可能没有太多时间写的很详细了。
1.链表管理
freertos里面的任务管理,queue,semaphore管理等都借助于双向链表,它定义了个通用的数据结构


struct xLIST_ITEM
{
    portTickType xItemValue; //链表节点的数据项,通常用在任务延时,表示                              //一个任务延时的节拍数      
    volatile struct xLIST_ITEM * pxNext; //通过这两个成员变量将所有节点
    volatile struct xLIST_ITEM * pxPrevious;//链接成双向链表 
    void * pvOwner;  //指向该item的所有者,通常是任务控制块            
    void * pvContainer; //指向此链表结点所在的链表                    
};
这个数据结构定义了一个通用的链表节点;下面的数据结构定义了一个双向链表
typedef struct xLIST
{
    volatile unsigned portBASE_TYPE uxNumberOfItems;//表示该链表中节点的数目
    volatile xListItem * pxIndex;//用于遍历链表,指向上次访问的节点         
    volatile xMiniListItem xListEnd;//链表尾结点
} xList;

而下面这个数据结构用在xList中,只是为了标记一个链表的尾,是一个marker
struct xMINI_LIST_ITEM
{
    portTickType xItemValue;
    volatile struct xLIST_ITEM *pxNext;
    volatile struct xLIST_ITEM *pxPrevious;
};
typedef struct xMINI_LIST_ITEM xMiniListItem;

对于链表的操作也定义了一系列的函数和宏,在list.c文件中。如初始化个链表,吧一个节点插入链表等。
初始化链表:
void vListInitialise( xList *pxList )
{
    /* The list structure contains a list item which is used to mark the
    end of the list.  To initialise the list the list end is inserted
    as the only list entry. */
    pxList->pxIndex = ( xListItem * ) &( pxList->xListEnd );

    /* The list end value is the highest possible value in the list to
    ensure it remains at the end of the list. */
    pxList->xListEnd.xItemValue = portMAX_DELAY;

    /* The list end next and previous pointers point to itself so we know
    when the list is empty. */
    pxList->xListEnd.pxNext = ( xListItem * ) &( pxList->xListEnd );
    pxList->xListEnd.pxPrevious = ( xListItem * ) &( pxList->xListEnd );

    pxList->uxNumberOfItems = 0;
}
把一个节点插入到链表尾部:
void vListInsertEnd( xList *pxList, xListItem *pxNewListItem )
{
volatile xListItem * pxIndex;

    /* Insert a new list item into pxList, but rather than sort the list,
    makes the new list item the last item to be removed by a call to
    pvListGetOwnerOfNextEntry.  This means it has to be the item pointed to by
    the pxIndex member. */
    pxIndex = pxList->pxIndex;

    pxNewListItem->pxNext = pxIndex->pxNext;
    pxNewListItem->pxPrevious = pxList->pxIndex;
    pxIndex->pxNext->pxPrevious = ( volatile xListItem * ) pxNewListItem;
    pxIndex->pxNext = ( volatile xListItem * ) pxNewListItem;
    pxList->pxIndex = ( volatile xListItem * ) pxNewListItem;

    /* Remember which list the item is in. */
    pxNewListItem->pvContainer = ( void * ) pxList;

    ( pxList->uxNumberOfItems )++;
}
这些就不多说了。

 

2.任务控制块

typedef struct tskTaskControlBlock

{

       volatile portSTACK_TYPE     *pxTopOfStack;//指向堆栈顶

       xListItem                      xGenericListItem;   //通过它将任务连入就绪链表或者延时链表或者挂起链表中

       xListItem                      xEventListItem;//通过它把任务连入事件等待链表

       unsigned portBASE_TYPE    uxPriority;//优先级

       portSTACK_TYPE               *pxStack;              //指向堆栈起始位置

       signed portCHAR                 pcTaskName[ configMAX_TASK_NAME_LEN ];

       #if ( portCRITICAL_NESTING_IN_TCB == 1 )

              unsigned portBASE_TYPE uxCriticalNesting;

       #endif

 

       #if ( configUSE_TRACE_FACILITY == 1 )

              unsigned portBASE_TYPE    uxTCBNumber;//用于trace,debug时候提供方便

       #endif    

             

       #if ( configUSE_MUTEXES == 1 )

              unsigned portBASE_TYPE uxBasePriority;//当用mutex发生优先级反转时用

       #endif

 

       #if ( configUSE_APPLICATION_TASK_TAG == 1 )

              pdTASK_HOOK_CODE pxTaskTag;

       #endif

             

} tskTCB;

其中uxBasePriority用于解决优先级反转,freertos采用优先级继承的办法解决这个问题,在继承时,将任务原先的优先级保存在这个成员中,将来再从这里恢复任务的优先级。

 

3.系统全局变量

freertos将任务根据他们的状态分成几个链表。所有就绪状态的任务根据任务优先级加到对应的就绪链表中。系统为每个优先级定义了一个xList。如下:

static xList pxReadyTasksLists[ configMAX_PRIORITIES ];    /*< Prioritised ready tasks. */

此外,所有延时的任务加入到两个延时链表之一。

static xList xDelayedTaskList1;                                       

static xList xDelayedTaskList2;    

还定义了两个指向延时链表的指针:

static xList * volatile pxDelayedTaskList;                         

static xList * volatile pxOverflowDelayedTaskList;                   

freertos弄出两个延时链表是因为它的延时任务管理的需要。freertos根据任务延时时间的长短按序将任务插入这两个链表之一。在插入前先把任务将要延时的xTicksToDelay数加上系统当前tick数,这样得到了一个任务延时due time(到期时间)的绝对数值。但是有可能这个相加操作会导致溢出,如果溢出则加入到pxOverflowDelayedTaskList指向的那个链表,否则加入pxDelayedTaskList指向的链表。

freertos还定义了个pending链表:

static xList xPendingReadyList;

这个链表用在调度器被lock(就是禁止调度了)的时期,如果一个任务从非就绪状态变为就绪状态,它不直接加到就绪链表中,而是加到这个pending链表中。等调度器重新启动(unlock)的时候再检查这个链表,把里面的任务加到就绪链表中

 

static volatile xList xTasksWaitingTermination;            /*< Tasks that have been deleted - but the their memory not yet freed. */

static volatile unsigned portBASE_TYPE uxTasksDeleted = ( unsigned portBASE_TYPE ) 0;

一个任务被删除的时候加入到xTasksWaitingTermination链表中,uxTasksDeleted跟中系统中有多少任务被删除(即加到xTasksWaitingTermination链表的任务数目).

static xList xSuspendedTaskList;                               /*< Tasks that are currently suspended. */

这个链表记录着所有被xTaskSuspend挂起的任务,注意这不是那些等待信号量的任务。

 

static volatile unsigned portBASE_TYPE uxCurrentNumberOfTasks;记录了当前系统任务的数目

static volatile portTickType xTickCount;是自启动以来系统运行的ticks数

static unsigned portBASE_TYPE uxTopUsedPriority;记录当前系统中被使用的最高优先级,

static volatile unsigned portBASE_TYPE uxTopReadyPriority;记录当前系统中处于就绪状态的最高优先级。

static volatile signed portBASE_TYPE xSchedulerRunning  ;表示当前调度器是否在运行,也即内核是否启动了

 

4.任务管理

freertos与ucosii不同,它的任务控制块并不是静态分配的,而是在创建任务的时候动态分配。另外,freertos的优先级是优先级数越大优先级越高,和ucosii正好相反。任务控制块中也没有任务状态的成员变量,这是因为freertos中的任务总是根据他们的状态连入对应的链表,没有必要在任务控制块中维护一个状态。此外freertos对任务的数量没有限制,而且同一个优先级可以有多个任务。

 

先看任务创建:

/***************************************************************

**参数: pvTaskCode---任务函数名称

** pcName---任务名字,可选

**ucStackDepth---任务堆栈的深度,即大小

** pvParamenters---参数,即传给任务函数的参数,所有的任务函数原型是void task (void *pvParameters)

** uxPriority—任务优先级

** pxCreatedTask—可选,通过它返回被创建任务的tcb

*******************************************************************/

signed portBASE_TYPE xTaskCreate( pdTASK_CODE pvTaskCode, const signed portCHAR * const pcName, unsigned portSHORT usStackDepth, void *pvParameters, unsigned portBASE_TYPE uxPriority, xTaskHandle *pxCreatedTask )

{

signed portBASE_TYPE xReturn;

tskTCB * pxNewTCB;

#if ( configUSE_TRACE_FACILITY == 1 )

       static unsigned portBASE_TYPE uxTaskNumber = 0; /*lint !e956 Static is deliberate - this is guarded before use. */

#endif

 

       /*动态分配tcb和任务堆栈*/

       pxNewTCB = prvAllocateTCBAndStack( usStackDepth );

   /*如果分配成功的话*/

       if( pxNewTCB != NULL )

       {           

              portSTACK_TYPE *pxTopOfStack;

        /*初始化tcb*/

              prvInitialiseTCBVariables( pxNewTCB, pcName, uxPriority );

 

              /*计算堆栈的顶*/

              #if portSTACK_GROWTH < 0

              {

                     pxTopOfStack = pxNewTCB->pxStack + ( usStackDepth - 1 );

              }

              #else

              {

                     pxTopOfStack = pxNewTCB->pxStack;     

              }

              #endif

 

              /* 初始化任务堆栈,并将返回地址保存在tcb中的pxTopOfStack变量*/

              pxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pvTaskCode, pvParameters );

 

              /*关中断*/

              portENTER_CRITICAL();

              { /*更新系统的任务数*/

                     uxCurrentNumberOfTasks++;

                     if( uxCurrentNumberOfTasks == ( unsigned portBASE_TYPE ) 1 )

                     {

                            /*如果这是系统中第一个任务,则把它设为当前任务*/

                            pxCurrentTCB =  pxNewTCB;

 

                            /*如果这是系统中的第一个任务,那也就意味着内核刚准备启动,实际上这第一个任务一定是idle任务,这个时候我们要做一些系统初始化,即初始化那些全局链表*/

                            prvInitialiseTaskLists();

                     }

                     else

                     {    

                            /* 如果内核还没有运行,则把当前任务设成已经创建的任务中优先级最高的那个,将来内核一旦运行,调度器会马上选择它运行*/

                            if( xSchedulerRunning == pdFALSE )

                            {

                                   if( pxCurrentTCB->uxPriority <= uxPriority )

                                   {

                                          pxCurrentTCB = pxNewTCB;      

                                   }

                            }

                     }                         

 

                     /*我们记录下当前使用的最高优先级,这为了方便任务调度*/

                     if( pxNewTCB->uxPriority > uxTopUsedPriority )

                     {

                            uxTopUsedPriority = pxNewTCB->uxPriority;

                     }

 

                     #if ( configUSE_TRACE_FACILITY == 1 )

                     {

                            /* Add a counter into the TCB for tracing only. */

                            pxNewTCB->uxTCBNumber = uxTaskNumber;

                            uxTaskNumber++;

                     }

                     #endif

                     /*把新创建的任务加到就绪链表*/

                     prvAddTaskToReadyQueue( pxNewTCB );

 

                     xReturn = pdPASS;

                     traceTASK_CREATE( pxNewTCB );

              }

              portEXIT_CRITICAL();

       }

/*如果分配内存失败,我们返回错误*/

       else

       {

              xReturn = errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY;

              traceTASK_CREATE_FAILED( pxNewTCB );

       }

 

       if( xReturn == pdPASS )

       {

              if( ( void * ) pxCreatedTask != NULL )

              {

                     /*将新创建任务的tcb返回给调用者

                     *pxCreatedTask = ( xTaskHandle ) pxNewTCB;

              }

        /*如果调度器已经运行*/

              if( xSchedulerRunning != pdFALSE )

              {

                     /*如果新创建的任务的优先级高于当前正在运行的任务,则调度 */

                     if( pxCurrentTCB->uxPriority < uxPriority )

                     {

                            taskYIELD();

                     }

              }

       }

 

       return xReturn;

}

 

其中prvAllocateTCBAndStack分配tcb和stack内存,这个里面调用了pvportMalloc和pvPortFree函数来分配和释放内存,这两个函数对应于C标准库里面的malloc和free。但是标准库中的mallo和free存在以下缺点:并不是在所有的嵌入式系统中都可用,要占用不定的程序空间,可重人性欠缺以及执行时间具有不可确定性,而且多次反复调用可能导致严重的内存碎片。因此freertos在内存管理那块自己实现了这两个函数。

static tskTCB *prvAllocateTCBAndStack( unsigned portSHORT usStackDepth )

{

tskTCB *pxNewTCB;

 

       /* Allocate space for the TCB.  Where the memory comes from depends on

       the implementation of the port malloc function. */

       pxNewTCB = ( tskTCB * ) pvPortMalloc( sizeof( tskTCB ) );

 

       if( pxNewTCB != NULL )

       {

              /* Allocate space for the stack used by the task being created.

              The base of the stack memory stored in the TCB so the task can

              be deleted later if required. */

              pxNewTCB->pxStack = ( portSTACK_TYPE * ) pvPortMalloc( ( ( size_t )usStackDepth ) * sizeof( portSTACK_TYPE ) );

 

              if( pxNewTCB->pxStack == NULL )

              {

                     /* Could not allocate the stack.  Delete the allocated TCB. */

                     vPortFree( pxNewTCB );                   

                     pxNewTCB = NULL;                  

              }           

              else

              {

                     /* Just to help debugging. */

                     memset( pxNewTCB->pxStack, tskSTACK_FILL_BYTE, usStackDepth * sizeof( portSTACK_TYPE ) );

              }

       }

 

       return pxNewTCB;

}

 

再看任务删除

freertos的任务删除分两步完成,第一步在vTaskDelete中完成,FreeRTOS先把要删除的任务从就绪任务链表和事件等待链表中删除,然后把此任务添加到任务删除链表(即那个xTasksWaitingTermination),若删除的任务是当前运行任务,系统就执行任务调度函数.第2步则是在idle任务中完成,idle任务运行时,检查xTasksWaitingTermination链表,如果有任务在这个表上,释放该任务占用的内存空间,并把该任务从任务删除链表中删除。

/****************************************************************

**参数:pxTaskToDelete是一个指向被删除任务的句柄,这里其实就是等价于任务控制块

**如果这个句柄==NULL,则表示要删除当前任务

*******************************************************************/

void vTaskDelete( xTaskHandle pxTaskToDelete )

{

    tskTCB *pxTCB;

 

        taskENTER_CRITICAL();

        {

            /* 如果删除的是当前任务,则删除完成后需要进行调度*/

            if( pxTaskToDelete == pxCurrentTCB )

            {

                pxTaskToDelete = NULL;

            }

 

            /*通过传进来的任务句柄得到对应的tcb*/

            pxTCB = prvGetTCBFromHandle( pxTaskToDelete );

 

            traceTASK_DELETE( pxTCB );

 

            /* 把任务从就绪链表或者延时链表或者挂起链表中删除*/

            vListRemove( &( pxTCB->xGenericListItem ) );

 

            /* 判断任务是否在等待事件(semaphore消息队列等) */              

            if( pxTCB->xEventListItem.pvContainer )

            {//如果是,则把它从事件等待链表中删除

                vListRemove( &( pxTCB->xEventListItem ) );

            }

          //插入等待删除链表

            vListInsertEnd( ( xList * ) &xTasksWaitingTermination, &( pxTCB->xGenericListItem ) );

           //增加uxTasksDeleted计数

            ++uxTasksDeleted;

        }

        taskEXIT_CRITICAL();

 

        /*如果调度器已经运行,并且删除的是当前任务,则调度*/

        if( xSchedulerRunning != pdFALSE )

        {

            if( ( void * ) pxTaskToDelete == NULL )

            {

                taskYIELD();

            }

        }

}

再看空闲任务做的第2步工作:

static portTASK_FUNCTION( prvIdleTask, pvParameters )

{

    /* Stop warnings. */

    ( void ) pvParameters;

 

    for( ;; )

    {

        /* See if any tasks have been deleted. */

        prvCheckTasksWaitingTermination();

        …………………………….

这里prvCheckTasksWaitingTermination()就是干这第2步的工作:每次调用它删除一个任务

static void prvCheckTasksWaitingTermination( void )

{                          

    #if ( INCLUDE_vTaskDelete == 1 )

    {              

        portBASE_TYPE xListIsEmpty;

 

        /* ucTasksDeleted is used to prevent vTaskSuspendAll() being called

        too often in the idle task. */

        if( uxTasksDeleted > ( unsigned portBASE_TYPE ) 0 )

        {//禁止调度

            vTaskSuspendAll();

                xListIsEmpty = listLIST_IS_EMPTY( &xTasksWaitingTermination );              //打开调度

            xTaskResumeAll();

 

            if( !xListIsEmpty )

            {

                tskTCB *pxTCB;

                //关中断

                portENTER_CRITICAL();

                {          

                    pxTCB = ( tskTCB * ) listGET_OWNER_OF_HEAD_ENTRY( ( ( xList * ) &xTasksWaitingTermination ) );

                    vListRemove( &( pxTCB->xGenericListItem ) );

                    --uxCurrentNumberOfTasks;

                    --uxTasksDeleted;

                }

                portEXIT_CRITICAL();

               //释放内存,删除tcb

                prvDeleteTCB( pxTCB );

            }

        }

    }

    #endif

 

调度器的禁止和打开

这是一种同步机制,比关中断要温和点。禁止调度由vTaskSuspendAll实现,打开调度由xTaskResumeAll实现。

void vTaskSuspendAll( void )

{

    portENTER_CRITICAL();

        ++uxSchedulerSuspended;

    portEXIT_CRITICAL();

}

这个很简单,系统维护一个计数uxSchedulerSuspended,当它大于0时候表示禁止调度,等于0则打开调度(允许调度)。

signed portBASE_TYPE xTaskResumeAll( void )

{

register tskTCB *pxTCB;

signed portBASE_TYPE xAlreadyYielded = pdFALSE;

 

    /* It is possible that an ISR caused a task to be removed from an event

    list while the scheduler was suspended.  If this was the case then the

    removed task will have been added to the xPendingReadyList.  Once the

    scheduler has been resumed it is safe to move all the pending ready

    tasks from this list into their appropriate ready list. */

    portENTER_CRITICAL();

    {//将计数减一

        --uxSchedulerSuspended;

       //如果等于0,则允许调度

        if( uxSchedulerSuspended == ( unsigned portBASE_TYPE ) pdFALSE )

        {          

            if( uxCurrentNumberOfTasks > ( unsigned portBASE_TYPE ) 0 )

            {

                portBASE_TYPE xYieldRequired = pdFALSE;

               

                /* 将所有在xPendingReadyList中的任务移到对应的就绪链表中 */

                while( ( pxTCB = ( tskTCB * ) listGET_OWNER_OF_HEAD_ENTRY(  ( ( xList * ) &xPendingReadyList ) ) ) != NULL )

                {

                    vListRemove( &( pxTCB->xEventListItem ) );

                    vListRemove( &( pxTCB->xGenericListItem ) );

                    prvAddTaskToReadyQueue( pxTCB );

                   

                    /* 如果我们移动的任务优先级高于当前任务优先级,则需要一个调度 */

                    if( pxTCB->uxPriority >= pxCurrentTCB->uxPriority )

                    {

                        xYieldRequired = pdTRUE;

                    }

                }

 

                /* 如果在禁止调度期间,有时钟节拍中断发生,则我们把发生的次数记录在uxMissedTicks中,称为丢失的时钟节拍数;我们在这里模拟uxMissedTicks次时钟节拍中断,也就是说调用uxMissedTicks次时钟节拍isr: vTaskIncrementTick()。这样保证了所有任务的延时量不会出现偏差,它们将在正确的时间被唤醒*/

                if( uxMissedTicks > ( unsigned portBASE_TYPE ) 0 )

                {

                    while( uxMissedTicks > ( unsigned portBASE_TYPE ) 0 )

                    {

                        vTaskIncrementTick();

                        --uxMissedTicks;

                    }

 

                    /* As we have processed some ticks it is appropriate to yield

                    to ensure the highest priority task that is ready to run is

                    the task actually running. */

                    #if configUSE_PREEMPTION == 1

                    {

                        xYieldRequired = pdTRUE;

                    }

                    #endif

                }

               

                if( ( xYieldRequired == pdTRUE ) || ( xMissedYield == pdTRUE ) )

                {

                    xAlreadyYielded = pdTRUE;

                    xMissedYield = pdFALSE;

                    taskYIELD();

                }

            }

        }

    }

    portEXIT_CRITICAL();

 

    return xAlreadyYielded;

}

 

任务挂起与唤醒

freertos的任务关起与ucosii也不大一样。它把所有挂起的任务加到xSuspendedTaskList中,而且一旦调用vTaskSuspend()函数挂起一个任务,该任务就将从所有它原先连入的链表中删除(包括就绪表,延时表和它等待的事件链表),也就是说,和 ucosii不同,一旦一个任务被挂起,它将取消先前它的延时和对事件的等待。ucosii中是不同的,在ucosii里面一个任务被挂起仅仅是把任务的状态或上一个OS_STAT_SUSPEND并从就绪表中删除,如果先前这个任务正在等待某事件,则并不取消等待。

//如果传进来的pxTaskToSuspend==NULL,则表示挂起当前任务

void vTaskSuspend( xTaskHandle pxTaskToSuspend )

{

    tskTCB *pxTCB;

 

        taskENTER_CRITICAL();

        {

            /* Ensure a yield is performed if the current task is being

            suspended. */

            if( pxTaskToSuspend == pxCurrentTCB )

            {

                pxTaskToSuspend = NULL;

            }

 

            /* 得到对应任务tcb */

            pxTCB = prvGetTCBFromHandle( pxTaskToSuspend );

 

            traceTASK_SUSPEND( pxTaskToSuspend );

 

            /* 把任务从就绪表或者延时链表中删除 */

            vListRemove( &( pxTCB->xGenericListItem ) );

 

            /* 如果任务也在等待某事件,则取消等待 */                                               

            if( pxTCB->xEventListItem.pvContainer )

            {

                vListRemove( &( pxTCB->xEventListItem ) );

            }

           //插到xSuspendedTaskList

            vListInsertEnd( ( xList * ) &xSuspendedTaskList, &( pxTCB->xGenericListItem ) );

        }

        taskEXIT_CRITICAL();

 

        /* 如果挂起的是当前任务,则调度 */

        if( ( void * ) pxTaskToSuspend == NULL )

        {

            taskYIELD();

        }

    }

 

相反的唤醒就是把任务从xSuspendedTaskList中删除,加到对应的就绪链表中(根据任务的优先级),然后如果唤醒的任务优先级高于当前任务优先级,则调度。

void vTaskResume( xTaskHandle pxTaskToResume )

    {

    tskTCB *pxTCB;

 

        /* Remove the task from whichever list it is currently in, and place

        it in the ready list. */

        pxTCB = ( tskTCB * ) pxTaskToResume;

 

        /* The parameter cannot be NULL as it is impossible to resume the

        currently executing task. */

        if( pxTCB != NULL )

        {

            taskENTER_CRITICAL();

            {

                if( prvIsTaskSuspended( pxTCB ) == pdTRUE )

                {

                    traceTASK_RESUME( pxTCB );

 

                    /* As we are in a critical section we can access the ready

                    lists even if the scheduler is suspended. */

                    vListRemove(  &( pxTCB->xGenericListItem ) );

                    prvAddTaskToReadyQueue( pxTCB );

 

                    /* We may have just resumed a higher priority task. */

                    if( pxTCB->uxPriority >= pxCurrentTCB->uxPriority )

                    {

                        /* This yield may not cause the task just resumed to run, but

                        will leave the lists in the correct state for the next yield. */

                        taskYIELD();

                    }

                }

            }

            taskEXIT_CRITICAL();

        }

    }

 

任务调度

freertos支持多个任务具有相同的优先级,因此,当它被配置为可抢占内核时,调度算法既支持基于优先级的调度,也支持时间片轮流调度。任何时候调度器运行时它都选择处于就绪状态下的优先级最高的那个任务;如果有多个任务处于同一优先级,则freertos每个时钟节拍的中断服务程序中,将对这些任务应用换调度算法*轮流执行这些任务。

系统用uxTopReadyPriority全局变量记录当前处于就绪态的任务的最高优先级。调度的时候就根据这个uxTopReadyPriority直接找到就绪链表中pxReadyTasksLists[ uxTopReadyPriority ]的任务,进行运行。

一个任务可以通过调用taskYIELD()让出cpu,从而调度令一个任务运行。它的实现如下:

#define taskYIELD()                 portYIELD()

而portYIELD()是一个体系结构相关的函数,对于不同的mcu需要实现这么一个函数完成调度。我拿atmel的atmega323 mcu为例子,说明下具体实现。

 

/* Kernel utilities. */

extern void vPortYield( void ) __attribute__ ( ( naked ) );

#define portYIELD()                                  vPortYield()

/*

 * Manual context switch.  The first thing we do is save the registers so we

 * can use a naked attribute.

 */

void vPortYield( void ) __attribute__ ( ( naked ) );

void vPortYield( void )

{

       portSAVE_CONTEXT();

       vTaskSwitchContext();

       portRESTORE_CONTEXT();

 

       asm volatile ( "ret" );

}

portYIELD()就是vportYield(),它保存现场,然后调用vTaskSwitchContext()这个函数选择下一个运行的任务,然后portRESTORE_CONTEXT()完成任务切换。

 

void vTaskSwitchContext( void )

{

       traceTASK_SWITCHED_OUT();

 

       if( uxSchedulerSuspended != ( unsigned portBASE_TYPE ) pdFALSE )

       {

              /* 当前调度器被禁止,因此不允许调度,设xMissedYield=TRUE*/

              xMissedYield = pdTRUE;

              return;

       }

 

       taskCHECK_FOR_STACK_OVERFLOW();

 

       /* 找到包含有就绪任务的最高优先级队列 */

       while( listLIST_IS_EMPTY( &( pxReadyTasksLists[ uxTopReadyPriority ] ) ) )

       {

              --uxTopReadyPriority;

       }

 

       /* listGET_OWNER_OF_NEXT_ENTRY 从最高优先级队列上取下一个任务,设为pxCurrentTCB,即马上将要切换到该任务运行*/

       listGET_OWNER_OF_NEXT_ENTRY( pxCurrentTCB, &( pxReadyTasksLists[ uxTopReadyPriority ] ) );

 

       traceTASK_SWITCHED_IN();

       vWriteTraceToBuffer();

}

 

这里注意的是listGET_OWNER_OF_NEXT_ENTRY()宏并不是简单的从队列中取下第一个任务,而是walk through这个队列,比如上一次调度它从这个队列上取下的是第一个任务,那么这次调度选中的则是该队列中的第2个任务。这样就保证了同一优先级的多个任务之间公平的平分处理器时间。

选中任务后(用pxCurrentTCB指向它)。那么在portRESTORE_CONTEXT()中就完成最后的切换。因此这个地方有些有趣,函数vTaskSwitchContext()从名称看给人感觉是完成任务切换的,但是其实并不是这样,它只完成选择下一个运行的任务(也就是将要切换过去的任务),真正的切换时在portRESTORE_CONTEXT()中就完成的。

 

任务调度还可以发生在时钟节拍中断isr中,这个当然也是与cpu体系结构相关的。仍然以atmega323为例。它用的是定时器1的比较中断A作为时钟节拍产生器。其中断isr是:

void SIG_OUTPUT_COMPARE1A( void ) __attribute__ ( ( signal, naked ) );

       void SIG_OUTPUT_COMPARE1A( void )

       {

              vPortYieldFromTick();

              asm volatile ( "reti" );

       }

而vPortYieldFromTick()就是完成调度。代码如下:

void vPortYieldFromTick( void ) __attribute__ ( ( naked ) );

void vPortYieldFromTick( void )

{

    //保存现场

       portSAVE_CONTEXT();

    /*检查延时任务链表,如果发现有任务延时已经到期,则将该任务加到就绪链表*/

       vTaskIncrementTick();

    //挑选下一个运行的任务,准备切换过去

       vTaskSwitchContext();

   //完成任务切换

       portRESTORE_CONTEXT();

 

       asm volatile ( "ret" );

}


 

引用链接:点击打开链接

0 0
原创粉丝点击