FreeRTOS学习记录--任务创建函数详解
阅读原文时间:2022年04月06日阅读:1

开局一张图。一步一步分析就好。

(一)什么是任务?

  在多任务系统中,我们按照功能不同,把整个系统分割成一个个独立的,且无法返回的函数,这个函数我们称为任务;任务包含几个属性:任务堆栈,任务函数、任务控制块、任务优先级;下面主要介绍一下任务控制块,其他都比较容易理解。

(二)什么是任务控制块?

  任务控制块内包含了该任务的全部信息,任务的执行需要通过任务调度器来控制,那么任务调度器怎么“控制”任务实体的呢?就要抓住任务的小辫子---“任务控制块”,系统对任务的全部操作都可以通过任务控制块来实现!它是一种特别的数据结构。

  在任务创建函数xTaskCreat()创建任务的时候就会自动给每个任务分配一个任务控制块。

typedef struct tskTaskControlBlock
{
volatile StackType_t *pxTopOfStack; /*任务堆栈栈顶指针*/

#if ( portUSING\_MPU\_WRAPPERS == 1 )  
    xMPU\_SETTINGS    xMPUSettings;        /\*MPU相关设置\*/  
#endif

ListItem\_t            xStateListItem;        /\*状态列表项,这是一个内置在TCB控制块中的一个链表节点,通过这个节点,将任务挂到其他链表中  
                                                比如就绪列表,阻塞列表,挂起列表等\*/  

ListItem\_t            xEventListItem;        /\*事件列表项,用于引用事件列表中的任务\*/  
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;        /\*debug的时候用到\*/  
    UBaseType\_t        uxTaskNumber;        /\*trace的时候用到\*/  
#endif

#if ( configUSE\_MUTEXES == 1 )  
    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 )  
    struct    \_reent xNewLib\_reent;        /\*定义一个newlib结构体变量\*/  
#endif

#if( configUSE\_TASK\_NOTIFICATIONS == 1 )    /\*任务通知相关变量\*/  
    volatile uint32\_t ulNotifiedValue;        /\*任务通知值\*/  
    volatile uint8\_t ucNotifyState;            /\*任务通知状态\*/  
#endif

/\* 用来标记任务是动态创建还是静态创建\*/  
#if( tskSTATIC\_AND\_DYNAMIC\_ALLOCATION\_POSSIBLE != 0 )  
    uint8\_t    ucStaticallyAllocated;         /\*静态创建此变量为pdTURE;动态创建此变量为pdFALSE\*/  
#endif

#if( INCLUDE\_xTaskAbortDelay == 1 )  
    uint8\_t ucDelayAborted;  
#endif

} tskTCB;

注:#if 开头的都是条件编译,咱们可以先不用理解。基本结构如下:

   指针pxStack指向堆栈的起始位置,任务创建时会分配指定数目的任务堆栈,申请堆栈内存函数返回的指针就被赋给该变量。

   很多刚接触FreeRTOS的人会分不清指针pxTopOfStack和pxStack的区别,这里简单说一下:pxTopOfStack指向当前堆栈栈顶,随着进栈出栈,pxTopOfStack指向的位置是会变化的;pxStack指向当前堆栈的起始位置,一经分配后,堆栈起始位置就固定了,不会被改变了。那么为什么需要pxStack变量呢,这是因为随着任务的运行,堆栈可能会溢出,在堆栈向下增长的系统中,这个变量可用于检查堆栈是否溢出;如果在堆栈向上增长的系统中,要想确定堆栈是否溢出,还需要另外一个变量pxEndOfStack来辅助诊断是否堆栈溢出。

(三)任务是怎么创建出来的?

  任务有两种创建方式,动态创建静态创建,两者的区别就是: 静态创建时候任务控制块和任务堆栈的内存是由用户自己定义的,任务删除的时候,内存不能自动释放。动态创建,任务堆栈和任务控制块的内存是由系统自动创建的,自动释放的。

 动态创建任务的函数为 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;

#define portSTACK\_GROWTH    //-1表示满减栈  
#if( portSTACK\_GROWTH > 0 ){  
}  
#else{ /\* portSTACK\_GROWTH<0  代表堆栈向下增长 \*/  
    StackType\_t \*pxStack;  
    /\* 任务栈内存分配,stm32是向下增长的堆栈,获取到的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 /\* portSTACK\_GROWTH \*/

if( pxNewTCB != NULL )  
{  
    /\* 新建任务初始化 \*/  
    prvInitialiseNewTask( pxTaskCode, pcName, ( uint32\_t ) usStackDepth, pvParameters, uxPriority, pxCreatedTask, pxNewTCB, NULL );  
    /\* 把任务添加到就绪列表中 \*/  
    prvAddNewTaskToReadyList( pxNewTCB );  
    xReturn = pdPASS;  
}  
else{  
    xReturn = errCOULD\_NOT\_ALLOCATE\_REQUIRED\_MEMORY;  
}

