欢迎光临
我们一直在努力

【Linux】 进程(4)七大进程状态深度解析


  Linux 七大进程状态深度解析  

内核源码中的状态定义

进程状态存储在 task_struct 的 state 和 exit_state 字段中,定义于 include/linux/sched.h:

struct task_struct {
volatile long state; /* 运行中状态:-1 不可运行, 0 可运行, >0 停止 */
int exit_state; /* 退出中状态:EXIT_ZOMBIE / EXIT_DEAD */
// …
};

具体状态标志:

/* tsk->state 使用 */
#define TASK_RUNNING 0x0000
#define TASK_INTERRUPTIBLE 0x0001
#define TASK_UNINTERRUPTIBLE 0x0002
#define TASK_STOPPED 0x0004
#define TASK_TRACED 0x0008

/* tsk->exit_state 使用 */
#define EXIT_ZOMBIE 0x0010
#define EXIT_DEAD 0x0020

关键区分:state 字段管理进程"活着"时的状态(R/S/D/T/t),

      exit_state 字段管理进程"退出过程中"的状态(Z/X)。

                  两者互斥使用。


一、R — TASK_RUNNING(运行态 / 就绪态)

定义

#define TASK_RUNNING 0x0000

            值为 0,是所有状态中唯一表示"可以被调度执行"的状态。

语义拆解

   TASK_RUNNING 实际上包含了传统操作系统中的两个子状态:

子状态含义
运行中(Running) 进程正在 CPU 上执行指令
就绪(Ready) 进程已准备好,在运行队列(runqueue)中等待被调度

        Linux 不区分这两者,统一用 TASK_RUNNING 表示。

        区别仅在于:该进程的 task_struct 是否正在被某个 CPU 的 current 指针指向。

进入 R 态的触发条件

  • 进程被 fork 创建后,初始即为 TASK_RUNNING
  • 睡眠进程等待的事件发生,被 wake_up() 唤醒
  • 停止态进程收到 SIGCONT 信号
  • 时间片耗尽后被调度器换下 CPU,但仍保持 R 态(在就绪队列中)

离开 R 态的触发条件

  • 主动调用 schedule() 放弃 CPU(如等待某资源)
  • 时间片耗尽,被调度器抢占(但仍在 R 态就绪队列中,不算离开)
  • 收到停止信号进入 T 态
  • 调用 exit() 退出

运行队列与调度

        所有处于 TASK_RUNNING 的进程都挂在每 CPU 运行队列(struct rq)上。

        CFS 调度器从红黑树中挑选 vruntime 最小的进程执行。

CPU 0 的运行队列(红黑树,按 vruntime 排序):

[P3: vruntime=100]
/ \\
[P1: vruntime=80] [P5: vruntime=150]
/
[P2: vruntime=120]

调度器选择最左侧节点(vruntime 最小)→ P1 上 CPU 执行

实际场景

# 查看正在运行的进程
ps aux | awk '$8 ~ /R/'

一个 CPU 密集型程序(如死循环计算)会长期处于 R 态:

while (1) {
// 纯计算,不调用任何阻塞函数
result += compute_something();
}


二、S — TASK_INTERRUPTIBLE(可中断睡眠态)

定义

#define TASK_INTERRUPTIBLE 0x0001

语义

        进程在等待某个事件发生,主动放弃 CPU 进入睡眠。

        但它可以被信号唤醒——当一个信号递达时,进程会被唤醒去处理信号,即使等待的事件尚未发生。

进入 S 态的典型场景

场景内核操作
sleep(10) 定时器未到期前睡眠
read() 阻塞终端/网络 等待数据到达
wait() / waitpid() 等待子进程退出
pause() 等待任意信号
等待互斥锁/信号量 锁被其他进程持有
select() / poll() / epoll_wait() 等待 I/O 事件

内核中的睡眠机制

进程进入可中断睡眠的标准模式:

// 定义等待队列节点
DEFINE_WAIT(wait);

// 将自己加入等待队列,并设置状态
prepare_to_wait(&wq, &wait, TASK_INTERRUPTIBLE);

if (!condition) {
schedule(); // 主动调度,放弃 CPU
}

// 被唤醒后,从等待队列移除
finish_wait(&wq, &wait);

