进程与任务管理

本文合并自: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. 建立虚拟地址空间(分配一个页目录);
  2. 建立虚拟空间与可执行文件的映射(页目录项指向磁盘上的程序);
  3. 跳转到程序入口;
  4. 触发缺页异常;
  5. 在内存中寻找空闲页,将对应页换入;
  6. 建立映射,开始执行。
建立虚拟地址空间(分配页目录)
    -> 建立虚拟空间与可执行文件映射
    -> 跳转到程序入口
    -> 触发缺页异常
    -> 在内存中分配空闲页,将对应页换入
    -> 建立映射
    -> 开始执行

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),或对服务做一次滚动重启。

参考资料:https://www.cnblogs.com/JohnABC/p/5734571.html

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=="?"'    # 列出所有不带终端的进程

守护进程的传统创建步骤(编程视角,了解即可):

  1. fork() 后父进程退出,使子进程成为孤儿并被 PID 1 收养;
  2. 调用 setsid() 创建新会话,脱离原控制终端;
  3. 再次 fork(),确保进程不是会话首进程,从而不会重新获得控制终端;
  4. chdir("/") 切换工作目录,避免占用挂载点;
  5. umask(0) 重设文件权限掩码;
  6. 关闭继承的文件描述符,把 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 / 内存占用率 关注父进程关系与完整命令行

选用建议:

  1. 查看进程的 CPU 占用率和内存占用率:使用 ps aux。
  2. 查看进程的父进程 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 启动优先级

  1. 在 A 终端运行 sleep 999;
  2. 在 B 终端运行 ps -el | grep sleep,查看其 NI;
  3. 在 A 终端按 Ctrl + C 终止;
  4. 在 A 终端运行 nice -n 5 sleep 999;
  5. 在 B 终端运行 ps -el | grep sleep,观察 NI 变化;
  6. 在 A 终端按 Ctrl + C 终止;
  7. 在 A 终端运行 nice -n -5 sleep 999,观察结果(普通用户会因权限不足而失败,提示 cannot set niceness: Permission denied);
  8. 在 A 终端运行 sudo nice -n -5 sleep 999,观察结果;
  9. 在 B 终端运行 ps -el | grep sleep,确认 NI 为 -5;
  10. 在 B 终端运行 ps -aux | grep sleep,查看 PID;
  11. 在 B 终端运行 sudo kill -9 PID;
  12. 观察 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 动态调整

  1. 在 A 终端运行 sleep 999;
  2. 在 B 终端运行 ps -el | grep sleep,查看 NI 和 PID;
  3. 在 B 终端运行 renice 3 PID;
  4. 再次在 B 终端运行 ps -el | grep sleep,确认 NI 已变更为 3;
  5. 在 B 终端尝试 renice -n 0 -p PID(普通用户会失败,因为不能把 NI 调小);
  6. 在 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 实验:进程组信号

  1. 在 A 终端运行 sleep 999;
  2. 在 B 终端运行 ps -el | grep sleep,查看其 PID 和 PPID;
  3. 在 B 终端运行 ps -el > pgrp.txt;
  4. 使用 vim pgrp.txt 查看该 sleep 进程的父进程是谁;
  5. 在 B 终端运行 kill -9 PPID,观察 A 终端现象(父 bash 被杀,终端断开);
  6. 在 A 终端再次运行 sleep 999;
  7. 在 B 终端运行 ps -el | grep sleep,查看 PID 和 PPID;
  8. 在 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 实验:作业控制

  1. echo "hello job1" > 1_jobs.txt
  2. echo "hello job2" > 2_jobs.txt
  3. echo "hello job3" > 3_jobs.txt
  4. sleep 999
  5. jobs -l
  6. fg
  7. 按 Ctrl + Z
  8. jobs -l
  9. fg 1
  10. 按 Ctrl + Z
  11. jobs -l
  12. fg 2
  13. 按 Ctrl + Z
  14. fg 4
  15. 按 Ctrl + Z
  16. fg 4
  17. 按 Ctrl + C
  18. sleep 999
  19. 按 Ctrl + D

实验中请留意:Ctrl + Z 之后作业状态为 Stopped;fg 不带参数操作的是带 + 的当前作业;对不存在的作业号执行 fg 会报 no such job。

7.9 实验:nohup 后台不中断复制

  1. 生成一个约 5G 的文件:

    dd if=/dev/zero of=./5G.txt bs=100M count=50
  2. 在 A 终端运行前台复制:

    cp 5G.txt ./1_5G.txt
  3. 在 A 终端按 Ctrl + C;

  4. 过一段时间查看 1_5G.txt 的大小,确认不会继续变大(ls -lh 或 du -sh);

  5. 在 A 终端运行后台复制:

    cp 5G.txt ./2_5G.txt &
  6. 在 A 终端运行 jobs -l;

  7. 在 A 终端按 Ctrl + C(或关闭终端);

  8. 过一段时间查看 2_5G.txt 的大小,观察是否继续变大(普通 & 在终端关闭后会收到 SIGHUP 而终止);

  9. 在 A 终端运行:

    nohup cp 5G.txt ./3_5G.txt &
  10. 在 A 终端运行 jobs -l;

  11. 关闭 A 终端;

  12. 重新打开终端,过一段时间查看 3_5G.txt 的大小,确认其继续变大;

  13. 重复步骤 12,观察 3_5G.txt 的大小变化直到复制完成;

  14. 关闭终端后重新登录,用 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 验证

