FreeRTOS的学习(十三)——任务创建

FreeRTOS的学习系列文章目录

FreeRTOS的学习(一)——STM32上的移植问题
FreeRTOS的学习(二)——任务优先级问题
FreeRTOS的学习(三)——中断机制
FreeRTOS的学习(四)——列表
FreeRTOS的学习(五)——系统延时
FreeRTOS的学习(六)——系统时钟
FreeRTOS的学习(七)——1.队列概念
FreeRTOS的学习(七)——2.队列入队源码分析
FreeRTOS的学习(七)——3.队列出队源码分析
FreeRTOS的学习(八)——1.二值信号量
FreeRTOS的学习(八)——2.计数型信号量
FreeRTOS的学习(八)——3.优先级翻转问题
FreeRTOS的学习(八)——4.互斥信号量
FreeRTOS的学习(九)——软件定时器
FreeRTOS的学习(十)——事件标志组
FreeRTOS的学习(十一)——任务通知


目录

  • FreeRTOS的学习系列文章目录
  • 前言
  • 1 任务创建


前言

本文将分析阐述FreeRTOS的任务创建过程(以动态申请内存为例),并给出通用的步骤过程。


1 任务创建

任务创建使用xTaskCreate()。
xTaskCreate内部步骤主要如下:

  1. 使用函数 pvPortMalloc()给任务的任务堆栈申请内存,申请内存的时候会做字节对齐处理。
#if ( configSUPPORT_DYNAMIC_ALLOCATION == 1 )
//动态内存申请,与内存时FreeRTOS自动分配。
BaseType_t xTaskCreate( TaskFunction_t pxTaskCode,
                        const char * const pcName, /*lint !e971 Unqualified char types are allowed for strings and single characters only. */
                        const configSTACK_DEPTH_TYPE usStackDepth,
                        void * const pvParameters,
                        UBaseType_t uxPriority,
                        TaskHandle_t * const pxCreatedTask )
{
  1. 如果堆栈的内存申请成功的话就接着给任务控制块申请内存,同样使用函数pvPortMalloc(),反之,则将任务控制块赋空。
pxStack = pvPortMallocStack( ( ( ( size_t ) usStackDepth ) * sizeof( StackType_t ) ) );
 else
        {
            pxNewTCB = NULL;
        }
  1. 任务控制块内存申请成功的话就初始化内存控制块中的任务堆栈字段 pxStack,使用(1)中申请到的任务堆栈。
if( pxNewTCB != NULL )
            {
                /* Store the stack location in the TCB. */
                pxNewTCB->pxStack = pxStack;
            }
  1. 反之,如果任务控制块内存申请失败的话就释放前面已经申请成功的任务堆栈的内存。
else
            {
                vPortFreeStack( pxStack );
            }
  1. 标记任务堆栈和任务控制块是使用动态内存分配方法得到的。
#if ( tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE != 0 ) 
        {
            pxNewTCB->ucStaticallyAllocated = tskDYNAMICALLY_ALLOCATED_STACK_AND_TCB;
        }
#endif
  1. 使用函数 prvInitialiseNewTask()初始化任务,这个函数完成对任务控制块中各个字段的初始化工作!
//初始化新的任务
        prvInitialiseNewTask( pxTaskCode, pcName, ( uint32_t ) usStackDepth, pvParameters, uxPriority, pxCreatedTask, pxNewTCB, NULL );

6.1. 如果使能了堆栈溢出检测功能或者追踪功能的话就使用一个定值 taskSTACK_FILL_BYTE 来填充任务堆栈,这个值为 0xa5U。

#if ( ( configCHECK_FOR_STACK_OVERFLOW > 1 ) || ( configUSE_TRACE_FACILITY == 1 ) || ( INCLUDE_uxTaskGetStackHighWaterMark == 1 ) || ( INCLUDE_uxTaskGetStackHighWaterMark2 == 1 ) )
#define tskSET_NEW_STACKS_TO_KNOWN_VALUE    1
#else
#define tskSET_NEW_STACKS_TO_KNOWN_VALUE    0
#endif

填充堆栈。

#if ( tskSET_NEW_STACKS_TO_KNOWN_VALUE == 1 )
    {
        ( void ) memset( pxNewTCB->pxStack, ( int ) tskSTACK_FILL_BYTE, ( size_t ) ulStackDepth * sizeof( StackType_t ) );
    }
#endif /* tskSET_NEW_STACKS_TO_KNOWN_VALUE */

6.2. 计算堆栈栈顶 pxTopOfStack,arm的堆栈是从高到低使用的,所以此时portSTACK_GROWTH < 0

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

6.3. 保存任务的任务名,任务名数组添加字符串结束符’\0’。

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;
    }

6.4. 判断任务优先级是否合法,如果设置的任务优先级大于 configMAX_PRIORITIES,则
将优先级修改为 configMAX_PRIORITIES-1。初始化任务控制块的优先级字段 uxPriority。

if( uxPriority >= ( UBaseType_t ) configMAX_PRIORITIES )
    {
        uxPriority = ( UBaseType_t ) configMAX_PRIORITIES - ( UBaseType_t ) 1U;
    }
    else
    {
        mtCOVERAGE_TEST_MARKER();
    }
    pxNewTCB->uxPriority = uxPriority;

