FreeRTOS 任务调度 任务创建

@(嵌入式)

Freertos

FreeRtos

简述

FreeRTOS 的任务调度在 Source/include/task.c 中实现,包含了任务的创建、切换、挂起、延时和删除等所有功能。涉及到的链表组织见文章 <FreeRTOS 任务调度 List 组织> 。任务切换实现代码量比较大,因此关于任务调度这一块会分几个文章来描述,这一篇主要分析任务的创建的调用与实现。

分析的源码版本是 v9.0.0
(为了方便查看,github 上保留了一份源码Source目录下的拷贝)

任务状态

taskstate

系统运行过程,任务可能处于以下各种状态,各个状态之间切换的关系如上图所示。

  • Running
    运行状态, 当前正在执行,占有处理器的任务
  • Ready
    就绪状态,准备被运行的任务,没有被挂起和阻塞,但不是当前正在执行的任务,等待更高优先级任务或者同等级任务时间片结束释放处理器
  • Blocked
    阻塞状态,任务在等待一个事件而进入阻塞状态,比如延时、获取信号量等
  • Suspended
    挂起状态,任务由于调用 vTaskSuspend() 而被挂起不能被执行, 直到调用 xTaskResume() 重新恢复

使用示例

FreeRTOS 中创建任务并开始调度的基本框架如下 :

void vATaskFunction( void *pvParameters )
{
    for( ;; )
    {
    // -- 任务代码 --
    }
    // 任务不能有任何 返回
    // 对自行结束的任务,退出前需要自行清理
    vTaskDelete( NULL );
}

void main(void)
{
    static unsigned char ucParameterToPass;  
    xTaskHandle xHandle;  
    xTaskCreate( vATaskFunction, /*任务实现函数*/
                "TASK_NAME", /*任务名,方便调试*/
                STACK_SIZE,  /*任务堆栈大小 *StackType_t*/
                &ucParameterToPass, /*任务运行时的参数*/ 
                tskIDLE_PRIORITY, /*任务优先级*/
                &xHandle );  /*回传任务句柄,供其他地方引用任务*/
    // 其他任务和拉拉杂杂的初始化
    // 启动任务调度器 loop ....
}

任务创建函数中, 设置的栈大小单位由使用平台的 StackType_t 决定,不同平台栈指针对齐有自己的要求。
回传的句柄(指向TCB的指针)一般用于在其他任务中发送消息通知给任务,或者删除任务时引用。
任务成功创建后返回 pdPASS, 否则失败回传错误码。

另外,删除任务,可以通过其他任务中调用 voidvTaskDelete进行删除,此时该任务会从各种链表中移除,并且内存会被马上回收; 但是如果是任务自己调用删除,则其内存回收需要由空闲任务来完成(毕竟当前正在使用这些资源)。
使用 voidvTaskDelete 的前提是在 FreeRTOSConfig.h 设置 INCLUDE_vTaskDelete 为1(Tips !! API 在使用前最后需要看看是否需要设置对应的宏定义)。


叙述完上层的调用,后续介绍背后具体是如何实现的。

数据结构

TCB

任务调度离不开任务控制块(TCB), 用于存储任务的状态信息、运行时环境等。源代码见 tskTaskControlBlock, 以下具体介绍下这个数据结构。

