FreeRTOS(三)——应用开发(一)

文章详细解析了FreeRTOS实时操作系统的文件结构、任务管理机制、消息队列运作原理、信号量概念及其应用,深入讨论了任务调度策略、内存管理与中断处理,为嵌入式开发者提供FreeRTOS配置、任务创建、状态迁移及资源同步的全面指导。

文章目录

0x01 FreeRTOS文件夹

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jfXPQ13P-1682496278374)(C:\Users\user\AppData\Roaming\Typora\typora-user-images\image-20230419103423058.png)]

FreeRTOS 文件夹下的 Source 文件夹里面包含的是 FreeRTOS内核的源代码,Demo 文件夹里面包含了 FreeRTOS 官方为各个单片机移植好的工程代码。从Demo中可以得到FreeRTOSConfig.h

  • Source文件夹

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xnS8IfMB-1682496278374)(C:\Users\user\AppData\Roaming\Typora\typora-user-images\image-20230419105110294.png)]

  • include以及各种.c文件包含的是FreeRTOS的通用头文件和C文件,这两部分的文件试用于各种编译器和处理器,是通用的。

  • portblle:里面很多与编译器相关的文件夹,在不同的编译器中使用不同的支持文件。Keil文件与RVDS的文件是一样的,其中的MemMang文件是与内存管理相关的。
    在这里插入图片描述

  • RVDS:这个文件夹包含了各种处理器相关的文件夹,FreeRTOS需要软硬结合,不同的硬件接口文件是不一样的,需要编写代码来进行关联,这部分关联则叫关联文件,一般由汇编和C联合编写。FreeRTOS 为我们提供了 cortex-m0、m3、m4 和 m7 等内核的单片机的接口文件,只要是使用了这些内核的 mcu 都可以使用里面的接口文件。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-x5kbnxWQ-1682496278375)(C:\Users\user\AppData\Roaming\Typora\typora-user-images\image-20230419105704894.png)]

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aTaRMmrn-1682496278375)(C:\Users\user\AppData\Roaming\Typora\typora-user-images\image-20230419110709569.png)]

    里面的文件,里面只有“port.c”与“portmacro.h”两个文件,port.c 文件里面的内容是由 FreeRTOS 官方的技术人员为 Cortex-M3 内核的处理器写的接口文件,里面核心的上下文切换代码是由汇编语言编写而成。portmacro.h 则是 port.c 文件对应的头文件,主要是一些数据类型和宏定义。

  • MemMang:文件夹下存放的是跟内存管理相关的,总共有五个 heap 文件以及一个 readme 说明文件,这五个 heap 文件在移植的时候必须使用一个,因为 FreeRTOS 在创建内核对象的时候使用的是动态分配内存,而这些动态内存分配的函数则在这几个文件里面实现,不同的分配算法会导致不同的效率与结果。

  • Demo:各种开发平台完整的Demo。

  • FreeRTOS-Plus:reeRTOS-Plus 文件夹里面包含的是第三方的产品,一般我们不需要使用,FreeRTOSPlus 的预配置演示项目组件(组件大多数都要收费),大多数演示项目都是在 Windows 环境中运行的,使用 FreeRTOS windows 模拟器。

FreeRTOSConfig.h文件内容

#ifndef FREERTOS_CONFIG_H
#define FREERTOS_CONFIG_H

// 针对不同的编译器调用不同的stdint.h文件
// 针对不同的编译器调用不同的 stdint.h 文件,在 MDK 中,我们默
认的是__CC_ARM。
#if defined(__ICCARM__) || defined(__CC_ARM) || defined(__GNUC__)
  #include <stdint.h>
  extern uint32_t SystemCoreClock;
#endif
// 断言
#define vAssertCalled(char,int) printf("Error:%s,%d\r\n",char,int)
#define configASSERT(x) if((x)==0) vAssertCalled(__FILE__,__LINE__)

// FREERTOS基础配置选项

