进程状态流转的本质:Linux 内核队列与底层数据结构解密

#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 - 僵尸状态 */
};
  1. R (running)

    • 进程正在运行在运行队列中等待运行

    • 注意:在Linux中,处于运行队列中等待调度的进程也显示为R状态。

  2. S (sleeping) - 可中断休眠

    • 进程在等待某事件完成(如等待键盘输入、等待网络数据)。

    • 可以被信号中断(唤醒),是常见的阻塞状态。

  3. D (disk sleep) - 不可中断休眠

    • 通常发生在等待磁盘I/O时。

    • 不可被信号中断,是保护磁盘数据完整性的关键机制。

    • 模拟方法:使用 dd 命令进行大量磁盘写入。

bash 复制代码
        dd if=/dev/zero of=~/test.txt bs=4096 count=100000
  1. T (stopped) - 停止状态

    • 进程被暂停 ,通常由信号 SIGSTOP (19) 引起。

    • 可通过信号 SIGCONT (18) 继续运行。

  2. t (tracing stop)

    • 进程被调试器暂停(如gdb设置断点)。

    • 是T状态的一种特殊形式。

  3. Z (zombie) - 僵尸状态

    • 进程已终止,但其父进程尚未读取到子进程的退出状态信息

    • 目的:为了让父进程能够获取子进程的退出信息(退出码、退出信号等)。

    • 危害 :如果父进程一直不回收(不调用 wait()),僵尸进程会一直存在,导致内核资源(PCB)泄漏 。虽然进程的代码和数据已被释放,但其 task_struct 结构体仍占用内核内存。

    • 常驻内存的进程如果产生僵尸且不被回收,危害更大。

  4. X (dead) - 死亡状态

    • 进程最终状态,资源已完全回收。此状态瞬间存在,用户通常无法观察到。

3.1 孤儿进程

  • 孤儿进程 (Orphan Process) :在父子进程关系中,如果父进程先于子进程退出,子进程将成为孤儿进程。

  • 处理机制 :操作系统会将孤儿进程**"领养",通常由init进程(PID=1)** 或特定系统进程接管。

  • 为什么需要领养

    1. 防止僵尸进程累积:如果父进程退出且子进程未被领养,子进程终止后将无人回收,成为永久的僵尸进程,导致系统资源泄漏。

    2. 维护进程树结构:确保所有进程都有父进程,便于统一管理。

  • 特性与影响

    • 变为后台进程 :孤儿进程通常会成为后台进程,在终端关闭后仍可能继续运行。

    • 创建后台进程的方法 :在命令后添加 & 符号,如 ./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; // 链接到进程链表
};

优势

  1. 类型无关list_head 不包含具体数据类型,可嵌入任何结构体。

  2. 代码复用 :一套链表操作函数(增、删、遍历)可用于所有嵌入 list_head 的结构体。

  3. 通过偏移量获取父结构 :通过 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 对象的缓存池。

  • 优势

    1. 避免频繁向物理内存申请释放带来的碎片和性能开销。

    2. 快速重用已释放的 task_struct 对象。

    Slab缓存池
    ┌─────────────┐ ┌─────────────┐ ┌─────────────┐
    │ task_struct │ │ task_struct │ │ task_struct │
    │ (空闲) │ │ (空闲) │ │ (空闲) │
    └─────────────┘ └─────────────┘ └─────────────┘

6. 总结

  1. 进程状态是进程生命周期中的关键属性,Linux通过具体状态(R、S、D、T、Z等)管理进程行为。

  2. 状态转换的本质是队列间的移动,进程在运行队列、设备等待队列间流转,这体现了"先描述,再组织"的管理思想。

  3. 僵尸进程(Z状态) 是已终止但未被父进程回收的进程,会导致内核资源泄漏,必须通过父进程调用 wait() 系列函数回收。

  4. 深度睡眠(D状态) 是不可中断的,主要保护磁盘I/O操作,确保数据完整性。

  5. Linux内核通过通用链表Slab分配器等高效数据结构与内存管理机制,实现进程管理的性能优化。

相关推荐
freshman_y1 小时前
Linux开发中DTS和/proc/device-tree讲解
linux·嵌入式
Shan12051 小时前
大学计算机初学者之学习课程推荐
学习
啊我不会诶1 小时前
2024北京市赛补题
c++·算法
自我意识的多元宇宙1 小时前
数据结构--散列函数的构造方法
数据结构
如君愿1 小时前
考研复习 Day 25 | 习题--计算机网络第三章(数据链路层 上)、数据结构(串)
数据结构·计算机网络·考研
夏末蝉未鸣011 小时前
Sort-Merge Join【排序连接算法】详解(python代码实现,以FULL JOIN为例)
数据结构·算法
tjl521314_211 小时前
01C++ 分离编译与多文件编程
前端·c++·算法
_日拱一卒1 小时前
LeetCode:23合并K个升序链表
java·数据结构·算法·leetcode·链表·职场和发展
cany10001 小时前
C++ -- 泛型编程
java·开发语言·c++