Linux 进程管理:ps、top、htop、kill、信号与 nice
在 Linux 中,ps aux 给出进程的一次性快照,top 和 htop 则是动态刷新的进程列表并显示负载。结束进程使用 kill PID,默认发送温和的 SIGTERM;只有进程不响应时才使用 kill -9(SIGKILL)。优先级通过启动时的 nice 设置,或对运行中的进程使用 renice 调整,取值范围为 -20 到 19。
Linux 中每个正在运行的程序都是一个进程,拥有自己的 PID 编号、所有者、状态和优先级。使用 ps、top 和 htop 可以查看进程,使用 kill 结束进程,使用 nice 和 renice 调整它们的优先级。下面介绍如何读懂这些命令的输出、什么时候发送哪种信号,以及为什么 kill -9 不应该是第一反应。
什么是进程和 PID
进程是正在执行的程序:包括代码、内存、打开的文件以及一组属性。内核为每个进程分配唯一的标识符 PID,并记录 PPID——启动它的父进程编号。系统中第一个进程 PID 为 1,通常是 systemd,它是所有其他进程的“祖先”。
新进程的产生分两步。首先父进程调用 fork(),得到一份自己的副本;然后副本调用 execve(),把自己的代码替换为新程序的代码。当你在终端中输入 ls 时,bash 正是这样做的,并通过 wait() 等待子进程结束。
$ echo $$ # PID 当前 shell
4182
$ ps -o pid,ppid,cmd -p $$
PID PPID CMD
4182 4170 -bash
用 ps 查看进程
ps 只生成一次快照:显示命令执行那一刻的进程,然后退出。如果不带参数,它只显示当前终端的进程,因此实际中几乎总是使用下面两种写法之一。
$ ps aux | head -5
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.0 0.3 168540 12780 ? Ss 09:12 0:03 /sbin/init
root 2 0.0 0.0 0 0 ? S 09:12 0:00 [kthreadd]
www-data 1532 1.2 2.1 412308 86420 ? S 09:15 1:47 php-fpm: pool www
postgres 1610 0.4 3.8 221004 154312 ? Ss 09:15 0:31 postgres: checkpointer
其中 a 表示显示所有用户的进程,u 表示带所有者的详细格式,x 表示包括没有终端的进程(守护进程)。System V 风格的写法是 ps -ef,它会显示 PPID,便于了解谁启动了谁。
| 列 | 含义 |
|---|---|
PID | 进程标识符 |
%CPU、%MEM | 占用处理器和物理内存的比例 |
VSZ | 虚拟内存,单位 KB(包括未加载到内存中的部分) |
RSS | 实际占用的物理内存,单位 KB |
STAT | 进程状态(R、S、D、Z、T)及附加标志 |
TIME | 累计占用的 CPU 时间,而不是运行时长 |
ps 的实用技巧
# 内存占用前 10
ps aux --sort=-%mem | head -11
# CPU 占用前 10
ps aux --sort=-%cpu | head -11
# 自定义列
ps -eo pid,ppid,user,ni,stat,etime,cmd
# 进程树
ps -ef --forest
pstree -p
# 按名称查找 PID
pgrep -a nginx
pidof nginx
常见错误是用 ps aux | grep nginx 查找进程,结果输出中还包含 grep 自己。请使用 pgrep,或者使用 grep [n]ginx 这种写法,它不会与自身的命令行匹配。
进程状态:R、S、D、Z、T
STAT 列中的字母不是噪声,而是进程的真实状态。它决定了如何理解负载,以及 kill 是否真的有效。
| 代码 | 状态 | 实际含义 |
|---|---|---|
R | Running | 正在运行或在 CPU 队列中等待 |
S | Interruptible sleep | 等待事件(网络、输入、定时器);大多数进程处于此状态 |
D | Uninterruptible sleep | 在内核中等待 I/O,无法接收信号 |
Z | Zombie | 已结束,但父进程还没有取回其退出码 |
T | Stopped | 被 SIGSTOP 信号或 Ctrl+Z 暂停 |
字母后面的附加符号也有用:s 表示会话首进程,+ 表示终端前台进程,< 表示优先级较高,N 表示优先级较低,l 表示多线程。
处于 D 状态的进程
处于 D 状态的进程正在系统调用内部等待,例如等待磁盘或失去响应的 NFS 的回复。它不会接收任何信号,即使是 SIGKILL。因此在这里 kill -9 毫无作用:应当查找等待的原因——查看 iostat、dmesg 以及网络存储的状态。
僵尸进程
僵尸进程已经死亡:它不占用 CPU 和内存,只在进程表中占一行。无法杀死它,因为它本来就已经结束了。它会在父进程调用 wait() 之后消失,或者在父进程本身结束、僵尸被 PID 1 收养之后消失。
# 查找僵尸进程及其父进程
ps -eo pid,ppid,stat,cmd | awk '$3 ~ /Z/'
单个僵尸进程无害。如果它们有几百个并且数量还在增长,说明父程序有问题:需要修复或重启它。
top:实时查看进程和负载
top 显示同样的列表,但每隔几秒刷新一次,并给出系统的汇总信息。当“服务器变慢”时,这通常是第一个运行的命令。
top - 14:21:07 up 12 days, 3:02, 2 users, load average: 1.84, 1.20, 0.95
Tasks: 213 total, 2 running, 211 sleeping, 0 stopped, 0 zombie
%Cpu(s): 23.1 us, 4.2 sy, 0.0 ni, 70.3 id, 1.9 wa, 0.0 hi, 0.5 si, 0.0 st
MiB Mem : 7821.4 total, 412.6 free, 3105.8 used, 4303.0 buff/cache
MiB Swap: 2048.0 total, 2048.0 free, 0.0 used. 4398.2 avail Mem
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
1532 www-data 20 0 412308 86420 14212 R 38.2 1.1 1:47.31 php-fpm
1610 postgres 20 0 221004 154312 148900 S 6.0 1.9 0:31.08 postgres
最重要的是 %Cpu(s) 这一行:us 表示用户代码,sy 表示内核,id 表示空闲,wa 表示等待磁盘。处理器空闲而 wa 很高,说明瓶颈在 I/O 上。
top 的快捷键
| 按键 | 作用 |
|---|---|
P / M | 按 CPU / 按内存排序 |
1 | 分别显示每个 CPU 核心 |
c | 显示完整的命令行 |
u | 按用户过滤 |
k | 向进程发送信号 |
r | 修改进程的 nice 值 |
H | 显示线程 |
q | 退出 |
对于脚本和日志,批处理模式很有用:top -b -n 1 | head -20 输出一次快照,无需交互。要跟踪某个特定进程,使用 top -p 1532。
Load Average 是队列,而不是百分比
load average 的三个数字分别是过去 1、5 和 15 分钟内的平均队列长度。在 Linux 中,处于 R 和 D 状态的进程会计入其中,而普通的睡眠 S 不计入。应当把 LA 与 CPU 核心数(nproc)比较:在 4 核机器上,数值为 4 表示满负荷,数值为 12 则说明队列是处理能力的三倍。
如果 LA 很高而处理器空闲,就去找处于 D 状态的进程:ps -eo stat,pid,cmd | grep '^D'。通常原因是慢速磁盘或网络存储。
htop:更方便的 top 替代品
htop 显示的内容相同,但带有彩色的 CPU 核心指示条,支持滚动、鼠标操作,并可以显示进程树。一般需要先安装:sudo apt install htop 或 sudo dnf install htop。
F3或/— 按名称搜索进程;F4— 过滤器,只显示匹配的进程;F5或t— “父进程—子进程”树形视图;F6— 选择排序列;F7/F8— 降低或提高 nice 值;F9— 对选中进程发送信号的菜单;Space— 标记多个进程,进行批量操作。
在不能安装任何软件的服务器上,只用 top 就够了:它也包含所有关键操作。
如何结束进程:kill 和信号
kill 并不是“杀死”进程,而是向进程发送一个信号。接下来发生什么,取决于信号的种类以及程序如何处理它。
| 信号 | 编号 | 用途 |
|---|---|---|
SIGHUP | 1 | 终端断开;许多守护进程借此重新读取配置 |
SIGINT | 2 | 中断,与 Ctrl+C 相同 |
SIGKILL | 9 | 由内核强制结束,无法捕获 |
SIGTERM | 15 | 请求程序有序退出;kill 的默认信号 |
SIGSTOP | 19 | 暂停进程,无法捕获 |
SIGCONT | 18 | 继续已暂停的进程 |
SIGTSTP | 20 | 终端暂停,即 Ctrl+Z |
编号适用于 x86 和 ARM;完整列表可用 kill -l 查看。按名称指定信号比按编号更可靠。
kill 1532 # SIGTERM
kill -TERM 1532 # 同上,显式写出
kill -HUP $(pidof nginx) # 重新读取配置
kill -STOP 1532; kill -CONT 1532
kill -0 1532 && echo "进程仍在运行"
信号 0 不会发送任何东西,只检查进程是否存在,以及你是否有权向它发送信号——在脚本中很方便。
为什么不应该一上来就用 kill -9
SIGTERM 让程序有机会关闭文件、刷新缓冲区、删除锁文件并正确结束事务。SIGKILL 则是立即中断:未写完的数据会丢失,临时文件会残留,数据库在下次启动时可能需要恢复。
正确的顺序是:
- 发送
kill PID(SIGTERM)。 - 等待 5–30 秒,同时用
kill -0 PID检查。 - 只有进程仍然存活且不处于
D状态时,才使用kill -9 PID。
对于由 systemd 管理的服务,最好根本不手动操作 PID:systemctl stop 会自己发送 SIGTERM,等待超时后再发送 SIGKILL。
pkill 和 killall:按名称结束进程
pkill -f "python worker.py" # 按完整命令行匹配
pkill -u alice # 用户 alice 的所有进程
killall -TERM node # 所有名为 node 的进程
pgrep -l -f worker # 先确认哪些进程会被匹配
在进行大范围的 pkill 之前,务必先用相同的模式运行 pgrep:-f 会匹配命令行的任意部分,很容易误伤无关进程。
只能向自己的进程发送信号;对其他用户的进程则需要 sudo。错误信息 Operation not permitted 正是指这种情况。
进程优先级:nice 和 renice
nice 值告诉调度器应当让出多少处理器时间。范围从 -20(优先级最高)到 19(让给所有人),默认值为 0。在 top 中它显示在 NI 列,而 PR 是由它推导出的内部优先级。
# 以较低优先级启动耗资源的任务
nice -n 10 tar czf backup.tar.gz /var/www
# 降低正在运行进程的优先级
renice -n 15 -p 1532
# 提高优先级(仅 root)
sudo renice -n -5 -p 1610
# 某个用户的全部进程
sudo renice -n 10 -u alice
普通用户只能降低自己进程的优先级,负值只有 root 才能设置。需要明白 nice 的局限:它只影响 CPU。如果任务是通过磁盘拖慢系统的,应使用 ionice -c3 命令——idle 类别只有在磁盘空闲时才会获得磁盘访问。
后台进程与 shell 作业
在命令后加上 & 可以让它在后台运行,也可以按 Ctrl+Z 暂停。shell 会维护一个作业列表,由内置命令管理。
$ long_task &
[1] 5120
$ jobs
[1]+ Running long_task &
$ fg %1 # 调回前台
^Z # 暂停(SIGTSTP)
$ bg %1 # 在后台继续运行
关闭终端时,shell 会向作业发送 SIGHUP,它们随之结束。要让进程在退出后继续运行,可以使用 nohup 命令 &,或用 disown 将其与 shell 脱离,也可以使用 tmux。如果需要长期运行,最好把它做成 systemd 服务。
典型的诊断场景
谁占用了处理器
top -o %CPU # 或在 top 中按 P
ps -eo pid,user,%cpu,etime,cmd --sort=-%cpu | head
如果某个进程白白占用 CPU,先用 renice 降低它的优先级——这样做很安全,也能给你时间查明原因。
谁占用了端口
sudo ss -ltnp | grep :8080
sudo lsof -i :8080
输出中会给出 PID,之后可以在 /proc/PID/ 中找到可执行文件(exe)、工作目录(cwd)和参数(cmdline)。
进程意外消失
常见原因是 OOM Killer:内存不足时它会杀死进程。检查方法:dmesg -T | grep -i oom 或 journalctl -k | grep -i "killed process"。
总结
处理 Linux 进程只需要一小组命令:ps aux 用于快照,top 和 htop 用于观察,使用正确信号的 kill 用于结束进程,nice/renice 用于调整优先级。行动之前先看清进程的状态:僵尸进程和处于 D 状态的进程用信号解决不了问题。并且从 SIGTERM 开始,把 kill -9 留作最后手段。关于进程、内存、Load Average 和系统调用的更多内容,我们在 Linux 课程中有实际讲解。
常见问题
ps aux 和 ps -ef 有什么区别?
这是同一命令的两种语法:aux 是 BSD 风格,带有 %CPU、%MEM 和 STAT 列;-ef 是 System V 风格,带有 PPID 列。两者显示的进程集合相同,都是系统中的全部进程。
如何知道系统中运行了多少进程?
最简单的方法是查看 top 中的 Tasks 行,或执行 ps -e --no-headers | wc -l。线程数可以用 ps -eLf --no-headers | wc -l 查看。
为什么进程即使 kill -9 之后仍然没有结束?
要么它处于 D 状态,正在内核中等待 I/O;要么它是已经结束的僵尸进程。前一种情况需要排查磁盘或网络存储的问题,后一种则需要修复或重启父进程。
如果结束 PID 为 1 的进程会怎样?
内核保护 init:普通信号(包括用户发出的 SIGKILL)不会送达它,除非它自己处理这些信号。如果 init 真的退出了,系统会发生内核恐慌(kernel panic)。
如何限制进程使用的内存?
对于 systemd 服务,在 unit 文件中设置 MemoryMax=;对于一次性运行,使用 systemd-run --scope -p MemoryMax=500M 命令。它通过 cgroups 实现,比旧的 ulimit -v 更可靠。