//1:RTOS使用抢占式调度器 0:RTOS使用协作式调度器(时间片)
//协作式操作系统是任务主动释放CPU后,切换到下一个任务,任务切换的时机完全取决于正在运行的任务。
#define configUSE_PREEMPTION                     1
//支持静态内存
#define configSUPPORT_STATIC_ALLOCATION          1
//支持动态内存申请
#define configSUPPORT_DYNAMIC_ALLOCATION         1

// 置 1:使用空闲钩子(Idle Hook 类似于回调函数);置 0:忽略空闲钩子
// 空闲任务钩子是一个函数,这个函数由用户来实现, FreeRTOS 规定了函数的名字和参数:void vApplicationIdleHook(void ),
// 这个函数在每个空闲任务周期都会被调用
// 对于已经删除的 RTOS 任务,空闲任务可以释放分配给它们的堆栈内存。
// 因此必须保证空闲任务可以被 CPU 执行,使用空闲钩子函数设置 CPU 进入省电模式是很常见的,不可以调用会引起空闲任务阻塞的 API 函数
#define configUSE_IDLE_HOOK                      0

// 时间片钩子是一个函数,这个函数由用户来实现, FreeRTOS 规定了函数的名字和参数:void vApplicationTickHook(void )
// 时间片中断可以周期性的调用, 函数必须非常短小,不能大量使用堆栈,不能调用以”FromISR" 或 "FROM_ISR”结尾的 API 函数
#define configUSE_TICK_HOOK                      0
//置 1:使用时间片钩子(Tick Hook);置 0:忽略时间片钩子
//写入实际的 CPU 内核时钟频率,也就是 CPU 指令执行频率,通常称为 Fclk, Fclk 为供给 CPU 内核的时钟信号,我们所说的 cpu 主频为 XX MHz,就是指的这个时钟信号,相应的,1/Fclk 即为 cpu 时钟周期。
#define configCPU_CLOCK_HZ                       ( SystemCoreClock )

//RTOS 系统节拍中断的频率。即一秒中断的次数,每次中断 RTOS 都会进行任务调度
//在 FreeRTOS 中,系统延时和阻塞时间都是以 tick 为单位,配置 configTICK_RATE_HZ 的值可以改变中断的频率,从而间接改变了 FreeRTOS 的时钟周期(T=1/f)
#define configTICK_RATE_HZ                       ((TickType_t)1000)

//可使用的最大优先级
//低优先级数值表示低优先级任务
#define configMAX_PRIORITIES                     ( 16 )

//空闲任务使用的堆栈大小
#define configMINIMAL_STACK_SIZE                 ((uint16_t)128)
///系统所有总的堆大小
//FreeRTOS 内核总计可用的有效的 RAM 大小
#define configTOTAL_HEAP_SIZE                    ((size_t)3072)

//任务名字字符串长度
//这里定义的长度包括字符串结束符’\0’
#define configMAX_TASK_NAME_LEN                  ( 16 )

//启用可视化跟踪调试
#define configUSE_TRACE_FACILITY                 1
//与宏 configUSE_TRACE_FACILITY 同时为 1 时会编译下面 3 个函数
// prvWriteNameToBuffer()\ vTaskList()\ vTaskGetRunTimeStats()
#define configUSE_STATS_FORMATTING_FUNCTIONS     1

//系统节拍计数器变量数据类型,1 表示为 16 位无符号整形,0 表示为 32 位无符号整形
//这个值位数的大小决定了能计算多少个 tick
#define configUSE_16_BIT_TICKS                   0

// 互斥量以及队列长度设置
#define configUSE_MUTEXES                        1
#define configQUEUE_REGISTRY_SIZE                8

