Freertos应用与源码分析:创建任务-源码分析

目录

一、概述

1、前言

2、ITEM结构体

3、TCB结构体

4、链表初始化

二、内部源码

1、核心内容

2、源码部分

3、伪造现场

三、总结

一、概述

1、前言

        本文将介绍Freertos中创建任务(内部源码)。前言这里会简单介绍下链表、TCB结构体。本文展示源码做过精简,核心不变,建议大家可以自行对照阅读源码。

2、ITEM结构体

        ITEM结构体是TCB结构体的父类,尽管C语言没有这种概念。ITEM结构体是TCB结构体挂载在链表上的关键。

struct xLIST_ITEM
{
     /*该变量被用于排序*/
    configLIST_VOLATILE TickType_t xItemValue;   
    /*指向下一个链表*/
    struct xLIST_ITEM * configLIST_VOLATILE pxNext;     
    /*指向前一个链表*/
    struct xLIST_ITEM * configLIST_VOLATILE pxPrevious; 
    /*指向TCB结构体*/
    void * pvOwner;      
    /*指向链表头*/
    struct xLIST * configLIST_VOLATILE pxContainer;      
};
typedef struct xLIST_ITEM ListItem_t;  
3、TCB结构体

        TCB结构体就是任务!同时句柄也是TCB结构体,只不过换了个形式,所谓操作句柄进行删除、挂起操作就是在操作TCB结构体。为什么这么说?我们来看下TCB结构体源码。TCB结构体中包含栈顶地址、状态链表(包含就绪、阻塞、挂起三种状态)、事件链表(表示进行什么事件)、优先级、栈起始地址、函数名字(configMAX_TASK_NAME_LEN宏定义表示NAME长度)。这里做过大量精简、不过核心就是这些。

        状态链表和事件链表就是ITEM结构体。

typedef struct tskTaskControlBlock      
{
    volatile StackType_t * pxTopOfStack; /*栈顶*/
    ListItem_t xStateListItem;    /*状态链表*/                   
    ListItem_t xEventListItem;    /*事件链表*/                    
    UBaseType_t uxPriority;       /*优先级*/                     
    StackType_t * pxStack;        /*指向堆栈的起点*/            
    char pcTaskName[ configMAX_TASK_NAME_LEN ];      /*函数名字*/  
} tskTCB;
4、链表初始化

        众所周知,freertos运行的核心就是链表,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;     
PRIVILEGED_DATA static List_t xPendingReadyList;  
PRIVILEGED_DATA static List_t xSuspendedTaskList;

        下面是链表头相关初始化。vListInitialiseconfig()函数用于初始化相关链表头,MAX_PRIORITIES表示优先级数量。创建任务中会用到链表初始化函数。

static void prvInitialiseTaskLists( void )
{
    UBaseType_t uxPriority;
    /*创建不同优先级的就绪链表*/
    for( uxPriority = ( UBaseType_t ) 0U; uxPriority < ( UBaseType_t ) configMAX_PRIORITIES; uxPriority++ )
    {
        vListInitialise( &( pxReadyTasksLists[ uxPriority ] ) );
    }
    /*创建两个Delay链表*/
    vListInitialise( &xDelayedTaskList1 );
    vListInitialise( &xDelayedTaskList2 );  //延时溢出链表
    /*创建就绪但尚未执行的任务。*/
    vListInitialise( &xPendingReadyList );

    #if ( INCLUDE_vTaskDelete == 1 )
        {
            /*删除任务但是未释放内存的链表*/
            vListInitialise( &xTasksWaitingTermination );
        }
    #endif /* INCLUDE_vTaskDelete */
    
    /*创建挂起链表*/
    #if ( INCLUDE_vTaskSuspend == 1 )
        {
            vListInitialise( &xSuspendedTaskList );
        }
    #endif /* INCLUDE_vTaskSuspend */
    /*设置延时链表*/
    pxDelayedTaskList = &xDelayedTaskList1;
    /*设置延时溢出链表*/
    pxOverflowDelayedTaskList = &xDelayedTaskList2;
}

二、内部源码