typedef struct tskTaskControlBlock
{
    // 任务栈顶指针
    volatile StackType_t *pxTopOfStack;
    // 启用MPU 的情况下设置 
    #if ( portUSING_MPU_WRAPPERS == 1 )
        // 设置任务访问内存的权限
        xMPU_SETTINGS xMPUSettings;
    #endif
    
    // 状态链表项(Ready, Blocked, Suspended)
    // 任务处于不同状态 该项会被插入到对应的链表, 供链表引用任务
    ListItem_t xStateListItem;
    // 事件链表项
    // 比如任务延时挂起等,被插入到延时链表中,到时间或事件发生,链表引用唤醒任务
    ListItem_t xEventListItem;
    // 任务优先级 0 最低
    UBaseType_t uxPriority;
    // 任务栈内存起始地址
    StackType_t *pxStack;           
    // 任务名, 字符串, 一般供调试时使用
    char pcTaskName[ configMAX_TASK_NAME_LEN ];
    
    // 对于向上生长的栈, 用于指明栈的上边界,用于判断是否溢出
    #if ( portSTACK_GROWTH > 0 )
        StackType_t *pxEndOfStack;
    #endif
    
    // 边界嵌套计数
    #if ( portCRITICAL_NESTING_IN_TCB == 1 )
        UBaseType_t uxCriticalNesting;
    #endif

    #if ( configUSE_TRACE_FACILITY == 1 )
        // 调试, 标识这个任务是第几个被创建
        // 每创建一个任务, 系统有个全局变量就会加一, 并赋值给这个新任务
        UBaseType_t uxTCBNumber; 
        // 调试 供用户设置特定数值 
        UBaseType_t uxTaskNumber;
     #endif

    #if ( configUSE_MUTEXES == 1 )
        // 涉及互斥锁下的优先级继承(避免优先级反转), queue 那边介绍
        // 当优先级被临时提高(继承了拿锁被堵的高优先级任务)时,这个变量保存任务实际的优先级
        UBaseType_t uxBasePriority;     
        UBaseType_t uxMutexesHeld;
    #endif

    #if ( configUSE_APPLICATION_TASK_TAG == 1 )
        TaskHookFunction_t pxTaskTag;
    #endif

    #if( configNUM_THREAD_LOCAL_STORAGE_POINTERS > 0 )
        // 存储一些本地数据的指针
        void *pvThreadLocalStoragePointers[ configNUM_THREAD_LOCAL_STORAGE_POINTERS ];
    #endif

    #if( configGENERATE_RUN_TIME_STATS == 1 )
        // 记录任务运行状态下的总时间
        uint32_t ulRunTimeCounter;
    #endif

    #if ( configUSE_NEWLIB_REENTRANT == 1 )
        //为任务分配一个Newlibreent结构体变量
        // Newlib是一个C库函数,非FreeRTOS维护
        // 个人没用过,不清楚 
        struct  _reent xNewLib_reent;
    #endif

    #if( configUSE_TASK_NOTIFICATIONS == 1 )
        // 任务通知
        volatile uint32_t ulNotifiedValue;
        volatile uint8_t ucNotifyState;
    #endif

    #if( tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE != 0 )
        // 表明任务栈和TCB占用的是 heap 还是 stack
        // 供任务删除回收时判断
        uint8_t ucStaticallyAllocated; 
    #endif

    #if( INCLUDE_xTaskAbortDelay == 1 )
        uint8_t ucDelayAborted;
    #endif
} tskTCB;
typedef tskTCB TCB_t;

任务控制块中有两个链表项 xStateListItemxEventListItem, 在前面文章提到链表项中有一个指针指向所属的TCB。当任务状态变化或者等待事件的时候,将任务所属的这个链表项插入到对应的链表中,系统调度器就是通过这个方式追踪每个任务, 当符合条件的情况下,系统会通过该链表项引用任务,实现任务切换等操作。

链表

如上所述, 系统中包含的链表定义如下。

// 就绪任务链表 每个优先级对应一个链表
PRIVILEGED_DATA static List_t pxReadyTasksLists[ configMAX_PRIORITIES ];
// 延时任务链表
PRIVILEGED_DATA static List_t xDelayedTaskList1;                        
PRIVILEGED_DATA static List_t xDelayedTaskList2;                        
PRIVILEGED_DATA static List_t * volatile pxDelayedTaskList;
PRIVILEGED_DATA static List_t * volatile pxOverflowDelayedTaskList;
// 就绪任务链表,当任务调度器被挂起时,状态变换为就绪的任务先保存在此, 
// 恢复后移到 pxReadyTasksLists 中
PRIVILEGED_DATA static List_t xPendingReadyList;                
// 任务删除后,等待空闲任务释放内存
#if( INCLUDE_vTaskDelete == 1 )
    PRIVILEGED_DATA static List_t xTasksWaitingTermination;
    PRIVILEGED_DATA static volatile UBaseType_t uxDeletedTasksWaitingCleanUp = 
        ( UBaseType_t ) 0U;
