鸿蒙南向开发教程 Day 3 附录:线程与进程详解
目标:理解线程与进程的本质区别、状态模型、调度机制,以及在 OpenHarmony 轻量系统中的实现
一、进程(Process)是什么?
1.1 核心定义
进程是操作系统进行资源分配和调度的基本单位,是程序的一次执行实例。
┌─────────────────────────────────────────┐ │ 进程(Process) │ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │ │ 代码段 │ │ 数据段 │ │ 堆栈段 │ │ │ │ (Text) │ │ (Data) │ │ (Stack) │ │ │ └─────────┘ └─────────┘ └─────────┘ │ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │ │ 打开文件 │ │ 信号处理 │ │ 地址空间 │ │ │ │ 列表 │ │ 程序 │ │ (虚拟内存)│ │ │ └─────────┘ └─────────┘ └─────────┘ │ │ │ │ 资源所有权:内存、文件、设备、句柄等 │ └─────────────────────────────────────────┘1.2 进程的组成
| 组成部分 | 说明 | 特点 |
|---|---|---|
| 代码段(Text) | 程序指令的二进制代码 | 只读,可被多个进程共享 |
| 数据段(Data) | 全局变量、静态变量 | 可读写 |
| 堆(Heap) | 动态分配的内存(malloc) | 向上增长,需手动管理 |
| 栈(Stack) | 局部变量、函数参数、返回地址 | 向下增长,自动管理 |
| 进程控制块(PCB) | 操作系统管理进程的数据结构 | 包含 PID、状态、优先级等 |
1.3 进程的状态模型
┌─────────┐ │ 创建 │ ← fork() / 系统初始化 │ (New) │ └────┬────┘ ↓ ┌─────────┐ 调度器选择 ┌─────────┐ ┌──→ │ 就绪 │ ────────────────→ │ 运行 │ │ │(Ready) │ │(Running)│ │ └─────────┘ ←──────────────── └────┬───┘ │ ↑ 时间片用完/被抢占 │ │ │ ↓ I/O 请求 / 等待事件 │ ┌────┴────┐ ┌─────────┐ └────┤ 阻塞 │ ← I/O完成/事件到达 │ 阻塞 │ │(Blocked)│ │(Blocked)│ └─────────┘ └─────────┘ ↓ ┌─────────┐ │ 终止 │ ← exit() / 被杀死 │(Terminated) └─────────┘三态模型:就绪(Ready)→ 运行(Running)→ 阻塞(Blocked)
二、线程(Thread)是什么?
2.1 核心定义
线程是 CPU 调度的基本单位,是进程内的执行流。一个进程可以包含多个线程,它们共享进程的资源,但拥有独立的执行上下文。
┌─────────────────────────────────────────┐ │ 进程(Process) │ │ 共享资源:代码、数据、堆、文件等 │ │ ┌─────────────────────────────────────┐ │ │ │ 线程1 │ 线程2 │ 线程3 │ ... │ │ │ │ ┌────┐ │ ┌────┐ │ ┌────┐ │ │ │ │ │ │寄存器│ │ │寄存器│ │ │寄存器│ │ │ │ │ │ │程序计数器│ │ │程序计数器│ │ │程序计数器│ │ │ │ │ │ │栈指针 │ │ │栈指针 │ │ │栈指针 │ │ │ │ │ │ │状态字 │ │ │状态字 │ │ │状态字 │ │ │ │ │ │ └──┬─┘ │ └──┬─┘ │ └──┬─┘ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ 独立栈 │ 独立栈 │ 独立栈 │ │ │ │ └─────────────────────────────────────┘ │ │ 线程私有:寄存器、程序计数器、栈 │ └─────────────────────────────────────────┘2.2 线程的组成
| 组成部分 | 说明 | 是否共享 |
|---|---|---|
| 线程 ID(TID) | 线程唯一标识 | 私有 |
| 程序计数器(PC) | 下一条指令地址 | 私有 |
| 寄存器组 | 通用寄存器、状态寄存器 | 私有 |
| 栈(Stack) | 局部变量、函数调用链 | 私有 |
| 线程局部存储(TLS) | 线程私有全局变量 | 私有 |
| 代码段 | 程序指令 | ✅ 进程内共享 |
| 数据段/堆 | 全局变量、动态内存 | ✅ 进程内共享 |
| 打开文件 | 文件描述符表 | ✅ 进程内共享 |
2.3 线程的状态
线程状态与进程类似,但更轻量:
┌─────────┐ 创建 ┌─────────┐ 调度 ┌─────────┐ │ 新建 │ ──────→ │ 就绪 │ ──────→ │ 运行 │ │ (New) │ │(Ready) │ │(Running)│ └─────────┘ └────┬────┘ └───┬────┘ ↑ │ │ 阻塞事件 ↓ ┌────┴────┐ ┌─────────┐ │ 阻塞 │ ←───── │ 阻塞 │ │(Blocked)│ 事件到达 │(Blocked)│ └─────────┘ └─────────┘ ↓ ┌─────────┐ │ 终止 │ │(Dead) │ └─────────┘三、进程 vs 线程:核心对比
| 对比维度 | 进程(Process) | 线程(Thread) |
|---|---|---|
| 定义 | 资源分配的基本单位 | CPU 调度的基本单位 |
| 内存空间 | 独立的地址空间 | 共享进程的地址空间 |
| 切换开销 | 大(需切换页表、刷新 TLB) | 小(只需切换寄存器和栈) |
| 通信方式 | IPC(管道、消息队列、共享内存、套接字) | 直接读写共享变量(需同步机制) |
| 创建开销 | 大(复制地址空间) | 小(共享大部分资源) |
| 崩溃影响 | 不影响其他进程 | 可能导致整个进程崩溃 |
| 并发性 | 低(重量级) | 高(轻量级) |
| 系统限制 | 数量受内存限制 | 数量受栈空间限制 |
3.1 形象比喻
| 比喻 | 进程 | 线程 |
|---|---|---|
| 工厂模型 | 一家独立的工厂(有独立场地、设备、工人) | 工厂内的生产线(共享场地设备,独立执行任务) |
| 厨房模型 | 独立的厨房(有独立食材、厨具、厨师) | 厨房内的厨师(共享食材厨具,各自做菜) |
| 程序模型 | 打开的两个 Word 文档(独立编辑) | 一个 Word 内的拼写检查和自动保存(同时运行) |
四、OpenHarmony 轻量系统中的线程
4.1 系统架构
OpenHarmony 轻量系统(LiteOS-M)是单进程多线程架构:
┌─────────────────────────────────────────┐ │ OpenHarmony 轻量系统 │ │ (单进程,多线程) │ │ │ │ ┌─────────────────────────────────────┐ │ │ │ 内核空间 │ │ │ │ ┌─────────┐ ┌─────────┐ │ │ │ │ │ 线程调度 │ │ 内存管理 │ │ │ │ │ │ (Tick) │ │ (Heap) │ │ │ │ │ └─────────┘ └─────────┘ │ │ │ └─────────────────────────────────────┘ │ │ │ │ ┌─────────────────────────────────────┐ │ │ │ 用户空间 │ │ │ │ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ │ │ │ │ │主线程│ │任务A│ │任务B│ │中断 │ │ │ │ │ │Init │ │Thread│ │Thread│ │Handler│ │ │ │ └─────┘ └─────┘ └─────┘ └─────┘ │ │ │ │ │ │ │ │ 共享:全局变量、代码段、堆内存 │ │ │ │ 私有:栈空间(每个线程独立) │ │ │ └─────────────────────────────────────┘ │ └─────────────────────────────────────────┘关键特点:
- 没有独立的"进程"概念,整个系统运行在一个地址空间
- 所有线程共享全局内存
- 线程切换只需保存/恢复寄存器和栈指针
4.2 线程在 Hi3861 中的实现
Hi3861 使用LiteOS-M 内核,线程即 LiteOS 的任务(Task):
┌─────────────────────────────────────────┐ │ LiteOS-M 任务管理 │ │ │ │ ┌─────────────────────────────────────┐ │ │ │ 就绪队列(按优先级排序) │ │ │ │ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ │ │ │ │ │Prio0│→│Prio1│→│Prio2│→│Prio3│→...│ │ │ │ │High │ │ │ │ │ │Low │ │ │ │ │ └─────┘ └─────┘ └─────┘ └─────┘ │ │ │ └─────────────────────────────────────┘ │ │ │ │ ┌─────────────────────────────────────┐ │ │ │ 任务控制块(TCB) │ │ │ │ ┌─────────┐ ┌─────────┐ │ │ │ │ │任务ID │ │栈指针 │ │ │ │ │ │优先级 │ │程序计数器│ │ │ │ │ │状态 │ │寄存器保存│ │ │ │ │ │栈大小 │ │任务入口 │ │ │ │ │ └─────────┘ └─────────┘ │ │ │ └─────────────────────────────────────┘ │ └─────────────────────────────────────────┘4.3 CMSIS-RTOS2 线程 vs LiteOS-M 任务
| CMSIS-RTOS2 概念 | LiteOS-M 实现 | 说明 |
|---|---|---|
osThread | LOS_Task | 线程 = 任务 |
osThreadNew() | LOS_TaskCreate() | 创建任务 |
osThreadSuspend() | LOS_TaskSuspend() | 挂起任务 |
osThreadResume() | LOS_TaskResume() | 恢复任务 |
osThreadTerminate() | LOS_TaskDelete() | 删除任务 |
osPriorityNormal | 优先级数值 | 数值越小优先级越高 |
五、线程调度机制
5.1 抢占式调度
LiteOS-M 使用优先级抢占 + 时间片轮转调度:
时间轴 → Tick 0: [Task A: Prio 3] ← 运行 [Task B: Prio 5] 就绪 [Task C: Prio 7] 就绪 Tick 10: [Task B 就绪,Prio 5 > Task A 的 3?否] → Task A 继续 Tick 20: [Task D 就绪,Prio 2] → 抢占! [Task D: Prio 2] ← 运行(抢占) [Task A: Prio 3] 就绪(被抢占) [Task B: Prio 5] 就绪 [Task C: Prio 7] 就绪 Tick 30: [Task D 阻塞/I/O] → 调度最高就绪 [Task A: Prio 3] ← 运行(恢复)规则:
- 高优先级任务就绪 → 立即抢占低优先级任务
- 同优先级任务 → 时间片轮转(默认 10ms)
- 任务阻塞(等待 I/O、延时)→ 让出 CPU
5.2 线程状态转换
┌─────────┐ │ 创建 │ osThreadNew() │ (New) │ └────┬────┘ ↓ ┌─────────┐ 调度器选择 ┌─────────┐ ┌──→ │ 就绪 │ ────────────────→ │ 运行 │ │ │(Ready) │ │(Running)│ │ └────┬────┘ ←──────────────── └────┬────┘ │ ↑ 时间片用完/被抢占 │ │ │ ↓ osDelay / osMutexAcquire │ ┌────┴────┐ ┌─────────┐ └────┤ 阻塞 │ ← 延时到期/锁释放 │ 阻塞 │ │(Blocked)│ │(Blocked)│ │ │ │ │ │ osDelay │ │ osMutex │ │ 到期 │ │ 释放 │ └─────────┘ └─────────┘ ↓ ┌─────────┐ │ 终止 │ osThreadTerminate() │(Dead) │ └─────────┘六、线程栈(Stack)详解
6.1 栈的作用
栈是线程私有的内存区域,用于:
| 用途 | 说明 |
|---|---|
| 局部变量 | 函数内定义的变量 |
| 函数参数 | 传递给子函数的参数 |
| 返回地址 | 函数调用后返回到哪里 |
| 寄存器保存 | 上下文切换时保存寄存器值 |
| 中断现场 | 中断发生时保存 CPU 状态 |
6.2 栈溢出危害
栈空间(假设 1024 字节): ┌─────────────────────────────────────────┐ ← 栈底(高地址) │ 已使用区域 │ │ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ │ │ │main │ │funcA│ │funcB│ │funcC│ ... │ │ │局部 │ │局部 │ │局部 │ │大数组│ │ │ │变量 │ │变量 │ │变量 │ │[1024]│ │ ← 栈溢出!超过 1024 字节 │ └─────┘ └─────┘ └─────┘ └─────┘ │ │ ↑ │ │ 栈指针 SP │ ├─────────────────────────────────────────┤ │ 未使用区域 │ │ ... │ └─────────────────────────────────────────┘ ← 栈顶(低地址) 溢出后果: 1. 覆盖其他线程的栈 → 数据损坏 2. 覆盖全局变量 → 程序逻辑错误 3. 覆盖代码段 → 程序崩溃(HardFault)Hi3861 栈大小建议:
| 线程类型 | 建议栈大小 | 说明 |
|---|---|---|
| 简单任务 | 512 ~ 1024 字节 | 少量局部变量,无深层调用 |
| 一般任务 | 1024 ~ 2048 字节 | 正常函数调用,中等局部变量 |
| 复杂任务 | 2048 ~ 4096 字节 | 深层递归、大数组、网络协议栈 |
| 中断处理 | 与线程共享 | 中断使用当前线程的栈 |
七、线程安全与临界区
7.1 什么是线程安全?
线程安全:多个线程同时访问共享资源时,程序行为正确,数据一致。
线程不安全示例(无保护): Thread A: 读取 g_count = 5 → 计算 5+1 = 6 → [被抢占] Thread B: 读取 g_count = 5 → 计算 5+1 = 6 → 写入 g_count = 6 Thread A: [恢复] 写入 g_count = 6 结果:g_count = 6,但期望 = 7(两次++) 一次更新丢失!7.2 临界区保护方法
| 方法 | 适用场景 | 特点 |
|---|---|---|
| 关闭中断 | 单核、极短临界区 | 最简单,但影响实时性 |
| 互斥锁(Mutex) | 长临界区、可阻塞 | 支持递归、优先级继承 |
| 自旋锁(Spinlock) | 多核、短临界区 | 忙等待,不阻塞 |
| 原子操作 | 简单变量读写 | 硬件指令保证,无锁 |
Hi3861 是单核 Cortex-M0+,常用方法:
// 方法1:关中断(最轻量)uint32_tintSave=LOS_IntLock();// 保存并关闭中断// ... 临界区 ...LOS_IntRestore(intSave);// 恢复中断// 方法2:互斥锁(推荐,支持阻塞等待)osMutexAcquire(mid,osWaitForever);// ... 临界区 ...osMutexRelease(mid);八、总结
| 要点 | 内容 |
|---|---|
| 进程 | 资源分配单位,独立地址空间,重量级 |
| 线程 | CPU 调度单位,共享进程资源,轻量级 |
| OpenHarmony 轻量系统 | 单进程多线程,线程 = LiteOS-M 任务 |
| 线程组成 | 私有:寄存器、PC、栈;共享:代码、数据、堆 |
| 调度机制 | 优先级抢占 + 时间片轮转 |
| 栈管理 | 每个线程独立栈,注意溢出风险 |
| 线程安全 | 共享资源需临界区保护(关中断 / 互斥锁) |
九、下一步
继续 Day 4:软件定时器(Timer)—— 异步事件处理机制。