//某些运行 FreeRTOS 的硬件有两种方法选择下一个要执行的任务:通用方法和特定于硬件的方法(以下简称“特殊方法”)。
//一般是硬件计算前导零指令,如果所使用的,MCU 没有这些硬件指令的话此宏应该设置为 0
//通用方法:
//1.configUSE_PORT_OPTIMISED_TASK_SELECTION 为 0 或者硬件不支持这种特殊方法。
//2.可以用于所有 FreeRTOS 支持的硬件
//3.完全用 C 实现,效率略低于特殊方法。
//4.不强制要求限制最大可用优先级数目
//特殊方法:
//1.必须将 configUSE_PORT_OPTIMISED_TASK_SELECTION 设置为 1。
//2.依赖一个或多个特定架构的汇编指令(一般是类似计算前导零[CLZ]指令)。
//3.比通用方法更高效
//4.一般强制限定最大可用优先级数目为 32
#define configUSE_PORT_OPTIMISED_TASK_SELECTION  1
//启用协程,启用协程以后必须添加文件 croutine.c
#define configUSE_CO_ROUTINES                    0
//协程的有效优先级数目
#define configMAX_CO_ROUTINE_PRIORITIES          ( 2 )

//可选函数配置选项
#define INCLUDE_vTaskPrioritySet            1
#define INCLUDE_uxTaskPriorityGet           1
#define INCLUDE_vTaskDelete                 1
#define INCLUDE_vTaskCleanUpResources       0
#define INCLUDE_vTaskSuspend                1
#define INCLUDE_vTaskDelayUntil             0
#define INCLUDE_vTaskDelay                  1
#define INCLUDE_xTaskGetSchedulerState      1

//与中断有关选项
#ifdef __NVIC_PRIO_BITS
 /* __BVIC_PRIO_BITS will be specified when CMSIS is being used. */
 #define configPRIO_BITS         __NVIC_PRIO_BITS
#else
 #define configPRIO_BITS         4
#endif

//中断最低优先级
//这里是中断优先级,中断优先级的数值越小,优先级越高。而 FreeRTOS 的任务优先级是,任务优先级数值越小,任务优先级越低
#define configLIBRARY_LOWEST_INTERRUPT_PRIORITY   15

//系统可管理的最高中断优先级
//中断优先级数值在 0、1、2、3、4 的这些中断是不受 FreeRTOS 管理的,不可被屏蔽,也不能调用 FreeRTOS 中的 API 函数接口,而中断优先级在 5 到 15的这些中断是受到系统管理,可以被屏蔽的。
#define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 5
//用于配置 basepri 寄存器的,当 basepri 设置为某个值的时候,会让系统不响应比该优先级低的中断,而优先级比之更高的中断则不受影响。
#define configKERNEL_INTERRUPT_PRIORITY 		( configLIBRARY_LOWEST_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) )
//对需要配置的 SysTick 与 PendSV 进行偏移
//在 port.c 中会用到 configKERNEL_INTERRUPT_PRIORITY 这个宏定义来配置SCB_SHPR3(系统处理优先级寄存器,地址为:0xE000 ED20)
//中断优先级 0(具有最高的逻辑优先级)不能被 basepri 寄存器屏蔽,因此,configMAX_SYSCALL_INTERRUPT_PRIORITY 绝不可以设置成 0。
#define configMAX_SYSCALL_INTERRUPT_PRIORITY 	( configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) )

//与中断服务函数有关的配置选项
#define vPortSVCHandler    SVC_Handler
#define xPortPendSVHandler PendSV_Handler
#define xPortSysTickHandler SysTick_Handler

#endif

上面定义的宏决定FreeRTOS.h文件中的定义

