#Linux系统
1. 进程状态理论模型
1.1 基本进程状态
进程状态在操作系统中体现为 task_struct 结构体中的一个整数标志。理论模型通常包含以下状态:
| 状态 | 描述 | 触发条件 |
|---|---|---|
| 运行状态 (Running) | 进程正在CPU上执行 | 被调度器选中 |
| 就绪状态 (Ready) | 进程已准备就绪,等待CPU时间片 | 等待调度 |
| 阻塞状态 (Blocked) | 进程等待某事件(如I/O完成) | 发起I/O请求或等待资源 |
| 结束状态 (Terminated) | 进程已结束运行 | 进程执行完毕或被终止 |
1.2 挂起状态
当系统资源(尤其是内存)不足时,进程可能被交换到磁盘:
| 状态 | 描述 | 触发条件 |
|---|---|---|
| 就绪挂起 | 进程在磁盘中,换入内存即可运行 | 内存不足,被换出 |
| 阻塞挂起 | 进程在磁盘中等待事件 | 内存不足且进程阻塞 |
2. 进程状态的实际实现
2.1 运行队列与等待队列
-
运行队列 (runqueue):CPU维护一个调度队列,包含所有处于**就绪®**状态的进程。调度器从这个队列中选择进程执行。
-
等待队列 (wait_queue):每个设备(如键盘、磁盘、网卡)都有自己的等待队列,包含所有**阻塞(S/D)**在该设备上的进程。
运行队列 (runqueue)
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ task_struct │◄─►│ task_struct │◄─►│ task_struct │
│ (状态: R) │ │ (状态: R) │ │ (状态: R) │
└─────────────┘ └─────────────┘ └─────────────┘磁盘等待队列 (disk wait_queue)
┌─────────────┐ ┌─────────────┐
│ task_struct │◄─►│ task_struct │
│ (状态: D) │ │ (状态: D) │
└─────────────┘ └─────────────┘
│ │
└──────────────────┘
▼
磁盘设备结构
struct device
2.2 状态变化的本质
进程状态的变化,本质上就是进程在不同的队列(运行队列、设备等待队列)中进行流动。这最终归结为对数据结构的增删查改操作。
-
运行 → 阻塞:进程从运行队列移除,加入某个设备的等待队列。
-
阻塞 → 就绪:当设备就绪(如磁盘I/O完成),进程从设备等待队列移除,加入运行队列。
2.3 挂起与交换分区
-
当内存严重不足时,操作系统会将部分进程的数据和代码换出 到磁盘的交换分区(swap)。
-
被换出的进程处于挂起状态,其PCB可能保留在内存或也被换出。
-
当需要时,操作系统再将进程换入内存。
3. Linux进程状态定义
Linux内核中,进程状态用数组 task_state_array 表示:
c
static const char *const task_state_array[] = {
"R (running)", /* 0 - 运行或可运行 */
"S (sleeping)", /* 1 - 可中断休眠(浅睡眠)*/
"D (disk sleep)", /* 2 - 不可中断休眠(深度睡眠)*/
"T (stopped)", /* 4 - 停止状态 */
"t (tracing stop)", /* 8 - 跟踪停止(调试状态)*/
"X (dead)", /* 16 - 死亡状态 */
"Z (zombie)", /* 32 - 僵尸状态 */
};
-
R (running):
-
进程正在运行 或在运行队列中等待运行。
-
注意:在Linux中,处于运行队列中等待调度的进程也显示为R状态。
-
-
S (sleeping) - 可中断休眠:
-
进程在等待某事件完成(如等待键盘输入、等待网络数据)。
-
可以被信号中断(唤醒),是常见的阻塞状态。
-
-
D (disk sleep) - 不可中断休眠:
-
通常发生在等待磁盘I/O时。
-
不可被信号中断,是保护磁盘数据完整性的关键机制。
-
模拟方法:使用
dd命令进行大量磁盘写入。
-
bash
dd if=/dev/zero of=~/test.txt bs=4096 count=100000
-
T (stopped) - 停止状态:
-
进程被暂停 ,通常由信号
SIGSTOP(19) 引起。 -
可通过信号
SIGCONT(18) 继续运行。
-
-
t (tracing stop):
-
进程被调试器暂停(如gdb设置断点)。
-
是T状态的一种特殊形式。
-
-
Z (zombie) - 僵尸状态:
-
进程已终止,但其父进程尚未读取到子进程的退出状态信息。
-
目的:为了让父进程能够获取子进程的退出信息(退出码、退出信号等)。
-
危害 :如果父进程一直不回收(不调用
wait()),僵尸进程会一直存在,导致内核资源(PCB)泄漏 。虽然进程的代码和数据已被释放,但其task_struct结构体仍占用内核内存。 -
常驻内存的进程如果产生僵尸且不被回收,危害更大。
-
-
X (dead) - 死亡状态:
- 进程最终状态,资源已完全回收。此状态瞬间存在,用户通常无法观察到。
3.1 孤儿进程
-
孤儿进程 (Orphan Process) :在父子进程关系中,如果父进程先于子进程退出,子进程将成为孤儿进程。
-
处理机制 :操作系统会将孤儿进程**"领养",通常由init进程(PID=1)** 或特定系统进程接管。
-
为什么需要领养?
-
防止僵尸进程累积:如果父进程退出且子进程未被领养,子进程终止后将无人回收,成为永久的僵尸进程,导致系统资源泄漏。
-
维护进程树结构:确保所有进程都有父进程,便于统一管理。
-
-
特性与影响
-
变为后台进程 :孤儿进程通常会成为后台进程,在终端关闭后仍可能继续运行。
-
创建后台进程的方法 :在命令后添加
&符号,如./myprocess &。
-
4. Linux内核数据结构设计
4.1 设备管理的"先描述,再组织"
操作系统管理各种硬件设备(键盘、磁盘等)同样遵循"先描述,再组织"原则:
c
struct device {
int id; // 设备标识
int vendor; // 厂商信息](https://i-blog.csdnimg.cn/direct/aa87146ea31a4f1abb0da0a325da118c.png)
int type; // 设备类型
struct task_struct *wait_queue; // 等待该设备的进程队列
// ... 其他属性
};
所有设备通过链表组织,便于管理。
4.2 内核通用链表实现

