news 2026/7/15 5:26:26

FreeRTOS操作系统

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
FreeRTOS操作系统

一、任务创建

创建任务可以分为动态创建、静态创建。核心区别就在于任务控制块(TCB)和堆栈的内存由谁分配

  • 动态创建xTaskCreate()内存由 FreeRTOS 在运行时自动从堆(heap)里分配。
  • 静态创建xTaskCreateStatic()内存由你自己预先定义好数组,FreeRTOS 直接用,不碰堆。
1、任务栈

每个任务独立拥有的一块内存(RAM),专门用来保存该任务运行时的"现场"和临时数据。

1、任务栈里有什么:

  1. 上下文(Context):发生任务切换时,CPU 的寄存器(R0–R12、LR、PC、xPSR 等)会被自动压入该任务的栈——这就是"现场保护"。
  2. 局部变量:函数里int a;这种自动变量存在栈上。
  3. 函数调用链:调用函数时的返回地址、传参。
  4. 中断嵌套时的现场(若在该任务运行时进中断)。

2、任务栈大小的创建

动态创建

// 第3个参数 128 = 栈深度(字数),M3上 = 128 * 4 = 512 字节 xTaskCreate(vTask, "Task1", 128, NULL, 1, NULL); vTaskStartScheduler();

静态创建

#define TASK1_STACK_SIZE 128 //定义栈的大小 static StackType_t Task1Stack[TASK1_STACK_SIZE]; // static StaticTask_t Task1TCB;

2任务函数