1、核心内容
  • 初始化任务栈
  • 初始化TCB结构体
  • 伪造现场
  • 将TCB结构体放入就绪链表中

        初始化任务栈: 每个任务都有属于自己独立的栈空间,这是一定的。切换任务本质就是触发中断,把当前运行任务的CPU寄存器保存到当前任务的栈中,切换到下一个任务的栈,将此任务栈内保存的寄存器恢复到CPU内部寄存器中,便开始运行。这便是切换的本质。(后续会出一篇文章详细介绍)

        伪造现场: 为什么需要伪造现场?因为RTOS运行起来后,切换下一个任务,要取下一个任务的栈中存放的寄存器恢复到CPU内部寄存器。所以我们需要在正式运行之前,就要把每个任务的栈进行伪造(这里的伪造就是伪造部分寄存器,这些寄存器是CPU需要的寄存器),具体可以去看中断处理的过程这章内容。

2、源码部分

        这里做了大量精简,核心不变,建议对照原文进行学习。

        创建任务源码,这里主要分配任务栈、分配TCB结构体空间、执行设置TCB结构体函数、放入就绪链表中。

BaseType_t xTaskCreate( TaskFunction_t pxTaskCode,                     	 /*函数地址名*/
                            const char * const pcName,                     /*任务Name*/
                            const configSTACK_DEPTH_TYPE usStackDepth,      /*分配栈空间*/
                            void * const pvParameters,                      /*参数*/
                            UBaseType_t uxPriority,                         /*优先级*/
                            TaskHandle_t * const pxCreatedTask )            /*任务句柄*/
    {
        TCB_t * pxNewTCB;               
        BaseType_t xReturn;

                StackType_t * pxStack;

                /* 为正在创建的任务使用的堆栈分配空间 */
                pxStack = pvPortMallocStack( ( ( ( size_t ) usStackDepth ) * sizeof( StackType_t ) ) );
                if( pxStack != NULL )
                {
                    /* 为TCB分配空间. */
                    pxNewTCB = ( TCB_t * ) pvPortMalloc( sizeof( TCB_t ) ); 

                    if( pxNewTCB != NULL )
                    {
                        /* 记录栈首地址 */
                        pxNewTCB->pxStack = pxStack;
                    }
                    else
                    {
                        /* The stack cannot be used as the TCB was not created.  Free
                         * it again. */
                        vPortFreeStack( pxStack );
                    }
                }
                else
                {
                    pxNewTCB = NULL;
                }

        if( pxNewTCB != NULL )
        {
            /*伪造现场、设置TCB结构体*/
            prvInitialiseNewTask( pxTaskCode, pcName, ( uint32_t ) usStackDepth, pvParameters, uxPriority, pxCreatedTask, pxNewTCB, NULL );
            /*把TCB放入就绪链表*/
            prvAddNewTaskToReadyList( pxNewTCB );
            xReturn = pdPASS;
        }
        else
        {
            xReturn = errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY;
        }

        return xReturn;
    }

        这个函数会更详细的配置TCB结构体,传入的参数和创建任务相似。首先会进行栈对齐操作。NAME、优先级、Owner、Value初始化到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;

       /*向上对其分配栈*/
        pxTopOfStack = &( pxNewTCB->pxStack[ ulStackDepth - ( uint32_t ) 1 ] );
        pxTopOfStack = ( StackType_t * ) ( ( ( portPOINTER_SIZE_TYPE ) pxTopOfStack ) & ( ~( ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) ) ); 

    /*将Name赋值给TCB结构体 */
    if( pcName != NULL )
    {
        for( x = ( UBaseType_t ) 0; x < ( UBaseType_t ) configMAX_TASK_NAME_LEN; x++ )
        {
            pxNewTCB->pcTaskName[ x ] = pcName[ x ];

            if( pcName[ x ] == ( char ) 0x00 )
            {
                break;
            }
            else
            {
                mtCOVERAGE_TEST_MARKER();
            }
        }
        pxNewTCB->pcTaskName[ configMAX_TASK_NAME_LEN - 1 ] = '\0';
    }
    else
    {
        pxNewTCB->pcTaskName[ 0 ] = 0x00;
    }

    /*configMAX_PRIORITIES宏定义表示最大优先级*/
    configASSERT( uxPriority < configMAX_PRIORITIES );
    /*赋值优先级给TCB结构体*/
    if( uxPriority >= ( UBaseType_t ) configMAX_PRIORITIES )
    {
        uxPriority = ( UBaseType_t ) configMAX_PRIORITIES - ( UBaseType_t ) 1U;
    }
    else
    {
        mtCOVERAGE_TEST_MARKER();
    }
    /*初始化优先级到结构体中*/
    pxNewTCB->uxPriority = uxPriority;

    /*将状态链表和事件链表添加到对应链表头*/
    vListInitialiseItem( &( pxNewTCB->xStateListItem ) );
    vListInitialiseItem( &( pxNewTCB->xEventListItem ) );

    /* 状态Item.Owner->TCB结构体 */
    listSET_LIST_ITEM_OWNER( &( pxNewTCB->xStateListItem ), pxNewTCB );

    /* 将当前TCB结构体的优先级存储在事件Item->Value */
    listSET_LIST_ITEM_VALUE( &( pxNewTCB->xEventListItem ), ( TickType_t ) configMAX_PRIORITIES - ( TickType_t ) uxPriority );
  
    /* 事件Item.Owner->TCB结构体 */
    listSET_LIST_ITEM_OWNER( &( pxNewTCB->xEventListItem ), pxNewTCB );

      /*伪造现场(写入对应寄存器)*/
      pxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pxTaskCode, pvParameters );

    if( pxCreatedTask != NULL )
    {
        /* 手柄可以用来更改已创建任务的优先级,删除已创建的任务等.*/
        *pxCreatedTask = ( TaskHandle_t ) pxNewTCB;
    }
    else
    {
        mtCOVERAGE_TEST_MARKER();
    }
}

        伪造现场的函数在下节单独讲,这里介绍下讲TCB结构体放入就绪链表函数。

        pxCurrentTCB表示当前指向的TCB结构体。

        xSchedulerRunning表示当前是否处于运行状态。

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 )
            {
                /*如果当前选择的TCB->Prior 小于等于 New->Prior 则赋值*/
                if( pxCurrentTCB->uxPriority <= pxNewTCB->uxPriority )
                {
                    /*这就是为什么同优先级任务后创建的先执行*/
                    pxCurrentTCB = pxNewTCB;
                }
                else
                {
                    mtCOVERAGE_TEST_MARKER();
                }
            }
            else
            {
                mtCOVERAGE_TEST_MARKER();
            }
        }
        uxTaskNumber++;

        /*添加任务到就绪链表尾中*/
        prvAddTaskToReadyList( pxNewTCB );
    }
    /*退出临界区*/
    taskEXIT_CRITICAL();

    if( xSchedulerRunning != pdFALSE )
    {
        /* 如果创建的任务优先级高于当前任务那么它现在应该运行了 */
        if( pxCurrentTCB->uxPriority < pxNewTCB->uxPriority )
        {
            /*进行任务切换*/
            taskYIELD_IF_USING_PREEMPTION();
        }
        else
        {
            mtCOVERAGE_TEST_MARKER();
        }
    }
    else
    {
        mtCOVERAGE_TEST_MARKER();
    }
}
3、伪造现场

        在查看伪造现场函数之前,一定要对相关内部寄存器有一定了解,可以查看之前的文章(中断的处理过程)。

这里为了更好的理解,可以对着代码和图观看。

StackType_t * pxPortInitialiseStack( StackType_t * pxTopOfStack,    /*栈*/
                                     TaskFunction_t pxCode,					/*函数首地址*/
                                     void * pvParameters )					/*传入参数*/
{
    /* 伪造现场 */
    pxTopOfStack--;                                                      /* Offset added to account for the way the MCU uses the stack on entry/exit of interrupts. */
    *pxTopOfStack = portINITIAL_XPSR;                                    /*第二十五个位置设置为1 使用thumb指令集 */                                /* xPSR */
    pxTopOfStack--;
    *pxTopOfStack = ( ( StackType_t ) pxCode ) & portSTART_ADDRESS_MASK; /* PC */   /*函数地址*/
    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;
}

三、总结

        本文对创建任务源码做了大量精简,只保留核心内容,为了让学者更快的入门源码。后续会介绍任务调度(上下文切换),这也是操作系统的核心。但是在这之前,理解伪造现场和中断的处理过程也是至关重要的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值