2.4 进程管理
预计阅读时间:22 分钟
📖 目录
学习目标
学完本章后,你将能够:
- 使用
ps和pstree查看进程信息 - 理解 Linux 信号机制,正确使用
kill终止进程 - 使用
&、jobs、fg、bg管理前后台作业 - 使用
nohup让进程在终端关闭后继续运行 - 使用
nice和renice调整进程优先级 - 理解僵尸进程、孤儿进程的概念
核心知识
- 进程(Process)——正在运行的程序实例。每个进程有唯一的 PID(进程 ID)
- ps——进程快照查看(process snapshot),显示 PID、状态、CPU/内存使用
- 信号(Signal)——Linux 进程间通信的一种方式,用于通知进程执行某种操作(终止、暂停等)
- kill——发送信号给进程。默认发送 SIGTERM(15),
kill -9发送 SIGKILL - 后台作业(& / jobs / fg / bg)——Shell 的作业控制机制
- nohup——"no hang up",让进程忽略 SIGHUP 信号,终端关闭后继续运行
- nice 值——进程优先级(-20 最高,19 最低),默认 0
- 僵尸进程 / 孤儿进程——僵尸 = 已终止但父进程未回收;孤儿 = 父进程先于子进程结束
知识关联
- 前置知识:2.3:系统信息与性能查看 的 top 是进程实时监控工具,本章进一步深入进程管理操作
- 后续影响:2.12:systemd 深入 的 systemd 服务管理本质上也是进程管理
- 配套技术:1.7:用户与权限管理 的 grep 用于过滤进程列表;1.9:重定向与管道 的管道组合
ps aux | grep - 在整个体系中的位置:进程是你和运行中的程序打交道的接口——启动、监控、停止,这是系统管理的日常
原理讲解
CPU 有两种运行模式:用户态(User Mode)和内核态(Kernel Mode)。用户程序运行在用户态,只能访问自己的内存空间;需要访问硬件(磁盘、网卡)或执行特权操作(如修改进程优先级)时,必须通过系统调用(System Call)切换到内核态。这种设计的核心目的是隔离和保护:一个有 bug 的用户程序不会搞坏其他进程或内核;一个进程无法读取另一个进程的内存。这就是为什么 kill -9 别人的进程可能需要 root 权限——发送信号给其他用户的进程是"特权操作",必须通过内核中转。用户态/内核态的切换有性能开销(每次切换约 1-10 微秒),但这个开销换来的是整个系统的安全和稳定。
Linux 的 CFS 调度器通过 nice 值(-20 到 19)控制 CPU 时间分配。nice 值不是"绝对优先级",而是"权重":nice=0 的进程获得的 CPU 时间约为 nice=10 进程的 10 倍。CFS 为每个进程维护 vruntime(虚拟运行时间),选择 vruntime 最小的进程运行。nice 值通过权重表影响 vruntime 的增长速度:nice 值越高,权重越小,vruntime 增长得越快,被调度的机会就越少。但注意 nice 值只影响 CPU 时间——磁盘 I/O、网络带宽不受影响。对于需要严格实时响应的场景(如音频处理),需要使用 SCHED_FIFO 或 SCHED_RR 实时调度策略,这些策略绕过 CFS 直接使用优先级队列。
Shell 执行 cmd1 | cmd2 时:① 内核创建 pipe 对象(两个文件描述符:读端和写端);② fork() 创建两个子进程;③ cmd1 的 stdout 通过 dup2() 重定向到管道写端,cmd2 的 stdin 重定向到管道读端;④ 两个进程同时运行——cmd1 写满管道缓冲区(默认 64KB)时被阻塞,cmd2 读空时也被阻塞。这就是天然的"背压"机制:如果 cmd1 产生数据太快,管道会自动减慢 cmd1;如果 cmd2 处理太慢,cmd1 会被等待。整个过程不需要磁盘参与,数据在内核缓冲区中流转,这是管道高效的原因。管道的单向性(半双工)是其设计约束——如果需要双向通信,需要使用两个管道或命名管道(FIFO)。
1. 进程的核心属性——PID、PPID、状态
每个进程在系统中都有唯一的标识:
# PID(Process ID,进程 ID)
# PPID(Parent Process ID,父进程 ID)
# 进程状态(STAT)
# R (running) = 正在运行或可运行
# S (sleeping) = 正在等待某事完成(可中断睡眠)
# D (disk sleep) = 不可中断睡眠(通常在等待磁盘 I/O)
# Z (zombie) = 已终止,但父进程未回收其退出状态
# T (stopped) = 被暂停(如 Ctrl+Z)
# < = 高优先级(负 nice 值)
# N = 低优先级(正 nice 值)
# 查看进程的父子关系
pstree -p | head -20 # 进程树
# 输出: systemd(1)─┬─systemd-journal(256)
# 输出: ├─systemd-udevd(302)
# 输出: ├─sshd(789)───sshd(1234)───bash(1235)
# 输出: ├─nginx(890)───nginx(891)
# 输出: └─cron(678)
ps -ef --forest # 另一种进程树
# 输出: UID PID PPID C STIME TTY TIME CMD
# 输出: root 1 0 0 14:30 ? 00:00:01 /sbin/init
# 输出: root 256 1 0 14:30 ? 00:00:00 \_ /lib/systemd/systemd-journald
进程状态详解:top 的 S 列和 ps 的 STAT 列显示的是同一个状态码,只是 top 只显示主状态字母,ps 还会追加辅助标志。完整对应关系如下:
| 状态码 | 含义 | top 中显示 | 常见场景 |
|---|---|---|---|
R | Running,正在运行或可运行 | R | CPU 密集型计算、正在响应调度的进程 |
S | Sleeping,可中断睡眠 | S | 绝大多数进程:等待网络数据、定时器、用户输入 |
D | Disk sleep,不可中断睡眠 | D | 等待磁盘 I/O 完成。常见于数据库刷盘、文件系统 sync、NFS 卡住 |
Z | Zombie,僵尸 | Z | 子进程已退出但父进程未调用 wait() 回收 |
T | Stopped,被暂停 | T | Ctrl+Z 暂停、或收到了 SIGSTOP |
ps -eo pid,stat,wchan:30,cmd | grep '^ *[0-9]* D' 查看 D 状态进程阻塞在哪个内核函数(wchan 列),再对照排查。# 查看 D 状态进程及其阻塞点
ps -eo pid,stat,wchan:30,cmd | awk '$2 ~ /D/'
# 输出: 4567 D rpc_wait_bit_killable mount.nfs ...
# wchan 显示 rpc_wait_bit_killable → 进程卡在 NFS 请求上,检查 NFS 服务器
# ps 附加标志(STAT 列的第二三个字母)
# s = 会话领导者(session leader,通常是登录 Shell)
# + = 前台进程组(在前台运行)
# l = 多线程进程
# < = 高优先级(负 nice 值);N = 低优先级(正 nice 值)
ps aux | awk '$8 ~ /[D]/' # 筛选任意 D 状态(包含 Ds、Dl 等)
2. ps——进程快照查看
最常用的 ps aux(BSD 风格):
# ps aux 输出详解
ps aux
# 输出: USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
# 输出: root 1 0.0 0.1 168960 11960 ? Ss 14:30 0:01 /sbin/init
# 输出: root 256 0.0 0.0 41672 5940 ? Ss 14:30 0:00 /lib/systemd/systemd-journald
# 输出: alice 1234 0.5 2.0 412456 52348 ? Ss 14:30 0:15 gnome-shell
# USER PID %CPU %MEM VSZ RSS TT STAT STARTED TIME COMMAND
# alice 1234 0.5 2.0 412456 52348 ? Ss 10:00 0:15.23 gnome-shel
# ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑
# │ │ │ │ │ │ │ │ │ └── 命令
# │ │ │ │ │ │ │ │ └── 累计 CPU 时间
# │ │ │ │ │ │ │ └── 启动时间
# │ │ │ │ │ │ └── 进程状态
# │ │ │ │ │ └── 常驻内存大小 (RSS, KB)
# │ │ │ │ └── 虚拟内存大小 (VSZ, KB)
# │ │ │ └── 物理内存占比
# │ │ └── CPU 占比
# │ └── 进程 ID
# └── 用户
# 常用 ps 命令
ps aux # 所有进程(最常用)
ps auxf # 树形显示进程父子关系
ps aux --sort=-%cpu | head -10 # CPU 占用 Top 10
# 输出: USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
# 输出: firefox 3456 45.0 15.0 3456789 456789 ? Rl 14:30 45:23 /usr/lib/firefox
# 输出: ...
ps aux --sort=-%mem | head -10 # 内存占用 Top 10
ps -eLf # 显示线程(-L)
ps -p 1234 -o pid,ppid,cmd,%cpu # 自定义输出列
# 输出: PID PPID CMD %CPU
# 输出: 1234 1233 /usr/bin/gnome-shell 0.5
# 经典进程查看组合
ps aux | grep nginx # 查找 nginx 进程
# 输出: root 890 0.0 0.0 12345 6789 ? Ss 14:30 0:00 nginx: master process
# 输出: www-data 891 0.0 0.1 12345 7890 ? S 14:30 0:00 nginx: worker process
ps aux | grep -v grep | grep nginx # 排除 grep 自身(更干净)
pgrep nginx # 只显示 PID
# 输出: 890
# 输出: 891
pgrep -a nginx # PID + 命令
# 输出: 890 nginx: master process
# 输出: 891 nginx: worker process
3. 信号机制与 kill——如何温和/粗暴地终止进程
信号(Signal)是 Linux 进程间通信的一种方式。内核或另一个进程可以向目标进程发送信号,告诉它"该做某事了"。
| 信号 | 编号 | 含义 | 默认行为 |
|---|---|---|---|
| SIGHUP | 1 | 终端挂起/断开 | 终止进程 |
| SIGINT | 2 | 键盘中断 (Ctrl+C) | 终止进程 |
| SIGTERM | 15 | 终止信号(默认 kill) | 终止(可捕获,进程可清理后退出) |
| SIGKILL | 9 | 强制杀死(最后手段) | 终止(不可捕获、不可阻塞) |
| SIGSTOP | 19 | 暂停进程(不可阻塞) | 暂停执行 |
| SIGCONT | 18 | 继续已暂停的进程 | 继续执行 |
# kill —— 发送信号
kill 1234 # 发送 SIGTERM(15),请进程自行退出
# 输出: (无输出,如果成功发送信号)
kill -15 1234 # 同上,显式指定 SIGTERM
kill -9 1234 # SIGKILL——强制杀死(进程没有机会清理)
kill -SIGTERM 1234 # 用名称指定信号
# pkill —— 按名称发送信号
pkill nginx # 向所有名为 nginx 的进程发送 SIGTERM
pkill -9 nginx # 强制杀死所有 nginx 进程
pkill -u alice # 杀死 alice 的所有进程
# killall —— 按名称杀死(比 pkill 更精确,默认 SIGTERM)
killall nginx
# 常用模式:先温和再暴力
kill 1234 # 第一次:给进程清理的机会
sleep 3 # 等 3 秒
kill -9 1234 # 还没死?强制结束
| 终止方式 | 命令示例 | 作用范围 | 适用场景 |
|---|---|---|---|
kill PID | kill 1234 | 指定单个进程 | 精确终止已知 PID 的进程 |
kill -9 PID | kill -9 1234 | 指定单个进程(强制) | SIGTERM 无效时的最后手段 |
pkill 名称 | pkill nginx | 按进程名匹配所有 | 批量终止同名进程 |
pkill -u 用户 | pkill -u alice | 按用户匹配所有进程 | 清理某用户的全部进程 |
killall 名称 | killall nginx | 按进程名精确匹配 | 比 pkill 更精确(不匹配部分名称) |
4. 作业控制——前后台切换
Shell 的作业控制让你可以在一个终端中同时管理多个任务。
# 后台运行:命令末尾加 &
sleep 300 &
# [1] 12345 ← [作业号] PID
# 输出: [1] 12345
# 查看后台作业
jobs
# [1]- Running sleep 300 &
# [2]+ Running find / -name "*.conf" 2>/dev/null &
# 输出: [1]- Running sleep 300 &
# 输出: [2]+ Running find / -name "*.conf" 2>/dev/null &
# 把后台作业调到前台
fg %1 # 将作业 1 调到前台
# 输出: sleep 300
fg # 将最近的后台作业调到前台
# 暂停前台进程
# 在前台运行的程序按 Ctrl+Z
# 恢复暂停的作业到后台
bg %1 # 作业 1 在后台继续运行
# 输出: [1]+ sleep 300 &
# 完整的生命周期
# 1. 前台运行程序(没有 &)
# 2. 按 Ctrl+Z 暂停(变成 Stopped)
# 3. bg 让它在后台继续运行
# 4. fg 把它调回前台
# 5. Ctrl+C 终止
# 如果终端关闭后想让进程继续运行:
# 方案一:nohup
nohup long-running-script.sh &
# 输出: nohup: ignoring input and appending output to 'nohup.out'
# 忽略 SIGHUP 信号,输出写入 ~/nohup.out
# 方案二:disown
long-running-script.sh &
# 输出: [3] 12346
disown # 将作业从 Shell 的作业表中移除,关闭终端不会发 SIGHUP
# 输出: (无输出)
# 方案三:tmux 或 screen(最推荐)
tmux new -s mysession # 创建会话
# 输出: (进入 tmux 会话)
# 在会话中运行程序
# Ctrl+B D 断开(detach),程序继续运行
tmux attach -t mysession # 重新连接
5. 进程优先级——nice 和 renice
Linux 的 CPU 调度器根据 nice 值决定为每个进程分配多少 CPU 时间。
# nice 值范围:-20(最高优先级)到 19(最低优先级),默认 0
# 启动时设置优先级(nice)
nice -n 10 ./backup.sh # 以低优先级运行(不影响其他进程)
# 输出: (执行 backup.sh)
nice --10 ./urgent.sh # 以高优先级运行(需要 root)
# 修改运行中进程的优先级(renice)
renice -n 5 -p 1234 # 将 PID 1234 的 nice 设为 5
# 输出: 1234 (process ID) old priority 0, new priority 5
sudo renice -n -5 -p 1234 # 设为高优先级(需要 root)
# 输出: 1234 (process ID) old priority 5, new priority -5
# 查看进程的 nice 值
ps -eo pid,ni,cmd | grep backup
# 输出: 12345 10 ./backup.sh
# top 中 NI 列显示 nice 值
CFS 调度器原理——nice 值到底怎么起作用:现代 Linux 使用 CFS(Completely Fair Scheduler,完全公平调度器)。它不是按优先级"排队执行",而是给每个进程计算一个"虚拟运行时间"(vruntime),每次调度永远选择 vruntime 最小的进程运行。nice 值通过权重改变 vruntime 的增长速度:
# nice 值与权重的换算关系(内核维护的查找表,部分数据):
# nice -20 → 权重 88761(vruntime 增长最慢,获得最多 CPU)
# nice 0 → 权重 1024(基准)
# nice 19 → 权重 15(vruntime 增长最快,几乎拿不到 CPU)
# 直观理解:
# 一个 nice=0 的进程和一个 nice=10 的进程同时就绪,
# nice=0 的进程大约获得 10 倍于 nice=10 进程的 CPU 时间。
# 实操:观察 nice 值对 CPU 分配的影响
# 启动两个相同的计算任务,一个正常、一个低优先级
sha1sum /dev/zero & # nice=0,占满一个核
# 输出: [7] 15000
nice -n 19 sha1sum /dev/zero & # nice=19,几乎让出 CPU
# 输出: [8] 15001
top -b -n 1 | grep sha1sum
# 输出: 15000 alice 20 0 13100 456 120 R 99.7 0.0 0:30.12 sha1sum
# 输出: 15001 alice 39 19 13100 456 120 R 0.3 0.0 0:00.05 sha1sum
# 第一个进程 99.7% CPU,第二个只有 0.3%——这就是权重的效果
# 关于调度器的几个事实:
# 1. nice 值只影响 CPU 时间分配,不影响内存、I/O 等其他资源
# 2. 负 nice 值(高优先级)需要 root 权限
# 3. 实时调度(SCHED_FIFO/SCHED_RR)与 nice 无关,用于嵌入式/音频等场景
6. 僵尸进程和孤儿进程
僵尸进程(Zombie):子进程已终止,但父进程没有调用 wait() 读取其退出状态。僵尸进程不占用内存或 CPU,只占用内核进程表中的一项(有限资源)。如果僵尸进程大量堆积,系统可能无法创建新进程。
孤儿进程(Orphan):父进程先于子进程终止。孤儿子进程会被 PID 1(systemd)收养,成为"过继给 systemd 的孤儿"。
# 查找僵尸进程
ps aux | grep -w Z
# 输出: alice 9999 0.0 0.0 0 0 ? Z 14:30 0:00 [sh]
ps -eo pid,stat,cmd | grep "^.* Z"
# 输出: 9999 Z [sh]
# 僵尸进程不能被 kill -9 杀死——因为它已经"死了"。
# 唯一的办法是:杀死它的父进程(让其父进程回收僵尸)
实战:制造一个僵尸进程并清理它。僵尸进程产生的根本原因是父进程没有调用 wait() 读取子进程退出状态。用一小段 C 代码可以完整演示:
# 1. 写一个不回收子进程的程序
cat > /tmp/zombie.c <<'EOF'
#include <unistd.h>
#include <stdio.h>
int main() {
if (fork() == 0) {
printf("child pid: %d\n", getpid());
_exit(0); // 子进程立即退出
}
sleep(60); // 父进程睡 60 秒,期间不调用 wait()
return 0;
}
EOF
gcc -o /tmp/zombie /tmp/zombie.c
# 2. 运行它,子进程 3 秒后退出变僵尸
/tmp/zombie &
# 输出: child pid: 15123
sleep 3
ps -eo pid,ppid,stat,cmd | grep -w Z
# 输出: 15123 15122 Z [zombie] <defunct>
# Z 状态确认,PPID 是父进程 15122
# 3. 清理方法一:杀死父进程,僵尸由 init(systemd)收养并回收
kill 15122
ps -eo pid,stat,cmd | grep -w Z || echo "僵尸已清除"
# 输出: 僵尸已清除
# 4. 如果父进程是系统服务(如 sshd),不能杀——需要排查服务 bug
# 或者用 systemd 的 KillMode 重启服务:
# sudo systemctl restart sshd # 服务重启后其所有子进程被回收
# 5. 生产环境发现大量僵尸:先确认父进程是谁
ps -eo ppid,stat | awk '$2 ~ /Z/' | sort | uniq -c | sort -rn
# 输出: 87 789
# 87 个僵尸进程的父进程都是 789 → 排查 789 这个进程为什么不回收子进程
ps -p 789 -o pid,cmd
# 输出: 789 nginx: master process
7. 进程间通信(IPC)——进程之间怎么协作
每个进程有独立的内存空间,进程之间交换数据必须通过内核提供的 IPC 机制。本章的信号(signal)只是其中一种。
| 方式 | 速度 | 适用场景 | 例子 |
|---|---|---|---|
| 管道(pipe) | 快 | 父子进程间单向字节流 | ls | grep conf(Shell 管道就是 IPC) |
| 信号(signal) | 快 | 通知事件,不传数据 | kill -USR1 nginx 让 nginx 重开日志 |
| 共享内存(shm) | 最快 | 大量数据高频读写(配合信号量同步) | 数据库缓冲池、消息队列中间件 |
| 消息队列(msg) | 快 | 进程间传递结构化消息 | 传统微服务内部通信 |
| Socket | 中/慢 | 任意主机间通信(本机可用 Unix Socket 加速) | Nginx 与 PHP-FPM 的 unix:// 通信 |
# 查看系统当前 IPC 资源
ipcs -m # 共享内存段
# 输出: key shmid owner perms bytes nattch
# 输出: 0x00000000 12345 mysql 600 8388608 2
ipcs -q # 消息队列
ipcs -s # 信号量
# 查看哪个进程在使用共享内存(排查"内存去哪了")
lsof /dev/shm 2>/dev/null # /dev/shm 是 POSIX 共享内存的挂载点
# 输出: COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
# 输出: mysql 890 mysql DEL REG 0,32 8388608 12345 /dev/shm/xxx
# DEL 表示已删除但仍被进程持有的段(数据仍占用内存)
df -h /dev/shm
# 输出: Filesystem Size Used Avail Use% Mounted on
# 输出: tmpfs 7.8G 1.2G 6.6G 16% /dev/shm
8. systemd 与进程——服务化后的进程管理
现代 Linux 上,后台服务进程大多由 systemd 启动。用 systemd 的视角管理进程,比直接 kill 更优雅:
# 查看某服务管理的所有进程
systemctl status nginx
# 输出: ● nginx.service - A high performance web server...
# 输出: Active: active (running)
# 输出: CGroup: /system.slice/nginx.service
# 输出: ├─890 "nginx: master process"
# 输出: ├─891 "nginx: worker process"
# 输出: └─892 "nginx: worker process"
# CGroup 段列出该服务进程组的所有成员
# 优雅重启(不中断连接,nginx 会 fork 新进程替换旧的)
sudo systemctl reload nginx
# systemd-cgls —— 以树形查看整个 cgroup 层级
systemd-cgls --no-pager | head -30
# 输出: Control group /:
# 输出: ├─user.slice
# 输出: │ └─user-1000.slice
# 输出: │ └─session-3.scope
# 输出: │ └─1235 /bin/bash
# 输出: └─system.slice
# 输出: ├─nginx.service
# 输出: ├─sshd.service
# 输出: └─cron.service
# systemd 通过 cgroup 实现的资源限制(cgroup v2)
sudo systemctl set-property nginx.service CPUWeight=200
sudo systemctl set-property nginx.service MemoryMax=512M
systemctl show nginx.service -p MemoryMax -p CPUWeight
# 输出: MemoryMax=536870912
# 输出: CPUWeight=200
# 服务异常时的处理顺序(与直接 kill 对比)
sudo systemctl status mysqld # 看日志与状态
sudo systemctl restart mysqld # 正常重启
sudo systemctl kill -s KILL mysqld # 服务不响应时的强制手段
9. 进程监控实战——pidstat/pstree/htop 组合排查
把本章工具组合起来,走一遍"系统突然变慢"的标准排查流程:
# 第 1 步:整体判断——uptime + top 看负载构成
uptime
# 输出: 10:30:00 up 3 days, 2:31, 3 users, load average: 8.50, 7.20, 5.10
# 8 核机器 load 8.5 → CPU 过载,找凶手
# 第 2 步:pidstat 锁定嫌疑进程(比 top 更适合脚本化)
pidstat 1 5
# 输出: 10:30:01 1000 1234 45.0 3.0 0.0 48.0 2 java
# 输出: 10:30:02 1000 1234 46.2 2.8 0.0 49.0 2 java
# java 进程稳定吃掉近半个 CPU?继续深挖
# 第 3 步:pstree 看进程关系(确认是哪个服务的子进程)
pstree -p | grep java
# 输出: |-java(1234)-+-{java}(15001)
# 输出: |-{java}(15002)
# 输出: `-{java}(15003)
# 是哪个服务的 java?用 systemctl status 或 lsof -p 1234 查
# 第 4 步:htop 树形视图 + 过滤(交互式深挖)
htop
# F5 树形视图 → F4 输入 java 过滤 → F6 按 CPU% 排序
# 观察线程:按 H 显示线程,找到具体热点线程 TID
# 第 5 步:处理
# 如果是业务高峰,记录基线即可;如果是异常,按本章「信号机制与 kill」的信号流程处理
示例代码
# 练习 1:查看进程
ps aux | head -10
# 输出: USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
# 输出: root 1 0.0 0.1 168960 11960 ? Ss 14:30 0:01 /sbin/init
# 输出: ...
pstree -p | head -15
# 输出: systemd(1)─┬─systemd-journal(256)
# 输出: ├─sshd(789)───sshd(1234)───bash(1235)
# 练习 2:前后台作业
sleep 100 &
# 输出: [1] 12345
jobs
# 输出: [1]+ Running sleep 100 &
fg %1 # 调到前台
# 输出: sleep 100
# 按 Ctrl+Z 暂停
bg # 在后台继续
# 输出: [1]+ sleep 100 &
jobs
# 输出: [1]+ Running sleep 100 &
# 练习 3:信号和 kill
sleep 500 &
# 输出: [2] 12346
kill $! # $! = 上一个后台进程的 PID
# 输出: [2]+ Terminated sleep 500
# 另一个方式
sleep 500 &
# 输出: [3] 12347
PID=$!
echo "PID is $PID"
# 输出: PID is 12347
kill -15 $PID
# 练习 4:nohup
nohup sleep 300 &
# 输出: nohup: ignoring input and appending output to 'nohup.out'
# 输出: [4] 12348
# 关闭终端重新打开,用 ps aux | grep sleep 查看是否还在运行
# 练习 5:nice 和 renice
nice -n 15 find / -name "*.conf" 2>/dev/null &
# 输出: [5] 12349
ps -eo pid,ni,cmd | grep find
# 输出: 12349 15 find / -name *.conf
renice -n 10 -p $(pgrep -f "find / -name") 2>/dev/null
# 练习 6:处理挂起的进程
# 运行 top → 不退出 top,按 Ctrl+Z 暂停
jobs
# 输出: [6]+ Stopped top
bg %6 # top 在后台运行
fg %6 # 把它调回前台
# 按 q 退出
常见错误
| 错误/误区 | 解决方案 |
|---|---|
| "kill -9 是杀死进程的唯一方式" | 优先用 kill(SIGTERM,信号 15)——它让进程有机会清理资源、保存数据。只有等了 5-10 秒后进程还没死,才用 kill -9。 |
| "ps aux | grep 进程名 结果包含了 grep 自身" | 加 | grep -v grep 排除。或用 pgrep 进程名、ps -C 进程名。 |
| "终端关闭后后台进程就死了" | Shell 后台进程(&)在终端关闭时会收到 SIGHUP 信号而终止。用 nohup、disown、或 tmux 让进程独立于终端运行。 |
| "看到 Z 状态的进程以为是什么严重问题" | 少量僵尸进程通常无害。它们只是等待父进程回收的"已终止进程"。但如果大量堆积(100+),说明父进程有 bug——需要排查父进程为什么不调用 wait()。 |
"以为 kill -9 0 是杀死 PID 0" | kill -9 0 会杀死当前进程组的所有进程(包括当前 Shell!)。PID 0 是内核的 idle 进程,不能用 kill 杀死。 |
最佳实践
| # | 建议 | 说明 |
|---|---|---|
| 1 | 先 SIGTERM,后 SIGKILL | 始终给进程优雅退出的机会。SIGKILL 是核武器——用了就没有挽回余地。 |
| 2 | 用 pgrep 确认 PID 后再 kill | pgrep -a nginx 先列出 PID 和命令,确认目标正确后再 pkill nginx。 |
| 3 | 长时间运行的任务用 tmux | tmux 在后台保持会话,即使 SSH 断开也能重新连接。比 nohup 更灵活(可以随时 reconnect 查看输出)。 |
| 4 | 后台任务放特定目录运行 | 对于后台进程,先 cd /tmp 或专用工作目录再运行,避免"被占用目录无法卸载"的问题。 |
| 5 | 用 ulimit 限制进程资源 | 如果某程序失控(内存泄漏、fork 炸弹),ulimit -u 100 限制最大进程数,ulimit -v 1048576 限制虚拟内存。 |
练习题
- (概念)SIGTERM(15)和 SIGKILL(9)有什么区别?为什么优先用 SIGTERM?
- (概念)僵尸进程和孤儿进程有什么区别?各自如何产生?
- (实操)启动一个
sleep 300后台进程,用jobs查看,用fg调到前台,按 Ctrl+Z 暂停,用bg恢复到后台,最后用kill终止它。 - (实操)使用
ps aux --sort=-%mem | head -10找出当前系统内存占用最高的 10 个进程。 - (实操)用
nohup启动一个长时间运行的任务(如find / -type f 2>/dev/null &>),然后关闭终端重新打开,用ps确认进程还在运行。 - (探究 🔍)创建两个进程:
sleep 300 &和nohup sleep 300 &。用ps -o pid,comm,sid查看它们的 SID(会话 ID)。理解为什么 SIGHUP 会杀死作业而不杀死 nohup 进程。 - (探究 🔍)用
strace -p PID(需要 sudo)跟踪一个进程的系统调用。先找一个无害的进程(如sleep 500),观察 strace 输出。用 Ctrl+C 停止跟踪。
点击查看答案
- (概念)SIGTERM(15)允许进程捕获信号并执行清理(保存数据、释放资源)后优雅退出。SIGKILL(9)强制内核立即终止进程,无法被捕获、忽略或处理。优先用 SIGTERM 给进程优雅终止的机会,避免数据丢失或资源泄漏。
- (概念)僵尸进程:子进程已终止但父进程未调用 wait() 获取退出状态,进程表条目残留。孤儿进程:父进程先于子进程退出,子进程被 init(systemd,PID 1)收养。僵尸消耗进程表条目但无内存负担;孤儿不会造成资源泄漏。
- (实操)`sleep 300 &` → `jobs` → `fg %1` → Ctrl+Z(暂停)→ `bg %1`(后台继续)→ `kill %1`。完整练习了作业控制四步曲。验证:`ps aux | grep sleep` 确认进程已终止。
- (实操)`ps aux --sort=-%mem | head -10` 输出内存占用前十进程。关注 %MEM 列和 RSS 列(物理内存占用)。若发现某进程异常占用过大内存,用 `top -p PID` 持续监控该进程。
- (实操)`nohup find / -type f 2>/dev/null &`。关闭终端重新打开后 `ps aux | grep find` 确认进程继续运行。nohup 的作用是忽略 SIGHUP 信号。输出默认写入当前目录的 nohup.out。
- (探究 🔍)`sleep 300 &` 是当前会话的作业,终端关闭时收到 SIGHUP。`nohup sleep 300 &` 设置了忽略 SIGHUP。`ps -o pid,comm,sid` 显示两者 SID(会话 ID)相同,但 nohup 的进程信号掩码不同。理解 SIGHUP → 会话 → 作业的关系。
- (探究 🔍)终端 A 启动 `sleep 500 &`,记下 PID。终端 B 执行 `sudo strace -p PID`。strace 跟踪系统调用(如 rt_sigprocmask、nanosleep)。strace 是诊断"程序卡在哪里"的利器。按 Ctrl+C 安全退出。
学习检查点
学完本章后,请检验自己是否掌握以下内容:
| 检查项 | 自测问题 | 验证方法 |
|---|---|---|
| 概念理解 | 能用自己的话解释进程状态(R/S/D/Z/T)的含义 | 尝试向他人讲解 |
| 命令操作 | 能不查文档使用 ps、kill、jobs、nohup、nice 管理进程 | 在终端实际执行 |
| 原理掌握 | 能说出 SIGTERM 和 SIGKILL 的区别以及信号机制的工作原理 | 画出信号处理流程图 |
| 故障排查 | 能独立排查和处理僵尸进程(Z 状态)问题 | 模拟故障并修复 |
| 最佳实践 | 能说明为什么应该先用 kill 而不是 kill -9 终止进程 | 对比不同终止方式 |
本章总结
进程管理是 Linux 系统管理的核心操作。你学会了用 ps 查看进程、用 kill 发送信号控制进程、用 jobs/fg/bg 管理前后台作业、用 nohup 让进程脱离终端运行、用 nice/renice 调整进程优先级。理解僵尸进程和孤儿进程能帮你在排查系统异常时"看到"这些特殊状态。
速查表
| 概念 | 一句话定义 |
|---|---|
| PID / PPID | 进程 ID 和父进程 ID |
| ps aux | 查看所有进程的快照(BSD 风格) |
| 信号(Signal) | 进程间通信方式,通知进程执行某种操作 |
| kill / kill -9 | 发送 SIGTERM(优雅终止)/ SIGKILL(强制杀死) |
| jobs / fg / bg | Shell 作业控制:查看、调前台、调后台 |
| nohup | 忽略 SIGHUP,终端关闭后继续运行 |
| nice 值 | 进程优先级(-20 最高,19 最低) |
| 僵尸进程 | 已终止但未回收的进程(Z 状态) |
下一步:进入 2.5:时间管理与日志「时间管理与日志」,学习系统时间同步和日志管理。
延伸阅读
- 命令:
strace——跟踪进程的系统调用和信号,深入理解进程行为 - 命令:
lsof——列出进程打开的文件(List Open Files),排查"哪个进程在占用这个文件" - 命令:
tmux——终端复用器,比 nohup 更强大的会话管理工具 - 文章:搜索 "Linux process states" 了解更详细的进程状态(包括 D、S、R、T、Z、X)