4.1.2 进程生命周期与状态转换

进程生命周期描述了进程从创建到退出的完整路径,核心在于状态转换及其触发条件。理解这些状态有助于定位运行异常、资源占用及僵尸/孤儿问题。

文章图片

1. 进程的主要状态#

  • 新建(New):进程被创建但尚未进入就绪队列。
  • 就绪(Ready):进程具备运行条件,等待 CPU 调度。
  • 运行(Running):进程占用 CPU 正在执行。
  • 阻塞/睡眠(Blocked/Sleeping):等待事件或资源(I/O、锁、信号等)。
  • 停止(Stopped):被信号暂停(如 SIGSTOPSIGTSTP)。
  • 退出(Terminated/Zombie):进程执行完成或被终止,进入僵尸等待回收。

2. 状态转换路径与触发条件#

  • 新建 → 就绪fork()/clone() 完成,内核初始化完成。
  • 就绪 → 运行:被调度器分配到 CPU。
  • 运行 → 就绪:时间片耗尽或更高优先级进程抢占。
  • 运行 → 阻塞:等待 I/O、互斥锁、条件变量或资源。
  • 阻塞 → 就绪:I/O 完成、锁释放或信号到达。
  • 运行 → 停止:收到停止信号或调试器挂起。
  • 停止 → 就绪:收到 SIGCONT 继续执行。
  • 运行 → 退出:正常 exit() 或异常终止(SIGKILL)。

3. 命令示例与状态观测#

以下示例可直接在 Linux 终端执行,用于观察状态变化:

# 1) 创建一个子进程并让其进入睡眠(S)
sleep 300 &
echo "sleep pid=$!"

# 2) 查看进程状态(STAT 字段)
ps -o pid,ppid,stat,cmd -p $!

# 3) 发送停止信号,观察 T 状态
kill -STOP $!
ps -o pid,ppid,stat,cmd -p $!

# 4) 继续运行,观察返回 S
kill -CONT $!
ps -o pid,ppid,stat,cmd -p $!

# 5) 强制结束,观察是否产生 Z(需父进程不回收)
kill -9 $!
ps -o pid,ppid,stat,cmd | grep Z

命令说明:
- ps -o pid,ppid,stat,cmd -p PID:按指定字段输出;stat 含状态码。
- kill -STOP/-CONT:停止/恢复进程。
- kill -9:强制终止进程(SIGKILL),可能产生僵尸。

4. 僵尸与孤儿示例与排错#

4.1 僵尸进程复现与定位#

// /tmp/zombie.c
#include <unistd.h>
#include <stdlib.h>
int main() {
    pid_t pid = fork();
    if (pid == 0) {
        _exit(0);         // 子进程立即退出
    } else {
        sleep(300);       // 父进程不 wait,产生僵尸
    }
    return 0;
}
# 编译与运行(需要 gcc)
gcc /tmp/zombie.c -o /tmp/zombie
/tmp/zombie &

# 查找 Z 状态
ps -eo pid,ppid,stat,cmd | grep Z

排错步骤:
1. ps -eo pid,ppid,stat,cmd | awk '$3 ~ /Z/ {print}' 定位僵尸 PID。
2. pstree -p <PPID> 查看父子关系。
3. 处理方式:重启父进程或让父进程正确 wait()

4.2 孤儿进程验证#

# 开一个新终端,在其中运行
( sleep 600 & echo "child=$!" ) && exit

# 回到原终端,查看是否被 init/systemd 接管
ps -o pid,ppid,cmd -p <child_pid>
# 预期:PPID 变为 1

5. 运维排查要点与命令解释#

# 观察系统中 D 状态进程(不可中断睡眠)
ps -eo pid,ppid,stat,cmd | awk '$3 ~ /D/ {print}'

# 结合 top 定位高负载阻塞
top -H -p <PID>   # -H 展示线程级别

# 追踪父子树
pstree -p | less

解释:
- D 状态常见于磁盘/网络 I/O 卡顿。
- top -H 能查看线程级别状态,适合定位阻塞线程。

6. 最佳实践#

  • 服务程序中正确处理 SIGCHLD 并回收子进程。
  • 对外部依赖设置超时,避免长时间阻塞。
  • 高并发场景使用进程池/线程池减少频繁 fork/exit。

7. 练习题#

  1. 观察一个 sleep 进程从 ST 再回 S 的状态变化,并记录 stat
  2. 编写或运行 zombie.c,用 pspstree 定位僵尸。
  3. 使用 top -H -p 观察一个多线程程序的线程状态差异。