FreeRTOS学习记录–任务创建函数详解

虚幻大学 xuhss 290℃ 0评论

Python微信订餐小程序课程视频

https://edu.csdn.net/course/detail/36074

Python实战量化交易理财系统

https://edu.csdn.net/course/detail/35475
开局一张图。一步一步分析就好。

f17af7ff0c384bd7332b1b28cb3b3e1f - FreeRTOS学习记录--任务创建函数详解

(一)什么是任务?

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

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

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

  在任务创建函数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 开头的都是条件编译,咱们可以先不用理解。基本结构如下:

1ca00ead2c5bf152cb1f84c286af7bdb - FreeRTOS学习记录--任务创建函数详解

  

   指针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;    /*最终返回栈顶*/

复制代码
初始化堆栈完成之后堆栈如下图:

5e2eac3e45342ef42fd6078b3f8dff2b - FreeRTOS学习记录--任务创建函数详解

层层深入完毕,现在我们返回到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();
 }
}

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

转载请注明:xuhss » FreeRTOS学习记录–任务创建函数详解

喜欢 (0)

您必须 登录 才能发表评论!