人人都会AI编程

5.4 PID 与进程树:init 进程、父子进程关系

更新时间:2026-07-12

每个运行中的程序在 Linux 中都以一个进程的形式存在,而系统用一组数字、属性和关系来管理它们。其中最基本的概念就是 PID、父进程以及由此形成的进程树。理解这些,是掌握进程管理、系统监控和故障排查的第一步。

1. PID:进程身份证

PID(Process ID,进程标识符)是内核分配给每个进程的一个唯一整数。同一个时刻系统中不会有两个进程共享同一个 PID。当你用 kill 终止一个进程、用 top 监控资源占用,或用 ps 查看进程状态时,都是在通过 PID 指定目标。

查看所有进程及其 PID 的常用命令:

ps aux          # 列出所有用户进程,第二列为 PID
ps -ef          # 另一种格式,显示父子 PID
pidof sshd      # 快速查出 sshd 服务的 PID

系统还有一些特殊 PID:

  • PID 1:系统启动后内核创建的第一个用户态进程,即 init 进程,所有其他进程都直接或间接由它产生。
  • PID 0:空闲进程(idle),在 CPU 无任务时运行,存在于内核层面,用户空间看不到。
  • PPID:每个进程都记录着它的父进程 ID(Parent PID),即创建它的那个进程的 PID。

2. init 进程:所有进程的根

在传统 Unix 系统中,init 是 PID 为 1 的进程,由内核在引导完成后直接启动。它负责接管孤儿进程、初始化系统服务,并在系统关机时优雅地终止所有进程。现代 Linux 发行版多已采用 systemd 作为 PID 1,但它承担的职责类似:成为整个用户态进程树的根。

你可以这样查看 PID 1 的进程:

ps -p 1 -o pid,comm   # PID 和命令名
pstree -p             # 直观看到树状结构,最顶是 systemd(1)

如果一个进程的父进程意外终止,该进程就会变成孤儿进程。此时内核不会放任它悬空,而是会将它“过继”给 PID 1(init/systemd)。当孤儿进程终止时,init 会负责回收其资源(调用 wait()),避免残留。这就是 init 进程在进程树中的关键纠错作用。

3. 父子进程关系与创建

新进程的产生只有一个途径:由现有进程通过 fork()(或 clone())系统调用创建一个和自己几乎完全相同的副本,然后复制的子进程通常会紧接着用 exec() 系列调用替换自身为另一个程序。例如,当你在 shell 中输入 ls 时:

  1. shell 自身(如 bash)通过 fork() 创建一个子进程,这个子进程是 bash 的克隆体;
  2. 子进程通过 execve("/bin/ls", ...) 将自身替换为 ls 程序;
  3. 父进程(bash)此时通常会等待子进程结束(wait()),然后重新显示提示符。

父子关系可以通过 ps 查看:

ps -ef --forest        # 以树状缩进显示父子关系
ps -o pid,ppid,comm    # 输出 PID、PPID 和命令名

4. 进程树

所有进程通过父子关系链接成一棵庞大的树,根就是 PID 1。你可以用 pstree 命令将这种层次结构可视化:

pstree -p             # 显示 PID
pstree -s <PID>       # 高亮指定进程的整条祖先链
pstree -u <user>      # 只看某个用户的进程树

一个典型的输出片段:

systemd(1)─┬─sshd(872)───sshd(2134)───bash(2140)───pstree(3392)
           ├─nginx(1200)─┬─nginx(1201)
           │             └─nginx(1202)
           └─cron(910)

由此可以一目了然:pstree 是由 bash 创建的,bash 又是从 sshd 守护进程派生出来的远程会话,而 sshd 的最终父进程则是 systemd(1)

5. 僵尸进程:特殊的父子关系残骸

当子进程先于父进程终止,但父进程尚未调用 wait() 回收其退出状态时,子进程会短暂地以僵尸进程(状态 Z)存在于进程表中。此时,内核会保留它的 PID 和退出码,但释放了几乎所有的内存和资源。僵尸进程本身不占用内存,但如果大量堆积,可能会耗光可用的 PID,导致无法创建新进程。

查看到僵尸进程:

ps aux | grep 'Z'
top -b -n1 | grep zombie

消除僵尸进程的方法通常是让父进程主动处理子进程退役(调用 wait()),或者直接结束父进程(此时僵尸子进程会被 init 接管并回收)。在实际运维中,发现僵尸进程往往意味着父进程存在程序缺陷,没有正确回收子进程。

6. 实战:描述一个进程的生命链

通过一个简单实验来巩固理解。在终端执行 sleep 1000 &,然后:

echo $$                 # 当前 shell 的 PID,如 2140
jobs -l                 # 看到后台作业的 PID,如 3501
ps -o pid,ppid,comm -p 3501   # 显示 3501 的 PID 和 PPID(2140)
pstree -s 3501          # 显示从根到 3501 的祖先链

你会发现:

  • sleep 进程的 PID 为 3501,PPID 为 2140(即当前 bash);
  • 其祖先链向上追溯为:systemd(1) → sshd(872) → sshd(2134) → bash(2140) → sleep(3501)

这完美展示了一个进程从父进程 fork 而来,并最终归属于 init 进程的完整树状链条。

小结

PID 是进程的唯一编号,父子关系依托于 fork-exec 模型,所有进程最终汇聚成一棵以 PID 1(init/systemd)为根的进程树。这一设计精巧而统一:孤儿进程会被 init 收养,僵尸进程暴露了父进程的失责,而 pstree 等工具让我们能在一瞬间看清整个系统的运行脉络。掌握这些概念,是进一步学习进程信号、作业控制、资源限制和容器技术的坚实基础。