Linux 进程管理实战:进程关系、信号与资源排查
Linux 进程管理练习笔记
实验系统:Rocky Linux 虚拟机
实验目录:/opt/process_lab
说明:本文中的 PID 是当次实验的实际结果。进程重新启动或系统重启后,PID 会重新分配,复习时应以现场查询结果为准。
一、实验环境说明
1. 实验目标
这个环境用于在不修改 SSH、网络、系统启动等关键配置的前提下,练习以下内容:
- 进程与程序的区别
- PID、PPID 和进程树
ps、pgrep、pstree、top- 进程状态
R、S、T、Z - 信号与
kill - 前台任务、后台任务及 Shell 作业控制
nohup、disown- nice 值与调度优先级
- systemd 服务和 transient 临时单元
- 僵尸进程
- 模拟 CPU 异常的排查流程
2. 实验用户
沿用权限管理实验中的用户:
| 用户 | 用途 |
|---|---|
alice | 运行第一个普通睡眠进程 |
bob | 运行第二个普通睡眠进程及 CPU 异常进程 |
tom | 运行包含父子关系的进程树 |
root | 查看系统中的全部进程、发送信号和管理实验服务 |
3. 目录结构
/opt/process_lab/
├── bin/
│ ├── lab_sleep
│ ├── lab_cpu
│ ├── lab_worker.sh
│ ├── lab_tree.sh
│ └── lab_zombie.py
├── logs/
└── run/各程序的作用:
| 程序 | 作用 |
|---|---|
lab_sleep | sleep 的实验副本,用于观察普通等待进程 |
lab_cpu | 持续运行并消耗 CPU 的实验程序;它会不断输出内容,运行时必须妥善处理标准输出 |
lab_worker.sh | 循环执行 sleep 5,并捕获 TERM、HUP 信号写入日志 |
lab_tree.sh | 启动两个 lab_worker.sh 子进程,构造父子进程树 |
lab_zombie.py | 子进程退出后,父进程暂不调用 wait(),用于制造一个可控的僵尸进程 |
4. 初始进程布局
环境恢复后,主要实验进程如下:
systemd(1)
└── login
└── bash
├── runuser -u alice
│ └── lab_sleep
├── runuser -u bob
│ └── lab_sleep
└── runuser -u tom
└── lab_tree.sh
├── lab_worker.sh child_a
│ └── sleep 5
└── lab_worker.sh child_b
└── sleep 5此外还创建了一个由 systemd 直接管理的临时服务:
process-lab-service.service
└── lab_worker.sh service_worker
└── sleep 5lab_cpu 和 lab_zombie.py 不在初始状态运行,只在对应练习中启动,以免长期占用 CPU 或保留僵尸进程。
二、练习 1:查找进程并读取基本属性
目标
找到两个同名的 lab_sleep 进程,并理解 USER、PID、PPID、STAT、NI、TTY 和 COMMAND。
操作
pgrep -l lab_sleep
ps -C lab_sleep -o user,pid,ppid,stat,ni,tty,comm实际结果

结果中:
- PID
1460属于alice - PID
1462属于bob - 两个进程名称相同,但 PID 不同
PPID分别是1459、1461STAT=SNI=0TTY=tty1
概念
程序和进程
- 程序是磁盘中的可执行文件。
- 进程是程序的一次运行实例。
- 同一个程序可以同时运行多次,每次运行都会形成独立进程,并获得不同的 PID。
常见字段
| 字段 | 含义 |
|---|---|
USER | 进程的运行用户 |
PID | 当前进程 ID |
PPID | 直接父进程 ID |
STAT | 进程状态及附加标志 |
NI | nice 值 |
TTY | 关联终端 |
COMMAND | 命令名 |
S 状态
S 表示可中断睡眠(interruptible sleep)。本例中的 lab_sleep 正在等待计时器到期,因此处于睡眠状态,并不是异常“卡死”。
pgrep -l 与 pgrep -a
pgrep -l:显示 PID 和进程名。pgrep -a:显示 PID 和完整命令行。
三、练习 2:理解 PPID 和祖先进程链
目标
查找 lab_sleep 的直接父进程,并沿进程树追溯到 PID 1。
操作
ps -p 1459,1461 -o user,pid,ppid,stat,comm,args
pstree -sp 1460
pstree -sp 1462实际结果

