在单片机领域,进程(Process) 和 线程(Thread) 的概念与操作系统(如 Linux、Windows)中的定义有显著差异 —— 由于单片机资源(RAM、Flash、算力)极其有限,多数场景下不会运行支持完整进程管理的 “重型 OS”,而是使用实时操作系统(RTOS,如 FreeRTOS、uCOS) 或 “无 OS 裸机编程”。
在 RTOS 中,线程是资源调度的最小单位,而 “进程” 的概念几乎不被使用(或被简化为 “包含独立资源的线程组”,但极少实现)。下面结合单片机的实际应用场景,用 “裸机” 和 “RTOS” 两种典型开发模式举例,帮你理解二者的本质区别和工程意义。
一、先明确:单片机中 “进程” 为何极少出现?
在通用 OS 中,进程是 “资源分配的最小单位”—— 每个进程有独立的地址空间(代码、数据、堆栈),进程间资源隔离(如 A 进程不能直接访问 B 进程的内存),切换开销大。
但单片机的核心痛点是资源有限:比如一颗常用的 STM32F103 单片机,RAM 仅 20KB、Flash 仅 64KB,根本无法支撑多个 “独立地址空间” 的进程(仅一个进程的堆栈 + 数据就可能占满 RAM)。
因此,单片机领域的 “并发” 几乎全靠线程实现 —— 线程共享同一地址空间(共享代码、全局变量、外设寄存器),仅拥有独立的 “栈空间”,切换开销极小,适配单片机的资源约束。
二、举例 1:无 OS 裸机编程 ——“伪并发”,无线程 / 进程概念
在不使用 RTOS 的裸机开发中,单片机按 “顺序执行 + 中断” 的方式工作,没有真正的 “线程”,但可以通过 “状态机” 模拟 “并发”,帮助理解 “为什么需要线程”。
场景:一个简单的单片机项目(如 STM32),需实现 3 个功能:
LED 闪烁:每隔 500ms 翻转一次 LED(低优先级,允许被打断);
按键检测:每隔 10ms 读取一次按键状态(中优先级,需及时响应);
串口接收数据:当串口有数据传入时,立即读取并解析(高优先级,不能丢失数据)。
裸机实现方式(状态机 + 中断):
c
// 全局变量(共享资源)
uint8_t led_flag = 0; // LED翻转标志
uint8_t key_state = 0; // 按键状态
uint8_t uart_data = 0; // 串口接收数据
void main(void) {
// 初始化:LED、按键、串口、定时器
LED_Init();
Key_Init();
UART_Init(115200);
Timer_Init(1ms); // 定时器1ms中断一次
while(1) { // 主循环(顺序执行)
// 1. 处理LED闪烁(500ms一次)
if(led_flag) {
LED_Toggle();
led_flag = 0;
}
// 2. 处理按键检测(10ms一次)
if(key_flag) {
key_state = Key_Read();
key_flag = 0;
}
// 3. 处理串口数据(有数据才执行)
if(uart_flag) {
UART_Parse(uart_data);
uart_flag = 0;
}
}
}
// 定时器1ms中断服务函数(触发“伪并发”)
void Timer_IRQHandler(void) {
static uint16_t led_cnt = 0;
static uint16_t key_cnt = 0;
led_cnt++;
key_cnt++;
// 500ms到,置位LED标志
if(led_cnt >= 500) {
led_cnt = 0;
led_flag = 1;
}
// 10ms到,置位按键标志
if(key_cnt >= 10) {
key_cnt = 0;
key_flag = 1;
}
}
// 串口接收中断服务函数(高优先级,立即响应)
void UART_IRQHandler(void) {
if(UART_GetRxFlag()) {
uart_data = UART_ReadRxData();
uart_flag = 1;
UART_ClearRxFlag();
}
}
AI写代码
问题分析(为什么需要 RTOS 线程):
优先级混乱:若主循环中 “串口解析” 代码耗时较长(如解析复杂协议),会阻塞 “LED 闪烁” 和 “按键检测”,导致 LED 卡顿、按键响应延迟;
资源管理复杂:所有功能共享全局变量(如led_flag),需手动处理 “标志位竞争”,代码量大时易出错;
无真正并发:本质是 “中断触发标志 + 主循环轮询”,并非 “多个任务同时执行”。
三、举例 2:RTOS 编程(以 FreeRTOS 为例)——“真并发”,线程是核心
当使用 RTOS 时,上述 3 个功能会被拆分为3 个独立的线程,RTOS 内核负责按 “优先级” 调度线程,实现 “真并发”,且无需手动管理标志位和轮询。
核心概念(FreeRTOS 中):
线程(Task):每个线程对应一个 “功能任务”,有独立的栈空间(保存线程切换时的寄存器状态),共享全局资源(如外设、全局变量);
调度器(Scheduler):按线程优先级(高优先级先执行)和调度算法(如抢占式调度),决定当前执行哪个线程;
任务切换:RTOS 通过 “滴答定时器(SysTick)” 触发,保存当前线程的栈状态,加载下一个线程的栈状态,切换开销仅几微秒。
基于 FreeRTOS 的实现:
c
// 全局变量(线程共享资源)
uint8_t key_state = 0;
uint8_t uart_data = 0;
// 线程1:LED闪烁(优先级1,最低)
void LED_Task(void *pvParameters) {
while(1) {
LED_Toggle(); // 翻转LED
vTaskDelay(500 / portTICK_PERIOD_MS); // 延时500ms(期间释放CPU,调度其他线程)
}
}
// 线程2:按键检测(优先级2,中优先级)
void Key_Task(void *pvParameters) {
while(1) {
key_state = Key_Read(); // 读取按键状态
if(key_state == KEY_PRESSED) {
UART_SendData("Key pressed!\r\n"); // 触发串口发送
}
vTaskDelay(10 / portTICK_PERIOD_MS); // 延时10ms
}
}
// 线程3:串口数据处理(优先级3,最高)
void UART_Task(void *pvParameters) {
while(1) {
// 等待串口接收信号量(无数据时阻塞,释放CPU)
xSemaphoreTake(xUART_Semaphore, portMAX_DELAY);
UART_Parse(uart_data); // 解析数据(即使耗时久,也不会阻塞低优先级线程)
}
}
// 串口接收中断服务函数(触发高优先级线程)
void UART_IRQHandler(void) {
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
if(UART_GetRxFlag()) {
uart_data = UART_ReadRxData();
// 释放信号量,唤醒UART_Task(若UART_Task优先级更高,会触发任务切换)
xSemaphoreGiveFromISR(xUART_Semaphore, &xHigherPriorityTaskWoken);
UART_ClearRxFlag();
}
// 若有高优先级线程被唤醒,立即切换
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
void main(void) {
// 初始化硬件
LED_Init();
Key_Init();
UART_Init(115200);
// 初始化FreeRTOS:创建信号量、创建线程
xUART_Semaphore = xSemaphoreCreateBinary(); // 串口接收信号量
xTaskCreate(LED_Task, "LED_Task", 128, NULL, 1, NULL); // 栈128字,优先级1
xTaskCreate(Key_Task, "Key_Task", 128, NULL, 2, NULL); // 栈128字,优先级2
xTaskCreate(UART_Task, "UART_Task", 256, NULL, 3, NULL); // 栈256字,优先级3
// 启动RTOS调度器(从此主循环不再执行,由调度器管理线程)
vTaskStartScheduler();
// 若能执行到这里,说明调度器启动失败(如RAM不足)
while(1);
}
AI写代码
线程优势(对比裸机):
优先级明确:UART_Task 优先级最高,一旦有数据就会抢占 CPU,不会丢失数据;LED_Task 优先级最低,即使被抢占也不影响核心功能;
资源管理清晰:通过 “信号量(xUART_Semaphore)” 实现线程与中断的同步,避免全局标志位的混乱;
真正并发:线程延时(vTaskDelay)时会主动释放 CPU,其他线程可以执行,不会出现 “一个功能阻塞所有功能” 的问题;
代码模块化:每个功能对应一个线程,代码独立,便于维护和扩展(如新增 “温湿度采集线程”,只需再创建一个任务)。
四、单片机中 “进程” 的特殊场景(极少用)
只有在资源极其丰富的高端单片机(如带 MMU 内存管理单元的 ARM Cortex-A 内核单片机,如 STM32MP1 系列)中,才可能运行支持 “进程” 的 OS(如 Linux for MCU)。
举例:STM32MP1 运行 Linux,实现 “双进程”
STM32MP1 是一款 “双核单片机”(Cortex-A7 + Cortex-M4),A7 核运行 Linux,M4 核运行 FreeRTOS。在 A7 核的 Linux 中,可以创建两个进程:
进程 1:负责 UI 显示(如 LCD 显示温湿度),拥有独立的内存空间,使用 FrameBuffer 驱动;
进程 2:负责传感器采集(如读取温湿度传感器 SHT30),拥有独立的内存空间,使用 I2C 驱动;
进程间通过 “进程间通信(IPC,如管道、消息队列)” 交换数据(如进程 2 将温湿度数据通过管道发给进程 1)。
为什么极少用?
资源浪费:STM32MP1 的 RAM 需至少 128MB(远高于普通单片机的几十 KB),Flash 需几百 MB,成本和功耗都很高;
复杂度高:Linux 系统移植、进程管理、IPC 调试的难度远大于 RTOS 线程,普通单片机项目(如家电控制、工业传感器)完全用不到。
五、核心总结(单片机领域)
一句话概括:在单片机领域,“线程” 是实现并发的核心,“进程” 仅在高端场景中存在,普通项目只需关注 RTOS 线程即可。
————————————————
版权声明:本文为CSDN博主「CFZPL」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_56558360/article/details/151590665
|
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?注册
×
|