From d9997e4c1b2d68834fc81faee3cc45e9bba0d2e0 Mon Sep 17 00:00:00 2001 From: Natsume1710 <162396185+Natsume1710@users.noreply.github.com> Date: Tue, 17 Jun 2025 13:49:52 +0800 Subject: [PATCH] Update README.md --- 04-实时操作系统/README.md | 398 ++++++++++++++++++++++++++++++++++---- 1 file changed, 364 insertions(+), 34 deletions(-) diff --git a/04-实时操作系统/README.md b/04-实时操作系统/README.md index df13ba0..10e5d7c 100644 --- a/04-实时操作系统/README.md +++ b/04-实时操作系统/README.md @@ -26,44 +26,101 @@ - CMSIS-RTOS(ARM 标准接口) - Zephyr(Linux 基金会支持,适合物联网) +**RTOS vs 裸机系统** +| 特性 | 裸机系统 | RTOS(实时操作系统) | +|--------------|-----------------------------|---------------------------------| +| 任务管理 | 单任务 / 前后台系统 | 多任务并发,支持任务优先级 | +| 资源分配 | 手动管理 | 自动调度和资源管理 | +| 实时响应 | 依赖主循环结构 | 确定性调度,响应更稳定 | +| 开发难度 | 低(适合简单系统) | 高(需理解调度机制、堆栈管理) | + +**主流 RTOS 对比** +| RTOS | 开源 | 应用领域 | 特点 | +|------------|----------|--------------------------|----------------------------------------------| +| FreeRTOS | ✅ | 工业控制、消费电子 | 轻量级、广泛支持、文档完善 | +| RT-Thread | ✅ | 物联网、智能家居 | 国产、组件丰富(如文件系统、GUI) | +| μC/OS | ⚠️ 商用需授权 | 航空航天、医疗设备 | 支持安全认证(如 DO-178C)、稳定可靠 | +| VxWorks | ❌ | 国防、通信、航天 | 商业闭源、高可靠性、实时性能强 | + + --- ## 🔹 任务管理 -### 创建任务 +### 任务创建与内存布局 ```c -xTaskCreate(TaskFunction_t pxTaskCode, - const char * const pcName, - configSTACK_DEPTH_TYPE usStackDepth, - void *pvParameters, - UBaseType_t uxPriority, - TaskHandle_t *pxCreatedTask); +// 创建任务示例 +void vTaskFunction(void *pvParameters) { + for (;;) { + // 任务代码 + vTaskDelay(pdMS_TO_TICKS(100)); // 释放CPU + } +} + +// 任务创建 +xTaskCreate(vTaskFunction, "Task1", 256, NULL, 2, NULL); +``` +- 栈空间分配: + - 每个任务独立栈空间,需避免溢出(通过configCHECK_FOR_STACK_OVERFLOW检测)。 + - 计算方法:任务局部变量大小 + 函数调用深度 × 最大寄存器保存数。 + +### 任务状态转换 +```plaintext + 调度器选择 超时/事件发生 +就绪 ───────────→ 运行 ←─────────── 阻塞 + ↑ │ │ + │ └─── 调用vTaskDelay │ + │ │ + └─────── 调用vTaskSuspend ┘ + 或挂起API ``` -### 任务状态 -- Ready:就绪状态 -- Running:运行中 -- Blocked:等待事件/超时 -- Suspended:挂起 -- Deleted:已删除 ### 任务优先级与调度算法 -- 抢占式优先调度(Preemptive) -- 时间片轮转调度(Time Slicing) +- 抢占式调度: + - 基于任务优先级,高优先级任务可立即抢占当前运行任务。 + - 实现:FreeRTOS 通过pxCurrentTCB指针指向当前任务控制块(TCB)。 + +- 时间片轮转: + - 同优先级任务按时间片轮流执行(由configTICK_RATE_HZ决定)。 + - 示例:两个优先级相同的任务各执行 10ms。 --- ## 🔹 时间管理 -### 延时与定时 +### 任务延时实现 ```c -vTaskDelay(pdMS_TO_TICKS(1000)); // 延时 1 秒 +// 相对延时(从调用开始计算) +vTaskDelay(pdMS_TO_TICKS(100)); + +// 绝对延时(固定周期执行) +TickType_t xLastWakeTime = xTaskGetTickCount(); +const TickType_t xFrequency = pdMS_TO_TICKS(100); +for (;;) { + vTaskDelayUntil(&xLastWakeTime, xFrequency); + // 周期性任务代码 +} ``` ### 软件定时器 +- 单次触发:执行一次后停止。 +- 周期触发:按固定周期重复执行。 ```c -xTimerCreate(); -xTimerStart(); +// 创建并启动定时器 +TimerHandle_t xTimer = xTimerCreate( + "Timer", // 定时器名称 + pdMS_TO_TICKS(1000), // 周期1秒 + pdTRUE, // 周期模式 + (void *)0, // 定时器ID + vTimerCallback // 回调函数 +); +xTimerStart(xTimer, 0); + +// 定时器回调函数 +void vTimerCallback(TimerHandle_t xTimer) { + // 定时任务代码 +} ``` --- @@ -71,36 +128,232 @@ xTimerStart(); ## 🔹 线程间通信 ### 队列(Queue) -- 用于任务间数据传递(FIFO 结构) +- 特性: + - 线程安全的 FIFO 缓冲区,支持阻塞读写。 + - 最大长度和消息大小在创建时指定。 + ```c -xQueueCreate(); -xQueueSend(); -xQueueReceive(); +// 创建队列 +QueueHandle_t xQueue = xQueueCreate(5, sizeof(int)); // 5个int元素 + +// 发送消息(阻塞100ms) +int value = 100; +xQueueSend(xQueue, &value, pdMS_TO_TICKS(100)); + +// 接收消息(永久等待) +int received_value; +xQueueReceive(xQueue, &received_value, portMAX_DELAY); ``` ### 信号量(Semaphore) -- 二值信号量(同步) -- 计数信号量(共享资源数量) -- 互斥信号量(用于资源保护) +#### 二值信号量: +- 用于任务同步(如中断与任务通信)。 +```c +// 创建二值信号量 +SemaphoreHandle_t xSemaphore = xSemaphoreCreateBinary(); + +// 任务中获取信号量 +if (xSemaphoreTake(xSemaphore, portMAX_DELAY) == pdTRUE) { + // 获得信号量,执行临界区代码 +} + +// 中断中释放信号量 +BaseType_t xHigherPriorityTaskWoken = pdFALSE; +xSemaphoreGiveFromISR(xSemaphore, &xHigherPriorityTaskWoken); +portYIELD_FROM_ISR(xHigherPriorityTaskWoken); +``` + +#### 计数信号量(共享资源数量) +- 核心概念 + - 资源计数器:初始值为可用资源数量,用于控制对有限资源的访问。 + - 操作规则: + - xSemaphoreTake():获取信号量时计数器减 1,若计数器为 0 则阻塞。 + - xSemaphoreGive():释放信号量时计数器加 1,唤醒等待任务。 +- 典型应用场景 + - 多资源管理:如打印机池(假设有 3 台打印机) +```c +// 创建计数信号量(初始值=3,最大值=3) +SemaphoreHandle_t xPrinterSemaphore = xSemaphoreCreateCounting(3, 3); + +// 任务中请求打印机 +if (xSemaphoreTake(xPrinterSemaphore, portMAX_DELAY) == pdTRUE) { + // 获得打印机,执行打印任务 + vPrintTask(); + // 释放打印机 + xSemaphoreGive(xPrinterSemaphore); +} +``` + - 生产者——消费者缓冲区:用信号量跟踪缓冲区空 / 满状态。 +#### 互斥信号量(用于资源保护) +- 核心特性 + - 二值信号量的特例:初始值为 1,表示资源可用。 + - 优先级继承:解决优先级反转问题(低优先级任务持有锁时临时提升其优先级)。 +- 优先级反转示例 +```c +// 创建互斥锁 +SemaphoreHandle_t xMutex = xSemaphoreCreateMutex(); + +// 高优先级任务H +void vTaskHigh(void *pvParameters) { + for (;;) { + xSemaphoreTake(xMutex, portMAX_DELAY); // 获取锁 + // 临界区代码 + xSemaphoreGive(xMutex); // 释放锁 + } +} + +// 低优先级任务L +void vTaskLow(void *pvParameters) { + for (;;) { + xSemaphoreTake(xMutex, portMAX_DELAY); // 获取锁 + // 执行长时间操作(此时被中优先级任务M抢占) + xSemaphoreGive(xMutex); // 释放锁 + } +} +``` +- 问题:任务 L 持有锁时被任务 M 抢占,导致任务 H 无法执行(优先级反转)。 +- 解决:启用优先级继承后,任务 L 持有锁时临时提升至任务 H 的优先级,避免被 M 抢占。 ### 消息队列(Message Queue) -- 用于传递结构化数据或消息结构体 +#### 与普通队列的区别 +- 结构化数据传递:支持传递复杂数据类型(如结构体)。 +- 指针传递优化:可传递数据指针而非数据本身,减少内存拷贝。 + +#### 使用示例 +```c +// 定义消息结构体 +typedef struct { + uint8_t command; + uint32_t data; + void (*callback)(void); +} Message_t; + +// 创建消息队列(最多5个消息) +QueueHandle_t xMessageQueue = xQueueCreate(5, sizeof(Message_t)); + +// 发送消息 +Message_t xMessage = { + .command = 0x01, + .data = 100, + .callback = vProcessCallback +}; +xQueueSend(xMessageQueue, &xMessage, portMAX_DELAY); + +// 接收消息 +Message_t xReceivedMessage; +if (xQueueReceive(xMessageQueue, &xReceivedMessage, portMAX_DELAY) == pdTRUE) { + // 处理消息 + vProcessMessage(&xReceivedMessage); +} +``` +#### 消息队列 vs 普通队列 +| 特性 | 普通队列 | 消息队列 | +|--------------|----------------------------------|--------------------------------------------| +| 数据类型 | 固定大小字节块 | 支持结构体、指针等复杂数据类型 | +| 适用场景 | 简单数据传输(如 ADC 值) | 复杂命令传递(如协议解析、任务通信) | +| 内存效率 | 每次传输都需拷贝数据 | 可传递指针,减少内存拷贝,效率更高 | + ### 事件组(Event Group) - 类似标志位,可用于多任务同步 +```c +// 创建事件组 +EventGroupHandle_t xEventGroup = xEventGroupCreate(); + +// 任务1:设置事件位0 +xEventGroupSetBits(xEventGroup, 0x01); + +// 任务2:等待事件位0和1都置位 +EventBits_t uxBits = xEventGroupWaitBits( + xEventGroup, // 事件组句柄 + 0x03, // 等待位0和1 + pdTRUE, // 等待后清除位 + pdTRUE, // 等待所有位 + portMAX_DELAY // 永久等待 +); +``` --- ## 🔹 资源管理 ### 内存管理方式 -- 静态分配(推荐) -- 动态分配(需要注意碎片与失败处理) +#### 静态分配(推荐) +```c +// 使用静态内存创建任务 +StaticTask_t xTaskBuffer; +StackType_t xStack[256]; + +xTaskCreateStatic( + vTaskFunction, // 任务函数 + "Task1", // 任务名称 + 256, // 栈大小 + NULL, // 参数 + 2, // 优先级 + xStack, // 静态栈 + &xTaskBuffer // 静态任务控制块 +); +``` +#### 动态分配(需要注意碎片与失败处理) +- 原因:频繁分配 / 释放不同大小的内存块,导致空闲内存分散。 +- 示例: +```c +// 可能导致碎片的错误模式 +void vTask(void *pvParameters) { + for (;;) { + char *pcBuffer = (char *)pvPortMalloc(100); + // 使用缓冲区... + vPortFree(pcBuffer); // 释放后可能产生碎片 + vTaskDelay(pdMS_TO_TICKS(10)); + } +} +``` + +#### 安全使用动态内存的原则 +- 预分配固定大小块: +```c +// 预先分配对象池 +static uint8_t xObjectPool[10][100]; // 10个100字节的对象 +static BaseType_t xObjectAvailable[10] = {1}; // 标记可用状态 + +uint8_t *pvGetObject(void) { + for (int i = 0; i < 10; i++) { + if (xObjectAvailable[i]) { + xObjectAvailable[i] = 0; + return &xObjectPool[i][0]; + } + } + return NULL; +} +``` +- 检查分配结果: +```c +void *pvBuffer = pvPortMalloc(100); +if (pvBuffer == NULL) { + // 内存分配失败处理 + vHandleMemoryError(); +} +``` + ### 临界区保护 +- 关中断: ```c -taskENTER_CRITICAL(); -taskEXIT_CRITICAL(); +void vCriticalFunction(void) { + taskENTER_CRITICAL(); + // 临界区代码(禁止中断) + taskEXIT_CRITICAL(); +} +``` +- 互斥锁: +```c +// 创建互斥锁 +SemaphoreHandle_t xMutex = xSemaphoreCreateMutex(); + +// 获取锁 +xSemaphoreTake(xMutex, portMAX_DELAY); +// 临界区代码 +xSemaphoreGive(xMutex); // 释放锁 ``` --- @@ -108,17 +361,94 @@ taskEXIT_CRITICAL(); ## 🔹 FreeRTOS 配置与移植 ### 配置项(FreeRTOSConfig.h) -- `configUSE_PREEMPTION`:是否使用抢占式调度 -- `configMAX_PRIORITIES`:最大任务优先级数 -- `configMINIMAL_STACK_SIZE`:最小栈空间大小 +| 参数 | 描述 | 示例值 | +|-------------------------------|-----------------------------------|--------------------| +| `configUSE_PREEMPTION` | 是否使用抢占式调度 | `1`(启用) | +| `configTICK_RATE_HZ` | 系统滴答频率(Hz) | `1000`(1ms) | +| `configMAX_PRIORITIES` | 最大任务优先级数 | `5 ~ 32` | +| `configMINIMAL_STACK_SIZE` | 最小任务栈大小(以字为单位) | `128`(STM32) | +| `configSUPPORT_DYNAMIC_ALLOCATION` | 是否支持动态内存分配 | `1`(支持) | + ### 移植步骤 1. 提供 SysTick 定时器实现 2. 提供上下文切换代码(汇编) 3. 编写启动任务入口函数 `vTaskStartScheduler()` +### 移植关键点 +- 上下文切换实现(汇编): +```assembly +; Cortex-M3/M4 上下文切换示例(PendSV处理函数) +PendSV_Handler: + CPSID I ; 关中断 + MRS R0, PSP ; 获取进程栈指针 + CBZ R0, PendSV_NoSave ; 首次调用直接切换 + + ; 保存寄存器到当前任务栈 + SUBS R0, R0, #0x20 ; 调整栈指针 + STM R0, {R4-R11} ; 保存R4-R11 + LDR R1, =pxCurrentTCB ; 获取当前任务指针 + LDR R1, [R1] ; 加载任务控制块地址 + STR R0, [R1] ; 保存新的栈指针 + +PendSV_NoSave: + LDR R0, =pxCurrentTCB ; 获取当前任务指针 + LDR R1, [R0] ; 加载当前任务控制块 + LDR R0, [R1, #4] ; 加载下一个任务控制块 + STR R0, [R0] ; 更新当前任务指针 + LDR R0, [R0] ; 加载新任务栈指针 + LDM R0, {R4-R11} ; 恢复寄存器 + MSR PSP, R0 ; 更新进程栈指针 + ORR LR, LR, #0x04 ; 设置返回标志 + CPSIE I ; 开中断 + BX LR ; 返回 +``` --- +## 🔹 RTOS 调试与性能分析 + +### 调试工具与技术 +- 任务状态查看: +```c +// 获取任务运行时信息 +void vTaskList(char *pcWriteBuffer); + +// 示例输出: +// TaskName State Priority Stack Num +// Task1 Running 2 128 1 +// Task2 Blocked 1 256 2 +``` + +### 性能指标分析 +- CPU 使用率: +```c +// 计算CPU使用率(需配置configGENERATE_RUN_TIME_STATS=1) +uint32_t ulHighFrequencyTimerTicks; +vTaskGetRunTimeStats(&ulHighFrequencyTimerTicks); +``` +- 任务堆栈深度: +```c +// 检查任务栈剩余空间 +UBaseType_t uxHighWaterMark = uxTaskGetStackHighWaterMark(NULL); +``` + +--- + +## 面试高频问题 + +#### RTOS 中任务与线程的区别: +- 任务是 RTOS 调度的基本单位,线程是操作系统调度的基本单位;RTOS 任务通常更轻量级。 + +#### 信号量与互斥锁的区别: +- 信号量可用于同步和资源计数,互斥锁专用于资源保护,支持优先级继承避免死锁。 + +#### 如何避免 RTOS 中的死锁: +- 按相同顺序获取锁,使用带超时的锁获取函数,避免嵌套锁。 + +#### FreeRTOS 任务优先级设置原则: +- 关键任务(如传感器采样)设高优先级,非关键任务(如显示更新)设低优先级。 + +--- ## 🔹 实践应用场景 - 多任务协同:传感器数据采集 + 通信模块处理