进程链为:
systemd(1) → login(862) → bash(1374)
→ runuser(1459/1461) → lab_sleep(1460/1462)结论
lab_sleep的直接父进程是runuser。bash是其祖先进程,但不是直接父进程。- 两条进程链最终都能追溯到
systemd(1)。
原理
启动过程是:
bash启动runuser。runuser把运行身份切换成 Alice 或 Bob。runuser再启动lab_sleep。
因此 lab_sleep 的 PPID 指向 runuser,而不是当前用于查看进程的 Shell。
四、练习 3:观察父进程、子进程和孙进程
目标
观察 Tom 的进程树,验证子进程的 PPID。
操作
pgrep -af 'lab_tree|lab_worker'
pstree -p 1464
ps -p 1464,1465,1466 -o user,pid,ppid,stat,comm,args实际结果

分析
lab_tree.sh(1464)
├── lab_worker.sh(1465)
│ └── sleep
└── lab_worker.sh(1466)
└── sleep1464是父进程。1465、1466是它的两个子进程,PPID 都是1464。- 两个
sleep是更下一层的孙进程。 service_worker属于另一个 systemd 服务,不在1464的子树中。
命令侧重点
pstree:适合直观看进程之间的层级关系。ps:适合查看指定进程的用户、PID、PPID、状态、优先级和完整命令等属性。
五、练习 4:使用 SIGTERM 正常终止一个子进程
目标
只终止 child_a,观察父进程、兄弟进程以及内部 sleep 是否受影响。
操作
kill -TERM 1465
sleep 1
pstree -p 1464
ps -p 1464,1465,1466 -o pid,ppid,stat,comm
tail /opt/process_lab/logs/child_a.log实际结果

结果
1465收到SIGTERM后退出。- 它内部的
sleep子进程也消失。 - 父进程
1464继续运行。 - 兄弟进程
1466继续运行。 - 日志中出现
term pid=1465,证明脚本捕获了SIGTERM并执行清理逻辑。
概念
kill 的本质是“向进程发送信号”,不等于一定强制杀死进程。
SIGTERM:请求进程正常退出;程序可以捕获它并执行清理。SIGKILL:由内核立即终止进程;程序不能捕获、忽略或清理。
排障时通常先尝试 SIGTERM,只有进程拒绝退出且确认可以强制终止时,才考虑 SIGKILL。
六、练习 5:SIGSTOP 与 SIGCONT
目标
暂停一个进程但不终止它,再恢复运行。
一次常见错误
错误使用:
ps -s 1466 -o pid,ppid,stat,comm
这里的 -s 是按会话 ID(SID)筛选,不是按 PID 查询,因此看起来像“无事发生”。实际的暂停和恢复信号已经发送成功,只是查询方式不对。
正确参数是 -p:
kill -STOP 1466
ps -p 1466 -o pid,ppid,stat,comm
pstree -p 1464
kill -CONT 1466
ps -p 1466 -o pid,ppid,stat,comm正确结果

结论
- 收到
SIGSTOP后,STAT从S变为T。 - 暂停期间 PID 仍然存在,父子关系也仍保留。
- 收到
SIGCONT后恢复运行,状态重新变为S。
暂停和终止的区别
- 暂停:进程仍存在,只是暂时不被调度执行,可以恢复。
- 终止:进程退出,正常情况下 PID 消失并释放资源。
七、练习 6:前台、后台和 Shell 作业控制
目标
掌握 Ctrl+Z、jobs、bg、fg 和 Ctrl+C。
操作
sleep 300运行后按 Ctrl+Z,再执行:
jobs -l
bg
jobs -l
fgfg 后按 Ctrl+C,最后检查:
jobs -l实际结果

概念
Ctrl+Z:向前台作业发送终端暂停信号,使其进入Stopped状态。bg:让暂停的作业在后台继续运行。fg:把指定作业调到前台。Ctrl+C:向前台作业发送中断信号,通常使它终止。jobs -l:显示当前 Shell 管理的作业,并附带 PID。
当 jobs -l 没有输出时,说明当前 Shell 没有后台或暂停作业。
八、练习 7:多个后台作业、作业编号和 PID
目标
区分 Shell 作业编号与系统 PID,并练习指定作业。
操作
sleep 500 &
sleep 600 &
jobs -l
fg %1第一个作业回到前台后按 Ctrl+C,然后:
jobs -l
kill %2
jobs -l实际结果

概念
[1]、[2]:当前 Shell 分配的作业编号。8604、8608:内核分配的 PID。%2:引用当前 Shell 的第 2 号作业。8608:引用系统中的 PID 8608。
jobs 只能查看当前 Shell 自己管理的作业,不能显示其他终端启动的进程或系统服务。
作业标记:
+:当前默认作业,执行不带参数的fg、bg时优先选它。-:次选作业。
作业刚被终止时,Shell 可能显示一次 Terminated;再次执行 jobs 后通常会从作业表中清除。
九、练习 8:nohup 与 disown
目标
让程序忽略终端挂断信号,并把它从当前 Shell 的作业表中移除。
操作
nohup sleep 600 > /opt/process_lab/logs/nohup_test.log 2>&1 &
labpid=$!
echo $labpid
jobs -l
disown %+
jobs -l
ps -p "$labpid" -o pid,ppid,stat,tty,comm清理:
kill "$labpid"实际结果