处置:

  1. 短期:定时重启进程(或配置 systemd Restart= 与 MemoryLimit=);
  2. 中期:限制单进程内存(ulimit -v、cgroup、JVM -Xmx);
  3. 长期:交研发用 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 综合练习

  1. 用一条命令列出当前系统 CPU 占用最高的 5 个进程(含 PID、用户、命令)。

    ps -eo pid,user,%cpu,%mem,comm --sort=-%cpu | head -n 6
  2. 统计系统中僵尸进程的个数,并找出制造僵尸最多的父进程。

    ps -eo stat= | grep -c '^Z'
    ps -eo ppid,stat | awk '$2 ~ /^Z/ {print $1}' | sort | uniq -c | sort -rn | head
  3. 查看 22 端口被哪个进程占用,并列出该进程打开的所有文件。

    lsof -i :22
    lsof -p $(pgrep -x sshd | head -n 1)
  4. 卸载 /data 时提示 device is busy,写出定位与处理命令。

    fuser -mv /data          # 先看清是谁在占用
    lsof +D /data            # 查看具体被打开的文件
    fuser -km /data          # 确认无风险后再杀(生产慎用)
    umount /data
  5. 以最低 CPU 与磁盘优先级在后台执行一次全盘备份,并且关闭终端后不中断。

    nohup nice -n 19 ionice -c 3 tar -czf /backup/full.tar.gz /data > /var/log/backup.log 2>&1 &
  6. 判断某进程是否存在内存泄漏,写出观察方法。

    while true; do date '+%F %T' | tr '\n' ' '; ps -o rss= -p PID; sleep 60; done
    # RSS 长期单调递增且不回落 -> 疑似泄漏
  7. 服务进程凭空消失且无异常日志,如何确认是否被 OOM Killer 杀掉?

    dmesg -T | grep -i -E 'oom|killed process'
    grep -i 'Out of memory' /var/log/messages
    journalctl -k | grep -i oom
  8. 某台 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 判断负载高低的基准

十二、注意事项与最佳实践

  1. 谨慎使用 kill -9:SIGKILL 不会给进程清理资源的机会,可能导致数据丢失或文件损坏。优先使用 kill -15 优雅终止,等待数秒确认后再考虑强杀。数据库、消息队列等有状态服务应优先使用其自带的停止命令。
  2. 批量杀进程前必须先查:pkill 默认是子串匹配,pkill java 可能误杀。养成"先 pgrep -a 确认列表,再 pkill 执行"的习惯。
  3. 僵尸进程处理:直接 kill 僵尸进程无效,应找到并终止其父进程,让 PID 1(CentOS 6 为 init,CentOS 7 起为 systemd)接管并回收。少量瞬时僵尸属正常现象。
  4. 孤儿进程无害:孤儿进程会被 PID 1 收养并处理,无需担心。
  5. D 状态进程杀不掉:不可中断睡眠进程不响应任何信号,只能排查并解决底层 I/O 问题,或最终重启主机。
  6. 优先级调整权限:普通用户只能降低进程优先级(NI 调大)且不可逆,只有 root 才能提高优先级(NI 调小)。nice 只影响 CPU,磁盘 I/O 需用 ionice。
  7. kill -9 -1 高危:该命令会强制终止几乎所有进程,等同于强制关机,生产环境严禁使用。同理 pkill -u root、killall -9 不带过滤条件也极其危险。
  8. nohup 与 & 的区别:& 仅将任务放入后台,关闭终端后任务可能被 SIGHUP 终止;nohup 会忽略 SIGHUP,确保任务在终端关闭后继续运行。setsid 更彻底(新建会话),disown 可事后补救。
  9. nohup 输出务必显式重定向:否则默认写入当前目录 nohup.out,长时间运行可能撑满磁盘。
  10. jobs 只在当前会话有效:换终端后 jobs 看不到之前会话的后台任务,需用 ps 或 pgrep 查找。
  11. 远程执行长任务先进 tmux / screen:网络抖动导致 SSH 断开时,yum upgrade、数据迁移这类任务中断可能造成系统不可用。
  12. free 的 free 列小是正常的:Linux 会主动缓存以提升性能。CentOS 7 看 available,CentOS 6 看 -/+ buffers/cache 行的 free。不要在生产环境随意 drop_caches。
  13. 平均负载 ≠ CPU 使用率:负载包含 R 与 D 状态进程。负载高而 CPU 空闲时,重点排查磁盘 I/O。判断标准是与 nproc 核心数比较。
  14. vmstat/iostat 的第一行是开机以来的平均值,排障时应忽略第一行,看后续实时采样。
  15. ps 与 top 的 %CPU 含义不同:ps 是进程生命周期内的平均值,top 是刷新间隔内的瞬时值。判断"现在谁在吃 CPU"要用 top/pidstat。
  16. 提前部署 sysstat:故障往往在发生后才被报告,没有 sar 历史数据就无法复盘。建议所有生产机器安装并启用 sysstat。
  17. PID 上限不是 65535:由 /proc/sys/kernel/pid_max 决定,默认 32768,64 位系统可调至 4194304。
  18. CentOS 7 服务用 systemd 管理:直接 kill 由 systemd 托管的服务可能触发自动重启,应使用 systemctl stop/restart,详见 08-01-系统启动与服务管理.md。
  19. 降低批处理任务干扰的标准前缀:nice -n 19 ionice -c 3,适用于备份、全盘扫描、日志压缩等运维任务。
  20. 参考资料:僵尸进程机制详解 https://www.cnblogs.com/JohnABC/p/5734571.html ;信号完整说明 man 7 signal。
最后修改:2026 年 09 月 20 日
如果觉得我的文章对你有用,请随意赞赏