#endif
// 被挂起的任务链表
#if ( INCLUDE_vTaskSuspend == 1 )
    PRIVILEGED_DATA static List_t xSuspendedTaskList;                   
#endif

任务创建

FreeRTOS V9.0.0 版本提供三个函数用于创建任务

  • xTaskCreateStatic
    通过传递的静态内存创建任务
  • xTaskCreate
    通过动态申请的内存创建任务
  • xTaskCreateRestricted
    创建任务参数通过TaskParameters_t传递给函数,用户自己申请栈的内存,创建函数只负责申请 TCB 所需内存空间

项目中接触版本 V8.0.0, 发现有一些改动, 旧版中实际创建任务的函数实际是 xTaskGenericCreate, 参数比较多, 可以实现从 heap 动态申请内存或通过静态内存创建任务, 而一般用到的xTaskCreate 实际是一个宏,调用了 xTaskGenericCreate, 默认采用动态申请内存的方式。

以下主要介绍 xTaskCreateStaticxTaskCreate 这两个函数的实现。

静态创建任务

源代码 xTaskCreateStatic
静态的方式创建任务,需要用户先申请任务控制模块和任务栈需要的内存(一般使用静态内存),然后把内存地址传递给函数,函数负责其他初始化。
函数按顺序完成:

  • 根据用户传递内存,初始化任务 TCB
  • 初始化任务堆栈
  • 将新建任务加入到就绪链表中
  • 如果调度器运行,新任务优先级更高,触发系统切换
TaskHandle_t xTaskCreateStatic( 
    TaskFunction_t pxTaskCode,
    const char * const pcName,
    const uint32_t ulStackDepth,
    void * const pvParameters,
    UBaseType_t uxPriority,
    StackType_t * const puxStackBuffer,
    StaticTask_t * const pxTaskBuffer )
{
    TCB_t *pxNewTCB;
    TaskHandle_t xReturn;
    configASSERT( puxStackBuffer != NULL );
    configASSERT( pxTaskBuffer != NULL );
    
    if ((pxTaskBuffer != NULL) && (puxStackBuffer != NULL)) 
    {
        // 设置用户传递进来的任务控制块和栈的内存地址到对应指针变量
        pxNewTCB = (TCB_t *)pxTaskBuffer; 
        pxNewTCB->pxStack = (StackType_t *)puxStackBuffer;

        #if( tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE != 0 )
        {
            // 标识这个任务控制块和栈内存时静态的
            // 删除任务的时候, 系统不会做内存回收处理
            pxNewTCB->ucStaticallyAllocated = 
                tskSTATICALLY_ALLOCATED_STACK_AND_TCB;
        }
        #endif
        // 初始化任务控制块 下文介绍
        prvInitialiseNewTask( pxTaskCode, pcName,
            ulStackDepth, pvParameters, uxPriority, 
            &xReturn, pxNewTCB, NULL );
        
        // 把新任务插入就绪链表 下文介绍
        prvAddNewTaskToReadyList( pxNewTCB );
    }
    else 
    {
        xReturn = NULL;
    }
    return xReturn;
}

动态创建任务

源代码 xTaskCreate
动态创建任务, 调用函数内部向系统申请创建新任务所需的内存,包括任务控制块和栈。 所以调用这个函数,在内存堆空间不足或者碎片话的情况下,可能创建新任务失败,需要判断函数执行后是否成功返回。 其源码解析如下所示。