// FreeRTOS基础配置
// 1使能时间片调度(默认式使能)
#define configUSE_TIME_SLICING					1
// 置 1:使能低功耗 tickless 模式;置 0:保持系统节拍(tick)中断一直运行
#define configUSE_TICKLESS_IDLE 				0
// 空闲任务放弃 CPU 使用权给其他同优先级的用户任务
//满足条件才会起作用:1:启用抢占式调度;2:用户任务优先级与空闲任务优先级相等。一般不建议使用这个功能,能避免尽量避免,1:设置用户任务优先级比空闲任务优先级高,2:这个宏定义配置为 0。
#define configIDLE_SHOULD_YIELD					1
// 是否启动队列
#define configUSE_QUEUE_SETS 					0
// 开启任务通知功能,默认开启
#define configUSE_TASK_NOTIFICATIONS 			1
// 使用互斥信号量
#define configUSE_MUTEXES 						0
// 使用递归互斥信号量
#define configUSE_RECURSIVE_MUTEXES 			0
// 1 使用计数信号量
#define configUSE_COUNTING_SEMAPHORES 			0
// 设置可以注册的信号量和消息队列个数
#define configQUEUE_REGISTRY_SIZE 				0U
#define configUSE_APPLICATION_TASK_TAG 			0
//使用内存申请失败钩子函数
#define configUSE_MALLOC_FAILED_HOOK 			0
//大于 0 时启用堆栈溢出检测功能,如果使用此功能用户必须提供一个栈溢出钩子函数,如果使用的话,此值可以为 1 或者 2,因为有两种栈溢出检测方法
#define configCHECK_FOR_STACK_OVERFLOW 			0
//启用运行时间统计功能
#define configGENERATE_RUN_TIME_STATS 			0
//启用软件定时器
#define configUSE_TIMERS 						0
  • 抢占式调度:在这种调度方式中,系统总是选择优先级最高的任务进行调度,并且 一旦高优先级的任务准备就绪之后,它就会马上被调度而不等待低优先级的任务主动放弃 CPU,高优先级的任务抢占了低优先级任务的 CPU 使用权,这就是抢占。在实时操作系统中,这样子的方式往往是最适用的。而协作式调度则是由任务主动放弃CPU,然后才进行任务调度。
  • 当优先级相同的时候,就会采用时间片调度,这意味着 RTOS 调度器总是运行处于最高优先级的就绪任务,在每个FreeRTOS 系统节拍中断时在相同优先级的多个任务间进行任务切换。如果宏configUSE_TIME_SLICING 设置为 0,FreeRTOS 调度器仍然总是运行处于最高优先级的就 绪任务,但是当 RTOS 系统节拍中断发生时,相同优先级的多个任务之间不再进行任务切换,而是在执行完高优先级的任务之后才进行任务切换。

0x02 创建任务

  • 任务里面的延时函数必须使用 FreeRTOS 里面提供的延时函数,并不能使用我们裸机编程中的那种延时。
  • 这两种的延时的区别是 FreeRTOS 里面的延时是阻塞延时,即调用 vTaskDelay()函数的时候,当前任务会被挂起,调度器会切换到其它就绪的任务,从而实现多任务。
  • 如果还是使用裸机编程中的那种延时,那么整个任务就成为了一个死循环,如果恰好该任务的优先级是最高的,那么系统永远都是在这个任务中运行,比它优先级更低的任务无法运行,根本无法实现多任务。
  • 任务必须是一个死循环,否则任务将通过 LR 返回,如果 LR 指向了非法的内存就会产生 HardFault_Handler,而 FreeRTOS 指向一个任务退出函数prvTaskExitError(),里面是一个死循环,那么任务返回之后就在死循环中执行,这样子的任务是不安全的,所以避免这种情况,任务一般都是死循环并且无返回值的。

创建静态任务过程configSUPPORT_STATIC_ALLOCATION

使用静态创建任务时,需要将configSUPPORT_STATIC_ALLOCATION这个宏定义为1,并且需要实现函数vApplicationGetIdleTaskMemory()与 vApplicationGetTimerTaskMemory(),这两个函数是用户设定的空闲(Idle)任务与定时器(Timer)任务的堆栈大小,必须由用户自己分配,而不能是动态分配。并且需要定义一些全局变量如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MX66AbiK-1682496278376)(C:\Users\user\AppData\Roaming\Typora\typora-user-images\image-20230419161953387.png)]

并且创建好任务句柄。

使用STM32CubeMX生成的代码中,使用的是如下的宏定义进行线程的创建:

// 静态创建
#define osThreadStaticDef(name, thread, priority, instances, stacksz, buffer, control)  \
const osThreadDef_t os_thread_def_##name = \
{
     
      #name, (thread), (priority), (instances), (stacksz), (buffer), (control) }

宏定义中,##的作用就是把2个宏参数连接为1个数,或实现字符串的连接,#的作用就是将#后面的宏参数进行字符串的操作,也就是将#后面的参数两边加上一对双引号使其成为字符串。

所以

osThreadDef(Display, DisLCD_Task,osPriorityNormal, 0, 128);
//相当于
const   osThreadDef_t   os_thread_def_Display = {
   
    "Display", (DisLCD_Task), (osPriorityNormal), (0), (128)  }

对于osThreadDef_t为一个结构体,并且具有一个函数指针,

typedef struct os_thread_def  {
   
   
  char                   *name;        ///< Thread name 
  os_pthread             pthread;      ///< start address of thread function
  osPriority             tpriority;    ///< initial thread priority
  uint32_t               instances;    ///< maximum number of instances of that thread function
  uint32_t               stacksize;    ///< stack size requirements in bytes; 0 is default stack size
#if( configSUPPORT_STATIC_ALLOCATION == 1 )
  uint32_t               *buffer;      ///< stack buffer for static allocation; NULL for dynamic allocation
  osStaticThreadDef_t    *controlblock;     ///< control block to hold thread's data for static allocation; NULL for dynamic allocation
#endif
} osThreadDef_t;
typedef void (*os_pthread) (void const *argument);
typedef enum  {
   
   
  osPriorityIdle          = -3,          ///< priority: idle (lowest)
  osPriorityLow           = -2,          ///< priority: low
  osPriorityBelowNormal   = -1,          ///< priority: below normal
  osPriorityNormal        =  0,          ///< priority: normal (default)
  osPriorityAboveNormal   = +1,          ///< priority: above normal
  osPriorityHigh          = +2,          ///< priority: high
  osPriorityRealtime      = +3,          ///< priority: realtime (highest)
  osPriorityError         =  0x84        ///< system cannot determine priority or thread has illegal priority
} osPriority;

通过以上我们就相当于使用任务名创建了一个结构体变量,之后传入函数osThreadCreate中进行任务创建:

#define osThread(name)  \
&os_thread_def_##name
osThreadDef(defaultTask, StartDefaultTask, osPriorityNormal, 0, 128);
defaultTaskHandle = osThreadCreate(osThread(defaultTask), NULL);

osThreadCreate函数中调用了xTaskCreateStatic函数进行创建任务。

  • 在 FreeRTOS 系统中,每一个任务都是独立的,他们的运行环境都单独的保存在他们 的栈空间当中。那么在定义好任务函数之后,我们还要为任务定义一个栈,目前我们使用的是静态内存,所以任务栈是一个独立的全局变量。
  • 在大多数系统中需要做栈空间地址对齐,在 FreeRTOS 中是以 8 字节大小对齐,并且会检查堆栈是否已经对齐,其中 portBYTE_ALIGNMENT 是在 portmacro.h 里面定义的一个宏,其值为 8,就是配置为按 8 字节对齐,当然用户可以选择按 1、2、4、8、16、32 等字节对齐。
  • xTaskCreateStatic这个函数将任务主体函数、任务栈、任务控制块联合在一起。让任务可以随时被系统启动。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wsftFxot-1682496278376)(C:\Users\user\AppData\Roaming\Typora\typora-user-images\image-20230419160059259.png)]

当任务创建好后,是处于任务就绪(Ready),在就绪态的任务可以参与操作系统的调度。但是此时任务仅仅是创建了,还未开启任务调度器,也没创建空闲任务与定时器任务(如果使能了 configUSE_TIMERS 这个宏定义),那这两个任务就是在启动任务调度器中实现,每个操作系统,任务调度器只启动一次,之后就不会再次执行了。开启调度使用函数vTaskStartScheduler()

创建动态任务过程configSUPPORT_DYNAMIC_ALLOCATION

