芯片选型
Ciga Device — GD32F470系列
任务管理
任务处理API
操作 | API |
动态任务创建 | xTaskCreate |
任务删除 | vTaskDelete |
静态任务创建 | vTaskCreateStatic |
挂起任务 | vTaskSuspend |
恢复任务 | vTaskResume |
任务创建
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 );
TaskFunction_t pxTaskCode
: 表示的这个任务执行的函数,函数格式为:// typedef void (* TaskFunction_t)( void * ); void task(void *pvParameters);
void * const pvParameters
: 表示任务执行函数的参数,也就是上面函数中的参数部分const char * const pcName
: 表示给这个任务起的名字。const configSTACK_DEPTH_TYPE usStackDepth
: 表示启动任务所需要的栈的大小,单位是byte。通常根据任务的复杂度来进行设置。UBaseType_t uxPriority
: 表示任务的优先级。
以下是关于FreeRTOS任务优先级的几个要点:
- 数值越大,优先级越高:在FreeRTOS中,任务的优先级数值越大,优先级越高。例如,优先级为1的任务比优先级为0的任务具有更高的优先级。
- 优先级为0的任务是最低优先级:通常称为IDLE任务或空闲任务。该任务在没有其他任务需要运行时执行,确保系统在空闲时也有任务可以运行。
- 相同优先级的任务采用时间片轮转调度:当有多个任务具有相同优先级时,FreeRTOS会使用时间片轮转调度算法来平均分配CPU时间。每个任务在一轮时间片内执行一段时间,然后切换到下一个任务。
- 高优先级任务可以抢占低优先级任务:如果一个高优先级任务就绪并准备好运行,它可以抢占当前正在运行的低优先级任务,从而提供更好的实时性。
- 优先级反映任务调度顺序:任务的优先级决定了任务调度的顺序。当有多个任务就绪并等待运行时,任务调度器会选择具有最高优先级的就绪任务来执行。
需要注意的是,任务优先级的设置应根据应用的实时需求和任务间的相对重要性进行合理的规划。过多或过少的优先级级别可能导致调度问题或资源竞争。在任务优先级设置时,需要综合考虑系统的响应性、任务的相互影响和资源的使用情况等因素。
TaskHandle_t * const pxCreatedTask
: 任务句柄。可以理解为任务的实例。
创建任务的返回值说明:
BaseType_t
类型为创建任务的返回值,结果为pdPASS
或者pdFAIL
(成功或者失败)。
动态任务创建(常用)
- 此步骤可以省略。因为默认值为1。但是需要了解这个配置。配置
FreeRTOS.h
中的configSUPPORT_DYNAMIC_ALLOCATION
为1.
#ifndef configSUPPORT_DYNAMIC_ALLOCATION
/* Defaults to 1 for backward compatibility. */
#define configSUPPORT_DYNAMIC_ALLOCATION 1
#endif
- 定义任务执行函数。
void task(void *pvParameters) {
// TODO: 任务的业务逻辑
}
- 调用任务创建逻辑。
xTaskCreate(task1, "task1", 64, NULL, 2, &task1_handler);
点灯示例
点亮PE3和PD7的灯,通过两个不同的任务,进行灯的闪烁控制,观察效果。
#include "gd32f4xx.h"
#include "systick.h"
#include <stdio.h>
#include "main.h"
#include "FreeRTOS.h"
#include "task.h"
TaskHandle_t start_handler;
TaskHandle_t task1_handler;
TaskHandle_t task2_handler;
void task1(void *pvParameters) {
while(1) {
vTaskDelay(300);
gpio_bit_set(GPIOE, GPIO_PIN_3);
vTaskDelay(300);
gpio_bit_reset(GPIOE, GPIO_PIN_3);
}
}
void task2(void *pvParameters) {
while(1) {
vTaskDelay(1000);
gpio_bit_set(GPIOD, GPIO_PIN_7);
vTaskDelay(1000);
gpio_bit_reset(GPIOD, GPIO_PIN_7);
}
}
void start_task(void *pvParameters) {
taskENTER_CRITICAL();
xTaskCreate(task1, "task1", 64, NULL, 2, &task1_handler);
xTaskCreate(task2, "task2", 64, NULL, 2, &task2_handler);
vTaskDelete(start_handler);
taskEXIT_CRITICAL();
}
void GPIO_config() {
// 1. 时钟初始化
rcu_periph_clock_enable(RCU_GPIOE);
// 2. 配置GPIO 输入输出模式
gpio_mode_set(GPIOE, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, GPIO_PIN_3);
// 3. 配置GPIO 模式的操作方式
gpio_output_options_set(GPIOE, GPIO_OTYPE_PP, GPIO_OSPEED_MAX, GPIO_PIN_3);
// 1. 时钟初始化
rcu_periph_clock_enable(RCU_GPIOD);
// 2. 配置GPIO 输入输出模式
gpio_mode_set(GPIOD, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, GPIO_PIN_7);
// 3. 配置GPIO 模式的操作方式
gpio_output_options_set(GPIOD, GPIO_OTYPE_PP, GPIO_OSPEED_MAX, GPIO_PIN_7);
}
int main(void)
{
systick_config();
GPIO_config();
xTaskCreate(start_task, "start_task", 128, NULL, 1, &start_handler);
vTaskStartScheduler();
while(1) {}
}
静态任务创建(用的不多)
1. 配置FreeRTOS.h
中的configSUPPORT_STATIC_ALLOCATION
为1.
#i#ifndef configSUPPORT_STATIC_ALLOCATION
/* Defaults to 0 for backward compatibility. */
#define configSUPPORT_STATIC_ALLOCATION 1
#endif
2. 实现内存管理函数vApplicationGetIdleTaskMemory
和vApplicationGetTimerTaskMemory
StaticTask_t idle_task_tcb;
StackType_t idle_task_stack[configMINIMAL_STACK_SIZE];
StaticTask_t timer_task_tcb;
StackType_t timer_task_stack[configTIMER_TASK_STACK_DEPTH];
void vApplicationGetIdleTaskMemory( StaticTask_t ** ppxIdleTaskTCBBuffer,
StackType_t ** ppxIdleTaskStackBuffer,
uint32_t * pulIdleTaskStackSize )
{
* ppxIdleTaskTCBBuffer = &idle_task_tcb;
* ppxIdleTaskStackBuffer = idle_task_stack;
* pulIdleTaskStackSize = configMINIMAL_STACK_SIZE;
}
void vApplicationGetTimerTaskMemory( StaticTask_t ** ppxTimerTaskTCBBuffer,
StackType_t ** ppxTimerTaskStackBuffer,
uint32_t * pulTimerTaskStackSize )
{
* ppxTimerTaskTCBBuffer = &timer_task_tcb;
* ppxTimerTaskStackBuffer = timer_task_stack;
* pulTimerTaskStackSize = configTIMER_TASK_STACK_DEPTH;
}
3. 定义任务执行函数。
void task(void *pvParameters) {
// TODO: 任务的业务逻辑
}
4. 调用任务创建逻辑
TaskHandle_t xTaskCreateStatic(TaskFunction_t pxTaskCode,
const char * const pcName,
const uint32_t ulStackDepth,
void * const pvParameters,
UBaseType_t uxPriority,
StackType_t * const puxStackBuffer,
StaticTask_t * const pxTaskBuffer )
StackType_t * const puxStackBuffer
:任务栈大小。得自己指定,不可更改。StaticTask_t * const pxTaskBuffer
:任务控制块,用来存储任务的堆栈空间,任务的状态和优先级等。- 返回值为任务的句柄。
点灯示例
#include "gd32f4xx.h"
#include "systick.h"
#include <stdio.h>
#include "main.h"
#include "FreeRTOS.h"
#include "task.h"
StaticTask_t idle_task_tcb;
StackType_t idle_task_stack[configMINIMAL_STACK_SIZE];
StaticTask_t timer_task_tcb;
StackType_t timer_task_stack[configTIMER_TASK_STACK_DEPTH];
TaskHandle_t start_handler;
TaskHandle_t task1_handler;
TaskHandle_t task2_handler;
#define TASK_STACK_SIZE 128
StackType_t task_stack[TASK_STACK_SIZE];
StaticTask_t task_tcb;
#define TASK1_STACK_SIZE 64
StackType_t task1_stack[TASK1_STACK_SIZE];
StaticTask_t task1_tcb;
#define TASK2_STACK_SIZE 64
StackType_t task2_stack[TASK2_STACK_SIZE];
StaticTask_t task2_tcb;
void task1(void *pvParameters) {
while(1) {
vTaskDelay(300);
gpio_bit_set(GPIOE, GPIO_PIN_3);
vTaskDelay(300);
gpio_bit_reset(GPIOE, GPIO_PIN_3);
}
}
void task2(void *pvParameters) {
while(1) {
vTaskDelay(1000);
gpio_bit_set(GPIOD, GPIO_PIN_7);
vTaskDelay(1000);
gpio_bit_reset(GPIOD, GPIO_PIN_7);
}
}
void start_task(void *pvParameters) {
taskENTER_CRITICAL();
task1_handler = xTaskCreateStatic(task1, "task1", TASK1_STACK_SIZE, NULL, 2, task1_stack, &task1_tcb);
task2_handler = xTaskCreateStatic(task2, "task2", TASK2_STACK_SIZE, NULL, 2, task2_stack, &task2_tcb);
vTaskDelete(start_handler);
taskEXIT_CRITICAL();
}
void vApplicationGetIdleTaskMemory( StaticTask_t ** ppxIdleTaskTCBBuffer,
StackType_t ** ppxIdleTaskStackBuffer,
uint32_t * pulIdleTaskStackSize )
{
* ppxIdleTaskTCBBuffer = &idle_task_tcb;
* ppxIdleTaskStackBuffer = idle_task_stack;
* pulIdleTaskStackSize = configMINIMAL_STACK_SIZE;
}
void vApplicationGetTimerTaskMemory( StaticTask_t ** ppxTimerTaskTCBBuffer,
StackType_t ** ppxTimerTaskStackBuffer,
uint32_t * pulTimerTaskStackSize )
{
* ppxTimerTaskTCBBuffer = &timer_task_tcb;
* ppxTimerTaskStackBuffer = timer_task_stack;
* pulTimerTaskStackSize = configTIMER_TASK_STACK_DEPTH;
}
void GPIO_config() {
// 1. 时钟初始化
rcu_periph_clock_enable(RCU_GPIOE);
// 2. 配置GPIO 输入输出模式
gpio_mode_set(GPIOE, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, GPIO_PIN_3);
// 3. 配置GPIO 模式的操作方式
gpio_output_options_set(GPIOE, GPIO_OTYPE_PP, GPIO_OSPEED_MAX, GPIO_PIN_3);
// 1. 时钟初始化
rcu_periph_clock_enable(RCU_GPIOD);
// 2. 配置GPIO 输入输出模式
gpio_mode_set(GPIOD, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, GPIO_PIN_7);
// 3. 配置GPIO 模式的操作方式
gpio_output_options_set(GPIOD, GPIO_OTYPE_PP, GPIO_OSPEED_MAX, GPIO_PIN_7);
}
int main(void)
{
systick_config();
GPIO_config();
start_handler = xTaskCreateStatic(start_task, "start_task", TASK_STACK_SIZE, NULL, 2, task_stack, &task_tcb);
vTaskStartScheduler();
while(1) {}
}
动态任务与静态任务的区别
在FreeRTOS中,任务可以使用静态分配方式或动态分配方式创建。这两种方式在任务创建和内存管理方面存在一些区别。
静态任务:
- 静态任务是在编译时分配内存的任务。
- 在创建静态任务时,需要提前为任务分配足够的内存空间。
- 静态任务的内存分配是固定的,任务的内存大小在编译时确定,并在运行时保持不变。
- 静态任务使用 xTaskCreateStatic() 函数创建。
动态任务:
- 动态任务是在运行时分配内存的任务。
- 在创建动态任务时,不需要提前为任务分配内存空间,而是在运行时使用动态内存分配函数进行分配。
- 动态任务的内存分配是动态的,任务的内存大小可以根据需要进行调整。
- 动态任务使用 xTaskCreate() 函数创建。
区别:
- 静态任务的内存分配是在编译时完成,而动态任务的内存分配是在运行时完成。
- 静态任务需要手动为任务分配内存空间,而动态任务会自动进行内存分配和释放。
- 静态任务的内存大小在编译时确定,不能在运行时改变;而动态任务的内存大小可以在运行时进行动态调整。
- 静态任务对内存的使用是固定的,不会有内存碎片的问题;而动态任务的内存使用可能存在碎片化的风险。
选择静态任务还是动态任务取决于具体的应用需求和系统约束。
静态任务在一些资源有限的系统中更常用,可以避免动态内存分配的开销和内存碎片问题。
而动态任务可以在运行时根据需要动态分配内存,灵活性更高。
通常采用动态任务创建更多。
任务优先级
任务的优先级等级是在FreeRTOSConfig.h
中定义的,configMAX_PRIORITIES
定义了最大任务优先级值,默认值为5。那么优先级取值为0到4。数值越大优先级越高。
我们采用日志打印的方式进行验证,开启两个任务,分别打印日志,开启任务时设置不同优先级进行测试,以下是示例代码。
#include "gd32f4xx.h"
#include "systick.h"
#include <stdio.h>
#include "main.h"
#include "FreeRTOS.h"
#include "task.h"
#include "usart0.h"
TaskHandle_t start_handler;
TaskHandle_t task1_handler;
TaskHandle_t task2_handler;
void task1(void *pvParameters) {
while(1) {
printf("task1\r\n");
vTaskDelay(1000);
}
}
void task2(void *pvParameters) {
while(1) {
printf("task2\r\n");
vTaskDelay(1000);
}
}
void Usart0_recv(uint8_t *data, uint32_t len) {
printf("recv: %s\r\n", data);
}
void start_task(void *pvParameters) {
taskENTER_CRITICAL();
xTaskCreate(task1, "task1", 64, NULL, 2, &task1_handler);
xTaskCreate(task2, "task2", 64, NULL, 3, &task2_handler);
vTaskDelete(start_handler);
taskEXIT_CRITICAL();
}
int main(void)
{
systick_config();
Usart0_init();
printf("start\r\n");
xTaskCreate(start_task, "start_task", 128, NULL, 1, &start_handler);
vTaskStartScheduler();
while(1) {}
}
任务操作
任务挂起
// 挂起任务
vTaskSuspend(xTaskHandle);
任务恢复
// 恢复任务
vTaskResume(xTaskHandle);
任务删除
BaseType_t xTaskDelete(TaskHandle_t xTaskToDelete);
其中,xTaskToDelete 是要删除的任务的句柄(TaskHandle_t 类型)。可以将任务的句柄传递给 xTaskDelete() 函数,以删除指定的任务。
任务删除的几个要点如下:
- 当前任务的删除:如果在任务的执行过程中调用 xTaskDelete(NULL),表示删除当前任务。当前任务将被立即删除,并且不会继续执行后续代码
- 删除其他任务:如果要删除除当前任务之外的任务,需要传递相应任务的句柄给 xTaskDelete() 函数。这样,指定的任务将被删除。
- 任务删除的影响:任务删除后,其占用的资源(如堆栈、任务控制块等)会被释放,其他任务可以继续执行。删除任务时需要注意任务间的同步和资源释放,以避免产生悬空指针或资源泄漏等问题。
- 返回值:xTaskDelete() 函数的返回值是 BaseType_t 类型,表示任务删除成功与否。如果任务删除成功,返回值为 pdPASS。如果任务删除失败,返回值为 errTASK_NOT_DELETED。
- 需要确保配置了如下宏:
#define INCLUDE_vTaskDelete 1
需要注意的是,在任务删除之前,需要确保不再需要该任务的执行,并且合理处理任务间的同步和资源释放。不正确地删除任务可能会导致未定义行为和系统不稳定性。
代码示例
通过按键来操作任务的操作,点击按钮挂起任务,恢复任务。
#include "gd32f4xx.h"
#include "systick.h"
#include <stdio.h>
#include "main.h"
#include "FreeRTOS.h"
#include "task.h"
#include "usart0.h"
TaskHandle_t start_handler;
TaskHandle_t task_key_handler;
TaskHandle_t task1_handler;
TaskHandle_t task2_handler;
void task1(void *pvParameters) {
while(1) {
printf("task1\r\n");
vTaskDelay(1000);
}
}
void task2(void *pvParameters) {
while(1) {
printf("task2\r\n");
vTaskDelay(1000);
}
}
void task_key(void *pvParameters) {
uint32_t flag = 0;
FlagStatus pre_state = RESET;
BaseType_t result;
while(1) {
FlagStatus state = gpio_input_bit_get(GPIOA, GPIO_PIN_0);
if(SET == state && pre_state == RESET) {
// 当前高电平, 上一次为低电平,按下
pre_state = state;
if(flag == 0) {
// 挂起
vTaskSuspend(task1_handler);
} else if(flag == 1) {
// 恢复
vTaskResume(task1_handler);
}
flag++;
if(flag > 1) flag = 0;
} else if(RESET == state && pre_state == SET) {
// 当前高电平, 上一次为低电平,抬起
pre_state = state;
}
vTaskDelay(20);
}
}
void Usart0_recv(uint8_t *data, uint32_t len) {
printf("recv: %s\r\n", data);
}
void start_task(void *pvParameters) {
taskENTER_CRITICAL();
xTaskCreate(task_key, "task_key", 64, NULL, 2, &task_key_handler);
xTaskCreate(task1, "task1", 64, NULL, 2, &task1_handler);
xTaskCreate(task2, "task2", 64, NULL, 3, &task2_handler);
vTaskDelete(start_handler);
taskEXIT_CRITICAL();
}
static void GPIO_config() {
// 时钟初始化
rcu_periph_clock_enable(RCU_GPIOA);
// 配置GPIO模式
gpio_mode_set(GPIOA, GPIO_MODE_INPUT, GPIO_PUPD_PULLDOWN, GPIO_PIN_0);
}
int main(void)
{
//NVIC_SetPriorityGrouping(NVIC_PRIGROUP_PRE4_SUB0);
systick_config();
GPIO_config();
Usart0_init();
printf("start\r\n");
xTaskCreate(start_task, "start_task", 128, NULL, 1, &start_handler);
vTaskStartScheduler();
while(1) {}
}
任务相关机制
任务控制块
在tasks.c文件中,有一段结构体的定义如下,其作为xTaskCreate
的最后一个参数,我们可以得到任务相关的信息。
/*
* Task control block. A task control block (TCB) is allocated for each task,
* and stores task state information, including a pointer to the task's context
* (the task's run time environment, including register values)
*/
typedef struct tskTaskControlBlock /* The old naming convention is used to prevent breaking kernel aware debuggers. */
{
volatile StackType_t * pxTopOfStack; /*< Points to the location of the last item placed on the tasks stack. THIS MUST BE THE FIRST MEMBER OF THE TCB STRUCT. */
ListItem_t xStateListItem; /*< The list that the state list item of a task is reference from denotes the state of that task (Ready, Blocked, Suspended ). */
ListItem_t xEventListItem; /*< Used to reference a task from an event list. */
UBaseType_t uxPriority; /*< The priority of the task. 0 is the lowest priority. */
StackType_t * pxStack; /*< Points to the start of the stack. */
char pcTaskName[ configMAX_TASK_NAME_LEN ]; /*< Descriptive name given to the task when created. Facilitates debugging only. */ /*lint !e971 Unqualified char types are allowed for strings and single characters only. */
...
} tskTCB;
结构体 tskTaskControlBlock 是任务控制块,为每个任务分配一个任务控制块,存储任务状态信息,包括指向任务上下文的指针(任务的运行时环境,包括寄存器值)。
该结构体包括以下成员:
volatile StackType_t * pxTopOfStack
:指向任务堆栈上最后一个放置的项目的位置。这必须是 TCB 结构体的第一个成员。ListItem_t xStateListItem
:任务状态列表项所引用的列表表示任务的状态(就绪、阻塞、挂起)。ListItem_t xEventListItem
:用于从事件列表中引用任务。UBaseType_t uxPriority
:任务的优先级。0 是最低优先级。StackType_t * pxStack
:指向堆栈的起始位置。char pcTaskName[configMAX_TASK_NAME_LEN]
:在创建任务时赋予任务的描述性名称。仅用于便于调试
任务调度机制
任务调度机制,是一种任务调度的策略,如果简单的片面理解,其实就是一句代码:vTaskStartScheduler();
当然,这句代码内部做了什么事情,怎么实现任务调度的,才是任务调度机制的内核。
任务调度机制按照代码流水账式阅读,大概干了以下事情:
- 创建空闲任务
- 如果使能软件定时器,则创建定时器任务
- 关闭中断,防止调度器开启之前或过程中,受中断干扰,会在运行第一个任务时打开中断
- 初始化全局变量,并将任务调度器的运行标志设置为已运行
- 初始化任务运行时间统计功能的时基定时器
- 调用函数 xPortStartScheduler()
总的核心在xPortStartScheduler
内部,流水账式的看,大概做了:
- 优先级配置
- 初始化timer中断
- 启动第一个任务
- 任务上下文切换
总结起来看,任务调度机制,主要做的事情有:
- 任务优先级:每个任务都有一个唯一的优先级,优先级值越低,表示任务优先级越高。在任务创建时,可以指定任务的优先级。
- 任务就绪列表:FreeRTOS 会维护一个就绪列表,其中包含所有就绪(可执行)状态的任务。就绪列表按照任务的优先级进行排序。
- 调度器:FreeRTOS 调度器负责根据任务的优先级选择下一个要执行的任务。调度器会选择就绪列表中优先级最高的任务进行执行。
- 任务切换:当一个任务不再是最高优先级任务时,调度器会进行任务切换,将当前任务挂起并切换到下一个要执行的任务。任务切换是通过上下文切换来实现的,它保存当前任务的上下文,并恢复下一个任务的上下文。
- 抢占式调度:如果有更高优先级的任务进入就绪状态,FreeRTOS 调度器会立即抢占当前任务,并切换到更高优先级的任务。这种调度方式称为抢占式调度,它确保了高优先级任务能够及时得到执行。
- 时间片轮转调度(可选):FreeRTOS 还提供了时间片轮转调度的功能。通过配置选项,可以为任务启用时间片轮转调度算法,使任务在同一优先级中轮流获取执行时间片。这可以确保在同一优先级任务中,任务能够公平地共享 CPU 时间。
总的来说,FreeRTOS 的任务调度机制是基于优先级的抢占式调度。它允许高优先级任务抢占低优先级任务的执行,并确保任务按照优先级顺序得到执行。这样可以实现实时性要求和任务间的优先级关系,确保系统能够及时响应关键任务。
一些名词:
- TCB:
Task Control Block
任务控制块,也就是任务栈的区块。 - PSP:
Process Stack Pointer
PSP 是任务堆栈指针,用于保存任务的上下文信息。每个任务都有自己的 PSP,用于保存任务的寄存器值、局部变量和函数调用信息等。任务切换时,当前任务的上下文会保存到其对应的 PSP,然后切换到下一个任务的 PSP。任务切换可以由任务调度器或中断处理程序触发。 - MSP:
Main Stack Pointer
MSP 是 Cortex-M 处理器的主堆栈指针,用于保存中断处理和异常处理期间的堆栈帧。当系统初始化时,MSP 被设置为默认的堆栈起始地址,也是全局堆栈的起始地址。当发生中断或异常时,处理器会自动切换到 MSP,并将相关的上下文保存到 MSP 所指向的堆栈空间。
任务切换时,PSP 和 MSP 的使用方式如下:
- 当一个任务被创建时,它的初始堆栈指针(PSP)被设置为任务的堆栈顶部地址。
- 当任务正在执行时,它的堆栈指针(PSP)指向任务的堆栈空间,该空间用于保存任务的上下文信息。
- 当任务切换发生时,当前任务的上下文会保存到其对应的 PSP,然后从下一个任务的 PSP 恢复上下文,并开始执行下一个任务。
需要注意的是,PSP 和 MSP 是 Cortex-M 处理器的特定寄存器,并由处理器硬件自动管理。FreeRTOS 通过使用这些寄存器来实现任务的上下文切换和堆栈管理,以实现多任务调度的功能。在任务编程中,通常不需要直接操作这些寄存器,而是通过 FreeRTOS 提供的 API 和调度器来进行任务的创建、切换和管理。
临界区
taskENTER_CRITICAL();
// 任务创建代码
taskEXIT_CRITICAL();
在 FreeRTOS 中,临界区是一种机制,用于保护共享资源的访问,防止多个任务同时访问和修改共享资源而引发竞态条件。
FreeRTOS 提供了两种方式实现临界区:
- 任务间的临界区:通过使用任务间的临界区宏来限制在当前任务中执行的代码片段。这样,当任务执行到临界区时,FreeRTOS 将禁止其他具有相同或更低优先级的任务抢占当前任务,从而保证临界区代码的原子性。常用的任务间临界区宏包括:
taskENTER_CRITICAL()
:进入临界区。禁用调度器,阻止其他任务抢占当前任务。taskEXIT_CRITICAL()
:退出临界区。启用调度器,允许其他任务抢占当前任务。
示例用法:
taskENTER_CRITICAL();
// 此处编写临界区代码
taskEXIT_CRITICAL();
- 中断服务例程(ISR)中的临界区:在中断服务例程中,通过使用中断服务例程临界区宏来保护共享资源的访问。中断服务例程的临界区宏与任务间的临界区宏类似,具有类似的功能和用法。常用的中断服务例程临界区宏包括:
portENTER_CRITICAL()
:进入中断服务例程的临界区。禁用任务调度器和其他中断,保证临界区代码的原子性。portEXIT_CRITICAL()
:退出中断服务例程的临界区。恢复任务调度器和其他中断的正常运行。
示例用法:
void ISR_Handler()
{
portENTER_CRITICAL();
// 临界区代码
portEXIT_CRITICAL();
}
使用临界区可以确保在访问共享资源时的原子性和可靠性,避免竞态条件和数据不一致的问题。但需要注意,临界区的使用应当尽量保持简短,避免在临界区中执行复杂或耗时的操作,以减少系统的响应时间和提高并发性能。同时,要合理地选择临界区的粒度和范围,以平衡保护共享资源的需要和系统的实时性要求。
以下是一些情况下可能需要考虑在任务创建时加入临界区逻辑的情况:
- 任务创建期间需要访问和操作共享资源:如果在任务创建过程中需要访问共享资源,可以在任务创建之前进入临界区,以防止其他任务或中断干扰共享资源的正确初始化。
- 多任务环境下的任务创建同步:在多任务系统中,如果多个任务的创建具有依赖关系,需要确保任务按照特定的顺序创建,可以使用临界区来同步任务的创建过程,以保证任务创建的顺序和依赖关系。
- 避免竞争条件:如果在任务创建过程中存在可能引发竞争条件的情况,可以使用临界区来避免竞争条件的发生。例如,多个任务创建函数调用之间共享的全局变量可能会导致竞争条件,此时可以使用临界区来保护变量的访问。
需要注意的是,临界区的使用应该谨慎,并根据具体情况进行评估。过多地使用临界区可能导致系统响应性下降和任务调度效率降低。只在必要的情况下使用临界区,以确保正确的资源访问和任务创建顺序。
总结起来,不是每次创建任务都需要加入临界区逻辑,而是根据具体的应用需求和场景来决定是否需要在任务创建过程中使用临界区。
内存管理
内存管理算法,其实就是几个文件,heap_1.cheap_2.cheap_3.cheap_4.cheap_5.c。我们默认选择的是heap_4.c。下表是他们的对比:
算法 | 特点 | 应用场景 |
heap_1 | 最简单的内存管理方案,使用静态全局数组作为堆空间,内存块以字节为单位进行分配和释放,没有对齐要求。 | 适用于资源受限的嵌入式系统,特别是具有严格内存限制的应用。由于它的实现简单且占用的内存较少,适用于具有严格资源限制的小型设备。 |
heap_2 | 类似于heap_1,但在分配内存块时会对齐到4字节边界 | 适用于资源受限的系统,需要字节对齐的内存分配。适用于大多数嵌入式系统,特别是那些需要对齐访问的硬件设备。 |
heap_3 | 使用静态全局数组作为堆空间,使用位图来跟踪内存块的分配状态,支持字节对齐的内存块分配。 | 适用于资源受限的系统,需要字节对齐的内存分配。与heap_2相比,heap_3提供了更高级的内存分配功能,可以更有效地管理和利用堆空间。 |
heap_4 | 使用动态分配的内存作为堆空间,通过标准的malloc()和free()函数进行内存分配和释放。 | 适用于具有较大内存需求的应用,支持动态分配和释放内存。适用于大型嵌入式系统或应用,具有较高的灵活性和内存管理需求。 |
heap_5 | 使用自定义的内存分配器接口,允许用户根据需求自定义内存分配器的实现。 | 适用于需要高度定制的内存管理方案的应用。通过自定义内存分配器接口,用户可以根据具体的需求实现自己的内存管理策略,例如使用特定的内存分配算法或集成外部内存管理器。 |
选择合适的内存管理算法取决于应用的需求和系统的资源限制。如果系统资源非常有限,可以选择heap_1或heap_2。如果需要字节对齐的内存分配,可以选择heap_2或heap_3。如果需要动态分配和释放内存,可以选择heap_4。如果需要更高度定制的内存管理方案,可以选择heap_5,并根据具体需求自定义内存分配器的实现。
需要注意的是,选择适当的内存管理算法时应考虑以下因素:
●系统资源限制:根据系统的内存大小和可用性选择合适的算法。如果系统资源非常有限,则应选择较小的算法。
●内存对齐需求:如果应用需要进行字节对齐的内存分配,选择支持对齐的算法。
●动态内存需求:如果应用需要动态分配和释放内存,选择支持动态内存管理的算法。
●灵活性和定制性需求:如果应用需要高度定制的内存管理方案,可以选择提供自定义接口的算法。
根据应用的具体需求和系统的资源限制,选择合适的内存管理算法可以提高内存的利用率和系统的性能。