嵌入式:FreeRTOS
一.基础知识
1.操作系统的分类:
分时操作系统:
时间片轮转,每个任务分配固定的时间去执行,没有优先级概念;
实时操作系统:
永远执行优先级最高的任务,任务优先级相同时,按照分时(时间片)进行操作;
2.操作系统的作用:
管理底层硬件,为上层应用提供接口;
3.什么是相对延时,绝对延时?
相对延时看“多久以后”(时间段),绝对延时看“什么时候到”(固定时间)。
4.FreeRTOS是如何进行裁剪的?
FreeRTOSConfig.h是FreeRTOS的工程配置文件,可通过修改配置(相应宏的打开和关闭)来 裁剪FreeRTOS的功能;
5.进程,线程和任务的联系和区别:
进程包含线程;
在RTOS中,任务等价于线程,例如创建一个任务等价于创建一个线程
包含关系:一个进程可以包含多个线程。
执行关系:操作系统调度器调度的是线程,而不是进程。进程只是为这些线程提供了一个“容器”和资源。
6.处理器具体指的是?
处理器(Processor)指的是硬件,通常特指 CPU(中央处理器,Central Processing Unit) ,它是计算机的“大脑”。
7.单核与多核的联系和区别:
多核指的不是多个CPU,而是一个CPU(运算器+控制器+寄存器)中放了多个计算核心;
单核:1 套控制器,多个运算器
多核:多套独立的控制器 + 各自的运算器
8.什么是任务同步?
任务同步的本质是协调多个任务的执行节奏,确保在共享资源访问或逻辑时序上不发生冲突。它包括顺序同步(A完B做,依赖前驱)、互斥同步(排队上锁,保护资源)和事件同步(多条件唤醒)。在FreeRTOS中,分别由二值信号量、互斥量和事件组来实现。
二.如何学习freertos?
官网链接:
https://www.freertos.org/zh-cn-cmn-s/Documentation/01-FreeRTOS-quick-start/01-Beginners-guide/00-Overview
三.FreeRTOS基础
1.什么是FreeRTOS?
FreeRTOS 是一个轻量级的 ,可裁剪的,免费的 实时操作系统内核;
2.任务创建
2.1 任务创建分为 :静态创建,动态创建(较多使用)
2.2 静态创建 和 动态创建 的定义和区别:
动态创建 (xTaskCreate) —— “租房”模式:任务所需的内存(任务控制块TCB和栈)是在程序运行时,由系统自动从“FreeRTOS堆”里申请来的。代码更简洁,你不用操心内存细节,系统会帮你打理一切。但系统如果申请不到内存,任务就会创建失败。
// 任务函数原型
void vTaskFunction(void *pvParameters);
// 创建任务
TaskHandle_t xTaskHandle = NULL;
BaseType_t xReturned;
xReturned = xTaskCreate(
vTaskFunction, // 1. 任务函数名
"TaskName", // 2. 任务描述名(调试用)
1024, // 3. 栈大小(单位:字,不是字节!)
(void*)¶m, // 4. 传给任务的参数
1, // 5. 任务优先级(数字越大优先级越高)
&xTaskHandle // 6. 任务句柄(用于后续操作)
);
if (xReturned == pdPASS) {
// 任务创建成功
}
静态创建 (xTaskCreateStatic) —— “住自己家”模式:任务所需的内存是在编译时由你提前准备好的(比如定义好的全局数组)。程序运行后,系统直接使用你提供的空间,完全不会进行动态内存分配。代码会稍显复杂,因为你需要自己定义这些内存空间,但内存的分配时机和位置完全由你掌控,更加“硬核”可靠。
// 1. 提前分配好静态内存
StackType_t uxTaskStack[ 1024 ]; // 任务栈
StaticTask_t xTaskTCB; // 任务控制块(TCB)
// 2. 创建任务
TaskHandle_t xTaskHandle = NULL;
xTaskHandle = xTaskCreateStatic(
vTaskFunction, // 任务函数
"StaticTask", // 任务名
1024, // 栈大小
NULL, // 参数
1, // 优先级
uxTaskStack, // 指向预先分配的栈
&xTaskTCB // 指向预先分配的TCB
);
if (xTaskHandle != NULL) {
// 任务创建成功
}
注意:如果启用了静态创建 (configSUPPORT_STATIC_ALLOCATION = 1),系统内部必需的空闲任务和定时器任务所需的内存也需要由你提供。你需要实vApplicationGetIdleTaskMemory()和vApplicationGetTimerTaskMemory()这两个回调函数,为它们预先分配好内存空间。这点在切换静态创建模式时很容易被忽略。
3.任务创建的标准流程
// 1. 声明任务函数
void vTask1(void *pvParameters);
void vTask2(void *pvParameters);
// 2. 定义句柄(可选)
TaskHandle_t xTask1Handle = NULL;
void main(void) {
// 3. 创建任务
xTaskCreate(vTask1, "Task1", 128, NULL, 1, &xTask1Handle);
xTaskCreate(vTask2, "Task2", 128, NULL, 2, NULL); // 不关心句柄
// 4. 启动调度器(任务开始执行)
vTaskStartScheduler();
// 理论上不会执行到这里
for( ;; );
}
// 5. 实现任务函数(无限循环)
void vTask1(void *pvParameters) {
for( ;; ) {
// 任务主体
vTaskDelay(pdMS_TO_TICKS(1000)); // 必须主动让出CPU!
}
}
4.任务删除
// 删除自身(推荐)
vTaskDelete(NULL);
// 删除其他任务
if (xTaskHandle != NULL) {
vTaskDelete(xTaskHandle);
xTaskHandle = NULL; // 习惯性置空
}
5.任务之间四种运行状态
就绪态:任务创建后就处于就绪态,高优先级的排在前面;
运行态:任务运行
阻塞态:阻塞态的任务在等待事件(时间、信号量、队列消息等),事件发生后进入就绪态。阻塞态有超时机制,超时后自动进入就绪态
挂起态:挂起态没有超时机制,必须主动调用 vTaskResume() 才能恢复