该任务任务使用的栈和任务控制块是在创建任务的时候FreeRTOS 动态分配的,并不是预先定义好的全局变量。在上面的任务中,任务控制块和任务栈的内存空间都是从内部的 SRAM 里面分配的,具体分配到哪个地址由编译器决定。

现在我们开始使用动态内存,即堆,其实堆也是内存,也属于 SRAM。FreeRTOS 做法是在 SRAM 里面定义一个大数组,也就是堆内存,供 FreeRTOS 的动态内存分配函数使用,在第一次使用的时候,系统会将定义的堆内存进行初始化,这些代码在 FreeRTOS 提供的内存管理方案中实现。

使用动态内存时候,不用跟使用静态内存那样要预先定义好一个全局的静态的任务控制块空间任务控制块是在任务创建的时候分配内存空间创建,任务创建函数会返回一个指针,用于指向任务控制块,所以要预先为任务栈定义一个任务控制块指针,也是我们常说的任务句柄

//任务句柄是一个指针,用于指向一个任务,当任务创建好之后,它就具有了一个任务句柄以后我们要想操作这个任务都需要通过这个任务句柄,如果是自身的任务操作自己,那么这个句柄可以为 NULL。
typedef TaskHandle_t osThreadId;
osThreadId ReceiveHandle;
osThreadId SendHandle;
/* definition and creation of Receive */
osThreadDef(Receive, ReceiveTask, osPriorityIdle, 0, 128);
ReceiveHandle = osThreadCreate(osThread(Receive), NULL);

/* definition and creation of Send */
osThreadDef(Send, SendTask, osPriorityIdle, 0, 128);
SendHandle = osThreadCreate(osThread(Send), NULL);


// 动态创建
#define osThreadDef(name, thread, priority, instances, stacksz)  \
const osThreadDef_t os_thread_def_##name = \
{
     
      #name, (thread), (priority), (instances), (stacksz), NULL, NULL }

typedef struct os_thread_def  {
   
   
  char                   *name;        ///< Thread name 
  os_pthread             pthread;      ///< start address of thread function
  osPriority             tpriority;    ///< initial thread priority
  uint32_t               instances;    ///< maximum number of instances of that thread function
  uint32_t               stacksize;    ///< stack size requirements in bytes; 0 is default stack size
#if( configSUPPORT_STATIC_ALLOCATION == 1 )
  uint32_t               *buffer;      ///< stack buffer for static allocation; NULL for dynamic allocation
  osStaticThreadDef_t    *controlblock;     ///< control block to hold thread's data for static allocation; NULL for dynamic allocation
#endif
} osThreadDef_t;
typedef void (*os_pthread) (void const *argument);
typedef enum  {
   
   
  osPriorityIdle          = -3,          ///< priority: idle (lowest)
  osPriorityLow           = -2,          ///< priority: low
  osPriorityBelowNormal   = -1,          ///< priority: below normal
  osPriorityNormal        =  0,          ///< priority: normal (default)
  osPriorityAboveNormal   = +1,          ///< priority: above normal
  osPriorityHigh          = +2,          ///< priority: high
  osPriorityRealtime      = +3,          ///< priority: realtime (highest)
  osPriorityError         =  0x84        ///< system cannot determine priority or thread has illegal priority
} osPriority;

此时调用的是函数xTaskCreate来创建任务,并且在如下进行创建:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tO0hj60h-1682496278377)(C:\Users\user\AppData\Roaming\Typora\typora-user-images\image-20230419162728074.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-b5L00ICw-1682496278377)(C:\Users\user\AppData\Roaming\Typora\typora-user-images\image-20230419162334029.png)]

堆内存的大小为 configTOTAL_HEAP_SIZE , 在FreeRTOSConfig.h 中由我们自己定义,configSUPPORT_DYNAMIC_ALLOCATION 这个宏定义在使用 FreeRTOS 操作系统的时候必须开启。

之后即可启动任务:vTaskStartScheduler()。

0x03 FreeRTOS启动流程

第一种启动流程:

这种启动方式也就是上面讲解的启动方式。

  • 外设硬件初始化
  • RTOS系统初始化
  • 创建各种任务
  • 启动RTOS调度器
  • 编写函数实体:
  • 任务实体通常是一个不带返回值的无限循环的 C 函数,函数体必须有阻塞的情况出现,不然任务(如果优先权恰好是最高)会一直在 while 循环里面执行,导致其它任务没有执行的机会。

第二种启动流程:

在 main 函数中将硬件和 RTOS 系统先初始化好,然后创建一个启动任务后就启动调度器,然后在启动任务里面创建各种应用任务,当所有任务都创建成功后,启动任务把自己删除。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-v9JEI2xQ-1682496278378)(C:\Users\user\AppData\Roaming\Typora\typora-user-images\image-20230419163739600.png)]

启动流程概述

系统上电时第一个执行的启动文件是由汇编编写的复位函数Reset_Handler,复位函数会调用C库函数__main,其函数主要工作是初始化系统的堆栈。

创建任务xTaskCreate()函数

在 main()函数中,我们直接可以对 FreeRTOS 进行创建任务操作,因为 FreeRTOS 会自动帮我们做初始化的事情,比如初始化堆内存。其实也就是对即将使用的堆栈进行初始化,根据任务个数来决定。在此函数中,进行了堆内存的初始化:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tO6j9wwM-1682496278378)(C:\Users\user\AppData\Roaming\Typora\typora-user-images\image-20230419164409024.png)]

其分配内存函数:

void *pvPortMalloc( size_t xWantedSize )
{
   
   
BlockLink_t *pxBlock, *pxPreviousBlock, *pxNewBlockLink;
void *pvReturn = NULL;

	vTaskSuspendAll();
	{
   
   
		/*如果这是对 malloc 的第一次调用,那么堆将需要初始化来设置空闲块列表。 */
		if( pxEnd == NULL )
		{
   
   
			prvHeapInit();
		}
		else
		{
   
   
			mtCOVERAGE_TEST_MARKER();
		}
        ....
    }
}

其堆的初始化函数:

static void prvHeapInit( void )
{
   
   
    BlockLink_t *pxFirstFreeBlock;
    uint8_t *pucAlignedHeap;
    size_t uxAddress;
    size_t xTotalHeapSize = configTOTAL_HEAP_SIZE;

	/* 确保堆在正确对齐的边界上启动。 */
	uxAddress = ( size_t ) ucHeap;

	if( ( uxAddress & portBYTE_ALIGNMENT_MASK ) != 0 )
	{
   
   
		uxAddress += ( portBYTE_ALIGNMENT - 1 );
		uxAddress &= ~( ( size_t ) portBYTE_ALIGNMENT_MASK );
		xTotalHeapSize -= uxAddress - ( size_t ) ucHeap;
	}

	pucAlignedHeap = ( uint8_t * ) uxAddress;

	/*xStart 用于保存指向空闲块列表中第一个项目的指针。 void 用于防止编译器警告 */
	xStart.pxNextFreeBlock = ( void * ) pucAlignedHeap;
	xStart.xBlockSize = ( size_t ) 0;

	/*pxEnd 用于标记空闲块列表的末尾,并插入堆空间的末尾 */
	uxAddress = ( ( size_t ) pucAlignedHeap ) + xTotalHeapSize;
	uxAddress -= xHeapStructSize;
	uxAddress &= ~( ( size_t ) portBYTE_ALIGNMENT_MASK );
	pxEnd = ( void * ) uxAddress;
	pxEnd->xBlockSize = 0;
	pxEnd->pxNextFreeBlock = NULL;

	/*首先,有一个空闲块,其大小可以占用整个堆空间,减去 pxEnd 占用的空间*/
	pxFirstFreeBlock = ( void * ) pucAlignedHeap;
	pxFirstFreeBlock->xBlockSize = uxAddress - ( size_t ) pxFirstFreeBlock
评论 2
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

郑烯烃快去学习

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值