return xReturn;  

}

之后,又调用了函数 prvInitialiseNewTask()来新建任务初始化。我们看看下面是如何定义的。

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;

/\* 计算栈顶的地址 \*/  
#if( portSTACK\_GROWTH < 0 ){  
    /\* 把栈空间的高地址分配给栈顶 \*/  
    pxTopOfStack = pxNewTCB->pxStack + ( ulStackDepth - ( uint32\_t ) 1 );  
    /\* 栈对齐----栈要8字节对齐 \*/  
    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 /\* portSTACK\_GROWTH \*/  
{  
}  
#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();  
    }  
}

/\* \\0补齐字符串 \*/  
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;  
/\* 任务状态表 事件表初始化 \*/  
vListInitialiseItem( &( pxNewTCB->xStateListItem ) );  
vListInitialiseItem( &( pxNewTCB->xEventListItem ) );  
/\* 设置任务控制块中的状态列表项的成员变量ower ,是属于PxNewTCB(拥有该结点的内核对象) \*/  
listSET\_LIST\_ITEM\_OWNER( &( pxNewTCB->xStateListItem ), pxNewTCB );  
/\*更改事件列表项中的成员变量xItemValue的值,目的是列表在排列的时候,是按照优先级由大到小排列 \*/  
listSET\_LIST\_ITEM\_VALUE( &( pxNewTCB->xEventListItem ), ( TickType\_t ) configMAX\_PRIORITIES - ( TickType\_t ) uxPriority );   
/\*设置任务控制块中事件列表项的成员变量ower,同上\*/  
listSET\_LIST\_ITEM\_OWNER( &( pxNewTCB->xEventListItem ), pxNewTCB );

#if( portUSING\_MPU\_WRAPPERS == 1 ){

}  
#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();  
}  

}

  prvInitialiseNewTask()函数的形参,出来xTaskCreat()的形参之外,又多出来pxNewTCB和xRegions两个形参;

后面又调用了 pxPortInitialiseStack(StackType_t *pxTopOfStack, TaskFunction_t pxCode, void *pvParameters)

来初始化任务堆栈。

StackType_t *pxPortInitialiseStack(StackType_t *pxTopOfStack, TaskFunction_t pxCode, void *pvParameters){
pxTopOfStack--; /* 入栈程序状态寄存器 */
*pxTopOfStack = portINITIAL_XPSR; /* xPSR */

pxTopOfStack--;        /\* 入栈PC指针 \*/  
\*pxTopOfStack = ( ( StackType\_t ) pxCode ) & portSTART\_ADDRESS\_MASK;    /\* PC \*/

pxTopOfStack--;        /\* 入栈LR链接寄存器 \*/  
\*pxTopOfStack = ( StackType\_t ) prvTaskExitError;    /\* LR \*/

pxTopOfStack -= 5;    /\* 跳过R12, R3, R2 and R1这四个寄存器,不初始化 \*/  
\*pxTopOfStack = ( StackType\_t ) pvParameters;    /\* R0作为传参入栈 \*/

pxTopOfStack--;        /\* 保存EXC\_RETURN的值,用于退出SVC或PendSV中断时候,处理器处于什么状态\*/  
\*pxTopOfStack = portINITIAL\_EXEC\_RETURN;

pxTopOfStack -= 8;    /\* 跳过R11, R10, R9, R8, R7, R6, R5 and R4这8个寄存器,不初始化 \*/  
return pxTopOfStack;    /\*最终返回栈顶\*/

初始化堆栈完成之后堆栈如下图:

层层深入完毕,现在我们返回到xTaskCreat()函数后面,看看  prvAddNewTaskToReadyList( pxNewTCB ); 函数是怎么把任务添加到就绪列表中!

static void prvAddNewTaskToReadyList( TCB_t *pxNewTCB )
{

taskENTER\_CRITICAL();  
{  
    uxCurrentNumberOfTasks++;  
    if( pxCurrentTCB == NULL )   //正在运行的任务块为NULL,没有任务运行;  
    {  
        pxCurrentTCB = pxNewTCB;  //将新任务控制块赋值给pxCurrentTCB

        if( uxCurrentNumberOfTasks == ( UBaseType\_t ) 1 ) //为1说明正在创建的任务是第一个任务。  
        {

            prvInitialiseTaskLists();   //初始化列表,就绪列表、阻塞列表等等  
        }  
        else  
        {  
            mtCOVERAGE\_TEST\_MARKER();  
        }  
    }  
    else  
    {

        if( xSchedulerRunning == pdFALSE ) //判断任务调度器是否运行,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();  
}  

}

  一定要耐心分析,别无他法,加油!不难。

手机扫一扫

移动阅读更方便

阿里云服务器
腾讯云服务器
七牛云服务器

你可能感兴趣的文章