FreeRTOS
启动流程
- FreeRTOS中,数值越大优先级越高,0代表最低的优先级
- FreeRTOS为了任务启动和任务切换使用了三个异常:SVC,PendSV,SysTick
- SVC(系统服务调用):启动任务,用户使用SVC发出对系统服务函数的请求,间接访问硬件,它就会产生一个SVC异常
- PendSV(可挂起系统调用):任务切换,可像普通中断一样挂起
- SysTick用于产生系统节拍时钟,提供一个时间片
任务管理
-
在任意时刻,只有一个任务得到运行;宏观上,所有的任务都在同时执行
-
任务调度器会在任务切出时,会将任务执行的执行环境存入该任务的栈空间中,一个系统能运行多少任务,取决于可用的SRAM的大小
-
任务栈的大小为字,默认128,即512个字节,也是FreeRTOS推荐的最小的任务栈
-
任务调度器对中断处理函数,调度器上锁部分的代码和禁止中断的代码不可抢占、
-
任务延时函数
- vTaskDelay (const TickType_t xTicksToDelay) 相对性的延时,会受到高优先级任务的影响,它指定的时间是调用vTaskDelay之后开始计算
- vTaskDelayUntill( ) 绝对延时函数
-
任务的创建分为静态创建(xTaskCreateStatic)和动态创建(xTaskCreate),CMISI封装的FreeRTOS直接使用osThreadCreate进行封装,根据相关的条件进行判断
osThreadId osThreadCreate (const osThreadDef_t *thread_def, void *argument) { TaskHandle_t handle; #if( configSUPPORT_STATIC_ALLOCATION == 1 ) && ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) if((thread_def->buffer != NULL) && (thread_def->controlblock != NULL)) // threa_def->buffer和thread_def->controlblock是在cmisi_os.h中的osThreadDef_t内定义的,仅由静态创建支持 { handle = xTaskCreateStatic((TaskFunction_t)thread_def->pthread,(const portCHAR *)thread_def->name, thread_def->stacksize, argument, makeFreeRtosPriority(thread_def->tpriority), thread_def->buffer, thread_def->controlblock); } else { if (xTaskCreate((TaskFunction_t)thread_def->pthread,(const portCHAR *)thread_def->name, thread_def->stacksize, argument, makeFreeRtosPriority(thread_def->tpriority), &handle) != pdPASS) { return NULL; } } #elif( configSUPPORT_STATIC_ALLOCATION == 1 ) handle = xTaskCreateStatic((TaskFunction_t)thread_def->pthread,(const portCHAR *)thread_def->name, thread_def->stacksize, argument, makeFreeRtosPriority(thread_def->tpriority), thread_def->buffer, thread_def->controlblock); #else if (xTaskCreate((TaskFunction_t)thread_def->pthread,(const portCHAR *)thread_def->name, thread_def->stacksize, argument, makeFreeRtosPriority(thread_def->tpriority), &handle) != pdPASS) { return NULL; } #endif return handle; }
/// Thread Definition structure contains startup information of a thread. /// \note CAN BE CHANGED: \b os_thread_def is implementation specific in every CMSIS-RTOS. 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;
-
静态任务创建之后,删除静态任务后,内存不能进行释放,需要前期固定栈的大小,动态任务创建时任务控制块和栈的内存动态分配,删除之后可以回收
-
xxxFromISRz的函数是在中断中使用的函数
-
当任务切出时,其执行环境会被保存在该任务的栈空间内
-
在调用vTaskSuspend(TaskHandle_t xTaskToSuspend)函数时,如果传递的句柄为NULL时,则挂起当前正在运行的任务,此时会立刻进行任务的切换,和vTaskDelete()函数差不多,删除正在运行的任务时,传递参数为NULL;
-
vTaskDelay()为相对延时,vTaskDelayUntil()为绝对延时
消息队列
- 常用于任务间通信的 数据结构
- 异步通信
- 支持先进先出(FIFO),后进先出(LIFO)
- 消息队列占用内存大小=[消息控制块的大小+(单个消息空间的大小*消息队列的长度)]
- 每个消息队列可以存放任意类型的数据
- 发送到队列的消息是通过复制来实现的,这就说明队列中存储的是原始数据,而不是原始数据的引用
- xQueueCreateStatic() 创建消息队列时,使用的是静态内存分配
信号量
-
二值信号量 计数信号量 互斥信号量 递归互斥量 使用场景 任务与任务,中断与任务进行同步 计数,资源的保护 具有优先级继承,保护临界资源,简单的任务互锁 互斥量不能够再中断服务函数中使用,因为其特有的优先级继承机制只在任务中起作用,在中断的上下文环境中毫无意义 -
一种任务间的通信机制,实现任务之间的同步或临界资源的互斥访问
-
信号量是一个非负整数,所有获取它的任务都会将该数值减1
-
计数信号量可以看作长度大于1的队列,信号量使用者不关心存储在队列中的消息,只需要关心队列中是否有消息互斥量
互斥量
- 一种特殊的二值信号量
- 互斥量和信号量的不同之处在于,它支持互斥量所有权,递归访问以及防止优先级翻转的特性
- 为防止优先级翻转使用的是优先级继承的方法,优先级继承算法是指暂时提高某个占有某种资源的低优先级任务的优先级,使之与所有等待该资源的任务中优先级最高的那个任务的优先级相等,而释放资源后,优先级回到初始设定的值
- 互斥量不能在中断服务函数中使用,因为其特有的优先级继承机制只在任务中起作用,在中断的上下文中没有意义
事件
-
完成事件类型的通信,无数据传输
-
与信号量不同的是,事件可以实现一对多,多对多的同步
-
任务通过逻辑与,逻辑或和一个或者多个事件来建立关联,形成一个事件组
-
事件的“逻辑或”被称为独立同步,任务感兴趣的任一事件发生即可唤醒
-
事件的“逻辑与”被称为关联型同步,任务感兴趣的若干事件发生后才被唤醒
-
当任务等待事件的时间超过设定的阻塞时间,任务将直接从阻塞态进入就绪态
-
信号量只能识别单一同步动作,而不能同时等待多个事件的同步
-
事件接收成功后,必须使用xClearOnExit选项清除已接收的事件类型
-
任务因为等待某个或者多个事件发生而进入阻塞状态,当事件发生时会被唤醒
软件定时器
-
软件定时器是由操作系统提供的一类系统接口,它构建在硬件定时器的基础上,使系统能够提供不受硬件定时器服务。
-
软件定时器回调函数的上下文是任务
-
软件定时器在创建之后,当经过设定的时钟计数值后会触发用户定义的回调函数
-
软件定时器分为单次模式和周期模式
- 单次模式:定时时间到了,只执行一次回调函数之后就将该定时器删除
- 周期模式:该模式下,定时器会按照设置的定时时间循环执行回调函数,直到用户将定时器删除
-
软件定时器的过程中极有可能被中断打断,所以其精度不高
-
软件定时器以系统周期为计时单位,系统节拍是系统心跳节拍,表示系统时钟的频率,类似人的心跳1s能跳多少下
-
软件定时器的定数值必须是系统时钟节拍的整数倍
任务通知
-
每个任务都有一个32位的通知值,可以替代二值信号量,计数信号量,事件组,长度为1的队列
-
不可以替代互斥信号量,因为互斥信号量有优先级继承
-
限制:
- 只能有一个任务接收通知消息,因为必须指定接收通知的任务,事件可以多对多进行同步
- 只有等待通知的任务可以被阻塞,发送通知的任务在任何情况下都不会因为发送失败而进入阻塞状态
内存管理
内存管理 | 内存管理策略 | 场景/优劣势 |
---|---|---|
heap_1.c | 只能申请内存,不能释放,申请的内存是常量 | 安全性高,内存利用率不高 |
heap_2.c | 支持释放内存,采用一种最佳匹配算法,可以满足申请空间的最小值,无法将相连的两个小内存合成大的 | 申请内存大小比价固定,比如反复删除的任务,队列,信号量等内核对象;对于大小不定的,容易产生内存碎片 |
heap_3.c | 重新封装了标准C库中的malloc()和free() | 具有不确定性,很可能增大RTOS内核的大小 |
heap_4.c | 比heap_2.c多一种合并算法,可以把相邻的空闲内存合并成更大的一块 | 可用于分配和随即释放字节内存的程序,也可使用heap_2.c适用的 |
heap_5.c | 比heap_4.c更进一步,可以合并多个非连续的内存区 | 图形显示、GUI 等,可能芯片内部的 RAM是不够用户使用的,需要外部 SDRAM |
-
heap_1.c heap_2.c heap_4.c 内存堆实际上是一个很大的数组,定 义 为 static uint8_t ucHeap[ configTOTAL_HEAP_SIZE] , 而宏 定 义configTOTAL_HEAP_SIZE 则表示系统管理内存大小,单位为字,在 FreeRTOSConfig.h 中由用户设定。
-
对于 heap_3.c这种内存管理方案,它封装了 C 标准库中的 malloc()和 free()函数,封装后的 malloc()和 free()函数具备保护,可以安全在嵌入式系统中执行。因此,用户需要通过编译器或者启动文件设置堆空间。
-
heap_5.c 方案允许用户使用多个非连续内存堆空间,每个内存堆的起始地址和大小由用户定义。这种应用其实还是很大的,比如做图形显示、GUI 等,可能芯片内部的 RAM是不够用户使用的,需要外部 SDRAM,那这种内存管理方案则比较合适。
-
要 注 意 的 是 在 使 用 heap_3.c 方 案 时 , FreeRTOSConfig.h 文 件 中 的configTOTAL_HEAP_SIZE 宏定义不起作用。在 STM32 系列的工程中,这个由编译器定义的堆都在启动文件里面设置,单位为字节
-
heap_4.c 内存管理方案的空闲块链表不是以内存块大小进行排序的,而是以内存块起始地址大小排序,内存地址小的在前,地址大的在后,因为 heap_4.c 方案还有一个内存合并算法,在释放内存的时候,假如相邻的两个空闲内存块在地址上是连续的,那么就可以合并为一个内存块,这也是为了适应合并算法而作的改变。