BaseType_t xTaskCreate( 
    TaskFunction_t pxTaskCode,
    const char * const pcName,
    const uint16_t usStackDepth,
    void * const pvParameters,
    UBaseType_t uxPriority,
    TaskHandle_t * const pxCreatedTask )    
{
    TCB_t *pxNewTCB;
    BaseType_t xReturn;

    // 如果是向下增长的栈, 先申请栈内存再申请任务控制块内存
    // 可以避免栈溢出覆盖了自己任务控制块
    // 对应向上增长的则相反
    
    // 在旧版本 V8.0.0 中没有这么处理,统一先 TCB 后 Stack
    // 项目上碰到平台栈向下增长, 栈溢出错时候覆盖了自己的 TCB 
    // 导致调试的时候无法获取出错任务信息(比如任务名)
    #if( portSTACK_GROWTH > 0 )
    {
        // 申请任务控制块内存
        pxNewTCB = (TCB_t *)pvPortMalloc(sizeof(TCB_t));
        if( pxNewTCB != NULL )
        {
            // 申请栈内存, 返回地址设置任务中的栈指针
            pxNewTCB->pxStack = (StackType_t *)pvPortMalloc(
                (((size_t)usStackDepth) * sizeof(StackType_t)));
                 
            if( pxNewTCB->pxStack == NULL )
            {
                // 栈内存申请失败, 释放前面申请的任务控制块内存
                vPortFree( pxNewTCB );
                pxNewTCB = NULL;
            }
        }
    }
    #else /*栈向下增长*/
    {
        StackType_t *pxStack;
        pxStack = (StackType_t *)pvPortMalloc(
            (((size_t)usStackDepth) * sizeof(StackType_t)));
        
        if( pxStack != NULL )
        {
            pxNewTCB = (TCB_t *)pvPortMalloc(sizeof(TCB_t));
            if( pxNewTCB != NULL )
            {
                pxNewTCB->pxStack = pxStack;
            }
            else
            {
                vPortFree( pxStack );
            }
        }
        else
        {
            pxNewTCB = NULL;
        }
    }
    #endif
    
    
    if( pxNewTCB != NULL )
    {
        // 成功申请所需内存 执行任务初始化操作
        
        #if( tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE != 0 )
        {
            // 标志任务控制块和栈是动态申请
            // 删除任务系统会自动回收内存
            pxNewTCB->ucStaticallyAllocated = 
                tskDYNAMICALLY_ALLOCATED_STACK_AND_TCB;
        }
        #endif /* configSUPPORT_STATIC_ALLOCATION */
        
        // 初始任务控制块
        prvInitialiseNewTask(pxTaskCode, pcName,
            (uint32_t)usStackDepth, pvParameters, 
            uxPriority, pxCreatedTask, pxNewTCB, NULL );
        
        // 将新任务插入到就绪链表  
        prvAddNewTaskToReadyList( pxNewTCB );
        xReturn = pdPASS;
    }
    else
    {
        // 创建任务失败,返回错误码
        xReturn = errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY;
    }
    return xReturn;
}

初始化任务控制块

在创建任务的函数中, 如果成功获得新任务所需要的内存空间, 则会调用以下函数对任务控制块 TCB 的成员变量进行初始化。

