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_sleepsleep 的实验副本,用于观察普通等待进程
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 5

lab_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

实际结果

两个 lab_sleep 进程及属性

结果中:

  • PID 1460 属于 alice
  • PID 1462 属于 bob
  • 两个进程名称相同,但 PID 不同
  • PPID 分别是 1459、1461
  • STAT=S
  • NI=0
  • TTY=tty1

概念

程序和进程

  • 程序是磁盘中的可执行文件。
  • 进程是程序的一次运行实例。
  • 同一个程序可以同时运行多次,每次运行都会形成独立进程,并获得不同的 PID。

常见字段

字段含义
USER进程的运行用户
PID当前进程 ID
PPID直接父进程 ID
STAT进程状态及附加标志
NInice 值
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

实际结果

runuser 与 lab_sleep 的祖先进程链

进程链为:

systemd(1) → login(862) → bash(1374)
→ runuser(1459/1461) → lab_sleep(1460/1462)

结论

  • lab_sleep 的直接父进程是 runuser。
  • bash 是其祖先进程,但不是直接父进程。
  • 两条进程链最终都能追溯到 systemd(1)。

原理

启动过程是:

  1. bash 启动 runuser。
  2. runuser 把运行身份切换成 Alice 或 Bob。
  3. 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

实际结果

Tom 的父子进程树

分析

lab_tree.sh(1464)
├── lab_worker.sh(1465)
│   └── sleep
└── lab_worker.sh(1466)
    └── sleep
  • 1464 是父进程。
  • 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

实际结果

SIGTERM 终止 child_a

结果

  • 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

误用 ps -s 后没有结果

这里的 -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

正确结果

STOP 后为 T,CONT 后恢复为 S

结论

  • 收到 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
fg

fg 后按 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"

实际结果

nohup 与 disown

分析

  • 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"

实际结果

nice 从 10 调整到 15

top 中观察 nice 进程

结果

  • 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

实际结果

systemd 临时服务状态

分析

  • 服务为 active (running)。
  • Main PID 为 1470。
  • 服务中有两个任务:lab_worker.sh 和它当前的 sleep 5 子进程。
  • 两者都位于:
/system.slice/process-lab-service.service
  • is-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

实际结果

停止 transient 服务

结论

  • 服务先显示 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

实际结果

重新创建 transient 服务

结论

  • 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"

实际结果

僵尸进程状态 Z

分析

  • 父进程 9586 为 S 状态。
  • 子进程 9587 为 Z 状态。
  • <defunct> 表明该子进程已经结束,但退出状态尚未被父进程读取。

原理

僵尸进程已经停止执行,不再消耗 CPU,也不再像普通进程那样占用主要内存;它只保留 PID、退出状态和少量进程表信息,供父进程通过 wait() 等系统调用回收。

少量僵尸通常不会造成明显负载,但大量僵尸会占用 PID 和进程表项,严重时可能导致系统无法创建新进程。


十五、练习 14:为什么 kill -9 无法清除僵尸

操作与结果

尝试 kill 僵尸并终止父进程

实验中执行了:

kill -9 9587

但僵尸进程本身已经死亡,无法再被信号“杀死一次”。正确处理方向是让父进程调用 wait();如果父进程异常且无法修复,可以结束父进程,使僵尸被 PID 1 或其他子进程回收者接管并回收。

随后使用:

kill "$zparent"
sleep 1
ps -p 9586,9587 -o pid,ppid,stat,comm

父进程和僵尸进程最终都消失。

本次操作中的两个易错点

  1. 查询时曾把 9587 误写成 9687,因此那一次 ps 输出不能用于判断僵尸是否还存在。
  2. 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

当时显示:

CPU 实验服务处于 failed

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 退出。

实际结果

top 中定位 bob 的高 CPU 进程

定位到:

  • 用户: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

实际结果

renice、SIGTERM 与最终验证

分析

  • NI 从 0 成功调整为 15。
  • STAT=RNs:

    • R:运行中。
    • N:具有正 nice 值。
    • s:会话首进程。
  • CPU 仍约 92%,因为 nice 不是 CPU 限额;没有足够竞争时,它仍可使用空闲 CPU。
  • 发送 SIGTERM 后,ps 查不到 PID 10148,说明进程已退出。
  • 服务先显示 inactive。
  • 该服务是 transient 单元,退出后定义被自动清除,因此随后 status 提示找不到单元。

本场景中,SIGTERM 被 systemd 视为正常终止信号,服务没有保留为 failed 状态。


十九、进程状态速查

状态含义典型场景
RRunning / Runnable,正在运行或等待 CPUCPU 计算任务
SInterruptible Sleep,可中断睡眠等待计时器、输入或事件
TStopped,已暂停SIGSTOP、Ctrl+Z
ZZombie,僵尸子进程已退出,父进程尚未回收
DUninterruptible Sleep,不可中断睡眠通常在等待某些内核 I/O
IIdle,空闲内核线程常见于内核工作线程

常见附加标志:

标志含义
N具有正 nice 值
s会话首进程
+位于前台进程组

二十、常用信号和操作速查

操作常见信号/作用说明
Ctrl+CSIGINT中断当前前台任务
Ctrl+ZSIGTSTP暂停当前前台任务
kill -TERM PIDSIGTERM请求正常退出,可捕获和清理
kill -STOP PIDSIGSTOP强制暂停,不能被捕获
kill -CONT PIDSIGCONT恢复暂停进程
kill -9 PIDSIGKILL强制终止,不能被捕获或清理
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 服务名

既要确认进程消失,也要确认服务状态和子进程是否清理干净。

本轮练习形成的记忆链

发现异常 → 确认进程 → 查看父子关系或服务归属
→ 降低影响 → 正常终止 → 验证进程 → 验证服务

二十二、复习自测题

  1. PID 与 PPID 分别表示什么?
  2. 为什么同一个可执行程序可以同时出现多个不同 PID?
  3. S、R、T、Z 分别代表什么?
  4. pstree 和 ps 分别更适合查看什么?
  5. SIGTERM 与 SIGKILL 的主要区别是什么?
  6. 为什么 kill -9 无法清理僵尸进程?
  7. Ctrl+Z、bg、fg、Ctrl+C 分别做什么?
  8. Shell 作业编号 %2 与 PID 有何区别?
  9. 为什么 disown 后 jobs 查不到进程,而 ps 仍可找到?
  10. nohup 能否让进程在系统重启后继续运行?为什么?
  11. nice 值从 0 调到 15,进程优先级升高还是降低?
  12. 为什么 nice 值已经是 15,CPU 使用率仍可能接近 100%?
  13. systemd 的 active、enabled、transient 各表示什么?
  14. 为什么 transient 服务退出后可能提示 Unit could not be found?
  15. 为什么排查到 systemd-journald 高 CPU 时,不应立即杀掉它?
  16. 管理 systemd 服务时,为什么通常优先 systemctl stop,而不是只杀 Main PID?

标签: none

添加新评论