RTOS实时系统
RTOS基础概念
什么是实时操作系统
实时操作系统(RTOS)保证任务在确定时间内响应,分为两类:
- 硬实时: 必须在规定时间内完成(如安全气囊,飞控系统)
- 软实时: 尽量在规定时间内完成(如多媒体播放)
RTOS vs 裸机开发:
| 特性 | 裸机 | RTOS |
|---|---|---|
| 开发难度 | 低 | 中 |
| 任务管理 | 手动轮询 | 自动调度 |
| 响应时间 | 不确定 | 可预测 |
| 代码复用 | 低 | 高 |
| 内存占用 | 小(0KB) | 大(5-20KB) |
| 适用场景 | 简单控制 | 复杂系统 |
任务调度算法
抢占式调度(Preemptive): 高优先级任务可随时打断低优先级任务,适合实时性要求高的场景。
协作式调度(Cooperative): 任务主动让出CPU,简单但响应慢。
// FreeRTOS任务状态转换
// Running -> Ready: 更高优先级任务就绪
// Running -> Blocked: 等待资源(延时/信号量)
// Running -> Suspended: 被挂起
// Blocked -> Ready: 资源可用/延时到期
FreeRTOS任务管理
任务创建
#include "FreeRTOS.h"
#include "task.h"
// 任务句柄
TaskHandle_t led_task_handle;
TaskHandle_t key_task_handle;
// LED任务
void LED_Task(void *pvParameters) {
while (1) {
LED_TOGGLE();
vTaskDelay(pdMS_TO_TICKS(500)); // 延时500ms
}
}
// 按键任务
void Key_Task(void *pvParameters) {
while (1) {
if (key_scan()) {
printf("Key pressed!\n");
}
vTaskDelay(pdMS_TO_TICKS(10)); // 10ms轮询
}
}
int main(void) {
// 创建任务
xTaskCreate(
LED_Task, // 任务函数
"LED", // 任务名称
128, // 栈大小(字)
NULL, // 参数
2, // 优先级
&led_task_handle // 任务句柄
);
xTaskCreate(Key_Task, "KEY", 128, NULL, 3, &key_task_handle);
// 启动调度器
vTaskStartScheduler();
// 正常不会执行到这里
while (1);
}
任务优先级分配原则
// FreeRTOS优先级: 0(最低) - configMAX_PRIORITIES-1(最高)
#define PRIORITY_IDLE 0 // 空闲任务(系统自动创建)
#define PRIORITY_LOW 1 // 后台任务(日志记录)
#define PRIORITY_NORMAL 2 // 普通任务(LED显示)
#define PRIORITY_HIGH 3 // 重要任务(数据处理)
#define PRIORITY_REALTIME 4 // 实时任务(中断后处理)
// 原则:
// 1. 实时性要求高的任务优先级高
// 2. 执行时间短的任务优先级可适当提高
// 3. 避免优先级反转(使用互斥锁)
任务控制函数
// 挂起任务
void suspend_led(void) {
vTaskSuspend(led_task_handle); // LED停止闪烁
}
// 恢复任务
void resume_led(void) {
vTaskResume(led_task_handle);
}
// 删除任务
void delete_task(void) {
vTaskDelete(key_task_handle); // 删除按键任务
key_task_handle = NULL;
}
// 任务通知(轻量级信号量)
void notify_task(void) {
xTaskNotifyGive(led_task_handle); // 通知LED任务
}
void LED_Task(void *pvParameters) {
while (1) {
ulTaskNotifyTake(pdTRUE, portMAX_DELAY); // 等待通知
LED_TOGGLE();
}
}
信号量(Semaphore)
二值信号量
用于任务同步,类似标志位:
SemaphoreHandle_t xBinarySem;
void setup(void) {
// 创建二值信号量
xBinarySem = xSemaphoreCreateBinary();
}
// 中断中释放信号量
void UART_IRQHandler(void) {
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
if (USART1->SR & USART_SR_RXNE) {
rx_buffer[rx_index++] = USART1->DR;
if (rx_index >= RX_SIZE) {
xSemaphoreGiveFromISR(xBinarySem, &xHigherPriorityTaskWoken);
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
}
}
// 任务中获取信号量
void UART_Task(void *pvParameters) {
while (1) {
if (xSemaphoreTake(xBinarySem, portMAX_DELAY) == pdTRUE) {
process_data(rx_buffer, rx_index);
rx_index = 0;
}
}
}
计数信号量
用于资源计数:
#define MAX_BUFFERS 5
SemaphoreHandle_t xCountingSem;
void setup(void) {
// 创建计数信号量,最大值5,初始值5
xCountingSem = xSemaphoreCreateCounting(MAX_BUFFERS, MAX_BUFFERS);
}
// 生产者任务
void Producer_Task(void *pvParameters) {
while (1) {
uint8_t *buffer = malloc(256);
// 生产数据
produce_data(buffer);
// 放入队列
xQueueSend(data_queue, &buffer, portMAX_DELAY);
xSemaphoreGive(xCountingSem); // 信号量+1
vTaskDelay(pdMS_TO_TICKS(100));
}
}
// 消费者任务
void Consumer_Task(void *pvParameters) {
while (1) {
xSemaphoreTake(xCountingSem, portMAX_DELAY); // 信号量-1
uint8_t *buffer;
xQueueReceive(data_queue, &buffer, portMAX_DELAY);
consume_data(buffer);
free(buffer);
}
}
互斥锁(Mutex)
防止多任务同时访问共享资源,支持优先级继承:
SemaphoreHandle_t xMutex;
void setup(void) {
xMutex = xSemaphoreCreateMutex();
}
// 安全访问共享资源
void access_shared_resource(void) {
if (xSemaphoreTake(xMutex, pdMS_TO_TICKS(100)) == pdTRUE) {
// 临界区代码
shared_counter++;
printf("Counter: %d\n", shared_counter);
xSemaphoreGive(xMutex); // 释放互斥锁
} else {
printf("Failed to acquire mutex\n");
}
}
死锁预防
// 错误示例:循环等待导致死锁
void Task_A(void) {
xSemaphoreTake(mutex_1, portMAX_DELAY);
vTaskDelay(10);
xSemaphoreTake(mutex_2, portMAX_DELAY); // 等待Task_B释放
// ...
xSemaphoreGive(mutex_2);
xSemaphoreGive(mutex_1);
}
void Task_B(void) {
xSemaphoreTake(mutex_2, portMAX_DELAY);
vTaskDelay(10);
xSemaphoreTake(mutex_1, portMAX_DELAY); // 等待Task_A释放
// ...
xSemaphoreGive(mutex_1);
xSemaphoreGive(mutex_2);
}
// 解决方案1:统一获取顺序
void Task_A_Fixed(void) {
xSemaphoreTake(mutex_1, portMAX_DELAY);
xSemaphoreTake(mutex_2, portMAX_DELAY);
// ...
xSemaphoreGive(mutex_2);
xSemaphoreGive(mutex_1);
}
void Task_B_Fixed(void) {
xSemaphoreTake(mutex_1, portMAX_DELAY); // 同样先获取mutex_1
xSemaphoreTake(mutex_2, portMAX_DELAY);
// ...
xSemaphoreGive(mutex_2);
xSemaphoreGive(mutex_1);
}
// 解决方案2:超时机制
void Task_C(void) {
if (xSemaphoreTake(mutex_1, pdMS_TO_TICKS(100)) == pdTRUE) {
if (xSemaphoreTake(mutex_2, pdMS_TO_TICKS(100)) == pdTRUE) {
// 成功获取两个锁
xSemaphoreGive(mutex_2);
}
xSemaphoreGive(mutex_1);
}
}
消息队列(Queue)
队列创建和使用
QueueHandle_t xQueue;
typedef struct {
uint8_t sensor_id;
float temperature;
float humidity;
} SensorData_t;
void setup(void) {
// 创建队列:10个元素,每个元素大小为SensorData_t
xQueue = xQueueCreate(10, sizeof(SensorData_t));
}
// 发送任务
void Sensor_Task(void *pvParameters) {
SensorData_t data;
while (1) {
data.sensor_id = 1;
data.temperature = read_temperature();
data.humidity = read_humidity();
// 发送到队列(阻塞100ms)
if (xQueueSend(xQueue, &data, pdMS_TO_TICKS(100)) != pdTRUE) {
printf("Queue full!\n");
}
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
// 接收任务
void Display_Task(void *pvParameters) {
SensorData_t data;
while (1) {
// 从队列接收(永久阻塞)
if (xQueueReceive(xQueue, &data, portMAX_DELAY) == pdTRUE) {
printf("Sensor %d: %.1f°C, %.1f%%\n",
data.sensor_id, data.temperature, data.humidity);
}
}
}
// 查询队列状态
void check_queue(void) {
UBaseType_t items = uxQueueMessagesWaiting(xQueue);
UBaseType_t spaces = uxQueueSpacesAvailable(xQueue);
printf("Queue: %d items, %d spaces\n", items, spaces);
}
队列在中断中使用
void ADC_IRQHandler(void) {
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
uint16_t adc_value = ADC1->DR;
// 中断中使用FromISR版本
xQueueSendFromISR(xQueue, &adc_value, &xHigherPriorityTaskWoken);
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
事件标志组(Event Group)
用于多个事件的同步:
EventGroupHandle_t xEventGroup;
#define EVENT_WIFI_CONNECTED (1 << 0)
#define EVENT_DATA_READY (1 << 1)
#define EVENT_SEND_COMPLETE (1 << 2)
void setup(void) {
xEventGroup = xEventGroupCreate();
}
// WiFi任务设置事件
void WiFi_Task(void *pvParameters) {
while (1) {
if (wifi_connect()) {
xEventGroupSetBits(xEventGroup, EVENT_WIFI_CONNECTED);
}
vTaskDelay(pdMS_TO_TICKS(5000));
}
}
// 数据发送任务等待事件
void Send_Task(void *pvParameters) {
while (1) {
// 等待WiFi连接和数据就绪
EventBits_t bits = xEventGroupWaitBits(
xEventGroup,
EVENT_WIFI_CONNECTED | EVENT_DATA_READY, // 等待的位
pdTRUE, // 返回后清除位
pdTRUE, // 等待所有位都设置
portMAX_DELAY
);
if (bits == (EVENT_WIFI_CONNECTED | EVENT_DATA_READY)) {
send_data();
xEventGroupSetBits(xEventGroup, EVENT_SEND_COMPLETE);
}
}
}
内存管理
FreeRTOS提供5种内存管理方案:
| 方案 | 特点 | 使用场景 |
|---|---|---|
| heap_1 | 只分配不释放 | 任务创建后不删除 |
| heap_2 | 允许释放,不合并碎片 | 已弃用 |
| heap_3 | 封装malloc/free | 需要线程安全的malloc |
| heap_4 | 合并相邻空闲块 | 通用(推荐) |
| heap_5 | 支持多内存区域 | 内部RAM+外部RAM |
// FreeRTOSConfig.h配置
#define configTOTAL_HEAP_SIZE (20 * 1024) // 20KB堆
// 动态分配内存
void *pvPortMalloc(size_t xWantedSize);
void vPortFree(void *pv);
// 查询剩余堆空间
size_t xPortGetFreeHeapSize(void);
// 查询历史最小剩余空间
size_t xPortGetMinimumEverFreeHeapSize(void);
// 使用示例
void task_example(void) {
uint8_t *buffer = pvPortMalloc(256);
if (buffer != NULL) {
// 使用buffer
vPortFree(buffer);
} else {
printf("Memory allocation failed!\n");
}
printf("Free heap: %d bytes\n", xPortGetFreeHeapSize());
}
内存碎片处理
// 使用内存池减少碎片
#define POOL_SIZE 10
typedef struct {
uint8_t buffer[256];
bool in_use;
} BufferPool_t;
BufferPool_t buffer_pool[POOL_SIZE];
uint8_t* alloc_from_pool(void) {
for (int i = 0; i < POOL_SIZE; i++) {
if (!buffer_pool[i].in_use) {
buffer_pool[i].in_use = true;
return buffer_pool[i].buffer;
}
}
return NULL;
}
void free_to_pool(uint8_t *buffer) {
for (int i = 0; i < POOL_SIZE; i++) {
if (buffer_pool[i].buffer == buffer) {
buffer_pool[i].in_use = false;
break;
}
}
}
FreeRTOS配置
// FreeRTOSConfig.h关键配置
#define configUSE_PREEMPTION 1 // 抢占式调度
#define configUSE_IDLE_HOOK 0 // 空闲钩子
#define configUSE_TICK_HOOK 0 // 时钟钩子
#define configCPU_CLOCK_HZ 72000000
#define configTICK_RATE_HZ 1000 // 1ms一个tick
#define configMAX_PRIORITIES 5
#define configMINIMAL_STACK_SIZE 128
#define configTOTAL_HEAP_SIZE 20480
#define configMAX_TASK_NAME_LEN 16
#define configUSE_TRACE_FACILITY 1
#define configUSE_16_BIT_TICKS 0
#define configIDLE_SHOULD_YIELD 1
#define configUSE_MUTEXES 1
#define configUSE_COUNTING_SEMAPHORES 1
#define configUSE_QUEUE_SETS 0
#define configUSE_RECURSIVE_MUTEXES 1
// 中断优先级配置
#define configKERNEL_INTERRUPT_PRIORITY (15 << 4)
#define configMAX_SYSCALL_INTERRUPT_PRIORITY (5 << 4)
高频面试题
1. RTOS中任务切换的过程是什么?
答案:
任务切换(Context Switch)分为两步:
- 保存当前任务上下文: 将CPU寄存器(R0-R12, LR, PC, PSR)压栈
- 恢复新任务上下文: 从新任务的栈中弹出寄存器
// Cortex-M3任务切换(汇编)
__asm void PendSV_Handler(void) {
// 保存当前任务
MRS R0, PSP // 获取任务栈指针
STMDB R0!, {R4-R11} // 保存R4-R11
// 保存栈指针到TCB
LDR R1, =pxCurrentTCB
LDR R1, [R1]
STR R0, [R1]
// 切换到新任务
// ...
// 恢复新任务
LDR R0, [R1]
LDMIA R0!, {R4-R11} // 恢复R4-R11
MSR PSP, R0
BX LR // 返回,硬件自动恢复R0-R3,R12,LR,PC,PSR
}
2. 什么是优先级反转?如何解决?
答案:
优先级反转: 高优先级任务被低优先级任务阻塞。
场景:
- 低优先级任务L获取互斥锁
- 高优先级任务H等待该锁,被阻塞
- 中优先级任务M抢占L,导致H长时间等待
解决方案:
- 优先级继承: L获取锁后,临时提升到H的优先级
// FreeRTOS的Mutex自动实现优先级继承
SemaphoreHandle_t xMutex = xSemaphoreCreateMutex();
- 优先级天花板: 获取锁时提升到所有可能使用该锁的任务的最高优先级
3. 信号量和互斥锁的区别?
答案:
| 特性 | 信号量 | 互斥锁 |
|---|---|---|
| 用途 | 同步 | 互斥访问 |
| 所有权 | 无 | 有(只能由获取者释放) |
| 优先级继承 | 不支持 | 支持 |
| 递归获取 | 不支持 | 支持(递归互斥锁) |
| 初始值 | 可为任意值 | 1 |
// 信号量:任何任务都可以Give
xSemaphoreGive(sem);
// 互斥锁:必须由Take的任务Give
xSemaphoreTake(mutex, portMAX_DELAY);
// ...
xSemaphoreGive(mutex);
4. vTaskDelay和vTaskDelayUntil的区别?
答案:
// vTaskDelay:相对延时
void Task1(void) {
while (1) {
do_work(); // 假设耗时10ms
vTaskDelay(pdMS_TO_TICKS(100)); // 实际周期:110ms
}
}
// vTaskDelayUntil:绝对延时,保证固定周期
void Task2(void) {
TickType_t xLastWakeTime = xTaskGetTickCount();
while (1) {
do_work(); // 假设耗时10ms
vTaskDelayUntil(&xLastWakeTime, pdMS_TO_TICKS(100)); // 周期:100ms
}
}
vTaskDelayUntil用于需要固定执行周期的任务(如采样、控制算法)。
5. 如何检测任务栈溢出?
答案:
方法1:配置检测
// FreeRTOSConfig.h
#define configCHECK_FOR_STACK_OVERFLOW 2
// 实现钩子函数
void vApplicationStackOverflowHook(TaskHandle_t xTask, char *pcTaskName) {
printf("Stack overflow in task: %s\n", pcTaskName);
while (1); // 停止系统
}
方法2:查询高水位
// 查询任务栈的剩余空间
UBaseType_t uxHighWaterMark = uxTaskGetStackHighWaterMark(NULL);
printf("Stack free: %d words\n", uxHighWaterMark);
方法3:手动检查栈填充
// FreeRTOS会用0xA5填充栈,检查栈底是否仍为0xA5