进程与任务管理
本文合并自:04-进程与任务管理.md、1-4-Linux进程管理.md、05-进程管理与压缩归档.md
适用环境:CentOS 7 / RHEL 7 为主,CentOS 6 / RHEL 6 的差异之处单独标注。
适用场景:日常运维排查、进程生命周期管理、CPU/内存资源调度、批量与后台任务执行、系统负载分析。
本章要点
- 理解程序与进程的区别,理解进程、PID、PPID,以及程序从 ELF 文件到运行进程的过程。
- 掌握
fork/exec进程创建模型,理解 1 号进程(init / systemd)的地位与作用。 - 掌握进程独立虚拟内存空间与进程间通信(IPC)机制。
- 掌握进程状态 R/S/D/T/t/Z/X 的含义,掌握孤儿进程、僵尸进程、守护进程的成因、危害与处理方法。
- 掌握
ps aux、ps -ef两种风格的字段含义与区别,以及top、htop、pstree、pgrep、pidof的使用。 - 掌握进程与文件的关联排查手段:
lsof、fuser、/proc/PID。 - 掌握信号机制,使用
kill、killall、pkill终止进程,理解trap信号捕捉与优雅停止。 - 掌握 NI 优先级范围与
nice、renice、ionice的使用,理解普通用户与 root 的权限差异。 - 理解进程组、会话的概念,使用
jobs、fg、bg、&、nohup、setsid、screen/tmux进行作业控制。 - 掌握系统负载与资源监控工具
uptime、vmstat、iostat、mpstat、sar、pidstat、free的输出解读。 - 具备常见进程类故障(CPU 100%、内存泄漏、僵尸进程、D 状态进程、OOM Killer)的排查思路。
范围说明:压缩归档
tar/gzip/unzip见05-01-软件包管理.md;计划任务crontab见04-02-计划任务与SSH等效性.md;内核参数与系统优化见04-03-Linux系统优化.md;systemd 服务管理见08-01-系统启动与服务管理.md。
一、进程基础
1.1 程序与进程
- 程序(Program):存放在磁盘上的、静态的可执行文件(指令与数据的集合),例如
/bin/cp。程序是"死"的,不消耗 CPU 与内存。 - 进程(Process):正在运行的程序实例,是"活"的。进程是操作系统进行资源分配和调度的基本单位,拥有独立的虚拟内存空间、文件描述符表、信号处理表等。
- 同一个程序可以同时对应多个进程。例如两个终端同时执行
vim,磁盘上只有一份/bin/vim程序,但系统中存在两个 vim 进程。
软件、程序、进程、线程的层次关系:
软件 -> 至少有一个程序 -> 至少有一个进程 -> 至少有一个线程
1.2 PID 与 PPID
- PID(Process ID):进程唯一标识符。
- PPID(Parent PID):父进程的 PID。
PID 的取值范围由 /proc/sys/kernel/pid_max 决定,默认为 32768(即最大 PID 为 32767);64 位系统可调大到 4194304(2^22)。
cat /proc/sys/kernel/pid_max # 查看当前 PID 上限
sudo sysctl -w kernel.pid_max=4194304 # 临时调大上限(64 位系统)
辨析(重要):部分早期教材称"PID 是 16 位正整数,取值范围 0–65535(2^16 = 65536)",这是不准确的说法。Linux 的 PID 上限由
pid_max决定,与 16 位短整型无关。另外,PID 0 是内核的 swapper / idle 任务,不是普通进程;普通用户态进程的 PID 从 1 开始分配。
查看自身与父进程的 PID:
echo $$ # 当前 Shell 自身的 PID
echo $PPID # 当前 Shell 的父进程 PID
echo $! # 最近一个放入后台的进程 PID
1.3 1 号进程(init / systemd)
PID 1 是内核启动的第一个用户态进程,是所有用户态进程的祖先,负责系统初始化以及收养孤儿进程、回收其退出状态。
| 系统版本 | 1 号进程 | 说明 |
|---|---|---|
| CentOS 6 | init(SysVinit) |
串行启动,依赖 /etc/inittab 与 /etc/rc.d/rc*.d 脚本 |
| CentOS 7 | systemd |
并行启动,使用 unit 管理服务,兼容 SysV 脚本 |
ps -p 1 -o pid,comm,args # 查看 1 号进程是谁
ls -l /proc/1/exe # CentOS 7 上指向 /usr/lib/systemd/systemd
1 号进程类似"民政局":任何进程的父进程先于它退出时,内核会把它的父进程重新设置为 1 号进程,由 1 号进程循环调用
wait()完成善后。
1.4 ELF 可执行文件分段
ELF(Executable and Linkable Format)是 Linux 下可执行文件的标准格式。
| 段名 | 作用 |
|---|---|
BSS |
存储未初始化的全局变量 |
DATA |
存储已初始化的全局变量 |
RODATA |
存储常量(只读数据段) |
TEXT |
存储指令集(代码段) |
查看 ELF 头信息:
readelf -h /bin/cp # 查看可执行文件的 ELF 头
readelf -l /bin/cp # 查看可执行文件的 ELF 头详细信息(段/程序头表)
file /bin/cp # 快速确认文件类型与位数
1.5 程序运行的过程
程序从磁盘加载到运行,大致经历以下步骤:
- 建立虚拟地址空间(分配一个页目录);
- 建立虚拟空间与可执行文件的映射(页目录项指向磁盘上的程序);
- 跳转到程序入口;
- 触发缺页异常;
- 在内存中寻找空闲页,将对应页换入;
- 建立映射,开始执行。
建立虚拟地址空间(分配页目录)
-> 建立虚拟空间与可执行文件映射
-> 跳转到程序入口
-> 触发缺页异常
-> 在内存中分配空闲页,将对应页换入
-> 建立映射
-> 开始执行
1.6 fork 与 exec:进程是怎么来的
除 1 号进程由内核直接创建外,Linux 中所有进程都由父进程通过 fork() 复制自身产生,再通过 exec() 族函数把自己"换成"另一个程序:
| 系统调用 | 作用 |
|---|---|
fork() |
复制当前进程,产生一个几乎完全相同的子进程。父进程中返回子进程 PID,子进程中返回 0 |
vfork() |
类似 fork,但不复制页表,子进程先运行,常紧接 exec 使用 |
clone() |
fork 的通用形式,可选择共享哪些资源,是创建线程的底层调用 |
exec() 族 |
用新的程序映像替换当前进程的地址空间。PID 不变,执行的程序改变 |
wait() / waitpid() |
父进程阻塞等待子进程结束,并取回其退出状态,完成资源回收 |
exit() |
进程主动结束,向父进程返回退出状态 |
Shell 执行一条命令的过程:
bash 收到命令
-> fork() 出一个子 bash
-> 子进程 exec() 加载目标程序(如 /bin/ls)
-> 父 bash 调用 wait() 等待子进程结束(前台执行)
-> 子进程 exit(),父 bash 取回退出状态($?),打印提示符
验证 fork/exec 的现象:
echo $$ # 记下当前 bash 的 PID
bash # 再开一个子 bash,PID 变了(fork)
echo $$ ; echo $PPID # 子 bash 的 PPID 即外层 bash 的 PID
exit # 退出子 bash
exec sleep 5 # exec 替换当前 Shell,5 秒后终端直接退出(PID 未变)
fork写时复制(COW):fork并不真正立刻复制父进程的全部内存,而是让父子进程共享同一批物理页并标记为只读,只有当某一方写入时才复制该页。因此fork的开销远小于想象。
1.7 独立虚拟内存空间与 IPC
每个进程拥有独立的虚拟内存空间,从而实现进程间的隔离。由于进程之间内存相互独立,需要通过内核提供的 IPC(Inter-Process Communication,进程间通信)机制进行交互:
| IPC 方式 | 作用与主要用途 |
|---|---|
| 信号 | 异常通知、事件触发 |
| 管道 | 基于 I/O 的进程间数据通信 |
| 套接字(socket) | 数据通信,多用于网络协议,也可用于本地进程间 |
| 消息队列 | 基于队列的数据通信,按队列方式传递消息 |
| 共享内存 | 将多个进程的虚拟地址映射到同一块物理内存 |
| 信号量(信号灯) | 并发处理,用于进程/线程的同步与互斥 |
ipcs # 查看系统中的共享内存、信号量、消息队列
ipcs -m # 只看共享内存段
ipcrm -m ID # 删除指定共享内存段
1.8 进程与线程的区别
- 进程:拥有独立内存空间,进程间通信需要借助 IPC。
- 线程:拥有独立的栈空间,但共享(依赖)所属进程的内存空间。
| 特性 | 进程 | 线程 |
|---|---|---|
| 内存空间 | 独立 | 共享所属进程空间 |
| 栈空间 | 进程内共享栈概念不同 | 每个线程独立栈空间 |
| 通信方式 | 需要 IPC | 可直接读写共享内存 |
| 创建开销 | 较大(需复制页表等) | 较小 |
| 崩溃影响 | 互不影响 | 一个线程崩溃可能拖垮整个进程 |
ps -eLf # 显示线程(LWP/NLWP 列)
ps -o pid,nlwp,comm -p PID # 查看某进程的线程数
top -H # top 中按线程展示
1.9 进程状态
进程从创建到销毁会在若干状态之间迁移,ps 的 STAT 列与 top 的 S 列显示的就是当前状态。
| 状态码 | 英文全称 | 含义 |
|---|---|---|
R |
Running / Runnable | 运行状态:正在 CPU 上执行,或在运行队列中等待被调度 |
S |
Interruptible Sleep | 可中断睡眠状态:等待某事件(如键盘输入、网络数据)完成,可被信号唤醒。系统中绝大多数进程处于此状态 |
D |
Uninterruptible Sleep | 不可中断睡眠状态:通常在等待磁盘 I/O 等内核操作完成,不响应信号,kill -9 也杀不掉 |
T |
Stopped | 停止/已暂停:收到 SIGSTOP/SIGTSTP(如 Ctrl + Z)后被挂起 |
t |
Tracing Stop | 被调试器(如 gdb、strace)跟踪而停止 |
Z |
Zombie | 僵尸进程:已退出但父进程尚未回收其退出状态 |
X |
Dead | 已彻底销毁,进程描述符被释放。该状态转瞬即逝,ps 基本看不到 |
状态迁移示意:
创建(fork)
|
v
+----> R 就绪/运行 <-----+
| | ^ |
| 等待 | | 事件完成 | SIGCONT
| I/O v | |
| S / D 睡眠 |
| |
+-- SIGSTOP/Ctrl+Z --> T 暂停
|
exit() 退出
v
Z 僵尸 --(父进程 wait)--> X 销毁
1.10 STAT 附加标志
ps 的 STAT 列除主状态字母外,还可能跟随附加标志:
| 标志 | 含义 |
|---|---|
< |
高优先级(NI 为负值) |
N |
低优先级(NI 为正值) |
s |
会话首进程(session leader) |
l |
多线程进程(multi-threaded) |
+ |
位于前台进程组 |
L |
有页面锁定在内存中(实时/加锁内存) |
示例:
Ssl+表示可中断睡眠、会话首进程、多线程且处于前台;T单独出现即为被Ctrl + Z暂停的任务,可用jobs -s查看,用bg/fg或kill -18恢复;R<表示正在运行且优先级被提高过。
1.11 父子进程与退出状态回收
在 UNIX/Linux 中,子进程通常由父进程创建,子进程还可再创建新进程。子进程的结束与父进程的运行是异步过程,父进程无法预测子进程何时结束。当子进程终止后,父进程需要调用 wait() 或 waitpid() 系统调用获取子进程的终止状态。
Linux 提供了一种机制,保证父进程只要想知道子进程的结束状态就一定能得到:
- 每个进程退出时,内核会释放其大部分资源,包括打开的文件、占用的内存等;
- 但仍保留少量信息(PID、运行时间、退出状态等)在进程描述符中;
- 这些信息直到父进程通过
wait()/waitpid()取走后才会释放。
1.12 孤儿进程
一个父进程退出,而它的一个或多个子进程仍在运行,这些子进程将成为孤儿进程(Orphan Process)。
- 孤儿进程会被 1 号进程收养(CentOS 6 为
init,CentOS 7 起为systemd),并由其完成状态收集工作; - 每当出现孤儿进程,内核会将其父进程设置为 PID 1,PID 1 会循环地
wait()已经退出的子进程; - 因此孤儿进程通常没有危害。
观察孤儿进程:
# 终端 A:让 bash 启动一个后台子进程,然后立刻退出这个 bash
bash -c 'sleep 300 &'
# 终端 B:查看 sleep 的 PPID,会发现已变为 1
ps -eo pid,ppid,stat,comm | grep sleep
1.13 僵尸进程
一个进程使用 fork 创建子进程后,如果子进程退出,而父进程并没有调用 wait() 或 waitpid() 获取子进程的状态信息,那么子进程的进程描述符仍会保留在系统中,这种进程称为僵尸进程(Zombie)。
机制说明:
任何子进程(PID 1 除外)在 exit() 之后都不会立即消失,而是留下僵尸进程的数据结构等待父进程处理,这是每个子进程结束时都要经过的阶段:
- 若父进程没来得及处理,使用
ps可看到子进程状态为Z; - 若父进程及时处理,可能
ps来不及看到僵尸状态,但不代表子进程没经历该阶段; - 若父进程在子进程结束前退出,子进程将由 PID 1 接管,由 PID 1 以父进程身份处理。
僵尸进程的危害:
- 内核会保留 PID、运行时间、退出状态等信息,进程号会一直被占用;
- 若父进程长期不处理,PID 资源被持续占用;
- 由于系统可用的进程号有限,大量僵尸进程会耗尽 PID,导致系统无法创建新进程。
处理僵尸进程:
要清除僵尸进程,只需将其父进程结束:父进程被杀死后,僵尸进程会被 PID 1 接管,转变为孤儿进程,再由 PID 1 完成状态回收。
# 1. 查找僵尸进程(简便方法)
ps -aux | grep Z
# 1'. 更精确的写法:只列出状态含 Z 的进程及其父进程
ps -eo pid,ppid,stat,comm | awk '$3 ~ /Z/ {print}'
ps -eo stat= | grep -c '^Z' # 统计僵尸进程个数
# 2. 结束其父进程,僵尸进程会被 PID 1 接管并回收
kill -9 父进程PID
注意:直接
kill -9僵尸进程通常无效,因为僵尸进程已经不再执行任何代码,应终止其父进程。生产环境中如果父进程是重要服务,不能随意杀掉,正确做法是修复程序(父进程应正确调用
wait(),或将SIGCHLD设为SIG_IGN),或对服务做一次滚动重启。
1.14 守护进程
守护进程(Daemon)是在后台长期运行、脱离控制终端、通常在系统启动时拉起并持续提供服务的进程,例如 sshd、crond、rsyslogd、httpd。
特征:
TTY列显示为?,表示不关联任何终端;- 父进程通常是 PID 1(
init/systemd); - 名称习惯以字母
d结尾; - 不受终端关闭(
SIGHUP)影响。
ps -ef | grep -E 'sshd|crond|rsyslogd' # 查看典型守护进程
ps -eo pid,ppid,tty,comm | awk '$3=="?"' # 列出所有不带终端的进程
守护进程的传统创建步骤(编程视角,了解即可):
fork()后父进程退出,使子进程成为孤儿并被 PID 1 收养;- 调用
setsid()创建新会话,脱离原控制终端; - 再次
fork(),确保进程不是会话首进程,从而不会重新获得控制终端; chdir("/")切换工作目录,避免占用挂载点;umask(0)重设文件权限掩码;- 关闭继承的文件描述符,把 0/1/2 重定向到
/dev/null。
在 Shell 层面,
nohup 命令 &与setsid 命令可以近似达到"脱离终端后台运行"的效果,详见第五章作业管理。CentOS 7 中规范的做法是把服务写成 systemd unit,参见08-01-系统启动与服务管理.md。
二、查看进程
2.1 ps:静态进程列表
ps(process status)用于查看当前系统中的进程快照(静态、非实时)。
命令格式:
ps [选项]
ps 同时支持三种风格的选项,容易混淆:
- UNIX(System V)风格:选项带一个连字符,如
ps -ef; - BSD 风格:选项不带连字符,如
ps aux; - GNU 长选项:选项带两个连字符,如
ps --sort=-%cpu。
ps -aux与ps aux在现代 procps 中效果基本一致(ps会提示按 BSD 风格解释),但严格来说-a/-u/-x与a/u/x语义不同。教学中两种写法都会见到,生产脚本中建议写ps aux或ps -ef。
2.1.1 常用选项
| 选项 | 作用 |
|---|---|
-a |
显示系统中与当前终端有关的进程信息(不含会话首进程) |
-e |
显示系统中所有的进程信息(可用 -A 替代) |
-f |
显示进程的所有信息(完整格式,full) |
-l |
以长格式显示进程信息(含 PRI、NI、WCHAN 等) |
-u |
以用户为主的进程状态;-u 用户名 表示按有效用户过滤 |
-x |
通常与 a 一起使用,列出较完整信息(包含不关联终端的进程) |
-o |
自定义输出字段 |
-p |
按 PID 过滤,如 ps -p 1234 |
-C |
按命令名过滤,如 ps -C sshd |
-L |
显示线程(配合 -f 即 ps -eLf) |
--sort |
按指定字段排序,- 为降序,如 --sort=-%cpu |
-H |
以缩进方式体现进程父子层级 |
--forest |
以 ASCII 树形展示进程层级 |
2.1.2 常用形式
ps # 列出与当前终端(当前 shell)有关的进程
ps -aux # 列出所有进程信息(BSD 风格)
ps aux # 同上,标准 BSD 写法
ps -ef # 列出所有进程信息(System V 风格)
ps -e -o pid,comm,ppid # 自定义显示字段
ps -el # 长格式,含 PRI/NI 优先级列
ps -eLf # 连线程一起显示
2.1.3 示例 1:ps(最简形式)
列出每一个与当前 shell 有关的进程的基本信息。
ps
PID TTY TIME CMD
25005 pts/0 00:00:00 bash
25010 pts/0 00:00:00 ps
字段含义:
| 字段 | 含义 |
|---|---|
PID |
进程的标志号(进程 ID) |
TTY |
进程建立时所对应的终端;若是 ? 则表示不占用终端 |
TIME |
进程累计使用的 CPU 时间 |
CMD |
执行进程的命令 |
2.1.4 示例 2:ps aux 字段详解(BSD 风格)
显示所有终端上所有用户的有关进程的所有信息。
ps aux
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.0 0.1 193832 6904 ? Ss 9月28 0:12 /usr/lib/systemd/systemd --switched-root --system --deserialize 22
| 字段 | 含义 |
|---|---|
USER |
用户名称(进程所属用户) |
PID |
进程号 |
%CPU |
进程占用 CPU 的百分比 |
%MEM |
进程占用物理内存的百分比 |
VSZ |
进程占用的虚拟内存大小(单位:KB) |
RSS |
进程占用的物理内存大小(Resident Set Size,单位:KB) |
TTY |
终端名称(缩写),为 ? 表示与终端无关,通常由系统启动 |
STAT |
进程状态(见 1.9、1.10 节) |
START |
进程的启动时间 |
TIME |
CPU 时间,即进程使用 CPU 的累计时间 |
COMMAND |
启动进程所用的命令和参数,过长会被截断显示 |
2.1.5 示例 3:ps -ef 字段详解(System V 风格)
显示系统中所有进程的全面信息。
ps -ef
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 9月28 ? 00:00:12 /usr/lib/systemd/systemd --switched-root --system --deserialize 22
root 25373 25005 0 04:29 pts/0 00:00:00 tar -t test.tar.gz
postfix 25852 1600 0 05:10 ? 00:00:00 pickup -l -t fifo -u
root 26766 1638 0 06:24 ? 00:00:00 sleep 60
root 26768 25005 0 06:24 pts/0 00:00:00 ps -ef
| 字段 | 含义 |
|---|---|
UID |
用户 ID(进程属主的用户 ID) |
PID |
进程 ID |
PPID |
父进程的 ID 号 |
C |
CPU 调度因子,进程最近使用的 CPU 估算,反映进程最近使用的 CPU 时间占比 |
STIME |
进程启动的时间,以"小时:分"形式显示 |
TTY |
完整的终端名称 |
TIME |
CPU 时间(累计使用 CPU 的时间) |
CMD |
完整的启动进程所用的命令和参数 |
关于
C列的辨析:早期教材称"数值越大表明是 CPU 密集型运算,执行优先级会降低;数值越小表明是 I/O 密集型运算,执行优先级会提高"。更准确的表述是:C反映进程最近使用的 CPU 时间占比,数值越大通常 CPU 占用越高,但它并非 CPU 密集型 vs I/O 密集型的直接判定指标,也不直接等于优先级。
2.1.6 两种风格的区别与选择
| 特性 | ps aux(BSD 风格) |
ps -ef(System V 风格) |
|---|---|---|
| 显示内容 | 包含 %CPU、%MEM、VSZ、RSS 等 |
包含 PPID、C、完整 CMD 等 |
COMMAND 列 |
过长时会截断显示 | 显示完整命令与参数 |
| 适用场景 | 关注 CPU / 内存占用率 | 关注父进程关系与完整命令行 |
选用建议:
- 查看进程的 CPU 占用率和内存占用率:使用
ps aux。 - 查看进程的父进程 ID 和完整命令行:使用
ps -ef。
实验:对比两种输出
ps -aux | head -n 15 # 关注 %CPU / %MEM
ps -ef | head -n 15 # 关注 PPID / 完整 CMD
对比两次输出的列差异,体会 BSD 风格与 System V 风格的适用场景。
2.1.7 常用组合与排序
# 按 CPU 占用降序取前 10(两种等价写法)
ps aux --sort=-%cpu | head -n 11
ps -eo pid,ppid,user,%cpu,%mem,comm --sort=-%cpu | head -n 11
# 按内存占用降序取前 10
ps aux --sort=-%mem | head -n 11
ps -eo pid,user,rss,%mem,comm --sort=-rss | head -n 11
# 只看优先级相关列
ps -eo pid,ni,pri,comm
ps -el | head
# 只看某个服务的进程
ps -C sshd -o pid,ppid,stat,comm
ps -ef | grep [s]shd # 用 [s] 技巧避免匹配到 grep 自身
# 按用户过滤
ps -u root
ps -u apache -o pid,%cpu,%mem,comm
# 树形展示父子关系
ps -ef --forest | head -n 30
ps -eHo pid,ppid,comm | head -n 30
# 查看进程的启动完整时间与运行时长
ps -eo pid,lstart,etime,comm | grep sshd
# 查看进程使用的线程数
ps -eo pid,nlwp,comm --sort=-nlwp | head
2.2 pstree:打印进程树
pstree 以树形结构展示进程间的父子关系,比 ps 更直观地体现"谁启动了谁"。
pstree # 以树形结构显示进程关系
pstree -p # 显示进程树并包含 PID
pstree -u # 显示用户切换(uid 变化)
pstree -a # 显示完整命令行参数
pstree -h # 高亮当前进程及其祖先
pstree -p PID # 只看某个进程的子树,如 pstree -p 1
pstree -p 用户名 # 只看某用户的进程树,如 pstree -p apache
systemd(1)─┬─NetworkManager(712)─┬─{NetworkManager}(725)
│ └─{NetworkManager}(727)
├─crond(1638)───sleep(26766)
└─sshd(1075)───sshd(25001)───bash(25005)───pstree(26800)
2.3 pgrep / pidof:按名称查找进程
pgrep vim # 查找名为 vim 的进程 PID
pgrep -u root # 查找 root 用户的所有进程 PID
pgrep -l sshd # 同时显示进程名
pgrep -a sshd # 显示完整命令行(procps-ng)
pgrep -x sleep # 精确匹配进程名(不做子串匹配)
pgrep -f "python app.py" # 匹配完整命令行,适合脚本类进程
pgrep -n sshd # 只取最新(newest)的那个 PID
pgrep -o sshd # 只取最老(oldest)的那个 PID
pgrep -c sshd # 只输出匹配到的进程个数
pgrep -P 1 # 查找父进程为 1 的所有子进程
pidof sshd # 输出该程序名对应的所有 PID(空格分隔)
pidof -s sshd # 只输出一个 PID
pgrep支持正则与丰富过滤条件,pidof更简单直接(按程序名精确匹配)。脚本中常用pgrep -f做"进程是否已在运行"的判断。
2.4 top:文本任务管理器
top 是一个实时刷新的文本界面任务管理器(相当于 Windows 的任务管理器),默认每 3 秒刷新一次。
top # 进入交互界面
top -d 1 # 指定刷新间隔为 1 秒
top -p PID # 只监控指定进程,可用逗号分隔多个 PID
top -u root # 只显示指定用户的进程
top -H # 按线程展示
top -c # 显示完整命令行
top -b -n 1 # 批处理模式,采集 1 帧后退出(适合写入文件/脚本采集)
2.4.1 top 界面头部详解
top - 09:12:31 up 12 days, 3:41, 2 users, load average: 0.15, 0.09, 0.06
Tasks: 213 total, 1 running, 212 sleeping, 0 stopped, 0 zombie
%Cpu(s): 2.3 us, 0.7 sy, 0.0 ni, 96.7 id, 0.3 wa, 0.0 hi, 0.0 si, 0.0 st
KiB Mem : 3865292 total, 201864 free, 1210324 used, 2453104 buff/cache
KiB Swap: 2097148 total, 2097148 free, 0 used. 2312196 avail Mem
第 1 行(等同 uptime):
| 内容 | 含义 |
|---|---|
09:12:31 |
当前系统时间 |
up 12 days, 3:41 |
系统已连续运行时长 |
2 users |
当前登录用户数 |
load average |
最近 1、5、15 分钟的平均负载(见第六章) |
第 2 行 Tasks: 进程总数,以及处于 running / sleeping / stopped / zombie 各状态的进程数。排查时先看 zombie 是否非 0。
第 3 行 %Cpu(s):
| 字段 | 全称 | 含义 |
|---|---|---|
us |
user | 用户态占用 CPU 百分比(不含 nice 调整过的进程) |
sy |
system | 内核态占用 CPU 百分比 |
ni |
nice | 被调整过 nice 值的用户进程占用百分比 |
id |
idle | 空闲百分比 |
wa |
iowait | 等待 I/O 完成的百分比,偏高说明磁盘/网络存储是瓶颈 |
hi |
hardware irq | 硬中断占用百分比 |
si |
software irq | 软中断占用百分比 |
st |
steal | 被虚拟化宿主机"偷走"的时间,虚拟机中偏高说明宿主机超卖 |
第 4、5 行内存与交换分区: total(总量)、free(空闲)、used(已用)、buff/cache(缓冲与缓存)、avail Mem(可用估算)。
注意:
buff/cache可以被回收,因此判断内存是否真正不足要看avail Mem,而不是free。CentOS 6 的 top 内存行没有avail Mem列,只有buffers与cached分开显示。
2.4.2 top 进程列表列含义
| 列 | 含义 |
|---|---|
PID |
进程 ID |
USER |
进程所属用户 |
PR |
内核调度优先级(priority),rt 表示实时进程 |
NI |
nice 值,范围 -20 ~ 19 |
VIRT |
虚拟内存总量(KB) |
RES |
常驻物理内存(KB),相当于 ps 的 RSS |
SHR |
共享内存大小(KB) |
S |
进程状态(R/S/D/T/Z) |
%CPU |
CPU 占用百分比 |
%MEM |
物理内存占用百分比 |
TIME+ |
累计使用的 CPU 时间,精确到 1/100 秒 |
COMMAND |
命令名或完整命令行(按 c 切换) |
2.4.3 top 交互快捷键
| 按键 | 作用 |
|---|---|
? 或 h |
显示帮助 |
q |
退出 top |
| 空格 | 立即实时刷新 |
k |
结束(kill)进程:先输入 PID,再输入信号(默认 15) |
r |
调整进程 nice 值(renice):先输入 PID,再输入 NI 值 |
d 或 s |
修改刷新间隔(秒) |
P(大写) |
按 %CPU 排序(默认) |
M(大写) |
按 %MEM 排序 |
T(大写) |
按累计 CPU 时间 TIME+ 排序 |
N(大写) |
按 PID 排序 |
1 |
展开/折叠每个 CPU 核心的独立使用率 |
H |
切换线程视图(显示各线程) |
c |
切换显示完整命令行 |
u |
只显示指定用户的进程(输入用户名,回车留空取消过滤) |
o |
添加过滤条件,如 %CPU>10 |
m |
切换内存显示方式(进度条 / 数值) |
t |
切换 CPU 与任务信息显示方式 |
E / e |
切换内存单位(KB/MB/GB…),大写作用于头部,小写作用于列表 |
z |
彩色/单色切换 |
b |
加粗/反显运行中的进程 |
W(大写) |
保存当前设置到 ~/.toprc,下次启动生效 |
f |
进入字段管理界面,选择要显示的列 |
L |
搜索字符串 |
2.4.4 将 top 的输出截取为文件(批处理模式)
# 每 0.5 秒刷新一次,共输出 2 帧,重定向到 /tmp/top.txt
top -d 0.5 -b -n 2 > /tmp/top.txt
-d 0.5:每 0.5 秒刷新一次;-b:批处理(batch)模式,适合输出到文件;-n 2:采集 2 帧后退出。
实验:截取 top 数据
top -d 0.5 -b -n 2 > /tmp/top.txt
cat /tmp/top.txt
2.5 htop:增强版 top
htop 是 top 的增强替代品,支持彩色显示、鼠标操作、横向滚动查看完整命令行、树形视图、直接选中进程发信号等。
yum install -y epel-release && yum install -y htop # CentOS 6/7 需 EPEL 源
htop
htop -u root # 只看指定用户
htop -p PID1,PID2 # 只看指定进程
htop -d 10 # 刷新间隔,单位 1/10 秒
常用按键:
| 按键 | 作用 |
|---|---|
F1 / h |
帮助 |
F2 / S |
设置(Setup),可自定义显示的仪表与列 |
F3 / / |
搜索进程 |
F4 / \ |
增量过滤(filter) |
F5 / t |
树形视图切换 |
F6 / < > |
选择排序字段 |
F7 / ] |
降低 nice 值(提高优先级,需 root) |
F8 / [ |
提高 nice 值(降低优先级) |
F9 / k |
向选中进程发送信号 |
F10 / q |
退出 |
空格 |
标记(tag)多个进程,可批量操作 |
u |
按用户过滤 |
H |
显示/隐藏用户线程 |
三、进程与文件的关联排查
进程运行时会打开文件、监听端口、占用挂载点。排查"设备无法卸载""端口被占用""删了文件但磁盘没释放"等问题时,需要把进程与文件关联起来。
3.1 lsof:列出打开的文件
lsof(list open files)在 Linux 中"一切皆文件"的语义下极为有用:普通文件、目录、块设备、库、socket、管道都算文件。
yum install -y lsof
lsof # 列出系统中所有打开的文件(输出极多)
lsof /var/log/messages # 谁在使用这个文件
lsof +D /data # 递归列出某目录下被打开的所有文件
lsof +d /data # 只看该目录本层
lsof -c sshd # 列出 sshd 进程打开的所有文件
lsof -p PID # 列出指定进程打开的所有文件
lsof -u root # 列出 root 用户打开的所有文件
lsof -u ^root # 排除 root 用户
lsof -i # 列出所有网络连接(socket)
lsof -i :22 # 查看 22 端口被谁占用
lsof -i tcp:80 # 查看 TCP 80 端口占用
lsof -i 4 -a -i tcp # 只看 IPv4 的 TCP 连接(-a 表示条件与运算)
lsof -n # 不做 IP 反解域名,加快速度
lsof -P # 不把端口号转换为服务名
lsof /dev/sdb1 # 查看谁在占用某块设备(umount 失败时用)
lsof -t -c nginx # 只输出 PID,便于配合 kill 使用
lsof | grep deleted # 查找已删除但仍被进程持有的文件(磁盘不释放的常见原因)
关键列含义:
| 列 | 含义 |
|---|---|
COMMAND |
进程名 |
PID |
进程 ID |
USER |
进程属主 |
FD |
文件描述符:cwd 当前目录、txt 程序文本、mem 内存映射、0u/1u/2u 标准输入/输出/错误,nu 表示编号为 n 的描述符(r 只读 / w 只写 / u 读写) |
TYPE |
文件类型:REG 普通文件、DIR 目录、CHR 字符设备、BLK 块设备、FIFO 管道、IPv4/IPv6 网络套接字、unix UNIX 域套接字 |
DEVICE |
设备号 |
SIZE/OFF |
文件大小或偏移量 |
NODE |
inode 号 |
NAME |
文件路径或网络连接信息 |
典型场景:删除了大日志文件后
df显示磁盘仍然满,是因为进程还持有该文件的描述符。用lsof | grep deleted找到进程,重启该进程(或> /proc/PID/fd/N清空)即可真正释放空间。
3.2 fuser:查找使用文件/目录的进程
yum install -y psmisc # fuser、killall、pstree 都在 psmisc 包中
fuser /var/log/messages # 显示使用该文件的进程 PID
fuser -v /var/log/messages # 详细模式,显示用户、PID、访问类型、命令
fuser -m /data # 列出使用该挂载点下任意文件的所有进程
fuser -mv /data # 详细模式(umount: device is busy 时必用)
fuser -k /data # 杀死使用该文件/目录的所有进程(默认 SIGKILL)
fuser -k -9 -m /data # 显式指定信号
fuser -ki -m /data # 交互式确认后再杀
fuser -n tcp 80 # 查看 TCP 80 端口被哪个进程占用
fuser -u /var/log/messages # 同时显示进程属主
访问类型标识:
| 标识 | 含义 |
|---|---|
c |
当前工作目录(cwd) |
e |
正在被执行的可执行文件 |
f |
打开的文件 |
F |
打开的文件(写入模式) |
r |
根目录 |
m |
内存映射文件或共享库 |
典型场景:
umount /data报device is busy,先fuser -mv /data看清是谁在占用,确认无风险后再fuser -km /data或让相关服务正常停止。切勿在生产环境直接-k数据库挂载点。
3.3 /proc/PID:内核暴露的进程信息
/proc 是内存中的虚拟文件系统,每个运行中的进程都对应一个 /proc/PID 目录,ps、top 的数据都来源于此。
ls /proc/$$ # 查看当前 Shell 对应的 proc 目录
| 路径 | 内容 |
|---|---|
/proc/PID/cmdline |
启动该进程的完整命令行(参数以 \0 分隔) |
/proc/PID/comm |
进程名(短名) |
/proc/PID/exe |
符号链接,指向可执行文件的真实路径 |
/proc/PID/cwd |
符号链接,指向进程的当前工作目录 |
/proc/PID/root |
符号链接,指向进程的根目录(容器/chroot 场景有用) |
/proc/PID/environ |
进程的环境变量(\0 分隔) |
/proc/PID/fd/ |
该进程打开的所有文件描述符(符号链接) |
/proc/PID/limits |
该进程的资源限制(对应 ulimit) |
/proc/PID/status |
人类可读的状态汇总:状态、PPID、线程数、内存各项、信号掩码等 |
/proc/PID/stat |
机器可读的状态数据(ps 解析的源) |
/proc/PID/statm |
内存使用情况(以页为单位) |
/proc/PID/smaps |
详细的内存映射信息,可分析内存构成 |
/proc/PID/io |
该进程的读写字节数统计 |
/proc/PID/task/ |
每个线程一个子目录(以 TID 命名) |
/proc/PID/wchan |
进程当前在内核中等待的函数(分析 D 状态进程的关键) |
/proc/PID/stack |
内核栈回溯(需 root,排查 D 状态进程) |
/proc/PID/oom_score |
OOM 评分,越高越容易被 OOM Killer 选中 |
/proc/PID/oom_score_adj |
OOM 评分调整值(-1000 ~ 1000),设为 -1000 表示豁免 |
常用查看命令:
cat /proc/1234/status | head -n 20 # 看状态、PPID、线程数
tr '\0' ' ' < /proc/1234/cmdline; echo # 可读地打印完整命令行
ls -l /proc/1234/exe # 程序真实路径(进程被删文件时仍可找到)
ls -l /proc/1234/cwd # 进程工作目录
ls -l /proc/1234/fd | head # 打开的文件
ls /proc/1234/fd | wc -l # 打开的文件描述符数量
cat /proc/1234/limits # 该进程的 ulimit 限制
cat /proc/1234/io # 读写量统计
cat /proc/1234/wchan; echo # D 状态进程卡在哪个内核函数
cat /proc/1234/oom_score # OOM 评分
# 相关的全局信息
cat /proc/loadavg # 平均负载(uptime 的数据源)
cat /proc/uptime # 系统运行秒数与空闲秒数
cat /proc/cpuinfo # CPU 信息
cat /proc/meminfo # 内存详情(free 的数据源)
cat /proc/stat # CPU 累计时间片统计(vmstat/top 的数据源)
四、控制进程:信号机制
4.1 信号是什么,从哪来
信号(Signal)是内核提供的一种异步通知机制,用于告知进程"发生了某件事"。信号源包括:
- 硬件异常:除零、非法内存访问(内核发送
SIGFPE、SIGSEGV); - 终端按键:
Ctrl + C发SIGINT、Ctrl + \发SIGQUIT、Ctrl + Z发SIGTSTP; - 命令主动发送:
kill、killall、pkill、top中按k; - 内核事件:子进程退出(
SIGCHLD)、定时器到期(SIGALRM)、内存不足(OOM Killer 发SIGKILL)。
进程收到信号后有三种处理方式:执行默认动作、捕捉后自定义处理、忽略。其中 SIGKILL(9) 与 SIGSTOP(19) 不可被捕捉、忽略或阻塞。
4.2 查看所有信号
kill -l # 列出系统支持的所有信号
kill -l 9 # 查询编号 9 对应的信号名(KILL)
man 7 signal # 查看信号的完整说明与默认动作
trap -l # Shell 内置命令,同样可列出信号
4.3 常用信号表
| 编号 | 名称 | 默认动作 | 含义与用途 |
|---|---|---|---|
1 |
SIGHUP |
终止 | 终端挂起或控制进程终止(终端断线)。守护进程常将其重定义为"重读配置文件",如 kill -1 $(pidof nginx) |
2 |
SIGINT |
终止 | 中断,等同于 Ctrl + C |
3 |
SIGQUIT |
终止 + 转储 | 退出,等同于 Ctrl + \,会产生 core dump |
4 |
SIGILL |
终止 + 转储 | 非法指令 |
6 |
SIGABRT |
终止 + 转储 | 程序调用 abort() 异常终止 |
8 |
SIGFPE |
终止 + 转储 | 算术异常(如除零) |
9 |
SIGKILL |
终止 | 强制杀死进程,不可被捕捉、忽略或阻塞 |
11 |
SIGSEGV |
终止 + 转储 | 段错误,非法内存访问 |
13 |
SIGPIPE |
终止 | 向已关闭读端的管道写数据 |
14 |
SIGALRM |
终止 | 定时器超时 |
15 |
SIGTERM |
终止 | 请求进程正常终止,kill 的默认信号,允许进程做清理后优雅退出 |
17 |
SIGCHLD |
忽略 | 子进程状态改变(退出/暂停),父进程据此调用 wait() 回收 |
18 |
SIGCONT |
继续 | 继续(恢复)已暂停的进程 |
19 |
SIGSTOP |
暂停 | 暂停进程,不可被捕捉、忽略或阻塞 |
20 |
SIGTSTP |
暂停 | 终端发起的暂停,等同于 Ctrl + Z,可被捕捉 |
编号 1–31 是标准信号,34–64 是实时信号(
SIGRTMIN~SIGRTMAX),日常运维基本用不到实时信号。
4.4 kill:向进程发信号
尽管名字叫 kill,它的本质是"向进程发送信号",不一定是杀死。
命令格式:
kill [选项] 进程PID
进程 PID 可使用 ps、pgrep、pidof 命令查询。
常用快捷键
Ctrl + C只能用来结束前台进程;后台进程需要使用kill命令终止。
常用选项(信号):
| 选项 | 信号含义 |
|---|---|
-l |
列出所有 kill 命令支持的信号 |
-1 |
终端断线(SIGHUP) |
-2 |
中断,同 Ctrl + C(SIGINT) |
-3 |
退出,同 Ctrl + \(SIGQUIT) |
-9 |
强制终止(SIGKILL,常用) |
-15 |
终止(SIGTERM,默认信号) |
-18 |
继续进程(SIGCONT) |
-19 |
暂停进程(SIGSTOP) |
使用示例:
kill PID # 不带信号即发送默认的 SIGTERM(15)
kill -15 PID # 请求进程正常终止(等价于上一行)
kill -9 PID # 强制杀死进程
kill -TERM PID # 用信号名代替编号,等价于 -15
kill -SIGKILL PID # 完整信号名写法
kill -1 PID # 让守护进程重读配置文件(如 nginx、rsyslogd)
kill -19 PID # SIGSTOP:暂停进程,进程状态变为 T(Stopped)
kill -18 PID # SIGCONT:恢复进程继续运行
kill -0 PID # 不发送真实信号,仅检测进程是否存在(脚本中做存活检测)
kill -9 PID1 PID2 PID3 # 一次向多个进程发信号
提示:
kill -19/kill -18是一对可用于"挂起—恢复"的组合,适合临时让某个占用资源的进程让出 CPU,而不必终止它。
实战示例:
当后台程序不再需要运行,或它占用了其他应用的端口时,可使用 kill 将其杀死:
ps -ef | grep vim
root 3268 2884 0 16:21 pts/1 00:00:00 vim install.log
root 3370 2822 0 16:21 pts/0 00:00:00 grep vim
kill 3268
再次执行时,因进程已经不存在会报错:
kill 3268
-bash: kill: (3268) - 没有那个进程
4.5 killall:按进程名批量发信号
killall 属于 psmisc 包,按精确进程名(默认不做子串匹配)杀死所有同名进程。
killall httpd # 向所有名为 httpd 的进程发 SIGTERM
killall -9 httpd # 强制杀死所有 httpd 进程
killall -HUP nginx # 让所有 nginx 进程重读配置
killall -u apache # 杀死 apache 用户的所有进程
killall -i httpd # 交互式确认后再杀
killall -v httpd # 显示详细操作过程
killall -w httpd # 等待进程真正结束后才返回
killall -r 'http.*' # 按正则匹配进程名
killall -e 长进程名 # 要求进程名完全匹配(含超过 15 字符的名字)
killall -s SIGTERM httpd # 显式指定信号
4.6 pkill:按模式匹配发信号
pkill 与 pgrep 是一对(同属 procps 包),选项语义完全一致,pgrep 查、pkill 杀。
pkill -9 关键词 # 杀死进程名中包含关键词的进程
pkill 关键词 # 默认发 SIGTERM(15)
pkill -9 -x 进程名 # 精确匹配进程名后杀死
pkill -9 -f "python app.py" # 按完整命令行匹配(脚本类进程必用)
pkill -u root # 杀死指定用户(root)的所有进程(慎用)
pkill -u apache -9 httpd # 组合条件:apache 用户的 httpd 进程
pkill -t pts/2 # 杀死指定终端上的所有进程(踢掉某个会话)
pkill -P 1234 # 杀死 PID 为 1234 的所有子进程
pkill -HUP rsyslogd # 让 rsyslogd 重读配置
pkill -n sshd # 只杀最新启动的那个
4.7 kill / killall / pkill 的区别
| 命令 | 所属包 | 目标指定方式 | 匹配方式 | 适用场景 |
|---|---|---|---|---|
kill |
内建/coreutils | PID、PGID(负数)、作业号 %N |
精确 PID | 已知 PID,最精确、最安全 |
killall |
psmisc | 进程名 | 进程名精确匹配(可 -r 用正则) |
批量结束同名进程 |
pkill |
procps | 进程名、命令行、用户、终端等 | 默认子串/正则匹配(-x 精确) |
条件灵活,适合按用户/终端/命令行批量处理 |
重要差异与风险:
killall在 Linux 上按进程名匹配;但在部分 UNIX(如 Solaris)上killall会杀死所有进程,跨平台脚本务必小心。pkill默认是子串匹配,pkill java会杀掉所有命令名含 java 的进程。动手前先用同参数的pgrep -a确认目标列表,这是最重要的安全习惯。- 三者都不能杀掉
Z(僵尸)与D(不可中断睡眠)状态的进程。
安全操作范式:
pgrep -a -f "keyword" # 第一步:先看清将要杀掉哪些进程
pkill -f "keyword" # 第二步:确认无误后发送 SIGTERM
sleep 5
pgrep -a -f "keyword" # 第三步:检查是否已退出
pkill -9 -f "keyword" # 第四步:仍然存活才使用 SIGKILL
4.8 优雅停止与强制杀死
| 对比项 | kill -15(SIGTERM 优雅停止) |
kill -9(SIGKILL 强杀) |
|---|---|---|
| 是否可捕捉 | 可以,进程能自定义清理逻辑 | 不可捕捉、不可忽略 |
| 进程能否收尾 | 能:刷新缓冲区、写完日志、关闭连接、删除 PID 文件、释放锁 | 不能,立即被内核销毁 |
| 数据风险 | 低 | 高:可能丢数据、损坏文件、残留锁与 PID 文件 |
| 生效速度 | 取决于进程自身,可能较慢或"不理" | 立即 |
| 适用场景 | 首选,尤其是数据库、消息队列等有状态服务 | 进程无响应、已确认无法优雅退出时的最后手段 |
标准停止流程(先柔后刚):
kill -15 PID # 1. 先请求优雅退出
sleep 5 ; ps -p PID # 2. 等待并确认
kill -9 PID # 3. 仍未退出再强杀
严重警告:
kill -9不会给进程清理资源的机会,可能导致数据丢失或文件损坏。对 MySQL、Redis、Kafka、Elasticsearch 等有状态服务尤其危险,应优先使用服务自带的停止命令(如systemctl stop mysqld、mysqladmin shutdown)。- 对 CentOS 7 下由 systemd 托管的服务,直接 kill 可能触发 systemd 自动重启(
Restart=策略),应使用systemctl stop,详见08-01-系统启动与服务管理.md。
4.9 trap:捕捉信号
Shell 内置命令 trap 用于在接收到信号时执行指定操作,常用于脚本中保证"无论如何都清理临时文件"。
trap 操作 信号源
trap "操作" 信号源
示例:
trap "echo caught" SIGINT # 捕捉 Ctrl + C(SIGINT)
trap "echo 收到信号,忽略" 2 3 # 同时捕捉 SIGINT 与 SIGQUIT
trap "" 2 # 忽略 SIGINT(Ctrl + C 失效)
trap - 2 # 恢复 SIGINT 的默认动作
trap -p # 查看当前已设置的捕捉
trap 'rm -f /tmp/lock.$$' EXIT # 脚本退出时(含正常退出)自动清理
脚本示例:不可被 Ctrl + C 中断的关键操作
#!/bin/bash
TMP=/tmp/work.$$
trap 'echo "正在收尾,请稍候..."; rm -f "$TMP"; exit 1' INT TERM
trap 'rm -f "$TMP"' EXIT
echo "开始处理关键任务"
for i in {1..10}; do
echo "step $i" >> "$TMP"
sleep 1
done
echo "任务完成"
注意:信号 9(
SIGKILL)和 19(SIGSTOP)不可被捕捉,因此trap无法拦截kill -9。这也是内核保留的"最终手段",确保管理员总能结束任何进程。
实验:使用 kill / pkill 终止进程
# A 终端
sleep 999
# B 终端
ps -aux | grep sleep # 查看 sleep 的 PID
sudo kill -9 PID # 通过 PID 杀死
# 或者
sudo pkill -9 sleep # 通过进程名杀死
# 或者
sudo killall -9 sleep # 按精确进程名杀死
观察 A 终端出现 已杀死(Killed)提示。若改用 kill -15,提示则为 已终止(Terminated)。
五、进程优先级
5.1 PRI 与 NI
Linux 用两个值描述进程的 CPU 调度优先级:
| 值 | 含义 |
|---|---|
PRI |
内核实际使用的调度优先级,由内核动态计算,管理员不能直接修改 |
NI |
nice 值,用户可调整的"谦让度",通过影响 PRI 间接影响调度 |
近似关系:PRI(new) = PRI(old) + NI。运维只需调整 NI。
NI 优先级范围:
- NI(nice 值)范围为 -20 到 19(共 40 个级别);
- 数值越小,优先级越高(越"不谦让",抢占 CPU 能力越强);
- 默认 NI 值为 0;
- 普通用户只能将优先级数值调大(降低优先级),不能调小(提高优先级);提高优先级需要 root 权限。
记忆:nice = 谦让。nice 值越大越"客气",越容易把 CPU 让给别人。
注意:nice 值只影响 CPU 时间的分配比例,不影响内存、磁盘 I/O 的分配,也不能保证某进程一定先执行。系统空闲时调整 nice 值几乎看不出差别,只有在 CPU 竞争激烈时才明显。
5.2 查看进程优先级
ps -el # 长格式,含 PRI 与 NI 列
ps -el | head
ps -eo pid,ni,pri,comm # 自定义只看优先级相关列
ps -eo pid,ni,comm # 只看 NI
ps -eo pid,ni,comm | grep sleep
ps axo pid,ni,comm,%cpu --sort=ni | head
top # 交互界面中的 PR 与 NI 列
nice # 不带参数时输出当前 Shell 的 nice 值
5.3 nice:启动时指定优先级
nice 用于在启动程序时指定其 NI 值。
命令格式:
nice [-n NI值] 命令 [参数]
示例:
nice -n 19 tar -xf mysql.tar.bz2 # 以最低优先级解压,尽量不影响业务
nice -n 10 ./backup.sh # 备份脚本降优先级运行
nice -19 command # 旧式简写:等价于 -n 19(易与负值混淆,不推荐)
sudo nice -n -20 command # 最高优先级,需 root
nice command # 不指定时默认增加 10
注意:命令名为
nice,不是nince。
5.4 renice:修改运行中进程的优先级
renice 用于更改正在运行进程的优先级。
命令格式:
renice [-n] NI值 [-p PID] [-g PGID] [-u 用户名]
示例:
sudo renice -n -19 -p PID # 推荐写法:-n 指定数值,-p 指定 PID
sudo renice -n 5 -p PID # 调低优先级(数值调大)
renice 3 PID # 旧式简写:调大 nice 值(普通用户可执行)
sudo renice -19 PID # 旧式简写:调到 -19(需 root)
sudo renice -n 5 -g PGID # 按进程组批量调整
sudo renice -n 5 -u apache # 按用户批量调整该用户的所有进程
注意:
sudo renice -19 PID是旧式简写语法,-19会被当作 nice 数值而非选项,容易与选项混淆;建议统一使用renice -n 数值 -p PID的形式。权限规则:普通用户只能对自己的进程、且只能把 NI 调大,而且调大之后不能再调回来(即不可逆);只有 root 可以任意调整。
5.5 在 top 中调整优先级
1. 运行 top
2. 按 r 键(renice)
3. 提示 "PID to renice:" -> 输入目标 PID,回车(直接回车则对排在第一位的进程操作)
4. 提示 "Renice PID xxx to value:" -> 输入新的 NI 值,回车
5. 观察进程列表中该进程的 NI 列已变化
- 输入负值需要 root 身份运行 top,否则报
Operation not permitted; htop中则可用F7(降低 nice 值/提高优先级)与F8(提高 nice 值/降低优先级)逐级调整。
5.6 ionice:调整磁盘 I/O 优先级
nice 只管 CPU,磁盘 I/O 的优先级由 ionice 控制(需要 I/O 调度器支持 CFQ / BFQ)。
命令格式:
ionice [-c 类别] [-n 级别] [-p PID] [命令]
调度类别(-c):
| 类别 | 名称 | 说明 |
|---|---|---|
0 |
none | 未指定,按默认(等同 best-effort,级别由 nice 值推算) |
1 |
realtime | 实时,优先获得磁盘访问,级别 0–7(0 最高)。需 root,滥用会饿死其他进程 |
2 |
best-effort | 尽力而为(默认类别),级别 0–7(0 最高,默认 4) |
3 |
idle | 空闲时才进行 I/O,只有磁盘无其他请求时才执行。最适合备份、校验任务 |
示例:
ionice -p 1234 # 查看指定进程当前的 I/O 优先级
ionice -c 3 -p 1234 # 把运行中的进程改为 idle 类别
ionice -c 2 -n 7 -p 1234 # best-effort 中的最低级别
ionice -c 3 tar -czf /backup/data.tar.gz /data # 以 idle 类别启动备份,几乎不影响业务 I/O
ionice -c 2 -n 0 dd if=/dev/zero of=/tmp/t bs=1M count=1024 # 提高该任务的 I/O 优先级
nice -n 19 ionice -c 3 ./nightly-job.sh # CPU 与 I/O 同时降优先级(经典组合)
实战建议:夜间备份、
find /全盘扫描、日志压缩这类任务,统一用nice -n 19 ionice -c 3前缀启动,可显著降低对线上业务的干扰。
5.7 优先级相关示例
# 占用 CPU 一个核心(NI 为默认值 0)
nice -n 0 cat /dev/urandom > /dev/null
# 通过 time 观察不同优先级下的执行耗时差异
time nice -n -20 head -c 1m /dev/urandom > /dev/null
# 观察最高优先级下的执行时间(负 nice 值需 root 权限)
# 数据量需足够大,否则命令瞬间结束,time 无法体现差异
time sudo nice -n -20 head -c 2G /dev/urandom > /dev/null
# 对比默认优先级下的耗时
time nice -n 0 head -c 2G /dev/urandom > /dev/null
实验设计要点:想看出 nice 的效果,必须让 CPU 处于竞争状态。建议先启动 N+1 个(N 为核心数)满载进程,再对比不同 nice 值进程的完成先后顺序。
制造 CPU 竞争的对比实验:
# 假设为 2 核系统,同时启动 4 个满载任务,其中两个降低优先级
nice -n 0 bash -c 'time md5sum /dev/zero' & # 观察谁跑得快
nice -n 19 bash -c 'time md5sum /dev/zero' &
top # 按 P 排序,对比两者的 %CPU 差异
pkill md5sum # 实验结束及时清理
5.8 实验:nice 启动优先级
- 在 A 终端运行
sleep 999; - 在 B 终端运行
ps -el | grep sleep,查看其 NI; - 在 A 终端按
Ctrl + C终止; - 在 A 终端运行
nice -n 5 sleep 999; - 在 B 终端运行
ps -el | grep sleep,观察 NI 变化; - 在 A 终端按
Ctrl + C终止; - 在 A 终端运行
nice -n -5 sleep 999,观察结果(普通用户会因权限不足而失败,提示cannot set niceness: Permission denied); - 在 A 终端运行
sudo nice -n -5 sleep 999,观察结果; - 在 B 终端运行
ps -el | grep sleep,确认 NI 为 -5; - 在 B 终端运行
ps -aux | grep sleep,查看 PID; - 在 B 终端运行
sudo kill -9 PID; - 观察 A 终端的现象(出现"已杀死"提示)。
对应命令小结:
nice -n 5 sleep 999 # 普通用户可调大 nice 值
sudo nice -n -5 sleep 999 # 负 nice 值需 root 权限
用 ps -eo pid,ni,comm | grep sleep 验证 NI 列是否生效。
5.9 实验:renice 动态调整
- 在 A 终端运行
sleep 999; - 在 B 终端运行
ps -el | grep sleep,查看 NI 和 PID; - 在 B 终端运行
renice 3 PID; - 再次在 B 终端运行
ps -el | grep sleep,确认 NI 已变更为 3; - 在 B 终端尝试
renice -n 0 -p PID(普通用户会失败,因为不能把 NI 调小); - 在 B 终端运行
sudo renice -n -5 -p PID,确认 root 可以调小。
对应命令小结:
renice 3 PID # 调大 nice 值(普通用户可执行)
sudo renice -n -5 -p PID # 调小 nice 值需 root 权限
六、进程组与会话
6.1 进程组
- 进程组包含一个或多个进程,用于对进程进行分组管理;
- 每个进程都属于某个进程组,默认与父进程同属一个进程组;
- 进程组拥有唯一标识符 PGID(pgrp),进程组组长的 PID 即为进程组 ID;
- 创建进程组的进程即组长,进程组组长不能再创建新的进程组。
ps -eo pid,pgid,ppid,sid,comm | head # 查看 PID / PGID / PPID / SID
ps -eo pid,pgid,comm | grep sleep # 先确认 PGID 再操作
6.2 向进程组发信号
由于进程组 ID 与组长 PID 一致,操作组长进程可直接使用 PID;操作整个进程组时,在 ID 前加负号。
| 目标 | 命令示例 |
|---|---|
| 进程组长 | kill -9 PID,如 kill -9 1023 |
| 整个进程组 | kill -9 -PGID,如 kill -9 -1023 |
| 本进程组 | kill -9 0 |
| 所有进程 | kill -9 -1 |
kill -9 1023 # 给进程组组长(PID 1023)发信号,只杀组长本身
kill -9 -1023 # 给进程组(PGID 1023)发信号,杀掉整个进程组
kill -9 0 # 给本进程组发信号
kill -9 -1 # 给所有进程发信号(高危!)
记忆要点:PID 前加负号即表示「进程组」,
-PGID会把信号发给该组内所有进程。注意:
kill -9 -1会强制杀死几乎所有进程,等同于强制关机,极度危险,严禁在生产环境随意使用。
6.3 实验:进程组信号
- 在 A 终端运行
sleep 999; - 在 B 终端运行
ps -el | grep sleep,查看其 PID 和 PPID; - 在 B 终端运行
ps -el > pgrp.txt; - 使用
vim pgrp.txt查看该 sleep 进程的父进程是谁; - 在 B 终端运行
kill -9 PPID,观察 A 终端现象(父 bash 被杀,终端断开); - 在 A 终端再次运行
sleep 999; - 在 B 终端运行
ps -el | grep sleep,查看 PID 和 PPID; - 在 B 终端运行
kill -9 -PPID,观察 A 终端现象(整组被杀)。
对应命令小结:
kill -9 PID # 只杀组长本身
kill -9 -PGID # 杀掉整个进程组
用 ps -eo pid,pgid,comm 先确认 PGID 再操作。
6.4 会话
- 一个会话(Session)包含一个或多个进程组;
- 一个会话通常对应一个控制终端(负责输入输出);
- 会话中的进程组分为前台进程组和后台进程组;
- 当前使用控制终端的进程组即前台进程组,其余均为后台进程组;
- 一个会话中只能有一个前台进程组,但可以有多个后台进程组;
- 会话和进程组一样,拥有唯一标识符(SID)和管理者(会长,session leader);
- 进程组组长不能创建会话(这正是守护进程要 fork 两次的原因)。
层次关系:
会话(Session,SID)
├── 前台进程组(PGID)
│ ├── 进程
│ └── 进程
└── 后台进程组(PGID)
└── 进程
ps -eo pid,pgid,sid,tty,stat,comm | head -n 20 # 观察会话与进程组
ps -j # 显示 PGID 与 SID
who ; w # 查看当前登录会话
tty # 查看当前终端设备名
七、前台与后台作业管理
一个程序运行时,可以将其放到后台运行,不再占用前台终端资源。ps 可查看当前会话的所有进程组,jobs 专门查看当前会话的后台进程组(任务/作业)。
7.1 jobs:查看后台任务
jobs # 查看当前会话的所有后台进程组(作业)
jobs -l # 列出后台进程组并显示 PID
jobs -s # 只列出已停止(暂停 Stopped)的任务
jobs -r # 只列出正在运行(Running)的任务
jobs -p # 只输出 PID
jobs %2 # 只看编号为 2 的作业
输出解读:
[1]- Stopped sleep 999
[2]+ Running cp 5G.txt ./2_5G.txt &
| 部分 | 含义 |
|---|---|
[1] [2] |
作业号(job number),仅在当前会话内有效,与 PID 无关 |
+ |
当前作业(默认作业),fg/bg 不带参数时操作的对象 |
- |
上一个作业(次默认作业) |
Stopped/Running/Done |
作业状态 |
| 后面部分 | 对应的命令行 |
7.2 前台、后台与快捷键
| 操作 / 快捷键 | 作用 |
|---|---|
Ctrl + Z |
将前台进程组切换到后台并停止(暂停),状态变为 T(在终端空跑时无效果) |
Ctrl + C |
终止(中断)当前终端中正在前台运行的任务,发送 SIGINT |
Ctrl + D |
在终端中输入时表示 EOF,关闭当前终端 / 退出当前 Shell |
Ctrl + \ |
退出并产生 core dump,发送 SIGQUIT |
命令 & |
将命令直接放到后台运行,如 gedit & |
fg |
将后台进程组切换到前台(默认将带 + 的当前作业调到前台) |
fg 2 / fg %2 |
将 jobs 列表中 ID 为 2 的后台任务调到前台 |
bg |
将后台进程组的停止状态改为运行状态 |
bg 2 / bg %2 |
将 jobs 列表中 ID 为 2 的已停止任务改为后台运行 |
kill %2 |
按作业号结束作业(等价于 kill 对应 PID) |
提示:作业号的标准写法是加
%前缀(jobspec),即fg %2/bg %2/kill %2;fg 2/bg 2是 Bash 允许的简写。脚本中建议统一使用%N形式,避免与 PID 混淆。区别辨析:
Ctrl + Z是暂停(进程还在,状态T,可用fg/bg/kill -18恢复);Ctrl + C是终止(进程消失,不可恢复)。二者最容易混淆。
7.3 后台运行示例
gedit & # 将 gedit 放到后台运行
vncviewer & # 在后台运行 vnc,不占用前台终端资源
sleep 999 & # 后台睡眠,立即返回提示符
放入后台时 Shell 会打印:
[1] 26801
即"作业号 1,PID 26801"。
7.4 完整的前后台切换演练
sleep 999 # 前台运行,终端被占用
# 按 Ctrl + Z -> [1]+ Stopped sleep 999,进程状态变为 T
jobs -l # 查看作业号与 PID
bg %1 # 恢复为后台运行,状态变为 Running(S)
jobs -l # 确认状态
fg %1 # 调回前台
# 按 Ctrl + C -> 终止该任务
jobs -l # 已无该作业
7.5 nohup:终端关闭后继续运行
生成指定大小的测试文件:
head -c 500M /dev/zero > swapfile
# 或者
dd if=/dev/zero of=./swapfile bs=100M count=5
直接在前台复制大文件时,终端必须等待复制完成才能继续使用:
cp /soft/* /test/ -rf
如果复制尚未完成就误关终端,程序也会随之终止,造成文件拷贝不完整。可在命令末尾加 & 让其在后台运行:
cp /soft/* /test/ -rf &
但 & 只是把任务放入后台,它仍属于当前会话;终端关闭时会话首进程会向该会话所有进程发送 SIGHUP,任务可能被终止。若希望命令在终端关闭后仍继续运行,需要使用 nohup:
nohup cp /soft/* /test/ -rf &
nohup(no hang up)会让进程忽略挂断(SIGHUP)信号。关闭当前终端后再开启新终端,使用 jobs -l 将看不到该任务(因为已经不在同一会话中),但复制仍会继续,可用 ps -ef | grep cp 确认。
输出重定向行为:
nohup ./run.sh & # 输出默认追加到当前目录 nohup.out
nohup ./run.sh > /var/log/run.log 2>&1 & # 推荐:显式指定日志文件并合并错误输出
nohup ./run.sh > /dev/null 2>&1 & # 完全丢弃输出
注意:
nohup会将输出重定向到当前目录的nohup.out(若未指定输出且当前目录不可写,则写入$HOME/nohup.out)。生产环境务必显式重定向,否则nohup.out可能悄悄涨到几十 GB 撑满磁盘。
7.6 setsid 与 disown
| 方式 | 原理 | 适用时机 |
|---|---|---|
nohup cmd & |
忽略 SIGHUP,仍在原会话内 | 启动前就决定要脱离终端 |
setsid cmd |
创建新会话,进程 PPID 变为 1,彻底脱离终端 | 启动前决定,且希望完全独立 |
disown |
把已在后台运行的作业从 Shell 作业表中移除 | 任务已经跑起来了才想起来脱离终端 |
(cmd &) |
子 Shell 中后台运行,子 Shell 退出后被 1 号进程收养 | 快速写法 |
# setsid:创建新会话,彻底脱离控制终端
setsid ping 127.0.0.1 > /tmp/ping.log 2>&1
ps -eo pid,ppid,sid,tty,comm | grep ping # PPID 为 1,TTY 为 ?
# setsid 与 nohup 组合(最稳妥)
setsid nohup ./run.sh > /var/log/run.log 2>&1 &
# 子 Shell 写法
(./run.sh > /var/log/run.log 2>&1 &)
# disown:任务已在后台运行,事后补救
./long-task.sh &
jobs -l
disown %1 # 从作业表移除指定作业,关闭终端不再收到 SIGHUP
disown -h %1 # 保留在作业表中,但标记为忽略 SIGHUP
disown -a # 移除所有作业
disown -r # 只移除正在运行的作业
实战建议:临时任务用
nohup ... &;已经忘记加nohup的长任务用Ctrl + Z→bg→disown -h %1补救;长期运行的服务应写成 systemd unit(见08-01-系统启动与服务管理.md)。
7.7 screen 与 tmux:可断开重连的终端会话
nohup 只能让任务后台运行,但看不到过程、不能再交互。screen / tmux 提供可分离(detach)与重连(attach)的虚拟终端,适合远程执行长时间交互式任务(编译、大规模数据迁移、yum upgrade),网络断开也不会中断任务。
screen 常用操作:
yum install -y screen
screen # 创建一个新会话并进入
screen -S mytask # 创建并命名会话
screen -ls # 列出所有会话(Attached / Detached 状态)
screen -r mytask # 重新连接指定会话
screen -r PID # 按会话 ID 重连
screen -d -r mytask # 强制从别处踢下并接管
screen -x mytask # 多人共享同一会话(同屏协作)
screen -S mytask -X quit # 从外部结束指定会话
exit # 在会话内退出并销毁该窗口
快捷键(前缀 Ctrl + a) |
作用 |
|---|---|
Ctrl + a 然后 d |
分离(detach)当前会话,任务继续在后台运行 |
Ctrl + a 然后 c |
创建新窗口 |
Ctrl + a 然后 n / p |
切换到下一个 / 上一个窗口 |
Ctrl + a 然后 数字 |
切换到指定编号的窗口 |
Ctrl + a 然后 " |
列出所有窗口供选择 |
Ctrl + a 然后 k |
强制关闭当前窗口 |
Ctrl + a 然后 ? |
查看帮助 |
tmux 常用操作(更现代的替代品):
yum install -y epel-release && yum install -y tmux
tmux # 创建新会话
tmux new -s mytask # 创建并命名会话
tmux ls # 列出所有会话
tmux attach -t mytask # 重连指定会话
tmux a -t mytask # 同上(简写)
tmux kill-session -t mytask # 结束指定会话
tmux kill-server # 结束所有会话
快捷键(前缀 Ctrl + b) |
作用 |
|---|---|
Ctrl + b 然后 d |
分离当前会话 |
Ctrl + b 然后 c |
创建新窗口 |
Ctrl + b 然后 n / p |
下一个 / 上一个窗口 |
Ctrl + b 然后 % |
左右分屏 |
Ctrl + b 然后 " |
上下分屏 |
Ctrl + b 然后 方向键 |
在分屏间移动光标 |
Ctrl + b 然后 x |
关闭当前面板 |
Ctrl + b 然后 [ |
进入复制模式,可翻阅历史输出 |
Ctrl + b 然后 s |
列出会话供切换 |
对比:
nohup适合"发出去就不管"的批处理任务;screen/tmux适合"要盯着看、还要能中途操作"的任务。远程升级系统、迁移数据库时强烈建议先进 tmux/screen 再动手。
7.8 实验:作业控制
echo "hello job1" > 1_jobs.txtecho "hello job2" > 2_jobs.txtecho "hello job3" > 3_jobs.txtsleep 999jobs -lfg- 按
Ctrl + Z jobs -lfg 1- 按
Ctrl + Z jobs -lfg 2- 按
Ctrl + Z fg 4- 按
Ctrl + Z fg 4- 按
Ctrl + C sleep 999- 按
Ctrl + D
实验中请留意:
Ctrl + Z之后作业状态为Stopped;fg不带参数操作的是带+的当前作业;对不存在的作业号执行fg会报no such job。
7.9 实验:nohup 后台不中断复制
-
生成一个约 5G 的文件:
dd if=/dev/zero of=./5G.txt bs=100M count=50 -
在 A 终端运行前台复制:
cp 5G.txt ./1_5G.txt -
在 A 终端按
Ctrl + C; -
过一段时间查看
1_5G.txt的大小,确认不会继续变大(ls -lh或du -sh); -
在 A 终端运行后台复制:
cp 5G.txt ./2_5G.txt & -
在 A 终端运行
jobs -l; -
在 A 终端按
Ctrl + C(或关闭终端); -
过一段时间查看
2_5G.txt的大小,观察是否继续变大(普通&在终端关闭后会收到 SIGHUP 而终止); -
在 A 终端运行:
nohup cp 5G.txt ./3_5G.txt & -
在 A 终端运行
jobs -l; -
关闭 A 终端;
-
重新打开终端,过一段时间查看
3_5G.txt的大小,确认其继续变大; -
重复步骤 12,观察
3_5G.txt的大小变化直到复制完成; -
关闭终端后重新登录,用
ps -ef | grep cp确认任务仍在执行。
命令小结:
dd if=/dev/zero of=./5G.txt bs=100M count=50 # 生成 5G 测试文件
cp /soft/* /test/ -rf & # 普通后台拷贝,关闭终端后任务可能中断
nohup cp 5G.txt ./3_5G.txt & # 脱离终端后台拷贝
nohup cp /soft/* /test/ -rf & # 使用 nohup 保持后台运行,即使关闭终端也不中断
ps -ef | grep cp # 换终端后用 ps 而不是 jobs 查看
八、系统负载与资源监控
8.1 uptime 与平均负载
uptime
w # 顶部第一行与 uptime 相同,并列出登录用户
cat /proc/loadavg # 平均负载的原始数据源
top # 第一行同样显示 load average
09:12:31 up 12 days, 3:41, 2 users, load average: 0.15, 0.09, 0.06
| 部分 | 含义 |
|---|---|
09:12:31 |
当前时间 |
up 12 days, 3:41 |
系统已连续运行 12 天 3 小时 41 分 |
2 users |
当前登录的用户数(同一用户多个终端算多个) |
load average |
最近 1 分钟、5 分钟、15 分钟的平均负载 |
/proc/loadavg 的 5 个字段:
0.15 0.09 0.06 2/213 26801
| 字段 | 含义 |
|---|---|
| 前三个 | 1/5/15 分钟平均负载 |
2/213 |
当前可运行进程数 / 系统进程总数 |
26801 |
最近创建的进程 PID |
平均负载到底是什么:
平均负载指单位时间内处于可运行状态(R)和不可中断状态(D)的平均进程数。因此它不等于 CPU 使用率:
- 大量进程等待 CPU → 负载高,CPU 使用率也高(CPU 密集型);
- 大量进程卡在磁盘 I/O(D 状态)→ 负载高,但 CPU 使用率可能很低(I/O 密集型)。
判断标准:与 CPU 核心数比较
nproc # 查看逻辑 CPU 核心数
grep -c 'model name' /proc/cpuinfo # 同上
lscpu | grep -E '^CPU\(s\)|每个核|Core' # 查看核心与线程详情
| 负载 / 核心数 | 判断 |
|---|---|
| < 0.7 | 系统健康,有余量 |
| 0.7 ~ 1.0 | 已比较繁忙,需要开始关注 |
| = 1.0 | 满载,无余量 |
| > 1.0 | 有进程在排队等待,出现延迟 |
| > 5.0 | 严重过载,业务响应明显受影响,需立即排查 |
趋势判断(三个数字对比):
| 现象 | 结论 |
|---|---|
| 1 分钟 >> 15 分钟 | 负载正在快速上升,可能有突发故障或流量高峰 |
| 1 分钟 << 15 分钟 | 高峰已过,负载正在下降,可能已自愈 |
| 三者接近且都很高 | 持续过载,需要扩容或优化 |
示例:4 核机器
load average: 6.00, 2.00, 1.00,说明 1 分钟内负载飙到 6(超过核心数 4),且相比 15 分钟前上涨了 6 倍,属于典型的突发过载,应立即用top、vmstat、pidstat定位元凶。
8.2 vmstat:系统整体状态
vmstat(virtual memory statistics)是最重要的整体性能概览工具,属于 procps-ng 包。
vmstat # 只输出一次(自开机以来的平均值,参考价值有限)
vmstat 2 # 每 2 秒采样一次,持续输出
vmstat 2 5 # 每 2 秒采样一次,共采 5 次(生产排查常用)
vmstat -S M 2 # 以 MB 为单位显示
vmstat -w 2 # 宽格式,列对齐更易读
vmstat -a 2 # 显示活跃/非活跃内存(替换 buff/cache 列)
vmstat -d # 显示磁盘统计
vmstat -s # 以列表形式显示内存与事件计数器汇总
vmstat -t 2 # 每行附带时间戳(长时间采集必加)
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
r b swpd free buff cache si so bi bo in cs us sy id wa st
2 0 0 201864 12345 245310 0 0 3 12 152 310 2 1 97 0 0
| 分组 | 列 | 含义与判读要点 |
|---|---|---|
procs |
r |
运行队列长度(正在运行 + 等待 CPU 的进程数)。持续大于 CPU 核心数说明 CPU 不足 |
b |
处于不可中断睡眠(D 状态)的进程数。持续不为 0 说明 I/O 有瓶颈 | |
memory |
swpd |
已使用的虚拟内存(交换分区)大小 KB。持续增长说明物理内存不足 |
free |
空闲物理内存 KB | |
buff |
用作缓冲(buffer,块设备元数据)的内存 KB | |
cache |
用作缓存(page cache,文件内容)的内存 KB | |
swap |
si |
每秒从交换分区换入内存的量 KB/s |
so |
每秒从内存换出到交换分区的量 KB/s。si/so 长期非 0 是内存告急的铁证 |
|
io |
bi |
每秒从块设备读入的块数(blocks in) |
bo |
每秒写入块设备的块数(blocks out)。数值大且 wa 高说明磁盘是瓶颈 |
|
system |
in |
每秒中断次数(interrupts),含时钟中断 |
cs |
每秒上下文切换次数(context switches)。异常高说明进程/线程过多或锁竞争严重 | |
cpu |
us |
用户态时间百分比。高说明应用自身在算 |
sy |
内核态时间百分比。高说明系统调用/中断频繁 | |
id |
空闲百分比 | |
wa |
等待 I/O 百分比。超过 20% 需重点排查磁盘 | |
st |
被虚拟化宿主机抢占的时间百分比。虚拟机中高说明宿主机资源超卖 |
判读口诀:
r大、us高 → CPU 不够,应用计算密集,查代码/扩容;b大、wa高、bi/bo大 → 磁盘 I/O 瓶颈;si/so持续非 0 → 内存不足,已开始换页,性能急剧下降;cs、in异常高而us/sy不高 → 上下文切换过于频繁(线程过多、锁竞争);st高 → 不是你的问题,是宿主机超卖。注意:
vmstat输出的第一行是自系统启动以来的平均值,排查时应忽略第一行,看后续的实时采样。
8.3 free:内存使用情况
free # 默认以 KB 为单位
free -h # 人类可读(自动带单位,最常用)
free -m # 以 MB 为单位
free -g # 以 GB 为单位
free -s 2 # 每 2 秒刷新一次
free -c 5 -s 1 # 每秒刷新,共 5 次
free -t # 增加一行总计(Total)
free -w # 宽格式,把 buffers 与 cache 分列显示(CentOS 7 较新版本)
cat /proc/meminfo # 原始数据源,字段最全
CentOS 7 输出:
total used free shared buff/cache available
Mem: 3865292 1210324 201864 12345 2453104 2312196
Swap: 2097148 0 2097148
| 列 | 含义 |
|---|---|
total |
物理内存总量 |
used |
已使用(= total - free - buff/cache) |
free |
完全空闲、未被使用的内存 |
shared |
共享内存(tmpfs 等) |
buff/cache |
缓冲区与页缓存合计,可被回收 |
available |
可用内存估算:无需换出 swap 即可分配给新程序的量 |
CentOS 6 输出(差异重点):
total used free shared buffers cached
Mem: 3865292 3663428 201864 0 123456 2453104
-/+ buffers/cache: 1086868 2778424
Swap: 2097148 0 2097148
| CentOS 6 特点 |
|---|
buffers 与 cached 分为两列显示,没有 buff/cache 合并列 |
没有 available 列 |
多出 -/+ buffers/cache 行:该行的 used 才是应用真正占用的内存,free 才是真正可用的内存 |
核心结论(面试高频):
- Linux 会主动把空闲内存用作缓存以加速文件访问,
free列很小是正常现象,不代表内存不足;- CentOS 7 看
available,CentOS 6 看-/+ buffers/cache行的free,这两个才是真实可用内存;- 只有当
available很小且swap used持续增长、vmstat的si/so非 0 时,才是真正的内存不足。
手动释放缓存(仅用于测试环境,生产慎用):
sync # 先把脏页写回磁盘
echo 1 > /proc/sys/vm/drop_caches # 释放 page cache
echo 2 > /proc/sys/vm/drop_caches # 释放 dentries 和 inodes
echo 3 > /proc/sys/vm/drop_caches # 释放以上全部
echo 0 > /proc/sys/vm/drop_caches # 恢复默认(不再主动释放)
警告:释放缓存会让后续文件访问全部回落到磁盘,性能会短时间明显下降。生产环境不要因为"看着 free 小"而去释放缓存。
查看交换分区使用情况:
swapon -s # 查看已启用的交换分区/文件
cat /proc/swaps # 同上
free -h | grep -i swap
# 找出使用 swap 最多的进程
for p in /proc/[0-9]*; do awk '/VmSwap/{print '"${p##*/}"', $2, $3}' $p/status 2>/dev/null; done | sort -k2 -nr | head
cat /proc/sys/vm/swappiness # 交换倾向(0-100,默认 30/60),调优见 04-03
8.4 sysstat 套件:iostat / mpstat / sar / pidstat
iostat、mpstat、sar、pidstat 都由 sysstat 包提供,需要先安装:
yum install -y sysstat
systemctl enable --now sysstat # CentOS 7:启用历史数据采集
service sysstat start # CentOS 6:启用历史数据采集
chkconfig sysstat on # CentOS 6:设置开机自启
8.4.1 iostat:磁盘 I/O 统计
iostat # 输出 CPU 与磁盘的平均统计(自开机)
iostat 2 # 每 2 秒刷新
iostat 2 5 # 每 2 秒采样,共 5 次
iostat -x 2 # 扩展统计(含 %util、await,排查磁盘必用)
iostat -d 2 # 只看磁盘
iostat -c 2 # 只看 CPU
iostat -xm 2 # 扩展统计并以 MB 为单位
iostat -xk 2 # 以 KB 为单位
iostat -x -d /dev/sda 2 # 只看指定设备
iostat -p sda 2 # 显示 sda 及其各个分区
iostat -t 2 # 每次输出附带时间戳
iostat -N 2 # 显示 LVM 设备名
Device: rrqm/s wrqm/s r/s w/s rkB/s wkB/s avgrq-sz avgqu-sz await r_await w_await svctm %util
sda 0.00 1.50 0.20 12.30 4.80 210.40 34.50 0.05 3.80 1.20 4.20 0.45 1.20
| 列 | 含义与判读要点 |
|---|---|
rrqm/s |
每秒合并的读请求数 |
wrqm/s |
每秒合并的写请求数 |
r/s |
每秒完成的读请求数(读 IOPS) |
w/s |
每秒完成的写请求数(写 IOPS) |
rkB/s |
每秒读取的 KB 数(读吞吐) |
wkB/s |
每秒写入的 KB 数(写吞吐) |
avgrq-sz |
平均每个请求的扇区数(请求大小),小值多说明随机 I/O |
avgqu-sz |
平均 I/O 队列长度。持续大于 1 说明磁盘开始排队 |
await |
平均每个 I/O 的等待时间(含排队 + 服务),单位 ms。机械盘超过 20ms、SSD 超过 5ms 即需关注 |
r_await |
读请求平均等待时间 ms |
w_await |
写请求平均等待时间 ms |
svctm |
平均服务时间 ms(新版内核已不可靠,仅作参考) |
%util |
设备使用率(有 I/O 请求的时间占比)。接近 100% 说明磁盘已饱和 |
判读口诀:
%util高 +await大 +avgqu-sz大 → 磁盘是瓶颈。再配合pidstat -d或iotop找出具体是哪个进程在压磁盘。
yum install -y iotop
iotop # 交互式查看各进程实时 I/O(类似 top)
iotop -o # 只显示实际有 I/O 的进程
iotop -oP # 只看进程(不展开线程)
iotop -b -n 3 # 批处理模式,采 3 次,便于脚本采集
8.4.2 mpstat:每个 CPU 核心的使用率
mpstat # 所有 CPU 的平均值
mpstat 2 # 每 2 秒刷新
mpstat 2 5 # 每 2 秒采样,共 5 次
mpstat -P ALL # 分别显示每个核心
mpstat -P ALL 2 # 每 2 秒显示每个核心(排查单核跑满必用)
mpstat -P 0 2 # 只看 0 号核心
mpstat -I SUM 2 # 显示中断统计汇总
CPU %usr %nice %sys %iowait %irq %soft %steal %guest %gnice %idle
all 2.31 0.00 0.72 0.31 0.00 0.05 0.00 0.00 0.00 96.61
0 98.50 0.00 1.00 0.00 0.00 0.00 0.00 0.00 0.00 0.50
1 0.50 0.00 0.25 0.25 0.00 0.00 0.00 0.00 0.00 99.00
核心价值:
top的%Cpu(s)是所有核心的平均值。上例中all只有 2.31%,看起来很闲,但 0 号核心已经 98.5% 跑满——这正是单线程程序打满单核的典型特征。排查"负载高但 CPU 看起来不高"时,必须用mpstat -P ALL(或 top 中按1)逐核查看。
8.4.3 sar:系统活动报告(含历史数据)
sar 最大的价值是能回溯历史:sysstat 会通过 cron 定期把数据存到 /var/log/sa/saDD(DD 为日期),故障过后仍可复盘。
# 实时采集
sar 2 5 # CPU 使用率,每 2 秒 5 次
sar -u 2 5 # 同上(-u 为 CPU,默认)
sar -P ALL 2 # 每个核心的 CPU 使用率
sar -r 2 5 # 内存使用情况
sar -R 2 5 # 内存页变化速率
sar -S 2 5 # 交换分区使用情况
sar -W 2 5 # 换页统计(pswpin/s、pswpout/s)
sar -b 2 5 # 整体 I/O 速率(tps、rtps、wtps)
sar -d 2 5 # 每个块设备的 I/O
sar -q 2 5 # 运行队列长度与平均负载
sar -n DEV 2 5 # 网络接口流量
sar -n TCP,ETCP 2 5 # TCP 连接统计
sar -v 2 5 # inode、文件句柄等内核表使用情况
sar -w 2 5 # 进程创建与上下文切换速率
# 查看历史数据
sar -f /var/log/sa/sa15 # 查看本月 15 日的历史 CPU 数据
sar -r -f /var/log/sa/sa15 # 查看 15 日的内存历史
sar -q -f /var/log/sa/sa15 # 查看 15 日的负载历史
sar -s 09:00:00 -e 12:00:00 -f /var/log/sa/sa15 # 只看指定时间段
sar -u -s 14:00:00 -e 15:00:00 # 查看今天 14-15 点的 CPU
sar -A # 输出所有可用统计
sar -o /tmp/sar.dat 2 30 # 采集并保存为二进制文件供后续分析
排障场景:客户反馈"昨天下午 3 点系统很卡",此时现场早已恢复。用
sar -q -f /var/log/sa/sa14 -s 15:00:00 -e 15:30:00即可看到当时的负载与运行队列,配合sar -r、sar -b判断是 CPU、内存还是磁盘问题。这是 sar 不可替代的价值。
8.4.4 pidstat:按进程统计资源
pidstat 弥补了 top/ps 的不足:可以持续采样、能单独统计某进程的 CPU / 内存 / I/O / 上下文切换。
pidstat # 所有进程的 CPU 统计(自开机平均)
pidstat 2 5 # 每 2 秒采样,共 5 次
pidstat -u 2 # CPU 使用(默认)
pidstat -r 2 # 内存使用(含 minflt/s、majflt/s 缺页统计)
pidstat -d 2 # 磁盘 I/O 读写速率(定位是谁在压磁盘)
pidstat -w 2 # 上下文切换次数(cswch/s 自愿、nvcswch/s 非自愿)
pidstat -t 2 # 展开到线程级别
pidstat -p PID 2 # 只监控指定进程
pidstat -p PID -urd 2 # 同时看该进程的 CPU、内存、磁盘
pidstat -C "nginx" 2 # 按命令名过滤
pidstat -l 2 # 显示完整命令行
pidstat -h 2 # 所有统计并排显示在一行(便于 awk 处理)
pidstat -T CHILD 2 # 统计包含子进程的资源消耗
UID PID %usr %system %guest %CPU CPU Command
0 1234 45.00 5.00 0.00 50.00 1 java
| 列 | 含义 |
|---|---|
%usr |
该进程在用户态占用的 CPU 百分比 |
%system |
该进程在内核态占用的 CPU 百分比 |
%wait |
该进程等待 CPU 的时间百分比(较新版本) |
%CPU |
合计 CPU 百分比 |
CPU |
该进程当前运行在哪个核心上 |
minflt/s |
每秒次缺页错误(可从内存/缓存满足) |
majflt/s |
每秒主缺页错误(需读磁盘,高说明内存不足或频繁换页) |
kB_rd/s |
每秒读磁盘 KB |
kB_wr/s |
每秒写磁盘 KB |
cswch/s |
每秒自愿上下文切换(等资源导致,如等 I/O、等锁) |
nvcswch/s |
每秒非自愿上下文切换(时间片耗尽被抢占,高说明 CPU 竞争激烈) |
8.5 top 与 ps 的差异
| 对比项 | ps |
top |
|---|---|---|
| 数据性质 | 静态快照,执行瞬间的一次采样 | 动态实时,按间隔持续刷新 |
%CPU 含义 |
平均值:进程整个生命周期内 CPU 时间 / 存活时间 | 瞬时值:上一个刷新间隔内的 CPU 占用 |
| 交互能力 | 无,纯输出 | 有:可排序、过滤、kill、renice |
| 排序 | 需 --sort 选项 |
按 P/M/T 等键即时排序 |
| 脚本友好度 | 高,输出稳定易解析 | 需 -b -n 批处理模式 |
| 适用场景 | 精确取字段、脚本采集、查看完整命令行 | 现场观察、找出当前最耗资源的进程 |
关键理解(面试常问):为什么同一个进程
ps aux显示%CPU只有 0.5,而top里却是 90%?因为
ps的%CPU= 进程累计 CPU 时间 ÷ 进程存活时间,是一个长期平均值。一个跑了 10 小时、刚刚才开始满载的进程,长期平均自然很低;而top反映的是刚刚这 3 秒的真实情况。结论:判断"现在谁在吃 CPU"用
top/pidstat;判断"这个进程一直以来是否耗 CPU"用ps的%CPU与TIME列。另外
top的%CPU在多核系统上可以超过 100%(如 4 核可达 400%),表示进程使用了多个核心;加-1参数或按I键(Irix/Solaris 模式切换)可改为按核心数归一化显示。
8.6 其他常用监控命令速览
dstat -tcmdny 2 # 一屏显示 CPU/内存/磁盘/网络/系统(需 yum install dstat)
sar -n DEV 2 # 网卡流量
iftop # 实时网络流量按连接排序(需 EPEL)
nethogs # 按进程显示网络占用(需 EPEL)
glances # 综合监控面板(需 EPEL)
watch -n 1 'ps -eo pid,%cpu,comm --sort=-%cpu | head' # 用 watch 组合任意命令做轮询
strace -p PID # 跟踪进程的系统调用(排查卡在哪个调用)
strace -c -p PID # 统计各系统调用的耗时占比
ltrace -p PID # 跟踪库函数调用
pstack PID # 打印进程各线程的调用栈(gdb 包提供)
gstack PID # 同上
sar -q # 运行队列与负载
tload # 以字符图形显示负载变化
vmstat -s # 内存与事件计数器汇总
九、常见故障排错清单
9.1 通用排查顺序
1. uptime / w -> 负载有多高?是上升还是下降?
2. top(按 1 看各核) -> CPU 花在 us / sy / wa / st 哪里?
3. vmstat 2 5 -> r / b 队列,si / so 换页,bi / bo 磁盘
4. free -h -> available 是否见底?swap 是否在涨?
5. iostat -x 2 -> %util / await 是否饱和?
6. mpstat -P ALL 2 -> 是否单核跑满?
7. pidstat -urd 2 -> 锁定具体是哪个进程
8. ps -eo pid,ppid,stat,wchan,comm -> 看状态与内核等待点
9. 日志:dmesg -T / /var/log/messages / journalctl
9.2 CPU 使用率 100%
排查步骤:
# 1. 确认整体负载与 CPU 分布
uptime
top # 按 P 排序,按 1 展开各核心
mpstat -P ALL 2 3 # 确认是全核繁忙还是单核跑满
# 2. 找出最耗 CPU 的进程
ps aux --sort=-%cpu | head -n 11
pidstat -u 2 3 | sort -k8 -nr | head
# 3. 定位到线程(Java、Nginx 等多线程程序必做)
top -H -p PID # 查看该进程内各线程的 CPU
ps -mp PID -o THREAD,tid,time | head
pidstat -t -p PID 2 3
# 4. 看进程在做什么
strace -c -p PID # 统计系统调用耗时(几秒后 Ctrl+C)
pstack PID # 打印调用栈
cat /proc/PID/status
ls -l /proc/PID/exe # 确认程序真实路径
tr '\0' ' ' < /proc/PID/cmdline # 确认启动参数
# 5. Java 应用专项
printf "%x\n" 线程TID # 把十进制 TID 转成十六进制
jstack PID > /tmp/jstack.log # 在栈日志中搜索该十六进制 nid
jstat -gcutil PID 1000 # 排查是否是 GC 导致的 CPU 飙高
判断与处置:
| 现象 | 可能原因 | 处置 |
|---|---|---|
us 高,单个进程独占 |
应用死循环 / 正则回溯 / 算法问题 | 定位线程栈修复代码;应急可 renice 降优先级或重启进程 |
us 高,多个同类进程 |
请求量过大 | 限流、扩容、加缓存 |
sy 高 |
系统调用/中断频繁、进程频繁创建 | strace -c 看热点调用;检查是否有脚本疯狂 fork |
wa 高而 us/sy 不高 |
磁盘 I/O 瓶颈(非 CPU 问题) | 转 9.5 节 D 状态与 iostat 排查 |
st 高 |
宿主机 CPU 超卖 | 联系云厂商或迁移实例 |
| 单核 100%、其他核空闲 | 单线程程序 / 中断未做绑核均衡 | 应用改多线程;检查 cat /proc/interrupts 与 irqbalance |
cs 上下文切换极高 |
线程数过多、锁竞争 | pidstat -w 定位;减少线程池大小 |
应急降压手段:
renice -n 19 -p PID # 立刻降低优先级,给业务腾出 CPU
kill -19 PID # 暂停该进程(保留现场,事后再分析)
kill -18 PID # 需要时恢复
kill -15 PID # 确认可以重启后优雅终止
taskset -cp 2,3 PID # 把进程绑到指定核心,隔离影响
9.3 内存泄漏排查
判定内存泄漏的关键是"持续增长且不回落"。
# 1. 确认系统级内存趋势
free -h
sar -r 1 10 # 观察 kbmemused / %memused 走势
sar -r -f /var/log/sa/sa$(date +%d) # 看今天全天趋势(关键)
vmstat 2 10 # 看 si/so 是否已开始换页
# 2. 找出内存占用最高的进程
ps aux --sort=-%mem | head -n 11
ps -eo pid,ppid,user,rss,vsz,%mem,etime,comm --sort=-rss | head -n 11
top # 按 M 排序
# 3. 持续观察目标进程的 RSS 是否单调递增(核心判据)
while true; do
date '+%F %T' | tr '\n' ' '
ps -o rss= -p PID
sleep 60
done | tee /tmp/rss.log
pidstat -r -p PID 60 # 同时观察 minflt/majflt
# 4. 分析进程内存构成
cat /proc/PID/status | grep -E 'VmRSS|VmSize|VmSwap|Threads'
cat /proc/PID/smaps | grep -i '^Rss' | awk '{s+=$2} END {print s" kB"}'
pmap -x PID | tail -n 3 # 查看内存映射汇总
pmap -d PID
# 5. 查看是谁在用 swap
for p in /proc/[0-9]*; do
awk '/VmSwap/{print '"${p##*/}"', $2, $3}' $p/status 2>/dev/null
done | sort -k2 -nr | head
判读要点:
| 现象 | 结论 |
|---|---|
RSS 持续单调上升,重启后归零并再次上升 |
典型内存泄漏,需修复代码 |
RSS 涨到某值后稳定 |
正常:应用内存池/JVM 堆已达上限 |
VSZ 大而 RSS 小 |
正常:只是申请了虚拟地址,未真正使用 |
buff/cache 大而 available 充足 |
正常现象,不是泄漏 |
available 见底 + swap 持续增长 + so 非 0 |
真正的内存告急,随时可能触发 OOM |
slab 持续增长(cat /proc/meminfo) |
内核对象泄漏,用 slabtop 进一步定位 |
slabtop -o # 查看内核 slab 分配情况
cat /proc/meminfo | grep -i slab
# 常见:dentry 缓存过大(大量文件遍历),可通过 drop_caches 验证
处置:
- 短期:定时重启进程(或配置 systemd
Restart=与MemoryLimit=); - 中期:限制单进程内存(
ulimit -v、cgroup、JVM-Xmx); - 长期:交研发用 Valgrind / gperftools / JVM MAT 分析并修复。
9.4 僵尸进程清理
# 1. 统计与定位
ps -aux | grep Z # 快速查看
ps -eo stat= | grep -c '^Z' # 统计僵尸个数
top | head -n 3 # Tasks 行的 zombie 计数
ps -eo pid,ppid,stat,user,comm | awk '$3 ~ /^Z/ {print}' # 列出僵尸及其父进程
# 2. 找出制造僵尸最多的父进程(元凶)
ps -eo ppid,stat | awk '$2 ~ /^Z/ {print $1}' | sort | uniq -c | sort -rn | head
# 3. 确认父进程身份,评估影响
ps -p 父进程PID -o pid,user,etime,args
# 4. 处置
kill -CHLD 父进程PID # 先温和尝试:提醒父进程去 wait()(多数情况无效)
kill -15 父进程PID # 优雅重启父进程
kill -9 父进程PID # 父进程无响应时强杀,僵尸随即被 PID 1 回收
systemctl restart 服务名 # CentOS 7:由 systemd 托管的服务用此方式重启
要点:
- 不能直接 kill 僵尸进程——它已经死了,不再执行代码,任何信号都无意义;
- 少量僵尸(个位数、瞬时出现)无需处理,那是进程退出的正常阶段;
- 需要处理的是数量持续增长的情况,根因是父进程程序有 bug(未调用
wait()/未处理SIGCHLD); - 真正的修复方式是让研发在父进程中正确回收子进程,或设置
signal(SIGCHLD, SIG_IGN); - 若父进程是 PID 1 仍出现大量僵尸,通常是容器内 1 号进程(如直接
CMD ["python","app.py"])不具备收割能力,应改用tini、dumb-init或--init。
9.5 D 状态(不可中断睡眠)进程
D 状态进程正在等待内核完成某项操作(通常是磁盘或网络文件系统 I/O),不响应任何信号,kill -9 也无效,且会计入平均负载,是"负载很高但 CPU 很闲"的常见元凶。
# 1. 找出 D 状态进程
ps -eo pid,ppid,stat,wchan:30,comm | awk '$3 ~ /D/ {print}'
ps aux | awk '$8 ~ /D/ {print}'
watch -n 1 "ps -eo stat,pid,comm | grep '^D'" # 持续观察是偶发还是持续
# 2. 确认它卡在内核的哪个函数(关键线索)
cat /proc/PID/wchan ; echo
cat /proc/PID/stack # 需 root,内核栈回溯
cat /proc/PID/status | grep -i state
# 3. 确认是否为磁盘/网络存储瓶颈
vmstat 2 5 # 看 b 列与 wa 列
iostat -x 2 5 # 看 %util 与 await
iotop -o # 看是谁在压磁盘
dmesg -T | tail -n 50 # 找 I/O error、SCSI 超时、NFS server not responding
常见原因与处置:
| 原因 | 线索 | 处置 |
|---|---|---|
| 磁盘正常但压力极大 | %util 接近 100%,await 高 |
限流、错峰、加 SSD、优化 SQL;等 I/O 完成后进程自然恢复 |
| 磁盘硬件故障 / 坏道 | dmesg 有 I/O error、medium error |
更换磁盘;检查 RAID 状态与 SMART(smartctl -a /dev/sda) |
| NFS / 网络存储不可达 | wchan 含 nfs,dmesg 有 nfs: server ... not responding |
恢复 NFS 服务端或网络;挂载时用 soft,intr 避免硬挂死 |
| 内存不足导致频繁换页 | si/so 高,majflt 高 |
释放/扩容内存,见 9.3 |
| 内核 bug / 驱动问题 | stack 停在固定内核函数,进程永久卡死 |
升级内核;只有重启机器能清除 |
重要结论:D 状态进程无法被杀死。若确认是短暂 I/O 压力,等待即可;若长期卡死(数分钟以上不动)且已排除存储问题,只能通过重启主机清除。这也是"为什么某些进程
kill -9打不死"的标准答案。
9.6 OOM Killer 与日志分析
物理内存与 swap 都耗尽时,内核会启动 OOM Killer(Out Of Memory Killer),按评分选一个进程发 SIGKILL 强制杀掉,以保住系统整体存活。
评分规则:
- 评分记录在
/proc/PID/oom_score,分数越高越先被杀; - 主要影响因素:占用内存越多分数越高,运行时间越短、越不重要(非 root)分数越高;
/proc/PID/oom_score_adj范围-1000 ~ 1000,可人为调整;设为-1000表示完全豁免。
cat /proc/PID/oom_score # 查看某进程的 OOM 评分
cat /proc/PID/oom_score_adj # 查看调整值
echo -1000 > /proc/PID/oom_score_adj # 保护关键进程(如数据库)不被 OOM 杀掉
echo 500 > /proc/PID/oom_score_adj # 让某进程优先被杀
# 找出当前最容易被 OOM 杀掉的进程
for p in /proc/[0-9]*; do
printf "%s %s %s\n" "$(cat $p/oom_score 2>/dev/null)" "${p##*/}" "$(cat $p/comm 2>/dev/null)"
done | sort -rn | head
查看 OOM 日志(排障必备):
# 内核环形缓冲区(-T 显示可读时间,CentOS 7 支持)
dmesg -T | grep -i -E 'oom|killed process'
dmesg | grep -i 'out of memory'
# 系统日志
grep -i -E 'oom|killed process' /var/log/messages
grep -i 'Out of memory' /var/log/messages*
# CentOS 7 的 journald
journalctl -k | grep -i oom # 只看内核消息
journalctl -k --since "2 hours ago" | grep -i oom
journalctl --since "2026-08-21 14:00" --until "2026-08-21 15:00" | grep -i oom
journalctl -b -1 -k | grep -i oom # 查看上一次启动的内核日志
典型日志片段解读:
Aug 21 14:32:11 web01 kernel: java invoked oom-killer: gfp_mask=0x201da, order=0, oom_score_adj=0
Aug 21 14:32:11 web01 kernel: Out of memory: Kill process 12345 (java) score 902 or sacrifice child
Aug 21 14:32:11 web01 kernel: Killed process 12345 (java) total-vm:8388608kB, anon-rss:6291456kB, file-rss:0kB
| 信息 | 含义 |
|---|---|
invoked oom-killer |
是谁的内存申请触发了 OOM(触发者不一定是被杀者) |
score 902 |
被选中进程的 OOM 评分 |
Killed process 12345 (java) |
实际被杀掉的进程 PID 与名称 |
total-vm / anon-rss |
被杀时的虚拟内存与匿名常驻内存,用于确认它确实是内存大户 |
判断"服务莫名消失"是否为 OOM:服务进程突然不见、日志无任何异常退出记录、
systemctl status显示signal=KILL,此时几乎一定是 OOM。到dmesg -T | grep -i killed一查即可确认。
预防措施:
# 1. 关键服务设置 OOM 豁免
echo -1000 > /proc/$(pidof mysqld)/oom_score_adj
# 2. CentOS 7 用 systemd 限制内存,避免单个服务拖垮整机
# 在 unit 中配置 MemoryLimit=2G / MemoryMax=2G(详见 08-01)
# 3. 配置足够的 swap 作为缓冲(不解决根因,但能争取告警处置时间)
dd if=/dev/zero of=/swapfile bs=1M count=2048
chmod 600 /swapfile
mkswap /swapfile
swapon /swapfile
echo '/swapfile swap swap defaults 0 0' >> /etc/fstab
# 4. 应用层限制:JVM 设置 -Xmx,容器设置 memory limit
# 5. 内核参数调优(vm.overcommit_memory、vm.swappiness)见 04-03-Linux系统优化.md
十、课堂练习
10.1 查看 ELF 信息
题目:查看 /bin/cp 的 ELF 头信息与段信息。
readelf -h /bin/cp
readelf -l /bin/cp
10.2 top 输出到文件
题目:每 0.5 秒采集一帧 top 数据,共采 2 帧,保存到 /tmp/top.txt。
top -d 0.5 -b -n 2 > /tmp/top.txt
cat /tmp/top.txt
10.3 对比 ps 两种风格
题目:分别用 BSD 风格与 System V 风格列出前 15 行进程,对比列的差异。
ps -aux | head -n 15 # 关注 %CPU / %MEM
ps -ef | head -n 15 # 关注 PPID / 完整 CMD
答案要点:aux 有 %CPU、%MEM、VSZ、RSS、STAT,COMMAND 会被截断;-ef 有 PPID、C,CMD 完整。看资源占用用 aux,看父子关系与完整命令用 -ef。
10.4 查找并结束进程
题目:启动一个 sleep 999,在另一个终端找到它并强制结束。
sleep 999 # 另开一个终端运行,或在末尾加 & 放入后台
ps -aux | grep sleep # 找到目标 PID
sudo kill -9 PID # 强制结束
扩展答案(三种等价做法):
kill -9 $(pgrep -x sleep)
pkill -9 -x sleep
killall -9 sleep
10.5 nice 练习
题目:分别以 nice 值 5 和 -5 启动 sleep 999,验证 NI 是否生效,并解释普通用户执行 nice -n -5 失败的原因。
nice -n 5 sleep 999 # 普通用户可调大 nice 值
sudo nice -n -5 sleep 999 # 负 nice 值需 root 权限
用 ps -eo pid,ni,comm | grep sleep 验证 NI 列是否生效。
答案要点:nice 值越小优先级越高,降低本进程优先级不影响他人,因此普通用户可为;提高优先级会抢占其他用户资源,属于特权操作,必须 root。
10.6 renice 练习
题目:对一个正在运行的进程先调大再调小 nice 值,观察权限差异。
renice 3 PID # 调大 nice 值(普通用户可执行)
sudo renice -n -5 -p PID # 调小 nice 值需 root 权限
答案要点:普通用户对 NI 的调整是不可逆的,调大后无法自行调回,必须 root 介入。
10.7 进程组信号
题目:区分"杀组长"与"杀整组"的效果。
kill -9 PID # 只杀组长本身
kill -9 -PGID # 杀掉整个进程组
用 ps -eo pid,pgid,comm 先确认 PGID 再操作。
答案要点:PID 前加负号表示进程组;kill -9 -1 会杀死几乎所有进程,属高危操作。
10.8 作业控制
题目:完成一次"前台 → 暂停 → 后台运行 → 回到前台"的完整切换。
echo "hello" > 1_jobs.txt
sleep 999 # 前台运行
Ctrl + z # 暂停并放入后台,状态变为 T
jobs -l # 查看作业号与 PID
bg %1 # 恢复为后台运行
fg %1 # 调回前台
答案要点:Ctrl + Z 后进程状态为 T(Stopped),bg 后变为 S/R;作业号只在当前会话内有效。
10.9 nohup 后台拷贝大文件
题目:生成 5G 测试文件并在关闭终端后仍能继续拷贝。
dd if=/dev/zero of=./5G.txt bs=100M count=50 # 生成 5G 测试文件
nohup cp 5G.txt ./3_5G.txt & # 脱离终端后台拷贝
关闭终端后重新登录,用 ps -ef | grep cp 确认任务仍在执行。
答案要点:& 只是后台运行,仍受 SIGHUP 影响;nohup 忽略 SIGHUP,因此终端关闭后任务继续。换终端后 jobs 看不到该任务,必须用 ps/pgrep 查找。
10.10 综合练习
-
用一条命令列出当前系统 CPU 占用最高的 5 个进程(含 PID、用户、命令)。
ps -eo pid,user,%cpu,%mem,comm --sort=-%cpu | head -n 6 -
统计系统中僵尸进程的个数,并找出制造僵尸最多的父进程。
ps -eo stat= | grep -c '^Z' ps -eo ppid,stat | awk '$2 ~ /^Z/ {print $1}' | sort | uniq -c | sort -rn | head -
查看 22 端口被哪个进程占用,并列出该进程打开的所有文件。
lsof -i :22 lsof -p $(pgrep -x sshd | head -n 1) -
卸载
/data时提示device is busy,写出定位与处理命令。fuser -mv /data # 先看清是谁在占用 lsof +D /data # 查看具体被打开的文件 fuser -km /data # 确认无风险后再杀(生产慎用) umount /data -
以最低 CPU 与磁盘优先级在后台执行一次全盘备份,并且关闭终端后不中断。
nohup nice -n 19 ionice -c 3 tar -czf /backup/full.tar.gz /data > /var/log/backup.log 2>&1 & -
判断某进程是否存在内存泄漏,写出观察方法。
while true; do date '+%F %T' | tr '\n' ' '; ps -o rss= -p PID; sleep 60; done # RSS 长期单调递增且不回落 -> 疑似泄漏 -
服务进程凭空消失且无异常日志,如何确认是否被 OOM Killer 杀掉?
dmesg -T | grep -i -E 'oom|killed process' grep -i 'Out of memory' /var/log/messages journalctl -k | grep -i oom -
某台 4 核服务器
load average: 8.00, 7.50, 7.20,但top中%Cpu(s)的id高达 90%,可能是什么原因?答案:负载统计包含 D 状态进程。CPU 空闲说明瓶颈不在 CPU,极可能是大量进程卡在磁盘 I/O。应查
vmstat的b/wa列、iostat -x的%util/await,并用ps -eo stat,pid,wchan,comm | grep '^D'找出 D 状态进程。
十一、常用命令速查表
| 分类 | 命令 | 说明 |
|---|---|---|
| 进程查看 | ps、ps aux、ps -ef |
查看进程快照 |
| 按 CPU 排序 | ps aux --sort=-%cpu \| head |
找出最耗 CPU 的进程 |
| 按内存排序 | ps aux --sort=-%mem \| head |
找出最耗内存的进程 |
| 查看优先级 | ps -el、ps -eo pid,ni,pri,comm |
查看 PRI 与 NI |
| 查看线程 | ps -eLf、top -H |
线程级别查看 |
| 实时监控 | top、htop |
实时任务管理器 |
| top 采集 | top -b -n 1 > /tmp/top.txt |
批处理模式写入文件 |
| 进程树 | pstree -p |
显示带 PID 的进程树 |
| 按名查找 | pgrep 名称、pgrep -u 用户、pidof 名称 |
按进程名或用户查找进程 |
| 按命令行查找 | pgrep -a -f "关键词" |
匹配完整命令行(脚本类进程) |
| 打开的文件 | lsof -p PID、lsof -i :端口 |
进程与文件/端口的关联 |
| 占用挂载点 | fuser -mv /挂载点 |
排查 device is busy |
| 进程详情 | cat /proc/PID/status、ls -l /proc/PID/fd |
内核暴露的进程信息 |
| 信号列表 | kill -l |
列出所有信号 |
| 优雅终止 | kill -15 PID(或 kill PID) |
请求进程正常退出(首选) |
| 强制杀死 | kill -9 PID |
立即销毁进程(最后手段) |
| 重读配置 | kill -1 PID、pkill -HUP 名称 |
让守护进程重载配置 |
| 暂停 / 恢复 | kill -19 PID / kill -18 PID |
SIGSTOP / SIGCONT |
| 存活检测 | kill -0 PID |
不发信号,仅判断进程是否存在 |
| 按名杀进程 | pkill -9 关键词、pkill -9 -x 名称 |
按名称匹配终止进程 |
| 按名杀进程 | killall -9 名称 |
按精确进程名批量终止 |
| 按终端杀进程 | pkill -t pts/2 |
踢掉某个终端会话 |
| 信号捕捉 | trap "操作" 信号 |
在 Shell 中捕捉信号并执行操作 |
| 启动优先级 | nice -n NI 命令 |
启动时指定 NI 值 |
| 调整优先级 | renice -n 数值 -p PID |
修改运行中进程的 NI 值 |
| I/O 优先级 | ionice -c 3 命令 |
以空闲级别执行磁盘 I/O |
| 绑定 CPU | taskset -cp 核心号 PID |
把进程绑定到指定核心 |
| 后台任务 | jobs -l |
列出当前会话后台任务及 PID |
| 后台启动 | 命令 & |
将命令放到后台运行 |
| 调至前台 | fg %编号 |
将后台任务调到前台 |
| 后台继续运行 | bg %编号 |
将已停止的后台任务改为运行 |
| 暂停前台任务 | Ctrl + Z |
前台任务放入后台并暂停 |
| 忽略挂断 | nohup 命令 & |
终端关闭后仍继续运行 |
| 新建会话 | setsid 命令 |
彻底脱离控制终端 |
| 事后脱离 | disown -h %编号 |
已运行的作业忽略 SIGHUP |
| 可重连会话 | screen -S 名称 / tmux new -s 名称 |
可分离与重连的终端会话 |
| 查僵尸进程 | ps -aux \| grep Z、ps -eo stat= \| grep -c '^Z' |
查看/统计状态为 Z 的僵尸进程 |
| 查 D 状态进程 | ps -eo stat,pid,wchan,comm \| grep '^D' |
排查不可中断睡眠进程 |
| 系统负载 | uptime、w、cat /proc/loadavg |
查看 1/5/15 分钟平均负载 |
| 整体状态 | vmstat 2 5 |
CPU/内存/交换/IO/上下文切换总览 |
| 内存使用 | free -h |
查看内存与交换分区 |
| 磁盘 I/O | iostat -x 2、iotop -o |
磁盘饱和度与按进程 I/O |
| 各核 CPU | mpstat -P ALL 2 |
逐核查看,排查单核跑满 |
| 历史数据 | sar -q -f /var/log/sa/saDD |
回溯历史负载/CPU/内存/IO |
| 按进程统计 | pidstat -urd 2 |
进程级 CPU/内存/IO 持续采样 |
| OOM 日志 | dmesg -T \| grep -i oom |
确认进程是否被 OOM Killer 杀掉 |
| OOM 豁免 | echo -1000 > /proc/PID/oom_score_adj |
保护关键进程 |
| 系统调用跟踪 | strace -c -p PID |
统计进程系统调用耗时 |
| 调用栈 | pstack PID |
打印各线程调用栈 |
| CPU 核心数 | nproc、lscpu |
判断负载高低的基准 |
十二、注意事项与最佳实践
- 谨慎使用
kill -9:SIGKILL不会给进程清理资源的机会,可能导致数据丢失或文件损坏。优先使用kill -15优雅终止,等待数秒确认后再考虑强杀。数据库、消息队列等有状态服务应优先使用其自带的停止命令。 - 批量杀进程前必须先查:
pkill默认是子串匹配,pkill java可能误杀。养成"先pgrep -a确认列表,再pkill执行"的习惯。 - 僵尸进程处理:直接 kill 僵尸进程无效,应找到并终止其父进程,让 PID 1(CentOS 6 为
init,CentOS 7 起为systemd)接管并回收。少量瞬时僵尸属正常现象。 - 孤儿进程无害:孤儿进程会被 PID 1 收养并处理,无需担心。
- D 状态进程杀不掉:不可中断睡眠进程不响应任何信号,只能排查并解决底层 I/O 问题,或最终重启主机。
- 优先级调整权限:普通用户只能降低进程优先级(NI 调大)且不可逆,只有 root 才能提高优先级(NI 调小)。
nice只影响 CPU,磁盘 I/O 需用ionice。 kill -9 -1高危:该命令会强制终止几乎所有进程,等同于强制关机,生产环境严禁使用。同理pkill -u root、killall -9不带过滤条件也极其危险。nohup与&的区别:&仅将任务放入后台,关闭终端后任务可能被SIGHUP终止;nohup会忽略SIGHUP,确保任务在终端关闭后继续运行。setsid更彻底(新建会话),disown可事后补救。nohup输出务必显式重定向:否则默认写入当前目录nohup.out,长时间运行可能撑满磁盘。jobs只在当前会话有效:换终端后jobs看不到之前会话的后台任务,需用ps或pgrep查找。- 远程执行长任务先进 tmux / screen:网络抖动导致 SSH 断开时,
yum upgrade、数据迁移这类任务中断可能造成系统不可用。 free的free列小是正常的:Linux 会主动缓存以提升性能。CentOS 7 看available,CentOS 6 看-/+ buffers/cache行的free。不要在生产环境随意drop_caches。- 平均负载 ≠ CPU 使用率:负载包含 R 与 D 状态进程。负载高而 CPU 空闲时,重点排查磁盘 I/O。判断标准是与
nproc核心数比较。 vmstat/iostat的第一行是开机以来的平均值,排障时应忽略第一行,看后续实时采样。ps与top的%CPU含义不同:ps是进程生命周期内的平均值,top是刷新间隔内的瞬时值。判断"现在谁在吃 CPU"要用top/pidstat。- 提前部署 sysstat:故障往往在发生后才被报告,没有
sar历史数据就无法复盘。建议所有生产机器安装并启用sysstat。 - PID 上限不是 65535:由
/proc/sys/kernel/pid_max决定,默认 32768,64 位系统可调至 4194304。 - CentOS 7 服务用 systemd 管理:直接 kill 由 systemd 托管的服务可能触发自动重启,应使用
systemctl stop/restart,详见08-01-系统启动与服务管理.md。 - 降低批处理任务干扰的标准前缀:
nice -n 19 ionice -c 3,适用于备份、全盘扫描、日志压缩等运维任务。 - 参考资料:僵尸进程机制详解 https://www.cnblogs.com/JohnABC/p/5734571.html ;信号完整说明
man 7 signal。
此处评论已关闭