6.5. 使能了互斥信号量功能,需要初始化相应的字段。

#if ( configUSE_MUTEXES == 1 )
    {
        pxNewTCB->uxBasePriority = uxPriority;
        pxNewTCB->uxMutexesHeld = 0;
    }

6.6. 、初始化列表项 xStateListItem和 xEventListItem,任务控制块结构体中有两个列表
项,这里对这两个列表项做初始化。

//初始化列表项
    vListInitialiseItem( &( pxNewTCB->xStateListItem ) );
    vListInitialiseItem( &( pxNewTCB->xEventListItem ) );

6.7. 设置列表项 xStateListItem和 xEventListItem属于当前任务的任务控制块,也就
是设置这两个列表项的字段 pvOwner 为新创建的任务的任务控制块。

listSET_LIST_ITEM_OWNER( &( pxNewTCB->xStateListItem ), pxNewTCB );
/******6.8所描述的内同********/
listSET_LIST_ITEM_OWNER( &( pxNewTCB->xEventListItem ), pxNewTCB );

6.8. 设置列表项xEventListItem的字段xItemValue为configMAX_PRIORITIES- uxPriority,比如当前任务优先级 3,最大优先级为 32,那么 xItemValue 就为 32-3=29,这就意味着 xItemValue
值越大,优先级就越小。

    listSET_LIST_ITEM_VALUE( &( pxNewTCB->xEventListItem ), ( TickType_t ) configMAX_PRIORITIES - ( TickType_t ) uxPriority ); 

列表的插入是按照xItemValue 的值升序排列的。

6.9. 调用函数 pxPortInitialiseStack()初始化任务堆栈。

pxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pxTaskCode, pvParameters );

在任务初始化函数中会对任务堆栈初始化,这个过程通过调用函数 pxPortInitialiseStack()来
完成,函数 pxPortInitialiseStack()就是堆栈初始化函数,函数源码如下:

StackType_t * pxPortInitialiseStack( StackType_t * pxTopOfStack,
                                     TaskFunction_t pxCode,
                                     void * pvParameters )
{
    /* Simulate the stack frame as it would be created by a context switch
     * interrupt. */
    pxTopOfStack--;                                                      /* Offset added to account for the way the MCU uses the stack on entry/exit of interrupts. */
    *pxTopOfStack = portINITIAL_XPSR;                                    /* 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;
}

堆栈是用来在进行上下文切换的时候保存现场的,一般在新创建好一个堆栈以后会对其先
进行初始化处理,即对Cortex-M内核的某些寄存器赋初值。这些初值就保存在任务堆栈中,保
存的顺序按照:xPSR、R15(PC)、R14(LR)、R12、R3 ~ R0、R11 ~ R14。
(1)、寄存器 xPSR 值为 portINITIAL_XPSR,其值为 0x01000000。xPSR 是 Cortex-M 的一个内核寄存器,叫做程序状态寄存器,0x01000000表示这个寄存器的bit24为1,表示处于Thumb状态,即使用的 Thumb 指令。
(2)、寄存器PC 初始化为任务函数 pxCode。
(3)、寄存器 LR初始化为函数 prvTaskExitError。
(4)、跳过 4 个寄存器,R12,R3,R2,R1,这四个寄存器不初始化。
(5)、寄存器 R0 初始化为 pvParameters,一般情况下,函数调用会将R0~R3 作为输入参数,R0 也可用作返回结果,如果返回值为 64 位,则 R1 也会用于返回结果(在《权威指南》“第 8章 深入了解异常处理”的 8.1.2 小节中有讲解,P188),这里的 pvParameters 是作为任务函数的参数,保存在寄存器 R0 中。
(6)、跳过 8 个寄存器,R11、R10、R8、R7、R6、R5、R4。

经过上面的初始化之后,此时的堆栈结果如下图所示:
FreeRTOS的学习(十三)——任务创建_第1张图片

6.10. 生成任务句柄,返回给参数 pxCreatedTask,从这里可以看出任务句柄其实就是任务
控制块。

if( pxCreatedTask != NULL )
    {
        /* Pass the handle out in an anonymous way.  The handle can be used to
         * change the created task's priority, delete the created task, etc.*/
        *pxCreatedTask = ( TaskHandle_t ) pxNewTCB;
    }
  1. 使用函数 prvAddNewTaskToReadyList()将新创建的任务加入到就绪列表中。
    7.1. 变量 uxCurrentNumberOfTasks 为全局变量,用来统计任务数量。
    7.2. 变量 uxCurrentNumberOfTasks 为 1 说明正在创建的任务是第一个任务!那么就需要先初始化相应的列表,通过调用函数 prvInitialiseTaskLists()来初始化相应的列表。
    7.3. 新创建的任务优先级比正在运行的任务优先级高,所以需要修改 pxCurrentTCB 为新建任务的任务控制块。
    7.4. 调用函数 prvAddTaskToReadyList()将任务添加到就绪列表中(将任务添加到就绪列表末尾)。前面的关于任务优先级的文章已经讲过了。
    7.5. 如果新任务的任务优先级最高,而且调度器已经开始正常运行了,那么就调用函数taskYIELD_IF_USING_PREEMPTION()完成一次任务切换。

你可能感兴趣的:(STM32,FreeRTOS,学习,单片机,嵌入式硬件)