分析
disown后,jobs查不到该作业。ps仍能查到进程,说明它仍存在于内核进程表中。- 当时 Shell 尚未退出,所以 PPID 仍是当前 Shell。
TTY仍显示pts/0,说明nohup并不等于彻底脱离终端。
概念
nohup主要让程序忽略SIGHUP。disown只是从当前 Shell 的作业表中移除作业,不是终止进程。nohup可以帮助程序在退出终端或 SSH 断开后继续运行。nohup不能让进程跨越关机或重启;系统重启后需要由 systemd 等机制重新启动程序。
$! 表示最近一个后台进程的 PID。本例中先保存为 labpid,后续即可使用 "$labpid" 准确引用。
十、练习 9:nice 值和进程调度优先级
目标
观察并调整 CPU 实验进程的 nice 值。
操作
nice -n 10 /opt/process_lab/bin/lab_cpu > /dev/null &
cpupid=$!
ps -p "$cpupid" -o pid,stat,ni,pri,pcpu,comm
renice -n 15 -p "$cpupid"
ps -p "$cpupid" -o pid,stat,ni,pri,pcpu,comm
top -p "$cpupid"退出 top 后清理:
kill "$cpupid"实际结果


结果
NI从10变为15。STAT=RN:R表示运行中。N表示具有正的 nice 值。
- CPU 使用率仍然很高。
原理
nice 数值越大,表示进程对其他进程越“友好”,在发生 CPU 竞争时愿意让出更多资源,因此自身调度优先级越低。
nice 是相对调度权重,不是 CPU 使用率上限。如果系统中没有足够强的竞争者,即使 NI 已经提高到 15,进程仍然可以使用大量空闲 CPU。
注意:该实验命令本身没有时间限制,必须手动清理。若希望自动结束,应配合timeout或由 systemd 设置RuntimeMaxSec。
十一、练习 10:观察 systemd 临时服务
目标
理解 systemd 服务的 active、enabled、transient、Main PID 和 cgroup。
操作
systemctl status process-lab-service --no-pager
systemctl is-active process-lab-service
systemctl is-enabled process-lab-service
pgrep -af service_worker实际结果

分析
- 服务为
active (running)。 - Main PID 为
1470。 - 服务中有两个任务:
lab_worker.sh和它当前的sleep 5子进程。 - 两者都位于:
/system.slice/process-lab-service.serviceis-enabled返回transient。
概念
active:服务当前是否正在运行。enabled:是否配置为在对应启动目标中自动启动。transient:临时运行单元,由运行时命令创建,定义通常位于/run/systemd/transient/。
/run 是运行时目录,重启后会重新建立。因此 transient 服务即使当前 active,也不会因为这一状态就在下次开机自动恢复。
systemd 通过 cgroup 管理服务内的一组进程,不只记录一个 Main PID。这使它可以统一跟踪和停止服务的主进程及其子进程。
十二、练习 11:停止 transient 服务
目标
观察停止临时服务后,进程和单元定义的变化。
操作
systemctl stop process-lab-service
systemctl is-active process-lab-service
pgrep -af service_worker
systemctl status process-lab-service --no-pager实际结果

结论
- 服务先显示
inactive。 pgrep没有输出,主进程及其sleep子进程均已消失。- 临时单元随后被 systemd 清除,
status提示Unit ... could not be found。
systemctl start process-lab-service 只能启动已经存在的服务单元。临时定义消失后,仅凭服务名无法知道应运行什么程序。
十三、练习 12:用 systemd-run 重新创建临时服务
目标
理解 systemctl start 与 systemd-run 的区别。
操作
systemd-run --unit=process-lab-service \
/opt/process_lab/bin/lab_worker.sh service_worker
systemctl status process-lab-service --no-pager
pgrep -af service_worker实际结果

结论
systemd-run同时创建临时单元定义并启动程序。- 新 Main PID 是
9483,不再是原来的1470。 - 原进程已经结束,新进程由内核重新分配 PID。
不能依赖某个程序永远使用固定 PID。管理长期服务时,应通过服务名、PID 文件或 systemd 查询 Main PID,而不是在脚本中写死旧 PID。
十四、练习 13:观察僵尸进程
目标
观察已经退出但尚未被父进程回收的子进程。
操作
python3 /opt/process_lab/bin/lab_zombie.py &
zparent=$!
sleep 1
ps -p "$zparent" -o pid,ppid,stat,comm,args
ps --ppid "$zparent" -o pid,ppid,stat,comm,args
pstree -p "$zparent"实际结果