static void prvInitialiseNewTask(
    TaskFunction_t pxTaskCode,
    const char * const pcName,
    const uint32_t ulStackDepth,
    void * const pvParameters,
    UBaseType_t uxPriority,
    TaskHandle_t * const pxCreatedTask,
    TCB_t *pxNewTCB,
    const MemoryRegion_t * const xRegions )
{
    StackType_t *pxTopOfStack;
    UBaseType_t x;
    
    // 如果开启了 MPU, 判断任务是否运行在特权模式
    #if( portUSING_MPU_WRAPPERS == 1 )
        BaseType_t xRunPrivileged;
        if( ( uxPriority & portPRIVILEGE_BIT ) != 0U )
        {
            // 优先级特权模式掩码置位
            // 任务运行在特权模式
            xRunPrivileged = pdTRUE;
        }
        else
        {
            xRunPrivileged = pdFALSE;
        }
        uxPriority &= ~portPRIVILEGE_BIT;
    #endif /* portUSING_MPU_WRAPPERS == 1 */
    
    #if( ( configCHECK_FOR_STACK_OVERFLOW > 1 ) 
        || ( configUSE_TRACE_FACILITY == 1 ) 
        || ( INCLUDE_uxTaskGetStackHighWaterMark == 1 ) )
    {
        // 调试 栈初始化填充指定数据(默认 0x5a)
        (void)memset(pxNewTCB->pxStack, 
                (int)tskSTACK_FILL_BYTE, 
                (size_t)ulStackDepth * sizeof(StackType_t));
    }
    #endif
    
    #if( portSTACK_GROWTH < 0 )
    {
        // 向下增长栈, 初始化栈顶在内存高位
        pxTopOfStack = pxNewTCB->pxStack + (ulStackDepth - (uint32_t )1);
        // 字节对齐处理
        pxTopOfStack = (StackType_t *)(((portPOINTER_SIZE_TYPE)pxTopOfStack) &
            (~(( portPOINTER_SIZE_TYPE)portBYTE_ALIGNMENT_MASK)));
        configASSERT((((portPOINTER_SIZE_TYPE)pxTopOfStack & 
            (portPOINTER_SIZE_TYPE)portBYTE_ALIGNMENT_MASK) == 0UL));
    }
    #else
    {
        // 向上增长栈, 初始化栈顶在内存低位
        pxTopOfStack = pxNewTCB->pxStack;
        // 字节对齐断言
        configASSERT((((portPOINTER_SIZE_TYPE)pxTopOfStack & 
            (portPOINTER_SIZE_TYPE)portBYTE_ALIGNMENT_MASK) == 0UL));
        // 设置上边界
        pxNewTCB->pxEndOfStack = 
            pxNewTCB->pxStack + (ulStackDepth - (uint32_t)1);
    }
    #endif /* portSTACK_GROWTH */

    // 存储任务名数组 方便调试
    for( x = (UBaseType_t)0; x < (UBaseType_t)configMAX_TASK_NAME_LEN; x++ )
    {
        pxNewTCB->pcTaskName[x] = pcName[x];
        // 字符串结束    
        if( pcName[ x ] == 0x00 )
        {
            break;
        }
        else
        {
            mtCOVERAGE_TEST_MARKER();
        }
    }
    // 确保任务名有正确字符串结尾
    pxNewTCB->pcTaskName[ configMAX_TASK_NAME_LEN - 1 ] = '\0';
    
    // 限制任务优先级在设置范围内
    if( uxPriority >= ( UBaseType_t ) configMAX_PRIORITIES )
    {
        uxPriority = (UBaseType_t)configMAX_PRIORITIES - (UBaseType_t)1U;
    }
    else
    {
        mtCOVERAGE_TEST_MARKER();
    }

    pxNewTCB->uxPriority = uxPriority;
    #if ( configUSE_MUTEXES == 1 )
    {
        pxNewTCB->uxBasePriority = uxPriority;
        pxNewTCB->uxMutexesHeld = 0;
    }
    #endif /* configUSE_MUTEXES */
    
    // 初始化包含的两个链表项
    vListInitialiseItem( &( pxNewTCB->xStateListItem ) );
    vListInitialiseItem( &( pxNewTCB->xEventListItem ) );
    
    // 设置状态链表项的 pvOwner 指向所属 TCB
    // 如此,系统可以通过该项引用到任务
    // 比如任务状态切换到就绪时,则这个链表项会被插入到 就绪链表
    // 系统从就绪链表取出这一项进而获得 TCB(ListItem->pvOwner),切换到运行状态 
    listSET_LIST_ITEM_OWNER( &( pxNewTCB->xStateListItem ), pxNewTCB );
    
    // 写入优先级 用于在对应事件链表中排序
    // 链表中是按从小到达排序,因此为了实现优先级高的在前
    // 两者相反,所以写入优先级的 “补数”
    // 保证优先级高的任务,插入时在链表靠前
    listSET_LIST_ITEM_VALUE(&(pxNewTCB->xEventListItem), 
        (TickType_t)configMAX_PRIORITIES - (TickType_t)uxPriority);
    // 设置所属 TCB, 同上  
    listSET_LIST_ITEM_OWNER( &( pxNewTCB->xEventListItem ), pxNewTCB );
    
    // 初始化嵌套 0
    #if ( portCRITICAL_NESTING_IN_TCB == 1 )
    {
        pxNewTCB->uxCriticalNesting = ( UBaseType_t ) 0U;
    }
    #endif /* portCRITICAL_NESTING_IN_TCB */

    #if ( configUSE_APPLICATION_TASK_TAG == 1 )
    {
        pxNewTCB->pxTaskTag = NULL;
    }
    #endif /* configUSE_APPLICATION_TASK_TAG */

    #if ( configGENERATE_RUN_TIME_STATS == 1 )
    {
        pxNewTCB->ulRunTimeCounter = 0UL;
    }
    #endif /* configGENERATE_RUN_TIME_STATS */

    #if ( portUSING_MPU_WRAPPERS == 1 )
    {
        // 设置 MPU,任务内存访问权限设置
        vPortStoreTaskMPUSettings(&(pxNewTCB->xMPUSettings), 
            xRegions, pxNewTCB->pxStack, ulStackDepth );
    }
    #else
    {
        // 避免编译报 warning 没有使用变量
        ( void ) xRegions;
    }
    #endif
    
    // 初始化任务局部数据指针
    #if( configNUM_THREAD_LOCAL_STORAGE_POINTERS != 0 )
    {
        for( x = 0; x < (UBaseType_t) configNUM_THREAD_LOCAL_STORAGE_POINTERS; x++ )
        {
            pxNewTCB->pvThreadLocalStoragePointers[ x ] = NULL;
        }
    }
    #endif
    
    // 初始化任务消息通知变量
    #if ( configUSE_TASK_NOTIFICATIONS == 1 )
    {
        pxNewTCB->ulNotifiedValue = 0;
        pxNewTCB->ucNotifyState = taskNOT_WAITING_NOTIFICATION;
    }
    #endif

    #if ( configUSE_NEWLIB_REENTRANT == 1 )
    {
        _REENT_INIT_PTR( ( &( pxNewTCB->xNewLib_reent ) ) );
    }
    #endif

    #if( INCLUDE_xTaskAbortDelay == 1 )
    {
        pxNewTCB->ucDelayAborted = pdFALSE;
    }
    #endif

    // 初始化栈 使其像任务已经运行了,但是被调度器中断切换,入栈做了现场保护
    // 当任务被调度器取出后, 可以直接执行出栈恢复现场,运行任务
    // 而不需要调度器额外特殊处理第一次运行的任务
    // 栈初始化涉及系统底层, 由对应平台移植层提供
    // 见下举例栈初始化
    #if( portUSING_MPU_WRAPPERS == 1 )
    {
        pxNewTCB->pxTopOfStack = pxPortInitialiseStack(pxTopOfStack, 
            pxTaskCode, pvParameters, xRunPrivileged);
    }
    #else /* portUSING_MPU_WRAPPERS */
    {
        pxNewTCB->pxTopOfStack = pxPortInitialiseStack(pxTopOfStack, 
            pxTaskCode, pvParameters);
    }
    #endif /* portUSING_MPU_WRAPPERS */

    if(( void *)pxCreatedTask != NULL )
    {
        // 返回任务引用, 可用于修改优先级,通知或者删除任务等.
        *pxCreatedTask = ( TaskHandle_t ) pxNewTCB;
    }
    else
    {
        mtCOVERAGE_TEST_MARKER();
    }
}