唤醒路径有两条:

  • 事件发生:其他进程/中断调用 wake_up(&wq),遍历等待队列唤醒进程
  • 信号递达:内核在信号处理路径中检查进程状态,如果是 TASK_INTERRUPTIBLE,则唤醒进程
  • 被信号唤醒后的行为

            进程被信号唤醒后,通常会检查是否有信号待处理。如果有,系统调用(如 read)会返回 -EINTR,表示"被信号中断":

    ssize_t ret = read(fd, buf, size);
    if (ret == -1 && errno == EINTR) {
    // 被信号中断,可以选择重试
    continue;
    }

            这就是为什么很多健壮的代码会在循环中重试被 EINTR 中断的系统调用。

    实际场景

       绝大多数后台服务进程在空闲时都处于 S 态——它们在等待客户端请求、等待 I/O 完成。

    # 统计 S 态进程数量(通常占绝大多数)
    ps aux | awk '$8 ~ /^S/' | wc -l


    三、D — TASK_UNINTERRUPTIBLE(不可中断睡眠态)

    定义

    #define TASK_UNINTERRUPTIBLE 0x0002

    语义

            进程在等待不可中断的事件,通常是底层 I/O 操作(磁盘读写、NFS 挂载等)。

            与 S 态的核心区别:不能被信号唤醒,只能由等待的事件完成来唤醒

    为什么需要不可中断?

    这是最常被问到的面试点。假设进程正在执行磁盘 read:

  • 进程通过系统调用进入内核态
  • 内核向磁盘控制器发出 I/O 请求
  • 磁盘正在机械地寻道、读写(毫秒级延迟)
  • 进程需要等待 I/O 完成
  • 如果此时允许信号中断这个进程:

    • 进程可能在 I/O 完成前就退出或处理信号
    • 内核态的 I/O 操作可能处于不一致状态
    • 已发出的磁盘请求无法干净地回滚
    • 可能导致数据损坏或内核崩溃

            因此,在 I/O 操作的关键路径上,进程必须设为不可中断睡眠,确保 I/O 完成前进程不会被干扰。

    进入 D 态的典型场景

    场景说明
    磁盘 read / write 等待物理 I/O 完成
    fsync / fdatasync 等待数据刷入磁盘
    NFS 网络文件系统操作 等待网络 I/O,网络不通时可能长时间 D 态
    直接 I/O(O_DIRECT) 绕过页缓存,直接等待磁盘
    某些设备驱动操作 如磁带机、原始设备访问

    D 态与系统负载

    D 态进程会被计入系统负载(load average)。

    这是一个重要的知识点:

            Linux 的 load average 不仅统计 R 态进程,还统计 D 态进程。

            因此,当系统 load 很高但 CPU 使用率很低时,往往意味着大量进程处于 D 态——磁盘 I/O 是瓶颈。

    # 查看 D 态进程
    ps aux | awk '$8 ~ /D/'

    # 用 iostat 确认磁盘瓶颈
    iostat -x 1

    D 态能被杀死吗?

            不能。 因为 D 态进程不响应信号,kill -9(SIGKILL)也无法终止它。进程会一直保持 D 态,直到:

  • 等待的 I/O 操作完成
  • I/O 超时(某些驱动有超时机制)
  • 系统重启
  •         这也是 D 态进程令人头疼的原因——如果磁盘或 NFS 出现故障,D 态进程可能永远卡在那里,唯一的解决办法是修复 I/O 子系统或重启。

    内核中的 io_schedule()

    进入不可中断睡眠通常使用 io_schedule():

    // 设置为不可中断睡眠并调度
    set_task_state(tsk, TASK_UNINTERRUPTIBLE);
    io_schedule(); // 内部调用 schedule(),并统计 iowait 时间

      io_schedule() 与普通 schedule() 的区别在于:它会将等待时间计入 iowait(/proc/stat 中的 iowait 字段),这也是 top 中 %wa 的来源。


    四、T — TASK_STOPPED(停止态)

    定义

    #define TASK_STOPPED 0x0004

    语义

            进程被暂停执行,既不在 CPU 上运行,也不在等待任何事件。它完全"冻结",直到收到恢复信号

    进入 T 态的方式

    方式说明
    SIGSTOP 信号 不可被捕获/忽略/阻塞,强制停止进程
    SIGTSTP 信号 终端 Ctrl+Z 发送,可被捕获
    SIGTTIN 信号 后台进程尝试读取终端时
    SIGTTOU 信号 后台进程尝试写入终端时
    ptrace 附加 调试器附加时,目标进程进入停止态

    从 T 态恢复

    只有一个方式:收到 SIGCONT 信号。

    # 停止一个进程
    kill -STOP 1234

    # 查看状态(应为 T)
    ps -o pid,stat,cmd -p 1234

    # 恢复进程
    kill -CONT 1234

    终端作业控制中的 T 态

    Ctrl+Z 是最常见的触发场景:

    $ sleep 100
    # 按下 Ctrl+Z
    ^Z
    [1]+ Stopped sleep 100

    $ jobs
    [1]+ Stopped sleep 100

    $ bg %1 # 后台继续运行(发送 SIGCONT)
    [1]+ sleep 100 &

    $ fg %1 # 前台恢复运行
    sleep 100

    T 态进程的资源

    处于 T 态的进程:

    • 保留所有内存资源(不会被换出,但可以被回收)
    • 保留文件描述符
    • 不在运行队列中,不会被调度
    • 不响应除 SIGCONT 外的大多数信号(但信号会被记录为挂起)

    注意:SIGKILL 可以终止 T 态进程吗?

            可以。SIGKILL 对 T 态进程有效,进程会直接进入退出流程。但其他常规信号(如 SIGTERM)在 T 态下不会被立即处理,要等进程恢复到 R/S 态后才会递达。


    五、t — TASK_TRACED(追踪态)

    定义

    #define TASK_TRACED 0x0008

    语义

            进程被调试器追踪时的暂停状态。当 gdb 等调试器通过 ptrace 附加到进程,并命中断点或单步执行时,目标进程进入 TASK_TRACED。

    与 T 态(STOPPED)的区别

    这是一个高频面试考点:

    对比项T(STOPPED)t(TRACED)
    触发方式 SIGSTOP / SIGTSTP / Ctrl+Z ptrace 断点 / 单步
    恢复方式 SIGCONT 信号 调试器调用 ptrace(PTRACE_CONT)
    能否被 SIGCONT 唤醒 不能(必须由 ptrace 恢复)
    信号处理 挂起信号等待恢复 调试器可拦截信号
    ps 显示 T t(小写)

    ptrace 与 TRACED 态

    ptrace 是 Linux 提供的进程追踪机制,gdb、strace、ltrace 等工具都基于它。

    // 父进程(调试器)追踪子进程
    pid_t child = fork();
    if (child == 0) {
    // 子进程:允许被追踪
    ptrace(PTRACE_TRACEME, 0, NULL, NULL);
    execl("/bin/ls", "ls", NULL);
    } else {
    // 父进程:等待子进程事件
    int status;
    waitpid(child, &status, 0);

    // 设置断点、单步执行等
    ptrace(PTRACE_SINGLESTEP, child, NULL, NULL);
    // 子进程执行一条指令后进入 TASK_TRACED
    }

            当子进程命中断点时,内核会发送 SIGTRAP 信号,子进程进入 TASK_TRACED,父进程通过 waitpid 获知。

    实际场景

    # 用 gdb 调试一个程序
    gdb ./myprogram
    (gdb) break main
    (gdb) run

    # 另一个终端查看
    ps aux | grep myprogram
    # wxx 7800 0.0 0.1 … t 10:30 0:00 ./myprogram
    # ↑ 小写 t,表示 TRACED 态


    六、Z — EXIT_ZOMBIE(僵尸态)

    定义

    #define EXIT_ZOMBIE 0x0010

            注意这是定义在 exit_state 字段中,而非 state 字段。

    语义

            进程已经执行完毕,释放了用户态资源(内存、文件描述符等),但内核中的 task_struct 尚未释放。它像一具"尸体"一样留在进程表中,等待父进程来"收尸"。

    僵尸进程的产生流程

    1. 子进程调用 exit() 或从 main 返回

    2. 内核释放子进程的用户态资源(地址空间、文件描述符、信号处理等)

    3. 子进程的 exit_state 设为 EXIT_ZOMBIE

    4. 内核向父进程发送 SIGCHLD 信号

    5. 子进程保留 task_struct(包含退出码、资源使用统计等信息)

    6. 父进程调用 wait()/waitpid() 读取子进程退出信息

    7. 内核释放子进程的 task_struct → 进入 EXIT_DEAD

            如果步骤 6 从未发生,子进程就永远停留在僵尸态。

    为什么需要僵尸态?

    这是面试高频问题。僵尸态存在的意义是:

            保存子进程的退出状态和资源使用信息,供父进程查询。

    父进程可能想知道:

    • 子进程是正常退出还是被信号杀死?
    • 退出码是多少?
    • 子进程累计使用了多少 CPU 时间?
    • 子进程的最大内存驻留集是多少?

            这些信息存储在 task_struct 中,必须等父进程通过 wait() 读取后才能释放。如果子进程一退出就完全销毁,父进程就无法获取这些信息了。

    代码演示

    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>

    int main() {
    pid_t pid = fork();

    if (pid < 0) {
    perror("fork");
    return 1;
    }

    if (pid == 0) {
    // 子进程:立刻退出
    printf("【子进程】PID=%d,即将退出\\n", getpid());
    exit(42); // 退出码 42
    } else {
    // 父进程:不调用 wait,睡眠 60 秒
    printf("【父进程】PID=%d,子进程 PID=%d,不回收,睡眠 60 秒\\n",
    getpid(), pid);
    sleep(60);
    }

    return 0;
    }

    运行后另开终端:

    ps aux | grep -E 'Z|defunct'
    # wxx 8101 0.0 0.0 0 0 pts/0 Z 10:35 0:00 [zombie_demo] <defunct>

    注意僵尸进程的 VSZ 和 RSS 都是 0——用户态内存已释放,只剩内核数据结构。

    僵尸进程的危害

    危害说明
    占用 PID 每个僵尸进程占用一个 PID 号,系统 PID 有限(默认 32768)
    占用内核内存 每个 task_struct 约 1.7KB,数量多时不可忽视
    无法直接杀死 kill 对僵尸进程无效,因为它已经"死了"
    影响进程创建 PID 耗尽后,fork 会失败返回 EAGAIN

    如何处理僵尸进程

    方法一:父进程调用 wait() / waitpid()

    int status;
    waitpid(pid, &status, 0); // 阻塞等待指定子进程
    // 或
    wait(&status); // 等待任意子进程


    方法二:SIGCHLD 信号处理(非阻塞回收)

    void sigchld_handler(int sig) {
    int saved_errno = errno;
    // WNOHANG:不阻塞,没有已退出的子进程则立即返回
    while (waitpid(-1, NULL, WNOHANG) > 0);
    errno = saved_errno;
    }

    int main() {
    struct sigaction sa;
    sa.sa_handler = sigchld_handler;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = SA_RESTART;
    sigaction(SIGCHLD, &sa, NULL);
    // …
    }

    为什么用 while 循环?

            因为多个子进程可能同时退出,而信号不排队,一次 SIGCHLD 可能对应多个子进程退出。


    方法三:父进程先退出,让 init 收养

            如果父进程先于子进程退出,子进程会被 init(PID 1)收养。init 有一个专门的 SIGCHLD 处理函数,会自动回收所有孤儿进程,不会产生僵尸。


    方法四:两次 fork(高级技巧)

    pid_t pid = fork();
    if (pid == 0) {
    // 第一个子进程
    pid_t grandchild = fork();
    if (grandchild == 0) {
    // 孙子进程:执行实际任务
    execvp(…);
    }
    // 第一个子进程立即退出,孙子进程被 init 收养
    exit(0);
    }
    // 父进程等待第一个子进程(很快退出)
    waitpid(pid, NULL, 0);

            孙子进程的父进程(第一个子进程)立即退出,孙子被 init 收养,自动回收,父进程也无需长期等待。


    如何"杀死"一个已经存在的僵尸进程

    僵尸进程无法被直接杀死,因为它已经退出了。唯一的方法是:

  • 杀死它的父进程:父进程死后,僵尸进程被 init 收养,init 会自动回收
  • 让父进程调用 wait:如果父进程还在运行,可以通过调试器等方式让它执行 wait
  • 重启系统:终极手段
  • # 找到僵尸进程的父进程 PID
    ps -o ppid= -p <zombie_pid>

    # 杀死父进程(谨慎!)
    kill <ppid>


    七、X — EXIT_DEAD(死亡态)

    定义

    #define EXIT_DEAD 0x0020

    同样定义在 exit_state 字段中。

    语义

            进程的最终状态。父进程已经通过 wait() 回收了子进程的退出信息,内核即将释放 task_struct。这个状态极其短暂——通常只持续几个指令周期,ps 命令几乎不可能捕捉到。

    进入 X 态的流程

    父进程调用 waitpid()

    内核调用 release_task()

    将子进程 exit_state 设为 EXIT_DEAD

    从进程表中移除(PID 可重用)

    释放 task_struct 内核栈

    释放其他剩余内核资源

    进程彻底消失

    为什么需要 X 态?

    既然 Z 态之后进程就要被释放了,为什么还要一个中间的 X 态?

            原因是并发安全。在多 CPU 系统中,释放 task_struct 的过程不是原子的。

    设置 EXIT_DEAD 标志可以:

  • 告知其他 CPU:这个进程正在被释放,不要访问它的 task_struct
  • 作为 release_task() 中的状态检查,防止重复释放
  • 在释放过程中保持一个稳定的状态标识
  • // kernel/exit.c 中的 release_task()
    void release_task(struct task_struct *p)
    {
    // …
    p->exit_state = EXIT_DEAD; // 标记为死亡态
    // … 执行释放操作
    call_rcu(&p->rcu, delayed_put_task_struct); // 延迟释放
    }

    X 态的可见性

    # 几乎不可能看到 X 态进程
    ps aux | awk '$8 ~ /X/'
    # 通常无输出

    如果你真的看到了 X 态进程,那可能是:

    • 系统处于极高负载,释放操作被延迟
    • 内核存在 bug
    • 你在 ps 输出刷新的瞬间恰好捕捉到了

    状态转换全景图

    fork()
    新建 ───────────────────────────→ R (TASK_RUNNING)

    ┌───────────────────┼───────────────────┐
    │ │ │
    等待事件/资源 时间片到 收到停止信号
    (可中断) (仍在就绪队列) (SIGSTOP/Ctrl+Z)
    │ │ │
    ▼ ▼ ▼
    S (可中断睡眠) R T (停止态)
    D (不可中断睡眠) │
    │ │
    事件完成/I/O完成 SIGCONT
    (D态只能被事件唤醒) │
    (S态还可被信号唤醒) │
    │ │
    └───────────────────┬───────────────────┘


    R

    ┌────────────────────┼────────────────────┐
    │ │ │
    ptrace断点 exit()/信号 正常执行
    │ │ │
    ▼ ▼ │
    t (追踪态) Z (僵尸态) │
    │ │ │
    ptrace CONT 父进程 wait() │
    │ │ │
    └────────────────────┼────────────────────┘


    X (死亡态)


    资源释放,消失


    补充知识

    1. 进程状态与 load average 的关系

            Linux 的 load average 统计的是处于不可中断睡眠(D)和运行/就绪(R)状态的进程数的指数衰减平均值。

    load average = 正在运行的进程(R) + 等待I/O的进程(D)

    这解释了一个经典问题:为什么 CPU 使用率很低,但 load average 很高?

            → 因为大量进程在 D 态等待磁盘 I/O,它们被计入 load 但不消耗 CPU。


    2. ps 中 STAT 列的附加字符

    除了基本状态字符,ps 的 STAT 列还可能包含附加修饰符:

    字符含义
    s 会话首进程(session leader),通常是 shell
    l 多线程进程
    + 位于前台进程组
    < 高优先级(nice 值为负)
    N 低优先级(nice 值为正)
    L 有页面锁定在内存中(mlock)

    例如 Ssl+ = 可中断睡眠 + 会话首进程 + 多线程 + 前台进程组。


    3. 内核线程的状态

            内核线程(如 kthreadd、kworker、migration)也使用相同的状态机。你在 ps 中看到的 [kworker/0:0] 这类带方括号的进程就是内核线程。它们通常处于 S 态(等待工作)或 R 态(处理工作)。


    4. 空闲进程的状态

            每个 CPU 都有一个空闲进程(idle task,PID 0),当没有其他 R 态进程时运行。空闲进程不占用进程表项,也不在 ps 中显示。


    高频面试题

    Q1:Linux 有哪几种进程状态?分别是什么含义?

    参考答案:

    • R(Running):运行或就绪,在 CPU 上执行或在运行队列等待调度
    • S(Sleeping):可中断睡眠,等待事件,可被信号唤醒
    • D(Disk sleep):不可中断睡眠,通常等待 I/O,不能被信号唤醒
    • T(Stopped):停止态,被 SIGSTOP 等信号暂停,SIGCONT 恢复
    • t(Traced):追踪态,被调试器暂停,由 ptrace 恢复
    • Z(Zombie):僵尸态,进程已退出但父进程未回收,保留 task_struct
    • X(Dead):死亡态,父进程已回收,task_struct 即将释放

    Q2:D 态和 S 态有什么区别?为什么需要 D 态?

    参考答案:

    • S 态可被信号唤醒,D 态不能被信号唤醒
    • D 态用于保护内核态关键操作(主要是 I/O)的完整性。如果在磁盘 I/O 过程中允许信号中断,可能导致 I/O 操作处于不一致状态,引发数据损坏
    • D 态进程会被计入 load average,S 态不会
    • kill -9 无法杀死 D 态进程

    Q3:僵尸进程是怎么产生的?如何避免和处理?

    参考答案:

    • 产生:子进程退出后,父进程未调用 wait()/waitpid() 回收,子进程的 task_struct 残留在进程表中
    • 避免:
    • 父进程显式调用 wait() 或 waitpid()
    • 注册 SIGCHLD 信号处理函数,在其中用 waitpid(-1, NULL, WNOHANG) 循环回收
    • 父进程先退出,子进程被 init 收养自动回收
    • 两次 fork,让孙子进程被 init 收养
    • 处理已存在的僵尸:杀死其父进程,让 init 收养并回收

    Q4:T 态和 t 态有什么区别?

    参考答案:

    • T 态由 SIGSTOP/SIGTSTP/Ctrl+Z 触发,可被 SIGCONT 恢复
    • t 态由 ptrace 调试触发(断点、单步),只能由调试器通过 ptrace(PTRACE_CONT) 恢复,SIGCONT 无效
    • T 态进程的信号会被挂起,t 态进程的信号可被调试器拦截和修改

    Q5:为什么 load average 高但 CPU 使用率低?

    参考答案:

    • load average 统计的是 R 态 + D 态进程数
    • D 态进程在等待 I/O(通常是磁盘),不消耗 CPU 但计入 load
    • 这种情况说明系统的瓶颈在磁盘 I/O,而非 CPU
    • 可用 iostat -x、iotop 排查磁盘瓶颈,用 ps aux | awk '$8 ~ /D/' 查看 D 态进程

    Q6:kill -9 能杀死哪些状态的进程?不能杀死哪些?

    参考答案:

    • 能杀死:R、S、T、t、Z(僵尸进程本就已死,kill 无意义但不报错)
    • 不能杀死:D 态(不可中断睡眠,不响应任何信号,包括 SIGKILL)
    • X 态瞬间即逝,不存在杀死的问题

    Q7:fork 之后子进程的初始状态是什么?

    参考答案:

    • TASK_RUNNING。子进程被创建后直接加入运行队列,等待被调度执行。
    • 但子进程是否立即运行取决于调度器,父子进程的执行顺序不确定。

    Q8:进程退出的完整流程是什么?

    参考答案:

  • 进程调用 exit()(或从 main 返回,编译器自动插入 exit)
  • 内核执行 do_exit():关闭文件描述符、释放地址空间、释放信号处理等用户态资源
  • 设置 exit_state = EXIT_ZOMBIE,向父进程发送 SIGCHLD
  • 保留 task_struct(含退出码、资源统计)
  • 父进程调用 wait()/waitpid(),内核执行 release_task()
  • 设置 exit_state = EXIT_DEAD,释放 task_struct 和内核栈
  • 进程彻底消失,PID 可被重用

  • 总结

    Linux 的七大进程状态构成了一个完整的生命周期状态机:

    • R 是进程"活着且能动"的状态
    • S/D 是进程"睡着等事件"的状态,区别在于能否被信号打断
    • T/t 是进程"被暂停"的状态,区别于是被信号还是调试器暂停
    • Z/X 是进程"已死"的状态,区别在于是否已被父进程回收

            理解这些状态不仅能帮助你写出更健壮的代码(正确处理 EINTR、避免僵尸进程),更是排查线上问题(load 高、进程卡死、系统响应慢)的必备知识。在面试中,进程状态也是操作系统和 Linux 系统编程方向的高频考点。


    赞(0)
    未经允许不得转载:171主机测评 » 【Linux】 进程(4)七大进程状态深度解析
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址