• FreeRTOS 任务创建分析


    FreeRTOS 任务创建和启动方式

    • Fang XS.
    • 1452512966@qq.com
    • 如果有错误,希望被指出,学习技术的路难免会磕磕绊绊

    FreeRTOS

    动态任务创建源码分析

    • 以STM32F103为例:
    • 注:
      使用该函数需使能宏configSUPPORT_DYNAMIC_ALLOCATION
      FreeRTOS\portable\RVDS\ARM_CM3\portmacro.h中定义了 #define portSTACK_GROWTH ( -1 )也就是堆栈增长方向为高地址向低地址增长;
    • 函数原型
     BaseType_t xTaskCreate( 	TaskFunction_t pxTaskCode,					/* 任务函数 */
                                const char * const pcName, 					/* 任务名称 */
                                const configSTACK_DEPTH_TYPE usStackDepth,	/* 任务堆栈大小 */
                                void * const pvParameters,					/* 传入给任务函数的参数 */
                                UBaseType_t uxPriority,						/* 任务优先级 */
                                TaskHandle_t * const pxCreatedTask );		/* 任务句柄 */
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 该函数做了如下工作
    1. 申请任务堆栈内存和TCB内存
      pxStack = pvPortMallocStack( ( ( ( size_t ) usStackDepth ) * sizeof( StackType_t ) ) );
      usStackDepth 就是调用API时填入的形参,即任务堆栈大小,StackType_t 在FreeRTOS\portable\RVDS\ARM_CM3\portmacro.h中定义为uint32_t就是4字节;
      即:申请的内存是(usStackDepth * 4)字节,这个任务栈内存是当发生任务切换时用来储存当前任务函数中的变量和上下文信息等的,一般会申请稍大于任务函数中变量占用内存之和的量;
      任务堆栈申请成功后执行 pxNewTCB = ( TCB_t * ) pvPortMalloc( sizeof( TCB_t ) );来申请TCB内存,如果申请不成功就释放申请过的任务堆栈内存,并返回errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY;
    //省略其他代码
    {
                    StackType_t * pxStack;
                    /* Allocate space for the stack used by the task being created. */
                    pxStack = pvPortMallocStack( ( ( ( size_t ) usStackDepth ) * sizeof( StackType_t ) ) ); /*lint !e9079 All values returned by pvPortMalloc() have at least the alignment required by the MCU's stack and this allocation is the stack. */
                    if( pxStack != NULL ){
                        /* Allocate space for the TCB. */
                        pxNewTCB = ( TCB_t * ) pvPortMalloc( sizeof( TCB_t ) ); /*lint !e9087 !e9079 All values returned by pvPortMalloc() have at least the alignment required by the MCU's stack, and the first member of TCB_t is always a pointer to the task's stack. */
                        if( pxNewTCB != NULL )
                        {
                            /* Store the stack location in the TCB. */
                            pxNewTCB->pxStack = pxStack;
                        }else{
                            /* The stack cannot be used as the TCB was not created.  Free
                             * it again. */
                            vPortFreeStack( pxStack );
                        }
                    }else{
                        pxNewTCB = NULL;
                    }
                }
    //省略其他代码
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    可以看到FreeRTOS中包含大量指针检测,此框架在Linux中同样广泛使用:

    	// 这个框架在linux中也广泛使用
    	xxx = malloc(n);
    	if(xxx != NULL){
    		// 成功处理
    	}else{
    		// 错误处理
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    1. 初始化该任务
      任务堆栈和TCB都申请成功后,调用了prvInitialiseNewTask( pxTaskCode, pcName, ( uint32_t ) usStackDepth, pvParameters, uxPriority, pxCreatedTask, pxNewTCB, NULL );来初始化一个任务,这个函数是针对TCB的初始化,做了以下重要工作:
      • 把整个任务栈填充为0xa5,用户就可以通过调用FreeRTOS中uxTaskGetStackHighWaterMark函数来获取任务堆栈的内存余量,来确定是否需要增加或减小任务堆栈,任务堆栈设置过大就浪费内存了,过小有溢出的可能,这一机制可以更方便的设置任务堆栈为合理的大小;
      • 然后依照任务堆栈大小和栈增长方向确定栈定地址
      • 储存任务名称,任务优先级到TCB
      • 初始化列表项,设置列表项的owner
    //省略其他代码
    	( void ) memset( pxNewTCB->pxStack, ( int ) tskSTACK_FILL_BYTE, ( size_t ) ulStackDepth * sizeof( StackType_t ) );
            }
        #endif /* tskSET_NEW_STACKS_TO_KNOWN_VALUE */
    
        /* Calculate the top of stack address.  This depends on whether the stack
         * grows from high memory to low (as per the 80x86) or vice versa.
         * portSTACK_GROWTH is used to make the result positive or negative as required
         * by the port. */
        #if ( portSTACK_GROWTH < 0 )
            {
                pxTopOfStack = &( pxNewTCB->pxStack[ ulStackDepth - ( uint32_t ) 1 ] );
                pxTopOfStack = ( StackType_t * ) ( ( ( portPOINTER_SIZE_TYPE ) pxTopOfStack ) & ( ~( ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) ) ); /*lint !e923 !e9033 !e9078 MISRA exception.  Avoiding casts between pointers and integers is not practical.  Size differences accounted for using portPOINTER_SIZE_TYPE type.  Checked by assert(). */
    
                /* Check the alignment of the calculated top of stack is correct. */
                configASSERT( ( ( ( portPOINTER_SIZE_TYPE ) pxTopOfStack & ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) == 0UL ) );
    
                #if ( configRECORD_STACK_HIGH_ADDRESS == 1 )
                    {
                        /* Also record the stack's high address, which may assist
                         * debugging. */
                        pxNewTCB->pxEndOfStack = pxTopOfStack;
                    }
                #endif /* configRECORD_STACK_HIGH_ADDRESS */
            }
    //省略其他代码
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    1. 添加该任务到任务就绪列表
      任务TCB初始化完成后,调用了 prvAddNewTaskToReadyList( pxNewTCB );这个函数做了以下重要工作:
      • 当前总任务数+1
      • 如果 是第一个创建的任务,就初始化任务列表
      • 否则
        • 如果当前运行的任务优先级小于新任务,当前TCB指针指向新任务
        • 将新任务TCB添加到就绪列表
      • 如果 调度器正在运行且当前任务优先级小于新创建任务,触发主动任务切换,运行新创建任务
    • 这一机制,挺适合用一个任务来创建其他任务,只要新创建的任务优先级高,于创建任务就切换到新创建的任务运行,等新创建的任务阻塞了,再创建下一个任务…,也就是正点原子用的任务创建方法。

    任务创建方法

    • 一共2种:
    1. 创建一个用来创建其他任务的任务,开启调度器;
      • 这种方式有一个需要注意的点:
        • 内存管理方案heap_1只分配不回收,所以使用这种方式创建任务,推荐的内存管理方案是heap_4.
      • 比如正点原子例程:
        在这里插入图片描述
      • 在这个任务里创建需要的任务,等任务创建完成再删除任务创建任务
        在这里插入图片描述
    2. 创建好所有任务再开启调度器
      • 这种方式适合所有内存管理方案
      • 一般情况下,创建的任务和信号量,队列等内核对象都是有用的,不需要删除。
      • 个人比较喜欢内存管理方案heap_1
      • 创建好所有任务后,开启调度器让调度器依照任务创建时指定的优先级等参数来决定运行那个任务
      • 例如:
        在这里插入图片描述
    • 这两种方法,在实际应用都比较常用。
    • RT-Thread中把main函数也当作一个线程,默认就是第一种方式创建任务。
  • 相关阅读:
    Prometheus 云原生 - 微服务监控报警系统 (Promethus、Grafana、Node_Exporter)部署、简单使用
    骑行耳机推荐,骑行最适合佩戴的几款运动耳机推荐
    头歌实践平台-数据结构-二叉树及其应用
    linux脚本执行docker容器命令
    【CTF】Crypto Writeup【思路已经告诉你了】
    SNPE教程二:环境搭建和算子支持
    pip install fitz -i https://pypi.doubanio.com/simple/
    你的NET程序需要保护吗?Agile.net 6.6.X 注入式Crack
    Win11怎么设置让CPU性能全开?Win11CPU怎么设置高性能模式?
    小车测速并通过串口发送速度数据
  • 原文地址:https://blog.csdn.net/m0_49319736/article/details/133174161