Linux内核使用一种巧妙的通用双向链表设计,实现数据结构的复用:
c
// 内核通用链表节点(只包含前后指针)
struct list_head {
struct list_head *next, *prev;
};
// 将链表节点嵌入到具体数据结构中
struct task_struct {
// 进程的各种属性...
int pid;
int status;
// ...
struct list_head tasks; // 链接到进程链表
};
优势:
-
类型无关 :
list_head不包含具体数据类型,可嵌入任何结构体。 -
代码复用 :一套链表操作函数(增、删、遍历)可用于所有嵌入
list_head的结构体。 -
通过偏移量获取父结构 :通过
list_head指针,可以计算出其所在外层结构体(如task_struct)的地址。
c
// 示例:从list_head指针获取包含它的task_struct指针
#define container_of(ptr, type, member) \
((type *)((char *)(ptr) - offsetof(type, member)))
5. 进程控制块的缓存管理
5.1 Slab分配器
内核使用Slab分配器 管理 task_struct 等内核对象的分配与释放:
-
目的:提高频繁创建和销毁进程时的性能。
-
机制 :维护一个
task_struct对象的缓存池。 -
优势:
-
避免频繁向物理内存申请释放带来的碎片和性能开销。
-
快速重用已释放的
task_struct对象。
Slab缓存池
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ task_struct │ │ task_struct │ │ task_struct │
│ (空闲) │ │ (空闲) │ │ (空闲) │
└─────────────┘ └─────────────┘ └─────────────┘ -
6. 总结
-
进程状态是进程生命周期中的关键属性,Linux通过具体状态(R、S、D、T、Z等)管理进程行为。
-
状态转换的本质是队列间的移动,进程在运行队列、设备等待队列间流转,这体现了"先描述,再组织"的管理思想。
-
僵尸进程(Z状态) 是已终止但未被父进程回收的进程,会导致内核资源泄漏,必须通过父进程调用
wait()系列函数回收。 -
深度睡眠(D状态) 是不可中断的,主要保护磁盘I/O操作,确保数据完整性。
-
Linux内核通过通用链表 和Slab分配器等高效数据结构与内存管理机制,实现进程管理的性能优化。