注意:
FreeRTOS优先级数字越大,优先级越高;(范围:0 ~ configMAX_PRIORITIES - 1,configMAX_PRIORITIES 是宏定义,一般默认是32;0最低,空闲任务固定使用0);
中断优先级是数值越小越高;
6.临界区
如果有一部分代码不希望执行中被打断(高优先级打断),就放入临界区;中断是否可以打断临界区,取决于配置文件中宏的配置;
临界区并非屏蔽所有中断。通过 configMAX_SYSCALL_INTERRUPT_PRIORITY 配置,优先级高于此值的中断仍然可以响应。也就是说,临界区只屏蔽低于或等于该优先级的中断。
相关函数:
taskENTER_CRITICAL();
taskEXIT_CRITICAL();
注意:
进入临界区和退出临界区要成对使用
关键代码使用,不要大片使用
7.任务调度原理
任务调度就是查询就绪列表中优先级最高的任务去执行,如果没有就绪任务,就执行空闲任务;
8.任务间的通信和同步方式
8.1信号量
信号量:是一个非负整数,所有获取他的任务都会将该整数减一,当该整数为0时,所有试图获取他的任务都将处于阻塞状态;
8.1.1 二值信号量
常用于任务同步(如中断通知任务)。
SemaphoreHandle_t xBinarySemaphore;
// 创建
xBinarySemaphore = xSemaphoreCreateBinary();
// 中断中释放(同步源)
void vISR_Handler(void) {
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
xSemaphoreGiveFromISR(xBinarySemaphore, &xHigherPriorityTaskWoken);
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
// 任务中获取(等待同步)
void vTask(void *pvParameters) {
for( ;; ) {
if (xSemaphoreTake(xBinarySemaphore, portMAX_DELAY) == pdTRUE) {
// 收到信号,执行相应操作
}
}
}
8.1.2 计数信号量
管理有限资源的数量。
SemaphoreHandle_t xCountingSemaphore;
// 创建:最大计数值为5,初始为5(5个可用资源)
xCountingSemaphore = xSemaphoreCreateCounting(5, 5);
// 使用资源前获取
if (xSemaphoreTake(xCountingSemaphore, portMAX_DELAY) == pdTRUE) {
// 成功获取一个资源
}
// 使用完后归还
xSemaphoreGive(xCountingSemaphore);
8.1.3 互斥信号量
自带优先级继承机制,防止优先级反转。
互斥信号量具有所有权概念——只有获取(Take)了互斥量的任务才能释放(Give)它。而普通二值信号量没有所有权限制,任何任务或ISR都可以释放。
SemaphoreHandle_t xMutex;
xMutex = xSemaphoreCreateMutex();
// 访问共享资源
xSemaphoreTake(xMutex, portMAX_DELAY);
// 修改共享数据
g_SharedData = 100;
xSemaphoreGive(xMutex);
8.2消息队列
FIFO方式传递数据,支持多任务收发。
QueueHandle_t xQueue;
// 创建队列:最多存5个整数
xQueue = xQueueCreate(5, sizeof(int));
int data = 10;
// 发送(阻塞100ms)
xQueueSend(xQueue, &data, pdMS_TO_TICKS(100));
int received;
// 接收(阻塞直到收到)
xQueueReceive(xQueue, &received, portMAX_DELAY);
8.3事件组
等待多个事件的条件组合(与/或逻辑)。
EventGroupHandle_t xEventGroup;
#define BIT_1 (1 << 0)
#define BIT_2 (1 << 1)
xEventGroup = xEventGroupCreate();
// 任务A设置事件位
xEventGroupSetBits(xEventGroup, BIT_1);
// 任务B等待BIT_1或BIT_2任一发生
EventBits_t bits = xEventGroupWaitBits(
xEventGroup,
BIT_1 | BIT_2, // 等待这些位
pdTRUE, // 退出时清除
pdFALSE, // 任一满足即可(或逻辑)
portMAX_DELAY
);
9.软件定时器
TimerHandle_t xTimer;
// 定时器回调
void vTimerCallback(TimerHandle_t xTimer) {
// 定时到,执行动作
}
// 创建自动重载定时器,周期1000ms
xTimer = xTimerCreate("Timer", pdMS_TO_TICKS(1000), pdTRUE, NULL, vTimerCallback);
// 启动定时器
xTimerStart(xTimer, portMAX_DELAY);
注意:软件定时器回调运行在定时器服务任务的上下文中,不应执行阻塞操作。
10.相对延时和绝对延时
相对延时看“多久以后”,绝对延时看“什么时候到”
void vTaskDelayUntil_Example(void *pvParameters) {
TickType_t xLastWakeTime = xTaskGetTickCount();
const TickType_t xFrequency = pdMS_TO_TICKS(1000);
for( ;; ) {
// 绝对延时:每次都在固定的时间点唤醒(无累积误差)
vTaskDelayUntil(&xLastWakeTime, xFrequency);
// 执行任务
}
}
void vTaskDelay_Example(void *pvParameters) {
for( ;; ) {
// 相对延时:从调用处开始延时1000ms(可能有累积误差)
vTaskDelay(pdMS_TO_TICKS(1000));
// 执行任务
}
}
区别:vTaskDelayUntil 适合周期执行,vTaskDelay 适合单次等待。
11.五种内存分配方案 (Heap 1-5)
常用heap 4;支持碎片合并,可分配对齐内存;
12.钩子函数
12.1钩子函数的含义
它是一种回调机制。FreeRTOS内核在内部预先定义了这些函数的弱符号(Weak Symbol)声明。如果你在代码中实现了同名函数,链接器就会用你的实现覆盖掉默认的空实现;如果你没实现,内核就直接跳过,不影响运行。
12.2 用来干什么的?(核心用途)
钩子函数主要用于在不修改FreeRTOS内核源码的前提下,向内核注入特定的用户行为,常见用途包括:
低功耗管理:在空闲任务中进入休眠模式(省电)。
错误捕获与调试:捕获栈溢出或内存分配失败的瞬间,保存现场。
性能监控/打点:在Tick中断中记录CPU的运行时间戳。
执行后台低优先级任务:比如将一些不重要的数据校验放在空闲钩子中处理。
12.3 怎么触发?(触发时机)
触发完全由内核自动发起,开发者不需要也不应该手动调用。不同钩子的触发环境(任务上下文 或 中断上下文)完全不同,这是使用时最大的陷阱。
13.空闲任务
空闲任务:启动任务调度器的时候,自动创建,优先级是最低的0,没有其他任务处于就绪态时候,就执行空闲任务。
主要职责:回收被删除任务的内存资源(TCB和栈空间)
如果使用了 vTaskDelete(),必须保证空闲任务有足够的CPU时间来执行清理工作
可配置 configUSE_IDLE_HOOK 启用空闲钩子
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐

所有评论(0)