分析
- 父进程
9586为S状态。 - 子进程
9587为Z状态。 <defunct>表明该子进程已经结束,但退出状态尚未被父进程读取。
原理
僵尸进程已经停止执行,不再消耗 CPU,也不再像普通进程那样占用主要内存;它只保留 PID、退出状态和少量进程表信息,供父进程通过 wait() 等系统调用回收。
少量僵尸通常不会造成明显负载,但大量僵尸会占用 PID 和进程表项,严重时可能导致系统无法创建新进程。
十五、练习 14:为什么 kill -9 无法清除僵尸
操作与结果

实验中执行了:
kill -9 9587但僵尸进程本身已经死亡,无法再被信号“杀死一次”。正确处理方向是让父进程调用 wait();如果父进程异常且无法修复,可以结束父进程,使僵尸被 PID 1 或其他子进程回收者接管并回收。
随后使用:
kill "$zparent"
sleep 1
ps -p 9586,9587 -o pid,ppid,stat,comm父进程和僵尸进程最终都消失。
本次操作中的两个易错点
- 查询时曾把
9587误写成9687,因此那一次ps输出不能用于判断僵尸是否还存在。 kill "%zparent"是错误写法:%2这类形式表示 Shell 作业号。$zparent才表示变量值。
正确写法:
kill "$zparent"十六、练习 15:CPU 异常排查中的一次真实故障
原始目标
用 systemd 启动一个最长运行五分钟、属于 Bob 的 CPU 异常进程,再通过 top 定位并处理。
最初使用:
systemd-run --unit=process-lab-cpu \
--uid=bob \
--property=RuntimeMaxSec=5min \
/opt/process_lab/bin/lab_cpu意外现象
top 中首先发现高 CPU 进程是 systemd-journald,而不是预期的 lab_cpu。
原因是 lab_cpu 实际为持续输出内容的实验程序,而 systemd-run 默认把服务标准输出交给日志系统。大量输出进入 journal,导致 systemd-journald 忙于处理日志。
此时不应直接杀掉 systemd-journald,因为它只是承受工作量的系统日志服务,真正的来源是 process-lab-cpu。
先停止实验服务
systemctl stop process-lab-cpu
systemctl is-active process-lab-cpu当时显示:

failed 表示服务已经不在运行,但 systemd 仍保留其失败结果,并不表示 stop 命令没有执行。
查看失败原因和日志占用
systemctl status process-lab-cpu --no-pager -l
systemctl reset-failed process-lab-cpu
journalctl --disk-usage实际结果

结果表明:
- 服务实际运行约 5 分钟。
- 达到
RuntimeMaxSec后被 systemd 发送SIGTERM。 Result: timeout指运行时限达到,不代表系统自身发生故障。- 日志中的大量
y来自lab_cpu。 - journal 总占用约
70.6M,无需清理。
这次故障带来的排障认识
CPU 占用最高的进程有时只是“受害者”。看到 systemd-journald 高负载时,需要继续追查谁在制造大量日志,而不是直接终止系统日志服务。
十七、练习 16:修正 CPU 实验并定位真正异常进程
修正启动方式
把标准输出和标准错误丢弃,避免再次冲击日志系统:
systemd-run --unit=process-lab-cpu \
--uid=bob \
--property=RuntimeMaxSec=5min \
--property=StandardOutput=null \
--property=StandardError=null \
/opt/process_lab/bin/lab_cpu用 top 定位
top在 top 中按大写 P,按 CPU 使用率排序,找到 lab_cpu 后记录 PID,并按 q 退出。
实际结果

定位到:
- 用户:
bob - PID:
10148 - CPU:约
92% - COMMAND:
lab_cpu
进一步查询:
ps -p 10148 -o user,pid,ppid,stat,ni,pcpu,comm,args结果:
- PPID 为
1,因为进程由 systemd 直接启动。 STAT=Rs:R:正在运行。s:会话首进程。
- nice 值为
0。 - 完整命令为
/opt/process_lab/bin/lab_cpu。
十八、练习 17:降低影响、正常终止并验证
调整优先级
renice -n 15 -p 10148
ps -p 10148 -o user,pid,ppid,stat,ni,pcpu,comm正常终止
kill -TERM 10148
sleep 1
ps -p 10148 -o pid,ppid,stat,comm
systemctl is-active process-lab-cpu
systemctl status process-lab-cpu --no-pager实际结果

