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 的链表,同样,当第一次调用将新任务插入就绪链表这个函数,会对系统涉及的几个链表进行初始化。

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

参考

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏大史住在大前端

webpack4.0各个击破(5)—— Module篇

使用webpack对脚本进行合并是非常方便的,因为webpack实现了对各种不同模块规范的兼容处理,对前端开发者来说,理解这种实现方式比学习如何配置webpac...

1262
来自专栏蓝天

strerror线程安全分析

答案是NO,但它有个线程安全的版本:strerror_r。借助Linux的man,即可看到详情:

1253
来自专栏扎心了老铁

springboot mybatis 事务管理

本文主要讲述springboot提供的声明式的事务管理机制。 一、一些概念 声明式的事务管理是基于AOP的,在springboot中可以通过@Transacti...

4897
来自专栏腾讯数据库技术

Online DDL过程介绍

3563
来自专栏linux驱动个人学习

Linux内存描述之内存页面page--Linux内存管理(四)

分页单元可以实现把线性地址转换为物理地址, 为了效率起见, 线性地址被分为固定长度为单位的组, 称为”页”, 页内部的线性地址被映射到连续的物理地址. 这样内核...

3091
来自专栏Java架构沉思录

分布式ID常见解决方案

在分布式系统中,往往需要对大量的数据如订单、账户进行标识,以一个有意义的有序的序列号来作为全局唯一的ID。

5372
来自专栏IT开发技术与工作效率

Redis 全中文总结

3004
来自专栏猿人谷

mybatis动态调用表名和字段名

  一直在使用Mybatis这个ORM框架,都是使用mybatis里的一些常用功能。今天在项目开发中有个业务是需要限制各个用户对某些表里的字段查询以及某些字段...

3056
来自专栏PingCAP的专栏

TiDB 源码阅读系列文章(十)Chunk 和执行框架简介

Chunk 本质上是 Column 的集合,它负责连续的在内存中存储同一列的数据,接下来我们看看 Column 的实现。

5.7K145
来自专栏情情说

深入浅出MyBatis:「映射器」全了解

上一篇总结了MyBatis的配置,详细说明了各个配置项,其中提到了映射器,它是MyBatis最强大的工具,也是使用最多的工具。

3516

扫码关注云+社区

领取腾讯云代金券