栈初始化举例

新任务初始化任务后,使得当前新建任务像已经运行,但是被调度器中断,栈中保存该任务被中断时的现场,但轮到该任务执行的时候,系统可以直接执行现场恢复,运行任务。
不同平台实现任务切换时的现场保护可能不一样,所以该函数由平台移植层提供
列举 Cotex-M3 没有MPU下的栈初始化函数, 向下增长栈。

StackType_t *pxPortInitialiseStack( StackType_t *pxTopOfStack, TaskFunction_t pxCode, void *pvParameters )
{
    // 模拟任务被切换前的现场保护
    // 调度切换回来可以统一执行恢复操作
    pxTopOfStack--; 
    *pxTopOfStack = portINITIAL_XPSR;   /* xPSR */
    pxTopOfStack--;
    // 指向任务函数
    *pxTopOfStack = ( ( StackType_t ) pxCode ) & portSTART_ADDRESS_MASK;
    pxTopOfStack--;
    *pxTopOfStack = ( StackType_t ) prvTaskExitError;   /* LR */
    pxTopOfStack -= 5;  /* R12, R3, R2 and R1. */
    // 传递参数
    *pxTopOfStack = ( StackType_t ) pvParameters;   /* R0 */
    pxTopOfStack -= 8;  /* R11, R10, R9, R8, R7, R6, R5 and R4. */

    return pxTopOfStack;
}

