一. 任务管理(调度)
1.1. 调度方式
FreeRTOS中提供的任务调度器是基于优先级的全抢占式调度:在系统中除了中断处理函数、调度器上锁部分的代码和禁止中断的代码是不可抢占的之外,系统的其他部分都是可以抢占的。
任务通常会运行在一个死循环中,也不会退出,如果一个任务不再需要,可以调用FreeRTOS中的任务删除API函数接口显式地将其删除。
1.2. 实时性保证
FreeRTOS内核中采用两种方法寻找最高优先级的任务,第一种是通用的方法,在就绪链表中查找从高优先级往低查找uxTopPriority,因为在创建任务的时候已经将优先级进行排序,查找到的第一个uxTopPriority就是我们需要的任务,然后通过uxTopPriority获取对应的任务控制块。第二 种方法则是特殊方法,利用计算前导零指令CLZ,直接在uxTopReadyPriority这个32位的变量中直接得出uxTopPriority,这样子就知道哪一个优先级任务能够运行,这种调度算法比普通方法更快捷,但受限于平台(在STM32中我们就使用这种方法)。
1.3. 任务状态
-
就绪(Ready):该任务在就绪列表中,就绪的任务已经具备执行的能力,只等待调度器进行调度,新创建的任务会初始化为就绪态。
-
运行(Running):该状态表明任务正在执行,此时它占用处理器,FreeRTOS调度器选择运行的永远是处于最高优先级的就绪态任务,当任务被运行的一刻,它的任务状态就变成了运行态。
-
阻塞(Blocked):如果任务当前正在等待某个时序或外部中断,我们就说这个任务处于阻塞状态,该任务不在就绪列表中。包含任务被挂起、任务被延时、任务正在等待信号量、读写队列或者等待读写事件等。
-
挂起态(Suspended):处于挂起态的任务对调度器而言是不可见的,让一个任务进入挂起状态的唯一办法就是调用 vTaskSuspend()函数;而把一个挂起状态的任务恢复的唯一途径就是调用 vTaskResume()或vTaskResumeFromISR()函数,我们可以这么理解挂起态与阻塞态的区别,当任务有较长的时间不允许运行的时候,我们可以挂起任务,这样子调度器就不会管这个任务的任何信息,直到我们调用恢复任务的API函数;而任务处于阻塞态的时候,系统还需要判断阻塞态的任务是否超时,是否可以解除阻塞。
1.4. 任务管理相关宏值
宏值 | 功能 |
---|---|
configUSE_TIME_SLICING | 是否使用时间片 |
configUSE_PORT_OPTIMISED_TASK_SELECTION | 最大支持优先级数目 |
INCLUDE_vTaskSuspend | xTaskResumeFromISR()接口使能 |
INCLUDE_vTaskDelay | vTaskDelay()接口使能 |
INCLUDE_vTaskDelayUntil | vTaskDelayUntil()接口使能 |
1.5. 任务管理相关API
函数 | 功能 | 备注 |
---|---|---|
vTaskSuspend(TaskHandle_t) | 任务挂起函数 | 被挂起的任务对于调度器不可见,得不到CPU的使用权 任务可自挂 |
vTaskSuspendAll() | 所有任务挂起(调度器挂起) | 将调度器锁定,上下文切换中断被挂起,可嵌套 |
vTaskResume(TaskHandle_t) | 挂起任务恢复 | 让挂起的任务重新进入就绪状态,会保留挂起前的状态信息 |
xTaskResumeFromISR(TaskHandle_t) | 挂起任务恢复 | 专门用在中断服务程序中 |
xTaskResumeAll() | 调度器恢复 | |
vTaskDelete(TaskHandle_t) | 任务删除函数 | 删除任务时,应用程序自己申请给任务的内存或任何其他资源必须由应用程序显式释放。 |
vTaskDelay(TickType_t) | 任务延时函数(相对延时) | 延时的时长由入参决定,单位为系统节拍周期,比如系统的时钟节拍周期为1ms,那么调用 vTaskDelay(1)的延时时间则为 1ms。 FreeRTOSConfig.h 中把 INCLUDE_vTaskDelay 定义为 1 来使能 并不是绝对延时,如果有更高优先级的任务或中断正在执行则会继续阻塞 |
vTaskDelayUntil(TickType_t * , TickType_t ) | 任务延时函数(绝对延时) | 常用于较精确的周期运行任务,当(*pxPreviousWakeTime + xTimeIncrement)时间到达后,vTaskDelayUntil()函数立刻返回,如果任务是最高优先级的,那么任务会立马解除阻塞 在FreeRTOSConfig.h中把INCLUDE_vTaskDelayUntil定义为1来使能。 |
二. 消息队列
2.1. 消息队列主要特性
-
消息支持先进先出方式排队,支持异步读写工作方式。
-
读写队列均支持超时机制。
-
消息支持后进先出方式排队,往队首发送"紧急"消息(LIFO)。
-
可以允许不同长度(不超过队列节点最大值)的任意类型消息。
-
一个任务能够从任意一个消息队列接收和发送消息。
-
多个任务能够从同一个消息队列接收和发送消息。
-
当队列使用结束后,可以通过删除队列函数进行删除。
-
发送到队列的消息是通过拷贝方式实现的,这意味着队列存储的数据是原数据,而不是原数据的引用。
-
无论是发送或者是接收消息都是以拷贝的方式进行,如果消息过于庞大,可以将消息的地址作为消息进行发送、接收。
2.2. 消息队列相关宏值
宏值 | 功能 |
---|---|
configSUPPORT_DYNAMIC_ALLOCATION | 使能动态内存分配(使能xQueueCreate调用) |
configSUPPORT _STATIC_ALLOCATION | 使能静态内存分配(使能xQueueCreateStatic调用) |
2.3. 消息队列相关API
函数 | 功能 | 备注 |
---|---|---|
QueueHandle_t xQueueCreate(uxQueueLength, uxItemSize) | 创建消息队列 | 在创建消息队列的时候,需要用户定义消息队列句柄 configSUPPORT_DYNAMIC_ALLOCATION 需为1 |
QueueHandle_t xQueueCreateStatic(UBaseType_t uxQueueLength, UBaseType_t uxItemSize, uint8_t *pucQueueStorageBuffer, StaticQueue_t *pxQueueBuffer ) | 创建消息队列(静态内存分配) | 在创建消息队列的时候,需要用户定义消息队列句柄,初始化内存 configSUPPORT _STATIC_ALLOCATION需为1 |
vQueueDelete(QueueHandle_t) | 删除消息队列 | 如果删除消息队列时,有任务正在等待消息,则不应该进行删除操作 |
BaseType_t xQueueSend(xQueue, pvItemToQueue, xTicksToWait) | 任务中发送消息 | 如果队列未满或者允许覆盖入队,FreeRTOS会将消息拷贝到消息队列队尾,否则,会根据用户指定的阻塞超时时间进行阻塞 当任务等待的时间超过了指定的阻塞时间,发送消息的任务或者中断程序会收到一个错误码errQUEUE_FULL。 |
BaseType_t xQueueSendFromISR(xQueue,pvItemToQueue,pxHigherPriorityTaskWoken) BaseType_t xQueueSendToBackFromISR(xQueue,pvItemToQueue,pxHigherPriorityTaskWoken) | 中断服务程序中发送消息 | |
BaseType_t xQueueSendToFront(xQueue, pvItemToQueue, xTicksToWait) | 任务中(中断服务中)发送紧急消息 | 当发送紧急消息时,发送的位置是消息队列队头而非队尾,这样,接收者就能够优先接收到紧急消息,从而及时进行消息处理。 |
xQueueGenericSend() | 发送消息的原始接口(上述发送消息的接口都是调用该原始接口实现) | |
BaseType_t xQueueReceive(xQueue, pvBuffer, xTicksToWait) | 任务中接收消息(删除消息) | 用于从一个队列中接收消息并把消息从队列中删除。接收的消息是以拷贝的形式进行的,所以我们必须提供一个足够大空间的缓冲区。 |
BaseType_t xQueuePeek(xQueue, pvBuffer, xTicksToWait) | 任务中接收消息(不删除消息) | 这个函数与xQueueReceive()函数的使用方法一样,只不过xQueuePeek()函数接收消息完毕不会删除消息队列中的消息 |
BaseType_t xQueueReceiveFromISR(xQueue, pvBuffer, pxHigherPriorityTaskWoken) | 中断中接收消息(删除消息) 中断中接收消息(不删除消息) | |
xQueueGenericReceive() | 接收消息的原始接口 |
三、信号量
3.1.基本概念
在多任务系统中,我们经常会使用信号量,比如,某个任务需要等待一个标记,那么任务可以在轮询中查询这个标记有没有被置位,但是这样子做,就会很消耗CPU资源并且妨碍其他任务执行,更好的做法是任务的大部分时间处于阻塞状态(允许其他任务执行),直到某些事件发生该任务才被唤醒去执行。
信号量是一个非负整数,常用于协助一组相互竞争的任务来访问临界资源。所有获取它的任务都会将该整数减一(获取它当然是为了使用资源),当该整数值为零时,所有试图获取它的任务都将处于阻塞状态。通常一个信号量的计数值用于对应有效的资源数,表示剩下的可被占用的互斥资源数。
3.2. 信号量种类
3.2.1.二值信号量
二值信号量和互斥信号量(以下使用互斥量表示互斥信号量)非常相似,但是有一些细微差别:互斥量有优先级继承机制,二值信号量则没有这个机制。这使得二值信号量更偏向应用于同步功能(任务与任务间的同步或任务和中断间同步),而互斥量更偏向应用于临界资源的访问。
3.2.2.计数信号量
将计数信号量用于事件计数与资源管理。每当某个事件发生时,任务或者中断将释放一个信号量(信号量计数值加1),当处理被事件时(一般在任务中处理),处理任务会取走该信号量(信号量计数值减1),信号量的计数值则表示还有多少个事件没被处理。我们也可以使用计数信号量进行资源管理,信号量的计数值表示系统中可用的资源数目,任务必须先获取到信号量才能获取资源访问权,当信号量的计数值为零时表示系统没有可用的资源,但是要注意,在使用完资源的时候必须归还信号量,否则当计数值为0的时候任务就无法访问该资源了。
3.2.3.互斥信号量
互斥信号量其实是特殊的二值信号量,由于其特有的优先级继承机制从而使它更适用于简单互锁,也就是保护临界资源(什么是优先级继承在后续相信讲解)。
3.2.4.递归信号量
对于已经获取递归互斥量的任务可以重复获取该递归互斥量,该任务拥有递归信号量的所有权。任务成功获取几次递归互斥量,就要返还几次,在此之前递归互斥量都处于无效状态,其他任务无 法获取,只有持有递归信号量的任务才能获取与释放。
3.3. 信号量相关宏值
宏值 | 功能 |
---|---|
configSUPPORT_DYNAMIC_ALLOCATION | 使能动态内存分配(使能xSemaphoreCreateBinary调用) |
configSUPPORT _STATIC_ALLOCATION | 使能静态内存分配(使能xQueueCreateStatic调用) |
3.4. 信号量相关API
函数 | 功能 | 备注 |
---|---|---|
SemaphoreHandle_t xSemaphoreCreateBinary() | 创建二值信号量 |
|
xSemaphoreTake(xSemaphore, xBlockTime) | 获取信号量 | 递归互斥量并不能使用这个API函数获取 |
xSemaphoreTakeFromISR(xSemaphore, *px HigherPriorityTaskWoken) | 获取信号量(中断) | 不能用于获取互斥量,因为互斥量 不可以在中断中使用 |
xSemaphoreGive(xSemaphore) | 释放信号量 | 真正的实现过程是调用消息队列通用发送函数,并且是不允许入队阻塞 不能释放由函数xSemaphoreCreateRecursiveMutex()创建的递归互斥量 |
xSemaphoreGiveFromISR(xSemaphore) | 释放信号量(中断) | 它不能释放互斥量,这是因为互斥量不可以在中断中使用,互斥量的优先级继承机制只能在任务中起作用,而在中断中毫无意义。 如果被唤醒的任务的优先级大于当前任务的优先级,在中断退出前执行一次上下文切换 |
SemaphoreHandle_t xSemaphoreCreateCounting(uxMaxCount, uxInitialCount) | 创建计数信号量 | |
vSemaphoreDelete( xSemaphore ) | 删除信号量 | 如果有任务阻塞在该信号量上,那么不要删除该信号量。 |
四、互斥量
4.1. 基本概念
互斥量又称互斥信号量(本质是信号量),是一种特殊的二值信号量,它和信号量不同的是,它支持互斥量所有权、递归访问以及防止优先级翻转的特性,用于实现对临界资源的独占式处理
互斥量与二值信号量最大的不同是:互斥量具有优先级继承机制,而信号量没有。
4.2. 优先级继承机制
在H任务申请该资源的时候,由于申请不到资源会进入阻塞态,那么系统就会把当前正在使用资源的L任务的优先级临时提高到与H任务优先级相同,此时M任务被唤醒了,因为它的优先级比H任务低,所以无法打断L任务,因为此时L任务的优先级被临时提升到H,所以当L任务使用完该资源了,进行释放, 那么此时H任务优先级最高,将接着抢占CPU的使用权, H任务的阻塞时间仅仅是L任务的执行时间,此时的优先级的危害降到了最低,
互斥量更适合于:可能会引起优先级翻转的情况。
递归互斥量更适用于:任务可能会多次获取互斥量的情况下。这样可以避免同一任务多次递归持有而造成死锁的问题。
另外需要注意的是互斥量不能在中断服务函数中使用,因为其特有的优先级继承机制只在任务起作用,在中断的上下文环境毫无意义。
4.3. 互斥量相关宏值
宏值 | 功能 |
---|---|
configUSE_MUTEXES | 开启互斥量使用 |
configSUPPORT_DYNAMIC_ALLOCATION | 使能动态内存分配 |
4.4. 互斥量相关API
函数 | 功能 | 备注 |
---|---|---|
SemaphoreHandle_t xSemaphoreCreateMutex() | 创建互斥量 | |
SemaphoreHandle_t xSemaphoreCreateRecursiveMutex() | 创建递归互斥量 | |
xSemaphoreTakeRecursive(xMutex, xBlockTime) | 获取递归互斥量 | |
xSemaphoreGiveRecursive(xMutex) | 释放递归互斥量 |
五、事件
5.1. 基本概念
与信号量不同的是,它可以实现一对多,多对多的同步。当一个事件发生时,可以有多个任务被唤醒,一个任务也可以等待多个事件的发生。
相比于全局变量标志位的好处:变量保护;不需要每次轮询变量,节省CPU资源;还有等待超时机制。
5.2. 事件相关宏值
宏值 | 功能 |
---|---|
configUSE_16_BIT_TICKS | EventBits_t类型大小, 0对应32位 24位用于存储事件, 1对应16位 8位用于存储事件 |
configSUPPORT_DYNAMIC_ALLOCATION | 使能动态内存分配 |
configTIMER_TASK_PRIORITY | 守护任务优先级(中断中的事件组置位和复位操作均由守护任务完成) |
5.3. 事件相关API
函数 | 功能 | 备注 |
---|---|---|
EventGroupHandle_t xEventGroupCreate() | 创建一个事件组 | |
vEventGroupDelete(EventGroupHandle_t) | 事件删除函数 | |
EventBits_t xEventGroupSetBits( EventGroupHandle_t, EventBits_t ) | 事件组置位函数(任务) | |
EventBits_t xEventGroupSetBitsFromISR(EventGroupHandle_t, EventBits_t, pxH igherPriorityTaskWoken) | 事件组置位函数(中断) | 置位事件组中的标志位是一个不确定的操作,因为阻塞在事件组的标志位上的任务的个数是不确定的。FreeRTOS是不允许不确定的操作在中断和临界段中发生的。GroupSetBitsFromISR()给FreeRTOS的守护任务发送一个消息,让置位事件组的操作在守护任务里面完成,守护任务是基于调度锁而非临界段的机制来实现的。 |
EventBits_t xEventGroupWaitBits(EventGroup, uxBitsToWaitFor, xClearOnExit, xWaitForAllBits, xTicksToWait) | 等待事件函数 | |
EventBits_t xEventGroupClearBits(xEventGroup, uxBitsToClear) | 事件组复位函数(任务) | |
xEventGroupClearBitsFromISR(xEventGroup, uxBitsToClear) | 事件组复位函数(任务) |
六、软件定时器
6.1. 基本概念
软件定时器在被创建之后,当经过设定的时钟计数值后会触发用户定义的回调函数。
一般系统利用SysTick作为软件定时器的基础时钟,软件定时器的回调函数类似硬件的中断服务函数,所以,回调函数也要快进快出
FreeRTOS提供的软件定时器支持单次模式和周期模式
FreeRTOS 通过一个prvTimerTask任务(也叫守护任务Daemon)管理软定时器,会在其执行期间检查用户启动的时间周期溢出的定时器,并调用其回调函数
6.2. 软件定时器相关宏值
宏值 | 功能 |
---|---|
configUSE_TIMERS | 使能软件定时器 |
configTICK_RATE_HZ | 系统节拍配置 |
configTIMER_TASK_PRIORITY | 守护任务优先级(该优先级应设置为所有任务中最高的优先级) |
configTIMER_TASK_STACK_DEPTH | 定时器任务栈大小 |
configTIMER_QUEUE_LENGTH | 定时器命令消息队列的长度 |
6.3. 软件定时器相关API
函数 | 功能 | 备注 |
---|---|---|
xTimerCreate(xTimer, xBlockTime) | 创建软件定时器 | 软件定时器在创建成功后是处于休眠状态的 |
xTimerStart(xTimer, xTicksToWait) | 激活软件定时器(任务) | |
xTimerStartFromISR(xTimer, pxHigherPriorityTaskWoken) | 激活软件定时器(中断) | 定时器守护任务的优先级大于或者等于当前被中断的任务的优先级,那么pxHigherPriorityTaskWoken的值会在函数xTimerStartFromISR()内部设置为pdTRUE,在中断退出之前需要执行一次上下文切换。 |
xTimerStop(xTimer, xBlockTime) | 停止软件定时器(任务) | |
xTimerStopFromISR(xTimer, pxHigherPriorityTaskWoken) | 停止软件定时器(中断) | 定时器守护任务的优先级大于或者等于当前被中断的任务的优先级,那么pxHigherPriorityTaskWoken的值会在函数xTimerStartFromISR()内部设置为pdTRUE,在中断退出之前需要执行一次上下文切换。 |
xTimerDelete(xTimer, xTicksToWait ) | 删除软件定时器 |
七、任务通知
7.1. 基本概念
每个任务都有一个32位的通知值,在大多数情况下,任务通知可以替代二值信号量、计数信号量、事件组,也可以替代长度为1的队列(可以保存一个32位整数或指针值)。
按照 FreeRTOS 官方的说法,使用任务通知比通过信号量等ICP通信方式解除阻塞的任务要快 45%,更加省RAM内存空间(任务通知的使用无需创建队列)。
也有以下限制:
只能有一个任务接收通知消息,因为必须指定接收通知的任务。
只有等待通知的任务可以被阻塞,发送通知的任务,在任何情况下都不会因为发送失败而进入阻塞态。
FreeRTOS 提供以下几种方式发送通知给任务:
发送通知给任务,如果有通知未读,不覆盖通知值。
发送通知给任务,直接覆盖通知值。
发送通知给任务,设置通知值的一个或者多个位,可以当做事件组来使用。
发送通知给任务,递增通知值,可以当做计数信号量使用。
只有在任务中可以等待通知,而不允许在中断中等待通知。如果任务在等待的通知暂时无效,任务会根据用户指定的阻塞超 时时间进入阻塞状态,
7.2. 任务通知相关宏值
宏值 | 功能 |
---|---|
configUSE_TASK_NOTIFICATIONS | 使能任务通知 |
7.3. 任务通知相关API
函数 | 功能 | 备注 |
---|---|---|
xTaskGenericNotify( TaskHandle_t xTaskToNotify, uint32_t ulValue, eNotifyAction eAction, uint32_t *pulPreviousNotificationValue ) | 发送任务通知接口(任务) | 任务通知API调用的通用底层接口 eNoAction:对象任务接收任务通知,但是任务自身的 | 任务通知值不更新,即形参ulValue没有用 eSetBits: 通知值按位或上ulValue。使用这种方法可以某些场景下代替事件组 eIncrement: 通知任务的通知值增加1,可以作为二值信号量和计数信号量的一种轻量型的实现 eSetValueWithOverwrite: 将被通知任务的通知值设置为ulValue。可以在某些场景下代替xQueueoverwrite()函数 eSetValueWithoutOverwrite: 如果被通知任务当前没有通知,则被通知任务的通知值设置为ulValue;在某些场景下替代长度为1的xQueuesend() |
xTaskGenericNotifyFromISR( TaskHandle_t xTaskToNotify, uint32_t ulValue, eNotifyAction eAction, uint32_t *pulPreviousNotificationValue, BaseType_t *pxHigherPriorityTaskWoken ) | 发送任务通知接口(中断) | |
xTaskNotifyGive(xTaskToNotify) | 发送任务通知(任务) | 默认执行eIncrement操作,可以作为二值信号量和计数信号量的一种轻量型的实现 |
vTaskNotifyGiveFromISR(xTaskToNotify, pxHigherPriorityTaskWoken) | 发送任务通知(中断) | 默认执行eIncrement操作,可以作为二值信号量和计数信号量的一种轻量型的实现 |
ulTaskNotifyTake(BaseType_t xClearCountOnExit, TickType_t xTicksToWait) | 接收任务通知 | 和发送通知API函数xTaskNotifyGive()、vTaskNotifyGiveFromISR()配合使用 xClearCountOnExit pdFALSE:在函数退出时将通知值减1,这种方法适用于实现计数信号量。 |
xTaskNotify(TaskHandle_t xTaskToNotify, uint32_t ulValue, eNotifyAction eAction) | 发送任务通知(任务) | 调用xTaskGenericNotify,可选择执行所有操作 |
xTaskNotifyFromISR(TaskHandle_t xTaskToNotify, uint32_t ulValue, eNotifyAction eAction, BaseType_t *pxHigherPriorityTaskWoken ) | 发送任务通知(中断) | 调用xTaskGenericNotifyFromISR,可选择执行所有操作 |
xTaskNotifyAndQuery(TaskHandle_t xTaskToNotify, uint32_t ulValue, eNotifyAction eAction,uint32_t * pulPreviousNotifyValue ) | 发送任务通知(任务) | 调用xTaskGenericNotify,可选择执行所有操作,并返回对象任务的上一个通知值 |
xTaskNotifyAndQueryFromISR(TaskHandle_t xTaskToNotify, uint32_t ulValue, eNotifyAction eAction,uint32_t * pulPreviousNotifyValue ) | 发送任务通知(中断) | 调用xTaskGenericNotifyFromISR,可选择执行所有操作,并返回对象任务的上一个通知值 |
xTaskNotifyWait(uint32_t ulBitsToClearOnEntry, uint32_t ulBitsToClearOnExit, uint32_t *pulNotificationValue, TickType_t xTicksToWait ) | 接收任务通知 | 全功能版的等待任务通知 |
八、内存管理
8.1. 基本概念
-
应该根据系统的特点来决定内存分配算法,静态可以保证设备的可靠性但是需要考虑内存上限,内存使用效率低,而动态则是相反。
-
在嵌入式实时操作系统中,调用 malloc()和 free()却是危险的,原因有以下几点:
-
这些函数在小型嵌入式系统中并不总是可用的,小型嵌入式设备中的RAM不足。
-
它们的实现可能非常的大,占据了相当大的一块代码空间。
-
他们几乎都不是安全的。
-
它们并不是确定的,每次调用这些函数执行的时间可能都不一样。
-
它们有可能产生碎片。
-
这两个函数会使得链接器配置得复杂。
-
如果允许堆空间的生长方向覆盖其他变量占据的内存,它们会成为debug的灾难。
-
8.2. 内存管理策略
不同的嵌入式系统具有不同的内存配置和时间要求。所以单一的内存分配算法只可能适合部分应用程序。FreeRTOS有针对性的提供了不同的内存分配管理算法,这使得应用于不同场景的设备可以选择适合自身内存算法。
文件 | 策略 | 优点 | 缺点 | 适用场景 |
---|---|---|---|---|
heap_1.c | 只申请不释放,申请时间固定 |
|
| 从不删除任务、队列、信号量、互斥量等的应用程序 |
heap_2.c | 内存块链表按大小排序 最佳匹配算法 |
|
| 反复的删除任务、队列、信号量、等内核对象且不担心内存碎片的应用程序 |
heap_4.c | 内存块链表按首地址排序 最佳匹配算法 合并算法 |
| - | 重复删除任务、队列、信号量、互斥量等的应用程序 |
heap_5.c | 最佳匹配算法 合并算法 用户自定义非连续内存区空间 |
|
| 芯片内部的RAM不够用户使用,需要外部SDRAM的应用程序 |
heap_3.c | 封装malloc和free 操作内存前挂起调度器、完成后再恢复调度器 链接器设置一个堆,malloc()和free()函数由编译器提供 |
|
| - |
-
对于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,那这种内存管理方案则比较合适。
8.3. 内存管理相关宏值
宏值 | 功能 |
---|---|
configTOTAL_HEAP_SIZE | 系统可支配内存大小(heap_1.c、heap_2.c和heap_4.c) |
8.4. 内存管理相关API
函数 | 功能 | 备注 |
---|---|---|
void *pvPortMalloc( size_t xSize ) | 内存申请函数 | |
void vPortFree( void *pv ) | 内存释放函数 | |
void vPortInitialiseBlocks( void ) | 初始化内存堆函数 | |
size_t xPortGetFreeHeapSize( void ) | 获取当前未分配的内存堆大小 | |
size_t xPortGetMinimumEverFreeHeapSize( void ) | 获取未分配的内存堆历史最小值 |
九、中断管理
9.1. 基本概念
-
同步异常:由内部事件(像处理器指令运行产生的事件)引起的,系统必须立刻进行处理而不能够依然执行原有的程序指令步骤
-
异步异常:由于外部异常源产生的异常,可以延缓处理甚至是忽略
-
中断:中断属于异步异常。所谓中断是指中央处理器CPU正在处理某件事的时候,外部发生了某一事件,请求CPU迅速处理,CPU暂时中断当前的工作,转入处理所发生的事件,处理完后,再回到原来被中断的地方,继续原来的工作,这样的过程称为中断。
-
中断能打断任务的运行,无论该任务具有什么样的优先级,因此中断一般用于处理比较紧急的事件,而且只做简单处理,例如标记该事件,在使用FreeRTOS系统时,一般建议使用信号量、消息或事件标志组等标志中断的发生,将这些内核对象发布给处理任务,处理任务再做具体处理。
-
通过中断机制,在外设不需要CPU介入时,CPU可以执行其他任务,而当外设需要CPU时通过产生中断信号使CPU立即停止当前任务转而来响应中断请求。这样可以使CPU避免把大量时间耗费在等待、查询外设状态的操作上,因此将大大提高系统实时性以及执行效率。
-
-
FreeRTOS的中断管理支持:
-
开/关中断。
-
恢复中断。
-
中断使能。
-
中断屏蔽。
-
可选择系统管理的中断优先级
-
-
中断号:每个中断请求信号都会有特定的标志,使得计算机能够判断是哪个设备提出的中断请求,这个标志就是中断号。
-
中断向量:中断服务程序的入口地址。
-
中断向量表:存储中断向量的存储区,中断向量与中断号对应,中断向量在中断向量表中按照中断号顺序存储。
-
当中断产生时,处理机将按如下的顺序执行:
-
保存当前处理机状态信息
-
载入异常或中断处理函数到PC寄存器
-
把控制权转交给处理函数并开始执行
-
当处理函数执行完成时,恢复处理器状态信息
-
从异常或中断中返回到前一个程序执行点
-
-
中断延迟:识别中断时间(读取中断向量表、找到中断服务程序首地址) + [等待中断打开时间] (等待当前中断响应完成)+ [关闭中断时间](中断被临界区代码主动屏蔽)
9.2. 中断管理相关API
宏值 | 功能 |
---|---|
configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY | 比该优先级低的中断可以被屏蔽,也能安全调用FreeRTOS提供的API函数接口。 |
十、CPU使用率统计
宏值 | 功能 |
---|---|
configGENERATE_RUN_TIME_STATS | 使能运行时间统计 |
configUSE_TRACE_FACILITY | 使能可视化跟踪调试 |
portCONFIGURE_TIMER_FOR_RUN_TIME_STATS() | 待实现 |
portGET_RUN_TIME_COUNTER_VALUE() | 待实现 |