3. 进程与线程基础:PCB、TCB与生命周期
好,咱们今天聊聊进程和线程最核心的几个数据结构。说实话,很多做嵌入式开发的朋友,写了几年代码,对PCB和TCB的理解还停留在“概念层面”。我个人觉得,不理解这些结构体,你很难真正驾驭QNX这样的硬实时系统。
3.1 进程控制块(PCB)—— 进程的“身份证”
每个进程在QNX内核里都有一个对应的PCB。你可以把它想象成进程的“户口本”。内核靠它来管理所有进程。
PCB里存了什么?我列几个关键字段:
- 进程ID(PID):唯一标识,就像身份证号。
- 进程状态:运行、就绪、阻塞、僵死等。
- 内存映射信息:代码段、数据段、堆栈的地址范围。
- 文件描述符表:打开了哪些文件、管道、设备。
- 信号处理表:每个信号对应的处理函数。
- 调度参数:优先级、调度策略、时间片。
- 父子进程指针:用于维护进程树。
嗯,这里要注意,QNX的PCB设计得非常紧凑。我当年在做一个车载项目时,系统里同时跑了40多个进程,每个PCB只占不到1KB。你想想看,如果每个PCB都膨胀到几KB,内存压力就大了。
核心要点:PCB是内核视角的进程描述。它不包含线程信息,线程信息在TCB里。
3.2 线程控制块(TCB)—— 真正的执行单元
QNX是纯微内核,线程才是调度单位。每个线程都有一个TCB。我习惯把TCB叫做“执行上下文”。
TCB里最重要的东西:
- 寄存器上下文:PC、SP、通用寄存器。这是线程切换时保存/恢复的关键。
- 线程状态:RUNNING、READY、BLOCKED、SUSPENDED。
- 所属进程指针:指向父PCB。
- 栈指针:用户栈和内核栈的地址。
- 调度信息:当前优先级、剩余时间片。
- 同步对象等待队列:如果线程在等mutex或condvar,这里会记录。
我曾经踩过一个坑:在QNX 6.5上,一个线程因为栈溢出,把TCB里的调度信息给覆盖了。结果系统调度器直接崩溃。从那以后,我每次创建线程都会显式设置栈大小,绝不用默认值。
避坑指南:我曾经见过有人把PCB和TCB混为一谈。记住:PCB是进程的,TCB是线程的。一个PCB可以挂多个TCB。在QNX里,进程是资源容器,线程才是干活的人。
3.3 PCB与TCB的关系
说白了,PCB和TCB是一对多的关系。一个进程至少有一个主线程,也可以有多个子线程。
我画个简单的结构图给你看:
PCB (进程控制块)
├── PID: 1234
├── 内存映射
├── 文件描述符表
└── 线程列表
├── TCB 1 (主线程)
│ ├── 寄存器上下文
│ ├── 栈指针
│ └── 调度信息
├── TCB 2 (工作线程)
│ ├── 寄存器上下文
│ ├── 栈指针
│ └── 调度信息
└── TCB 3 (定时器线程)
├── 寄存器上下文
├── 栈指针
└── 调度信息
你想想看,当内核做线程切换时,它只操作TCB,根本不管PCB。只有进程创建、销毁、内存管理这些操作,才会动到PCB。
3.4 进程生命周期
进程从出生到死亡,一共经历几个状态。我按QNX的实际流程给你捋一遍:
| 状态 | 说明 | 触发条件 |
|---|---|---|
| NEW | 进程刚被创建,PCB已分配,但还没就绪 | 调用fork()或spawn() |
| READY | 进程已就绪,等待调度器分配CPU | 资源就绪,进入就绪队列 |
| RUNNING | 正在CPU上执行 | 调度器选中该进程 |
| BLOCKED | 等待某个事件(I/O、信号、锁) | 调用阻塞式API |
| STOPPED | 被信号暂停 | 收到SIGSTOP等 |
| ZOMBIE | 进程已退出,但PCB还没被父进程回收 | 调用exit(),父进程未wait() |
嗯,这里有个细节。QNX里没有真正的“僵死”状态?其实有的。我记得有一次调试一个服务进程,发现它退出后PID一直占着。查了半天,原来是父进程忘了调用waitpid()。这就是典型的ZOMBIE。
个人经验:我建议你在写QNX服务程序时,一定要注册SIGCHLD信号处理函数,或者在主循环里定期调用waitpid()。否则系统里会积累一堆僵尸进程,最终导致无法创建新进程。
3.5 线程生命周期
线程的生命周期和进程类似,但更轻量。线程的状态包括:
- RUNNING:正在执行
- READY:就绪等待调度
- BLOCKED:等待同步对象、消息、信号
- INACTIVE:线程已创建但还没启动
- DEAD:线程已退出,资源待回收
线程的创建和销毁比进程快得多。我做过测试,在QNX 7.0上,创建一个线程大约只需要几微秒,而创建一个进程需要几十微秒。所以,如果你需要并发执行多个任务,用线程比用进程划算。
但是,线程也有代价。所有线程共享同一个地址空间,一个线程的野指针可能搞垮整个进程。我曾经在一个多线程数据采集项目里,因为一个线程的缓冲区溢出,导致另一个线程的控制结构被破坏。排查了整整两天才找到根因。
总结一下:
- PCB是进程的“户口本”,TCB是线程的“工作证”
- 进程是资源容器,线程是执行单元
- 进程状态有6种,线程状态有5种
- 创建线程比创建进程快10倍以上
- 线程共享地址空间,要小心内存越界
好,这一章就到这里。下一章咱们聊聊QNX的调度策略,看看内核是怎么决定“谁先跑”的。到时候我会分享一些实际项目中的调度调优经验。