Freertos学习:00-介绍
注:请各位读者注意,大部分的内容来自朱工以及官方的文档,有部分参考官方文档的内容进行调整,下不再重复。
FreeRTOS 介绍
FreeRTOS是一个迷你的实时操作系统内核。作为一个轻量级的操作系统,功能包括:任务管理、时间管理、信号量、消息队列、内存管理、记录功能、软件定时器、协程等,可基本满足较小系统的需要。由于RTOS需占用一定的系统资源(尤其是RAM资源),只有μC/OS-II、embOS、salvo、FreeRTOS等少数实时操作系统能在小RAM单片机上运行。相对μC/OS-II、embOS等商业操作系统,FreeRTOS操作系统是完全免费的操作系统,具有源码公开、可移植、可裁减、调度策略灵活的特点,可以方便地移植到各种单片机上运行。
功能和特点
用户可配置内核功能、多平台的支持
提供一个高层次的信任代码的完整性
目标代码小,简单易用、遵循MISRA-C标准的编程规范
强大的执行跟踪功能、堆栈溢出检测
没有限制的任务数量、没有限制的任务优先级
多个任务可以分配相同的优先权、支持优先级继承
队列,二进制信号量,计数信号灯和递归通信和同步的任务
支持协程(coroutine)
为什么选择Free-RTOS
行业内使用的RTOS实际上有很多种,而在MCU上面的主流就有:uC/os、FreeRTOS、Rt-Thead等。
我认为FreeRTOS最主要的优势不在于"文档齐全,内核的实现的有关文件较少,完全免费";而在于"ST的CubeMx中很方便就可以生成带有FreeRTOS的工程"。
从学习门槛的角度来讲,选择FreeRTOS是比较好的。操作系统在很多方面都是相同的,与学习编程语言一样,不要在纠结学习新东西时做太多的对比;先学完,再看看其他的,就可以了。
有机会本人也希望学完这个系统以后能够再学习Rt-Thread,支持国产系统。
那么系列文章,我们会从FreeRTOS v10
版本开始学习,学习的文档:
- 主要参考《
Mastering_the_FreeRTOS_Real_Time_Kernel-A_Hands-On_Tutorial_Guide
》。 - 少数内容是来自基于网上资料的二次修订与补充。
常规使用
FreeRTOS的内核极其小巧,只需要几个简单的文件就可以进行编译运行,当在不同的硬件上进行移植的时候,只需要修改portable目录里的文件即可完成对硬件的适配,实际上官方也提供了大量的已经完成移植的设备的portable文件,我们只需要简单的拷贝过来即可。
有关文档
官网文档:
- API参考手册: 《FreeRTOS_Reference_Manual》
- 了解源码:《Mastering_the_FreeRTOS_Real_Time_Kernel-A_Hands-On_Tutorial_Guide》
通过《Mastering_the_FreeRTOS_Real_Time_Kernel-A_Hands-On_Tutorial_Guide》手册可以了解:
1)工程创建方法
2)任务管理:主要描述任务优先级调度,抢占式或非抢占式调度等调度模型
3)堆内存管理:主要分析5种堆内存管理模型
4)队列管理
5)软时间管理
6)中断管理,主要是推迟中断处理方法
7)资源管理,主要是临界区,资源互斥访问
8)事件组
9) 任务通知,直接任务通信,无需中间对象,比消息队列,事件组和信号量更快,缺点是任务创建时需要分配8字节内存
任务状态:https://www.cnblogs.com/Liu-Jing/p/7189902.html
源码目录说明
FreeRTOS-Plus : 包含FreeRTOS+组件,包含了FreeRTOS与FatFs、TCP和UDP等组件进行融合的程序代码。
FreeRTOS :包含FreeRTOS实时内核源文件;包含了源码和各种单片机的Demo
下面介绍FreeRTOS
的有关目录。
Source
通用文件:
.c
文件:FreeRTOS内核代码文件,包括tasks.c、queue.c、list.c,以及分别实现软件定时、事件组和协程功能的timers.c、event_groups.c、croutine.c。include
: FreeRTOS内核代码头文件
FreeRTOS V10 contains a new source file,
stream_buffers.c
, and for
consistency has renamed theStackMacros.h
header filestack_macros.h
.
However, V10 is a drop-in compatible replacement for FreeRTOS V9.x.x, as
the new source file is only required to enable new features, and two copies of
the changed header file are provided – one with the old name and one with the new.
stack_macros.h
is only used internally within the FreeRTOS kernel code.FreeRTOS 10 contains two significant new features: Stream Buffers and Message Buffers.
Stream Buffers are an inter process communication (IPC) primitive optimized for
use in scenarios where there is only one reader and only one writer, such as
sending a stream of data from an interrupt service routine (ISR) to an RTOS task,
or from one processor core to another.Message Buffers build on top of Stream Buffers. Whereas as Stream Buffers send
a continuous stream of bytes, Message Buffers send discrete messages that can be
of varying length.
需要移植的:
Portable
:处理器特定代码
每个支持的处理器架构需要一小段与处理器架构相关的RTOS代码。
这个是RTOS移植层,它位于[相应编译器]/[相应CPU架构]子目录。
include
croutine.h
该文件是croutine.c的头文件,它们共同实现FreeRTOS协同程序功能。具体可看协程的作用。
deprecated_definitions.h
该头文件字面意思是弃用的定义。下面是文档里面对应的描述。
每个FreeRTOS端口都有一个唯一的portmacro.h头文件。最初,使用预处理器定义来确保预处理器为所使用的端口找到正确的portmacro.h文件。该方案不赞成设置编译器的include路径,以便找到正确的portmacro.h文件——不需要常量,并允许将portmacro.h文件放在与所使用端口相关的任何位置。下面的定义保留在代码中,仅用于向后兼容。新项目不应该使用它们。
其实就是通过define相对的单片机来声明与单片机相契合的portmacro.h头文件。事实上如前所述,直接将需要的portmacro.h放到自己的工程即可。
event_groups.h
该文件是event_groups.c的头文件,提供事件组功能,它允许与事件通信任务。
FreeRTOS.h
该头文件提供FreeRTOS的配置选项,文件内都是关于系统的宏定义(#ifdef-#define-#endif
),通过与FreeRTOSConfig.h配合进行FreeRTOS的配置,通过合理配置可实现FreeRTOS的裁剪。详见FreeRTOS 之 全配置项详解、裁剪(FreeRTOSConfig.h)。
list.h
该文件是list.c的头文件,列表的实现主要是为了调度器(scheduler),但也可以由应用程序代码使用。list_ts只能存储指向list_item_ts的指针。每个ListItem_t包含一个数值(xItemValue)。大多数情况下,列表按递减项值顺序排序。已经创建了包含一个列表项的列表。该项的值是可以存储的最大值,因此它总是在列表的末尾,充当标记。列表成员pxHead总是指向这个标记—即使它位于列表的末尾。这是因为尾部包含一个回滚指针,指向列表的真正头部。除了它的值之外,每个列表项还包含指向列表中下一项的指针(pxNext)、指向它所在列表的指针(pxContainer)和返回包含它的对象的指针。后面的两个指针用于提高列表操作的效率。在包含列表项的对象和列表项本身之间存在有效的双向链接。
message_buffer.h
该头文件为消息缓冲区,它可实现直接到任务通知,它利用流缓冲区(stream_buffer)进行数据的传输。详见RTOS Message Buffers 。
mpu_prototypes.h
该头文件包含的是关于MPU的API函数原型声明。
当使用MPU时,标准(非MPU) API函数被映射到启动“
MPU_
”的等效函数,该函数的原型在这个头文件中定义。这将导致应用程序代码调用MPU_
版本,该版本包装了具有特权的非mpu版本,提升然后降级代码,因此内核代码总是运行于完整的特权下。
mpu_wrappers.h
该头文件主要作用是将标准task .h API函数映射到相应的MPU。
portable.h
该头文件包含的是可移植层的API,它为每个端口定义对应的函数。
projdefs.h
该头文件定义了任务函数必须遵循的原型。
queue.h
该文件是queue.c的头文件,定义了队列的相关函数。详见队列。
在FreeRTOS实现内部,队列使用两个内存块。第一个块用于保存队列的数据结构。第二个块用于保存放置到队列中的项。如果使用xQueueCreate()创建队列,则在xQueueCreate()函数中自动动态分配这两个内存块。(详见Memory Management)。如果使用xQueueCreateStatic()创建队列,那么应用程序编写器必须提供队列将使用的内存。因此,xQueueCreateStatic()允许在不使用任何动态内存分配的情况下创建队列。
semphr.h
该头文件定义了关于二进制信号量/互斥量的函数原型。详见信号量。
在许多使用场景中,使用直接到任务的通知代替二进制信号量会更快、更节省内存。
stack_macros.h
该头文件包含了关于堆栈的宏定义。
如果正在交换的任务的堆栈当前溢出,或者看起来可能在过去溢出,则调用堆栈溢出钩子函数。将configCHECK_FOR_STACK_OVERFLOW设置为1将导致宏只检查当前堆栈状态——将当前堆栈顶部值与堆栈限制进行比较。将configCHECK_FOR_STACK_OVERFLOW设置为大于1也会导致检查最后几个堆栈字节,以确保在创建任务时设置的字节的值没有被覆盖。注意,第二个测试并不保证总是能够识别溢出堆栈。
StackMacros.h
该头文件是stack_macros.h的旧版本,如果使用此头文件会报警告。
#ifndef _MSC_VER /* Visual Studio doesn't support #warning. */
#warning The name of this file has changed to stack_macros.h. Please update your code accordingly. This source file (which has the original name) will be removed in future released.
#endif
stream_buffer.h
该文件为stream_buffer.c的头文件,定义关于流缓冲区的函数原型。详见流缓冲区。
流缓冲区用于将连续的数据流从一个任务或中断发送到另一个任务。它们的实现是轻量级的,这使得它们特别适合于中断到任务和核心到核心的通信场景。
独特FreeRTOS对象之间流缓冲区实现(也消息缓冲区实现,如消息缓冲区是建立在流缓冲区)的假定只有一个任务或中断,将写入缓冲区(the writer编写器),且只有一个任务或中断,从缓冲区读取(the reader阅读器)。对于编写器和读取器来说,不同的任务或中断是安全的,但是与其他FreeRTOS对象不同,拥有多个不同的编写器或多个不同的读取器是不安全的。如果有多个不同的编写器,那么应用程序编写器必须将每个调用放置到临界区中的一个写入API函数(如xStreamBufferSend())中,并将发送块时间设置为0。同样,如果要有多个不同的读取器,那么应用程序编写器必须将每个调用放置到读取API函数(如xStreamBufferRead())的临界部分中,并将接收块时间设置为0。
task.h
该文件为tasks.c的头文件,定义了关于任务(task)的创建、删除等操作函数。详见任务创建。
timers.h
该文件为timers.c的头文件,定义了关于软件定时器的函数。其中软件定时器用于在将来的某个固定时间或定期以固定的频率调度函数的执行。软件定时器执行的函数称为软件定时器的回调函数。
详见软件定时器。
在FreeRTOS实现内部,软件定时器使用一块内存,其中存储定时器数据结构。如果使用xTimerCreate()创建软件计时器,则在xTimerCreate()函数中自动动态分配所需的内存。(见内存管理)。如果使用xTimerCreateStatic()创建软件计时器,则应用程序编写器必须提供软件计时器将使用的内存。因此,xTimerCreateStatic()允许在不使用任何动态内存分配的情况下创建软件计时器。计时器是在休眠状态下创建的。可以使用xTimerStart()、xTimerReset()、xTimerStartFromISR()、xTimerResetFromISR()、xTimerChangePeriod()和xtimerchangefromisr() API函数将计时器转换为活动状
Portable
该文件夹包含了关于不同编译器环境下的单片机端口的定义、内存管理等文件。
如在keil编译器下则需要到RVDS文件夹内获取关于端口的文件,它主要包含两个文件port.c和portmacro.h,前者为端口实现portable.h中定义的函数,而后者为给定的硬件和编译器正确配置FreeRTOS。
假如要使用STM32F10x单片机,则选择portable\RVDS\ARM_CM3中所包含的文件。
在 FreeRTOS 的移植文件 ports.c 中有用到 SVC 中断的 0 号系统服务,即 SVC 0。此中断在 FreeRTOS
中仅执行一次, 用于启动第一个要执行的任务。 另外, 由于 FreeRTOS 没有配置 SVC 的中断优先级,
默认没有配置的情况下, SVC 中断的优先级就是最高的 0。 如果用户在不清楚自己配置的 PendSV 和
SysTick 中断是否跟实际情况一致时,可以进行硬件调试。
方法:MDK - Peripherals -
Core Peripherals
-Nested Vectored Interrupt Controller
打开后可以看到:SysTick和PendSV中断优先级分别为240与0,对240右移4位就是原来的值(OxOf左移4位得到240)。这里为什么要左移四位呢,因为STM32的优先级设置仅使用高4位。而SVC的优先级就是0,可以理解为0左移4位还是0。
MemMang
portable/MemMang
:内存堆实现范例(堆栈设计),heap_x.c文件提供了多种堆栈方案,详见附录。
Demo
FreeRTOS下载包中还包含各种处理器架构和编译器的演示例程。大多数的演示例程代码对所有移植都是通用的,位于FreeRTOS/Demo/Common/Minimal目录。FreeRTOS/Demo/Common/Full目录下的是历史遗留代码,仅用于PC。
FreeRTOS
|+-- Demo
| |+-- Common 所有例程都可以使用的演示例程文件
| |+-- Dir x 用于x平台的演示例程工程文件
| |+-- Dir y 用于y平台的演示例程工程文件
剩余的子目录包含预先配置好的工程,可以用于构建个人演示例程。子目录的命名与移植平台和编译器相关。每一个RTOS移植包都有自己的说明文档。
FreeRTOS 有关知识
基础知识
任务: 系统中独立,且无法返回的函数,官方给出的任务函数模板如下所示:
void vATaskFunction(void *pvParameters)
{
for(;;)
{
Task(); //任务主体
vTaskDelay(); //用于任务切换,常用的就是延时函数
}
vTaskDelete(NULL); //当任务跳出循环后,需要用该函数删除任务
}
任务栈: 栈是RAM里面一段连续的内存空间,用于存储任务的变量,栈的大小一般在启动文件中预先定义好,默认为512字节,也是FreeRTOS推荐的最小的任务栈。有多少个任务就需要定义多少个任务栈;
任务状态: 一共分为运行态、就绪态、阻塞态和挂起态。
任务优先级:每个任务都可以分配一个从0~(configMAX_PRIORITIES-1) 的优先级,且configMAX_PRIORITIES 不能超过32,不过考虑到RAM 的消耗,configMAX_PRIORITIES最好设置为一个满足应用的最小值。
优先级数字越低表示任务的优先级越低,0 的优先级最低,configMAX_PRIORITIES-1 的优先级最高。
补充:推荐用户将 Cortex-M3 、Cortex-M4 内核的 NVIC 优先级分组设置为 4, 即:
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);
这
样中断优先级的管理将非常方便。 这个也是官方强烈建议的。
任务控制块 TCB_t: 相当于任务的身份证,可以帮助系统对任务进行操作,所有对任务的操作否可以通过控制任务控制块来实现,控制块中包含的具体内容如下:
typedef struct tskTaskControlBlock
{
volatile StackType_t *pxTopOfStack; //任务堆栈栈顶
ListItem_t xStateListItem; //状态列表项
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 ) //trace或到 debug的时候用到的时候用到
UBaseType_t uxTCBNumber;
UBaseType_t uxTaskNumber;
#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;
#endif
#if( INCLUDE_xTaskAbortDelay == 1 )
uint8_t ucDelayAborted;
#endif
}tskTCB;
typedef tskTCB TCB_t;
其实这里并不需要记住很多,只需要大概了解TCB_t中存放了任务的全部信息就行。
任务状态
任务状态有2个:运行态、以及非运行状态。
FreeRTOS 中处于运行状态的任务永远是当前能够运行的最高优先级任务。
非运行状态(包含:阻塞状态、挂起状态、就绪状态):采用事件任务的意义在于不同优先级的任务可以被创建,并且不会导致高优先级把低优先级的任务遏制。
FreeRTOS会创建很多任务,每个任务有相同或不同的优先级,如果让任务全都处于准备执行状态,那么只有优先级最高的任务一直在执行,优先级低的任务全都被 “饿死” 了。为了解决这个问题,添加了“阻塞状态”。
阻塞状态
如果一个任务正在等待某个事件或外部中断,则这个任务处于阻塞态(blocked)。这会让任务主动让出CPU,让其他任务进行调度执行。进入阻塞状态的任务通常有一个“超时”周期,当事件超时后解除阻塞。
比如一个任务调用vTaskDelay()后会阻塞到延时周期到为止。任务也可能阻塞在队列或信号量事件上。
如何触发阻塞:
- 调用时间相关事件(定时器):设置定时器,延迟一段时间后执行下一个任务。
利用
void vTaskDelay()
API函数解决空循环,从而不会出现只执行高优先级任务,遏制低优先级任务的执行。
- 同步事件:源于其他任务或中断事件的发生。例如:某个任务可以在进入阻塞态以等待队列中的数据到来。
同步事件囊括了所有板级范围内的事件类型。实现互斥访问和互斥量都可以用来实现同步事件。
互斥信号量(recursive semaphore):FreeRTOS的队列,二值信号量,计数信号量,互斥信号量,递归信号量。
挂起状态
处于挂起状态的任务同样对调度器无效。仅当明确的分别调用挂起函数后,任务才会进入或退出挂起状态。
不可以指定超时周期事件(不可以通过设定超时事件而退出挂起状态)。
有关函数:
- 将任务挂起:
vTaskSuspend()
- 退出挂起态:
vTaskResume()
、vTaskResumeFromlSR()
就绪状态
除阻塞和挂起状态外的第三种非运行的子状态;代表任务可以准备执行,但是因为有一个同优先级或者更高优先级的任务处于运行状态而还没有真正执行。
调度方式
FreeRTOS 操作系统支持的任务调度方式:抢占式,时间片和合作式。
实际应用主要是:抢占式调度和时间片调度,合作式调度用到的很少。
什么是调度器?
简单的说,调度器就是使用相关的调度算法来决定当前需要执行的任务。所有的调度器有一些共同的特性:
- 调度器可以区分就绪态任务和挂起任务(由于延迟,信号量等待,邮箱等待,事件组等待等原因而使得任务被挂起)。
- 调度器可以选择就绪态中的一个任务,然后激活它(通过执行这个任务)。 当前正在执行的任务是运行态的任务。
不同调度器之间最大的区别就是如何分配就绪态任务间的完成时间。
嵌入式实时操作系统的核心就是调度器和任务切换,调度器的核心就是调度算法。任务切换的实现在不同的嵌入式实时操作系统中区别不大,基本相同的硬件内核架构,任务切换也是相似的。调度算法就有些区别了。
时间片调度和抢占式调度一般都是开启了的,因此:
- 各任务优先级相同时,使用时间片调度;
- 各任务优先级不同时,使用抢占式调度。
默认情况下,在freertos.h中使能了时间片调度:
#ifndef configUSE_TIME_SLICING
#define configUSE_TIME_SLICING 1
#endif
而抢占式调度一般在freertosconfig.h中使能:
#define configUSE_PREEMPTION 1
协作式调度
FreeRTOS 可以选择采用协作式调度。采用一个纯粹的协作式调度器,只可能在运行态任务进入阻塞态或是运行态任务显式调用taskYIELD()时,才会进行上下文切换。任务永远不会被抢占,而具有相同优先级的任务也不会自动共享处理器时间。协作式调度的这作工作方式虽然比较简单,但可能会导致系统响应不够快。
抢占式调度
每个任务都有不同的优先级,任务会一直运行直到被高优先级任务抢占或者遇到阻塞式的 API 函数,
比如 vTaskDelay。
在实际的应用中,不同的任务需要不同的响应时间。例如,我们在一个应用中需要使用电机,键盘和
LCD 显示。电机比键盘和 LCD 需要更快速的响应,如果我们使用合作式调度器或者时间片调度,那么电机将无法得到及时的响应,这时抢占式调度是必须的。
如果使用了抢占式调度,最高优先级的任务一旦就绪,总能得到 CPU 的控制权。 比如,当一个运行
着的任务被其它高优先级的任务抢占,当前任务的 CPU 使用权就被剥夺了,或者说被挂起了,那个高优
先级的任务立刻得到了 CPU 的控制权并运行。 又比如,如果中断服务程序使一个高优先级的任务进入就绪态,中断完成时,被中断的低优先级任务被挂起,优先级高的那个任务开始运行。
使用抢占式调度器,使得最高优先级的任务什么时候可以得到 CPU 的控制权并运行是可知的,同时
使得任务级响应时间得以最优化。
总的来说,学习抢占式调度要掌握的最关键一点是:每个任务都被分配了不同的优先级,抢占式调度
器会获得就绪列表中优先级最高的任务,并运行这个任务。
如果用户在 FreeRTOS 的配置文件 FreeRTOSConfig.h 中禁止使用时间片调度, 那么每个任务必须配
置不同的优先级。当 FreeRTOS 多任务启动执行后,基本会按照如下的方式去执行:
1、 首先执行的最高优先级的任务 Task1, Task1 会一直运行直到遇到系统阻塞式的 API 函数,比如延迟事件标志等待,信号量等待,Task1 任务会被挂起,也就是释放 CPU 的执行权,让低优先级的任务
得到执行。
2、FreeRTOS 操作系统继续执行任务就绪列表中下一个最高优先级的任务 Task2,Task2 执行过程中有
两种情况:
- Task1由于延迟时间到, 接收到信号量消息等方面的原因, 使得 Task1从挂起状态恢复到就绪态,
在抢占式调度器的作用下,Task2 的执行会被 Task1 抢占。 - Task2 会一直运行直到遇到系统阻塞式的 API 函数,比如延迟,事件标志等待,信号量等待, Task2任务会被挂起,继而执行就绪列表中下一个最高优先级的任务。
如果用户创建了多个任务并且采用抢占式调度器的话,基本都是按照上面两条来执行。 根据抢占式调度器,当前的任务要么被高优先级任务抢占,要么通过调用阻塞式 API 来释放 CPU 使用权让低优先
级任务执行,没有用户任务执行时就执行空闲任务。
空闲任务的责任是要将分配给已删除任务的内存释放掉。
时间片调度
每个任务都有相同的优先级,任务会运行固定的时间片个数或者遇到阻塞式的 API 函数,比如
vTaskDelay,才会执行同优先级任务之间的任务切换。
在小型的嵌入式 RTOS 中,最常用的的时间片调度算法就是 Round-robin
调度算法。这种调度算法可以用于抢占式或者合作式的多任务中。另外,时间片调度适合用于不要求任务实时响应的情况。
实现 Round-robin 调度算法需要给同优先级的任务分配一个专门的列表,用于记录当前就绪的任务,
并为每个任务分配一个时间片(也就是需要运行的时间长度,时间片用完了就进行任务切换)。
在 FreeRTOS 操作系统中只有同优先级任务才会使用时间片调度,另外还需要用户在FreeRTOSConfig.h
中使能宏定义:
#define configUSE_TIME_SLICING 1
// 默认情况下,此宏定义已经在 FreeRTOS.h 文件里面使能了,用户可以不用在 FreeRTOSConfig.h 文件中再单独使能。
https://www.cnblogs.com/yangguang-it/category/980679.html
附录:heap_x.c堆的管理方式
移植的时候需要选择一种内存管理方式。
source/portable/MemMang
:内存堆实现范例(堆栈设计),heap_x.c文件提供了多种堆栈方案。实际移植中需要选择下列一种方案(一般选择heap_4.c
)。
文件名 | 功能说明 |
---|---|
heap_1.c | 最简单的方式,不允许释放内存 |
heap_2.c | 允许释放内存,但不会合并相邻的空闲块。(容易造成内存碎片) |
heap_3.c | 简单地包装标准的malloc()和free()以保证线程安全 |
heap_4.c | 邻近空闲块的合并以避免碎片。包括绝对地址放置选项 |
heap_5.c | 根据heap_4,能够跨多个非相邻内存区域跨越堆 |
下面介绍适合它们使用的环境。
heap1.h
这是所有方式中最简单的实现。它不容许一旦被分配到被释放的内存。
功能:
- 如果您的应用程序永远不会删除任务,队列,信号量,互斥量等(可以覆盖大多数使用FreeRTOS的应用程序),则可以使用它。
- 始终是确定性的(总是花费相同的执行时间)并且不会导致内存碎片。
非常简单并从静态分配的数组中分配内存,这意味着它通常适用于不允许真正动态内存分配的应用程序。
heap2.h
该方案使用最佳拟合算法,并且与方案1不同,允许释放先前分配的块。它并不相邻的空闲块合并成一个单一的大的块。请参阅heap_4.c以获取执行无融合块的实现。
功能:
- 即使应用程序反复删除任务,队列,信号量,互斥量等,也可以使用以下有关内存碎片的警告。
- 如果不是,如果被分配和释放内存使用是随机的大小。例如:
- 如果应用程序动态创建和删除任务,并且分配给正在创建的任务的堆栈大小始终相同,则在大多数情况下可以使用heap2.c. 但是,如果分配给正在创建的任务的堆栈大小并不总是相同,那么可用的可用内存可能会碎片化为许多小块,最终导致分配失败。在这种情况下,heap_4.c将是一个更好的选择。
- 如果应用程序动态创建和删除队列,并且队列存储区域在每种情况下都相同(队列存储区域是队列项目大小乘以队列长度),则在大多数情况下可以使用heap_2.c. 但是,如果队列存储区域在每种情况下都不相同,则可用空闲内存可能会碎片化为许多小块,最终导致分配失败。在这种情况下,heap_4.c将是一个更好的选择。
- 该应用程序直接调用pvPortMalloc()和vPortFree(),而不是间接通过其他FreeRTOS API函数。
- 如果您的应用程序以不可预测的顺序排队,任务,信号量,互斥锁等,则可能导致内存碎片问题。几乎所有应用都不太可能,但应该牢记这一点。
- 不是确定性的 - 但是大多数标准C库malloc实现的效率要高得多。
heap3.h
这实现了标准C库malloc()和free()函数的简单包装,在大多数情况下,这些函数将与您选择的编译器一起提供。包装器只是使malloc()和free()函数线程安全。
功能:
- 需要链接器设置堆,并使用编译器库来提供malloc()和free()实现。
不确定性。 - 可能会大大增加RTOS内核代码的大小。
heap4.h
该方案使用第一拟合算法,并且与方案2不同,它将相邻的空闲存储块组合成单个大块(它确实包括合并算法)。
可用堆空间总量由configTOTAL_HEAP_SIZE设置 - 它在FreeRTOSConfig.h中定义。提供configAPPLICATION_ALLOCATED_HEAP FreeRTOSConfig.h配置常量以允许将堆放置在内存中的特定地址。xPortGetFreeHeapSize()API函数返回在调用函数时保持未分配的堆空间总量,xPortGetMinimumEverFreeHeapSize()API函数返回FreeRTOS应用程序引导的系统中存在的最小可用堆空间量。这两个函数都没有提供有关如何将未分配的内存分段为更小的块的信息。
功能:
- 即使应用程序反复删除任务,队列,信号量,互斥量等,也可以使用它们。比heap_2实现更不可能导致堆空间严重碎片化为多个小块 - 即使分配和释放的内存具有随机大小。
- 不是确定性的 - 但是大多数标准C库malloc实现的效率要高得多。
heap5.h
v8.1.0新增
此方案使用与heap_4相同的第一个拟合和内存合并算法,并允许堆跨越多个非相邻(非连续)内存区域。Heap_5通过调用vPortDefineHeapRegions()来初始化,并且在vPortDefineHeapRegions()执行之后才能使用。创建RTOS对象(任务,队列,信号量等)将隐式调用pvPortMalloc(),因此在使用heap_5时,必须在创建任何此类对象之前调用vPortDefineHeapRegions()。
vPortDefineHeapRegions()函数只需要单个参数。该参数是一个HeapRegion_t结构体类型数组。HeapRegion_t在portable.h中定义,如下所示:
typedef struct HeapRegion
{
/* 用于内存堆的内存块起始地址*/
uint8_t *pucStartAddress;
/* 内存块大小 */
size_t xSizeInBytes;
} HeapRegion_t;
这个数组必须使用一个NULL指针和0字节元素作为结束,起始地址必须从小到大排列。下面的代码段提供一个例子。MSVCWin32模拟器演示例程使用了heap_5,因此可以当做一个参考例程。
/* 在内存中为内存堆分配两个内存块.第一个内存块0x10000字节,起始地址为0x80000000,
第二个内存块0xa0000字节,起始地址为0x90000000.起始地址为0x80000000的内存块的
起始地址更低,因此放到了数组的第一个位置.*/
const HeapRegion_t xHeapRegions[] =
{
{ ( uint8_t * ) 0x80000000UL, 0x10000 },
{ ( uint8_t * ) 0x90000000UL, 0xa0000 },
{ NULL, 0 } /* 数组结尾. */
};
/* 向函数vPortDefineHeapRegions()传递数组参数. */
vPortDefineHeapRegions( xHeapRegions );
附录:优先级分配方案
FreeRTOS 优先级机制
FreeRTOS 优先级相关的几个重要知识点进行下说明,这些知识点在以后的使用中务必要掌握牢固。
- FreeRTOS 中任务的最高优先级是通过 FreeRTOSConfig.h 文件中的
configMAX_PRIORITIES
进行配置的,用户实际可以使用的优先级范围是 0 到configMAX_PRIORITIES – 1
。比如我们配置此宏定义为 5,那么用户可以使用的优先级号是0 ~ 4
,不包含 5,对于这一点,初学者要特别的注意。 - 用户配置任务的优先级数值越小,那么此任务的优先级越低,空闲任务的优先级是 0。
建议用户配置宏定义 configMAX_PRIORITIES
的最大值不要超过 32,即用户任务可以使用的优先级
范围是0到31。因为对于CM内核的移植文件,用户任务的优先级不超过32的话, portmacro.h
中的宏定义configUSE_PORT_OPTIMISED_TASK_SELECTION
会优化优先级列表中要执行的最高优先级任务的获取算法(对于 CM 内核的移植文件,此宏定义默认是使能的)。
相比通用的最高优先级任务获取算法,这两种方式的对比如下:
- 通用方式,没有优化---配置宏定义 configUSE_PORT_OPTIMISED_TASK_SELECTION 为 0:
- 跨平台
- 纯 C 编写,比专用方式效率低
- 可用的优先级数量不限制。
- 专用方式,进行优化---配置宏定义 configUSE_PORT_OPTIMISED_TASK_SELECTION 为 1:
- 部分平台支持。
- 这些平台架构有专用的汇编指令,比如 CLZ(Count Leading Zeros)指令,通过这些指令可以加速算法执行速度。 (比通用方式高效。)
- 有最大优先级数限制,通常限制为 32 个。
任务优先级分配方案
推荐以下面的任务优先级设置标准的:
实际项目也可以不采用这种方法,调试出适合项目需求的才是最好的。
优先级 | 任务 | 备注 |
---|---|---|
0 | 空闲任务 | 默认的系统任务 |
1 | 低优先级时间片调度任务 | |
.. | 高优先级后台任务 | 必须是阻塞式的 |
最高 | IRQ任务 | 必须是阻塞式的 |
说明:
- IRQ 任务:IRQ 任务是指通过中断服务程序进行触发的任务,此类任务应该设置为所有任务里面优先级最高的。
- 高优先级后台任务:比如按键检测,触摸检测,USB 消息处理,串口消息处理等,都可以归为这一类任务。
- 低优先级的时间片调度任务:比如 emWin 的界面显示,LED 数码管的显示等不需要实时执行的都可以归为这一类任务。 实际应用中用户不必拘泥于将这些任务都设置为优先级 1 的同优先级任务,可以设置多个优先级,只需注意这类任务不需要高实时性。
特别注意:IRQ 任务和高优先级任务必须设置为阻塞式(调用消息等待或者延迟等函数即可),只有
这样,高优先级任务才会释放 CPU 的使用权,从而低优先级任务才有机会得到执行。
中断优先级和任务优先级区别
中断优先级和任务优先级之间没有任何关系,不管中断的优先级是多少,中断的优先级永远高于任何任务的优先级,即任务在执行的过程中,中断来了就开始执行中断服务程序。
若在页首无特别声明,本篇文章由 Schips 经过整理后发布。
博客地址:https://www.cnblogs.com/schips/