void vMyTask(void *pvParameters) { // 可选:任务启动前的初始化 for (;;) { // 任务要循环做的事 vTaskDelay(pdMS_TO_TICKS(100)); // 延时100ms,让出CPU } // 注意:for(;;) 永不返回,任务函数不能执行到这里 }

1、TCB

Task Control Block(任务控制块),是 FreeRTOS 用来管理每个任务的核心数据结构——相当于任务的"身份证 + 档案袋"。

TCB 里主要记了什么(FreeRTOS 源码tskTaskControlBlock结构体)

typedef struct tskTaskControlBlock { volatile StackType_t *pxTopOfStack; /* 栈顶 */ ListItem_t xStateListItem; /* 任务节点 */ StackType_t *pxStack; /* 任务栈起始地址 */ char pcTaskName[ configMAX_TASK_NAME_LEN ]; /* 任务名称,字符串形式 */ TickType_t xTicksToDelay; /* 用于延时 */ UBaseType_t uxPriority; }tskTCB; typedef tskTCB TCB_t;

2、创建任务函数

1、动态函数

BaseType_t xTaskCreate( TaskFunction_t pxTaskCode, // ① 任务函数名(入口地址) const char *pcName, // ② 任务名(调试用字符串) uint16_t usStackDepth, // ③ 栈深度(单位:字, 128=512字节@M3) void *pvParameters, // ④ 传给任务的参数(不用就NULL) UBaseType_t uxPriority, // ⑤ 优先级(数值越大越高) TaskHandle_t *pxCreatedTask); // ⑥ 句柄输出(可NULL,要删任务时必用) xTaskCreate(led_task, "led_task", 128, NULL, 1, &led_task_handle); // ①函数 ②名字 ③栈 ④参数 ⑤优先级 ⑥句柄地址

2、静态函数

TaskHandle_t xTaskCreateStatic( TaskFunction_t pxTaskCode, // ① 任务函数名 const char *pcName, // ② 任务名 uint32_t ulStackDepth, // ③ 栈深度(字) void *pvParameters, // ④ 参数 UBaseType_t uxPriority, // ⑤ 优先级 StackType_t *puxStackBuffer, // ⑥ 你提供的栈数组 TCB_t *pxTaskBuffer); // ⑦ 你提供的TCB xTaskCreateStatic(vMyTask, "T1", 128, NULL, 1, xStack, &xTCB); // ①函数 ②名 ③栈 ④参数 ⑤优先级 ⑥栈数组 ⑦TCB地址

二、任务调度

FreeRTOS 决定"这一刻该让哪个任务占用 CPU"的机制。它是 RTOS 的"大脑"。

1. 调度核心原则:优先级抢占

  • 每个任务有优先级。
  • 高优先级任务一旦就绪,立即抢走 CPU(抢占),低优先级任务马上暂停。
  • 同优先级之间用时间片轮转(默认每个 tick 切换一次,让大家轮流跑)。

2. 任务的 4 种状态

  • 就绪:能跑,等 CPU。
  • 运行:正在跑。
  • 阻塞:在等延时/事件(vTaskDelayulTaskNotifyTake),主动让出 CPU→ 别的任务趁机跑。
  • 挂起vTaskSuspend()强制停,需显式恢复。

任务调度 = 按"优先级抢占 + 同优先级时间片"规则,在 SysTick/PendSV 驱动下,不断保存旧任务现场、恢复新任务现场,让多个任务"看起来同时跑"。

三、任务切换

把 CPU 从「任务 A」交给「任务 B」时,保存 A 的现场、恢复 B 的现场的过程。调度决定"切谁",切换负责"怎么切"。

1. 切换 = 换一套寄存器现场CPU 只有一套寄存器(R0~R15、xPSR)。任务 A 跑的时候这些寄存器里是 A 的数据。要让 B 跑,必须:

  • 保存:把 A 的寄存器值存进 A 的任务栈(之前pxPortInitialiseStack摆好的那片)。
  • 恢复:从 B 的任务栈把 B 的寄存器值弹回 CPU
  • 切换瞬间完成,B 就像"从来没被打断过"一样继续跑

2、两个栈指针:MSP vs PSP

  • MSP(主栈):中断/启动代码用。
  • PSP(进程栈):任务用。切换的本质就是把 PSP 指向新任务的栈顶(TCB 里的pxTopOfStack),CPU 之后就从新栈取数据。

3、

切换触发 + 执行位置(关键)切换不在 SysTick 里直接做,而在PendSV 中断里做:

SysTick中断 → 判断需要切换? → 挂起PendSV(请求切换) ↓ (SysTick返回) PendSV中断 → 真正做上下文切换(保存A/恢复B) ← 切换在这里发生 ↓ 返回线程模式, 运行任务B

为什么绕一层 PendSV?因为 SysTick 可能被更高优先级中断打断,把真正的切换放到最低优先级的 PendSV,能保证切换完整、不被撕裂

任务切换 = 在 PendSV 里把旧任务寄存器现场压进旧栈、从新栈恢复新任务现场、并把 PSP 指向新栈,从而无缝交接 CPU

四、任务间通信

FreeRTOS 任务之间传数据/通信,主要有6 种机制

① 全局变量(最朴素,但需注意)

int g_shared = 0; // 任务A写、任务B读
  • 简单,但不是线程安全的:多任务同时读写会竞争。
  • 适合:只读标志位、或配合临界区/关中断保护。

② 队列 Queue(最常用,正经传数据)

QueueHandle_t xQ = xQueueCreate(10, sizeof(int)); // 长度10,每项int // 任务A发 int val = 123; xQueueSend(xQ, &val, portMAX_DELAY); // 任务B收(阻塞等) int got; xQueueReceive(xQ, &got, portMAX_DELAY);
  • 线程安全,内部已加保护;可传任意结构体。
  • 适合:传传感器数据、命令包等。

任务通知 Task Notification(最轻量)

// 发送方(com_task) xTaskNotifyGive(power_task_handle); // 发"通知" // 接收方(power_task) uint32_t res = ulTaskNotifyTake(pdTRUE, 10000); // 阻塞等,超时10s if(res != 0) { /* 收到通知,执行关机 */ }
  • 每个任务自带一个 32 位通知值,比队列快、省内存
  • 缺点:只能一对一(一个通知只能指定一个目标任务)。

④ 信号量 Semaphore(用于"同步/资源计数")

SemaphoreHandle_t xSem = xSemaphoreCreateBinary(); xSemaphoreGive(xSem); // 释放 xSemaphoreTake(xSem, portMAX_DELAY); // 获取(阻塞)
  • 二值信号量:事件通知(类似单次通知)。
  • 计数信号量:资源池(如缓冲区空位数量)。
  • 只传"有/无"状态,不传具体数据。

⑤ 互斥量 Mutex(保护共享资源)

SemaphoreHandle_t xMutex = xSemaphoreCreateMutex(); xSemaphoreTake(xMutex, portMAX_DELAY); // 访问共享资源(如 LCD、全局结构体) xSemaphoreGive(xMutex);
  • 解决"全局变量竞争"问题,带优先级继承(防优先级反转)。
  • 和信号量区别:互斥量必须"谁拿谁放",用于保护资源而非传信号。

⑥ 事件组 Event Group(一对多/多事件组合)

EventGroupHandle_t xEv = xEventGroupCreate(); xEventGroupSetBits(xEv, BIT_0); // 置位 xEventGroupWaitBits(xEv, BIT_0|BIT_1, pdTRUE, pdTRUE, ...); // 等多个事件
  • 一个变量里多个位,可等"任意一个/全部"事件发生。
  • 适合:多事件组合触发。

五、任务同步

1、为什么要同步

多任务"并行"运行时,任务间需要协调:等某个事件发生、共享资源不被同时改写、多个条件都满足再执行。同步机制就是解决"何时能继续"和"数据不被破坏"的问题。

2、主要同步机制

信号量(Semaphore)

  • 二值信号量:只有 0/1 两种状态,常用于任务与中断/任务间的事件通知(同步,不保护资源)。
SemaphoreHandle_t xSem; // 任务A:等事件 xSemaphoreTake(xSem, portMAX_DELAY); // 阻塞等待 // 事件到了,往下执行 // 中断/任务B:触发事件 xSemaphoreGiveFromISR(xSem, &pxHigher); // 中断里用 FromISR 版本
  • 计数信号量:计数值 >0 表示还有资源/事件,适合"资源池"(如缓冲区数量)

互斥量(Mutex)

专门用来保护共享资源(临界区),防止多个任务同时访问。

SemaphoreHandle_t xMutex; xSemaphoreTake(xMutex, portMAX_DELAY); // 进入临界区 // 操作共享变量 xSemaphoreGive(xMutex); // 离开临界区

关键点:互斥量有优先级继承机制,能缓解优先级反转问题。不能在中断里用互斥量(中断不能阻塞)。

递归互斥量(Recursive Mutex)

同一个任务可以多次获取,必须释放相同次数,适合函数递归调用共享资源。

事件标志组(Event Group)

一个变量里多个 bit,支持"等任意一个"或"等全部",适合多事件组合触发

// 等 bit0 和 bit1 都置位才继续 xEventGroupWaitBits(eg, BIT0|BIT1, pdTRUE, pdTRUE, portMAX_DELAY);

任务通知(Task Notification)

FreeRTOS 的轻量级机制,取代信号量做同步,更快、更省内存(每个任务自带一个通知值)。

xTaskNotifyGive(taskHandle); // 发送通知 ulTaskNotifyTake(pdTRUE, portMAX_DELAY); // 等待通知
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/15 5:25:54

Matlab 之数据分布拟合:从交互式 APP 到编程实战

1. 数据分布拟合入门:为什么需要它?第一次拿到一组数据时,我常常会盯着屏幕发呆:这些数字到底遵循什么规律?是正态分布还是指数分布?这时候数据分布拟合就成了我的救命稻草。简单来说,分布拟合就…

作者头像 李华
网站建设 2026/7/15 5:25:08

C++社区养老服务系统实战:微服务架构、实时告警与性能优化

1. 项目概述与核心价值最近在整理过往的项目资料,翻到了一个挺有代表性的实战案例——一个基于C开发的社区养老服务系统。这个项目当时投入了不小的精力,从需求分析、架构设计到编码实现和部署优化,踩了不少坑,也积累了不少心得。…

作者头像 李华
网站建设 2026/7/15 5:23:09

C++高性能正则库RE2:原理、实践与性能优化指南

1. 项目概述:为什么我们需要另一个正则表达式库?如果你写过C,处理过字符串匹配、数据验证或者日志分析,那你肯定绕不开正则表达式。标准库里的std::regex用起来方便,但性能呢?稳定性呢?处理用户…

作者头像 李华
网站建设 2026/7/15 5:22:47

C++26编译期智能指针:系统级编程的内存安全革命

1. 项目概述:当C26的编译期智能指针遇上系统级编程如果你和我一样,长期在系统级编程的泥潭里摸爬滚打——无论是写一个高性能的网络服务器,还是维护一个嵌入式设备的内核驱动,最头疼的恐怕不是算法有多复杂,而是内存安…

作者头像 李华
网站建设 2026/7/15 5:22:38

Claude Code终端AI编程助手:面向CLI工程师的智能工作流增强方案

1. 这不是又一个命令行玩具:Claude Code 的真实定位与核心价值最近两周,我陆续收到十几条私信,清一色是:“Claude Code 到底值不值得花时间学?”“它和 Cursor、GitHub Copilot、CodeWhisperer 到底差在哪?…

作者头像 李华