教程

Linux 进程管理:ps、top、htop、kill、信号与 nice

更新于: · Skillaria.Top

在 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 是否真的有效。

代码状态实际含义
RRunning正在运行或在 CPU 队列中等待
SInterruptible sleep等待事件(网络、输入、定时器);大多数进程处于此状态
DUninterruptible sleep在内核中等待 I/O,无法接收信号
ZZombie已结束,但父进程还没有取回其退出码
TStopped被 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 并不是“杀死”进程,而是向进程发送一个信号。接下来发生什么,取决于信号的种类以及程序如何处理它。

信号编号用途
SIGHUP1终端断开;许多守护进程借此重新读取配置
SIGINT2中断,与 Ctrl+C 相同
SIGKILL9由内核强制结束,无法捕获
SIGTERM15请求程序有序退出;kill 的默认信号
SIGSTOP19暂停进程,无法捕获
SIGCONT18继续已暂停的进程
SIGTSTP20终端暂停,即 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 则是立即中断:未写完的数据会丢失,临时文件会残留,数据库在下次启动时可能需要恢复。

正确的顺序是:

  1. 发送 kill PID(SIGTERM)。
  2. 等待 5–30 秒,同时用 kill -0 PID 检查。
  3. 只有进程仍然存活且不处于 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 更可靠。

通过视频学习本主题

课程“Linux: с нуля до уверенного администрирования”结合实践详细讲解了这一主题。

前往课程