分析
NI从0成功调整为15。STAT=RNs:R:运行中。N:具有正 nice 值。s:会话首进程。
- CPU 仍约 92%,因为 nice 不是 CPU 限额;没有足够竞争时,它仍可使用空闲 CPU。
- 发送
SIGTERM后,ps查不到 PID10148,说明进程已退出。 - 服务先显示
inactive。 - 该服务是 transient 单元,退出后定义被自动清除,因此随后
status提示找不到单元。
本场景中,SIGTERM 被 systemd 视为正常终止信号,服务没有保留为 failed 状态。
十九、进程状态速查
| 状态 | 含义 | 典型场景 |
|---|---|---|
R | Running / Runnable,正在运行或等待 CPU | CPU 计算任务 |
S | Interruptible Sleep,可中断睡眠 | 等待计时器、输入或事件 |
T | Stopped,已暂停 | SIGSTOP、Ctrl+Z |
Z | Zombie,僵尸 | 子进程已退出,父进程尚未回收 |
D | Uninterruptible Sleep,不可中断睡眠 | 通常在等待某些内核 I/O |
I | Idle,空闲内核线程 | 常见于内核工作线程 |
常见附加标志:
| 标志 | 含义 |
|---|---|
N | 具有正 nice 值 |
s | 会话首进程 |
+ | 位于前台进程组 |
二十、常用信号和操作速查
| 操作 | 常见信号/作用 | 说明 |
|---|---|---|
Ctrl+C | SIGINT | 中断当前前台任务 |
Ctrl+Z | SIGTSTP | 暂停当前前台任务 |
kill -TERM PID | SIGTERM | 请求正常退出,可捕获和清理 |
kill -STOP PID | SIGSTOP | 强制暂停,不能被捕获 |
kill -CONT PID | SIGCONT | 恢复暂停进程 |
kill -9 PID | SIGKILL | 强制终止,不能被捕获或清理 |
nohup | 忽略 SIGHUP | 常用于终端断开后继续运行 |
推荐终止顺序:
确认目标 → SIGTERM → 等待并验证 → 必要时才使用 SIGKILL二十一、规范的进程故障排查流程
1. 发现异常
top
ps aux --sort=-%cpu先看 CPU、内存、负载和异常进程,不要看到高占用就立即强制杀进程。
2. 确认目标
pgrep -af 关键字
ps -p PID -o user,pid,ppid,stat,ni,pcpu,pmem,tty,comm,args确认运行用户、PID、PPID、状态、优先级、终端和完整命令,避免误伤同名进程。
3. 查看上下文
pstree -sp PID
systemctl status 服务名判断它是普通 Shell 进程、其他进程的子进程,还是由 systemd 管理的服务。
4. 降低影响
必要时先降低优先级:
renice -n 15 -p PID但要记住 nice 只是调度权重,不是 CPU 配额。
5. 正常处理
- 普通进程:优先
kill -TERM PID。 - systemd 服务:通常优先
systemctl stop 服务名,让 systemd 统一管理整个 cgroup。 - 不要一开始就使用
kill -9。
6. 验证
ps -p PID
pgrep -af 关键字
systemctl is-active 服务名
systemctl status 服务名既要确认进程消失,也要确认服务状态和子进程是否清理干净。
本轮练习形成的记忆链
发现异常 → 确认进程 → 查看父子关系或服务归属
→ 降低影响 → 正常终止 → 验证进程 → 验证服务二十二、复习自测题
- PID 与 PPID 分别表示什么?
- 为什么同一个可执行程序可以同时出现多个不同 PID?
S、R、T、Z分别代表什么?pstree和ps分别更适合查看什么?SIGTERM与SIGKILL的主要区别是什么?- 为什么
kill -9无法清理僵尸进程? Ctrl+Z、bg、fg、Ctrl+C分别做什么?- Shell 作业编号
%2与 PID 有何区别? - 为什么
disown后jobs查不到进程,而ps仍可找到? nohup能否让进程在系统重启后继续运行?为什么?- nice 值从 0 调到 15,进程优先级升高还是降低?
- 为什么 nice 值已经是 15,CPU 使用率仍可能接近 100%?
- systemd 的
active、enabled、transient各表示什么? - 为什么 transient 服务退出后可能提示
Unit could not be found? - 为什么排查到
systemd-journald高 CPU 时,不应立即杀掉它? - 管理 systemd 服务时,为什么通常优先
systemctl stop,而不是只杀 Main PID?