如上初始化后栈如下所示

-- 低位地址

pxStack-> ..
.. ..
pxTopOfStack-> R4
..
R11
R0
R1
R2
R3
R12
LR : prvTaskExitError
PC : pxCode
XPSR :portINITIAL_XPSR

-- 高位地址

初始化后,当任务第一次真正被运行,当前环境设置,使其从对应的函数入口开始执行。
其中LR 寄存器设置的地址是系统的出错处理函数,如果任务错误返回,就会调用该函数。
根据 约定, R0~R3保存调用时传递的参数。

插入就绪链表

任务创建初始化后,需要将任务插入到就绪链表中,通过调度器切换到运行状态。
该函数主要实现将新任务加入就绪链表,第一次调用该函数会进行系统必要的初始化,同时,判断是否需要马上执行任务切换,保证更高优先级的就绪任务可以及时获得CPU 的使用权限。

注意,这里提到的把任务插入到链表,是指将任务所含的链表项插入到合适的链表中,而但需要重新取回任务,则通过该链表项中指向所属任务的指针实现。

static void prvAddNewTaskToReadyList( TCB_t *pxNewTCB )
{
    // 进入边界, 关闭中断(平台相关,移植层实现)
    taskENTER_CRITICAL();
    {
        // 当前任务数加一
        uxCurrentNumberOfTasks++;
        if( pxCurrentTCB == NULL )
        {
            // 如果当前没有运行任务,设置新任务为当前运行任务
            pxCurrentTCB = pxNewTCB;

            if( uxCurrentNumberOfTasks == ( UBaseType_t ) 1 )
            {
                // 第一个任务,系统执行必要的初始化
                // 初始化各个链表
                prvInitialiseTaskLists();
            }
            else
            {
                mtCOVERAGE_TEST_MARKER();
            }
        }
        else
        {
            if( xSchedulerRunning == pdFALSE )
            {
                // 调度器没有运行
                // 新任务优先级优先级更高
                // 直接设置新任务为当前任务
                if( pxCurrentTCB->uxPriority <= pxNewTCB->uxPriority )
                {
                    pxCurrentTCB = pxNewTCB;
                }
                else
                {
                    mtCOVERAGE_TEST_MARKER();
                }
            }
            else
            {
                mtCOVERAGE_TEST_MARKER();
            }
        }
        // 记录创建任务数
        uxTaskNumber++;

        #if ( configUSE_TRACE_FACILITY == 1 )
        {
            // 调试追踪用
            pxNewTCB->uxTCBNumber = uxTaskNumber;
        }
        #endif /* configUSE_TRACE_FACILITY */
        traceTASK_CREATE( pxNewTCB );
        
        // 将任务加入到就绪链表
        // 不同优先级对应不同就绪链表
        // 宏实现,同时更新就绪的最高优先级
        prvAddTaskToReadyList( pxNewTCB );
        
        portSETUP_TCB( pxNewTCB );
    }
    // 退出边界,恢复中断
    taskEXIT_CRITICAL();

    if( xSchedulerRunning != pdFALSE )
    {
        // 调度器已经启动
        // 新任务优先级比正在运行的任务高
        // 触发系统执行任务切换
        if( pxCurrentTCB->uxPriority < pxNewTCB->uxPriority )
        {
            taskYIELD_IF_USING_PREEMPTION();
        }
        else
        {
            mtCOVERAGE_TEST_MARKER();
        }
    }
    else
    {
        mtCOVERAGE_TEST_MARKER();
    }
}

之前的文章分析过 FreeRtos 的链表,同样,当第一次调用将新任务插入就绪链表这个函数,会对系统涉及的几个链表进行初始化。

调度器会在每次任务切换中,依据优先级顺序从链表中选出合适的任务,相同优先级任务在同一个就绪链表中,系统按照时间片轮序调度(如果使能),

参考

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,530评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,403评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,120评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,770评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,758评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,649评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,021评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,675评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,931评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,751评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,410评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,004评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,969评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,042评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,493评论 2 343

推荐阅读更多精彩内容