一、进程的本质与 /proc
进程是运行中的程序实例。内核为每个进程维护一个 task_struct 数据结构,记录 PID、PPID、状态、打开的文件、内存映射、CPU 时间、UID/GID 等信息。
Linux 将所有进程信息通过 /proc 虚拟文件系统暴露给用户空间。/proc 存在于内存中,每个进程对应一个 /proc/[PID]/ 目录:
ls /proc/ # 数字目录即 PID
ls /proc/1234/ # 查看 PID 1234 的信息关键文件:
| 路径 | 内容 |
|---|---|
/proc/[PID]/cmdline | 启动该进程的完整命令行(含参数,\0 分隔) |
/proc/[PID]/status | 进程状态、内存用量、UID/GID、PPID |
/proc/[PID]/fd/ | 进程持有的所有文件描述符(含文件、socket、pipe、设备) |
/proc/[PID]/maps | 进程内存映射(加载的共享库、堆、栈) |
/proc/[PID]/exe | 指向可执行文件的软链接 |
/proc/[PID]/cwd | 指向当前工作目录的软链接 |
/proc/cpuinfo | CPU 型号、核心数、频率、flags |
/proc/meminfo | 内存总量、空闲、可用、缓存、交换 |
/proc/loadavg | 1/5/15 分钟平均负载 |
/proc/interrupts | 硬件中断分配统计(排查硬件瓶颈) |
/proc/filesystems | 内核支持的文件系统列表 |
/proc/sys/ | 内核运行时参数(sysctl 接口) |
认知:
ps、top、htop、pstree等所有进程查看工具,本质上都是读取/proc下的文件并格式化输出。直接访问/proc比任何命令都更原始、更可靠,同时也是最低层的接口。
二、进程树:pstree
进程是树形结构,所有进程的祖先是 PID 1(init 进程)。pstree 以树形展示进程关系,比 ps 的平铺列表直观得多。
pstree # 树形展示所有进程
pstree -p # 带 PID
pstree -u # 带所属用户
pstree -a # 带完整命令行参数
pstree 1234 # 仅显示该 PID 的子树示例输出:
systemd─┬─sshd───sshd───bash───pstree
├─nginx─┬─nginx
│ └─nginx
├─greetd
└─systemd-journal
进程树的本质:
- 父进程终止时,子进程被 PID 1 收养。
- 孤儿进程、僵尸进程、守护进程的形成机制只有在树形视角下才能真正理解。
三、查看进程:ps 的平铺列表
ps 读取 /proc 后以表格形式输出。不推荐死记 ps -ef 或 ps aux 的固定字段顺序,推荐按需自定义输出列:
ps -eo pid,ppid,user,comm,stat,pcpu,pmem,time,cmd
ps -p 1234 -o pid,ppid,user,comm,stat,pcpu,pmem,cmd常用列字段(ps -e -o 可自定义组合):
pid、ppid:进程 ID、父进程 IDuser、group:运行用户、组comm、cmd:命令名、完整命令行(含参数,推荐cmd)stat:进程状态(见下文)pcpu、pmem:CPU 占用率、内存占用率百分比time、etime:累计 CPU 时间、进程已运行时间(elapsed time)nice、priority:Nice 值(用户态优先级调整)、内核调度优先级
四、控制进程:kill 的本质是发信号
kill 并非“杀死”,而是向进程发送信号。信号是内核向进程异步通知事件的方式。
| 信号 | 编号 | 行为 | 用途 |
|---|---|---|---|
SIGTERM | 15 | 终止(可捕获) | 默认,优雅请求进程退出 |
SIGKILL | 9 | 终止(不可捕获) | 强制杀死,内核直接回收,进程无法拦截,最后手段 |
SIGSTOP | 19 | 暂停(不可捕获) | 挂起进程 |
SIGCONT | 18 | 继续 | 恢复被暂停的进程 |
SIGHUP | 1 | 终止/重载 | 终端断开,或通知守护进程重载配置(nginx、sshd 常用) |
SIGUSR1/SIGUSR2 | 10/12 | 用户自定义 | 应用程序约定的自定义控制信号 |
kill 1234 # SIGTERM,进程可做清理(释放资源、保存状态)后退出
kill -9 1234 # SIGKILL,强制回收,可能导致数据丢失或锁残留
kill -HUP 5678 # SIGHUP,通知进程重载配置警告:
kill -9是最后手段。进程持有锁文件、共享内存或未落盘数据时强制杀死,可能造成状态不一致或资源泄漏。
五、实时监控:top
top 是交互式实时进程视图,默认按 CPU 占用排序,刷新一次读一次 /proc。
只看这五行:
top - 06:45:48 up 13 min, 3 users, load average: 0.00, 0.01, 0.03
Tasks: 220 total, 1 running, 219 sleeping, 0 stopped, 0 zombie
%Cpu(s): 0.0 us, 0.1 sy, 0.0 ni, 99.9 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
KiB Mem : 3861520 total, 2349032 free, 707056 used, 805432 buff/cache
KiB Swap: 2097148 total, 2097148 free, 0 used. 2825968 avail Mem
| 行 | 关键字段 | 含义 |
|---|---|---|
| 第 1 行 | load average: x, y, z | 1/5/15 分钟平均运行队列长度(含运行 + 等待 CPU/I/O 的进程数),不等于 CPU 占用率。多核按核数折算(8 核满载为 8.0) |
| 第 2 行 | zombie | 僵尸进程数 |
| 第 3 行 | us/sy/id/wa | 用户态/内核态/空闲/等待 I/O 的 CPU 占比——wa 高说明磁盘瓶颈 |
| 第 4-5 行 | avail Mem | 真正可用的内存(含可回收 cache),比 free 更准确 |
交互按键(top 运行中按):
P/M/T:按 CPU / 内存 / 累计 CPU 时间排序k:杀死进程(会提示输入 PID 和信号编号)r:调整进程的 Nice 值q:退出
备选工具:
htop(更直观,支持鼠标交互和树形视图),btm(Rust 编写,更现代的资源监控),glances(跨平台,支持 Web 界面)。
六、进程状态
进程状态(ps stat 列可见):
| 状态 | 含义 |
|---|---|
| R | 正在运行或可运行(等待 CPU 调度) |
| S | 可中断睡眠(等待事件,如键盘输入、网络数据) |
| D | 不可中断睡眠(通常等待 I/O,如磁盘读写,无法被信号唤醒) |
| Z | 僵尸——子进程已退出,父进程未调用 wait() 回收其 task_struct |
| T | 被信号暂停(如 SIGSTOP) |
僵尸进程的形成机制:子进程终止时,内核保留其 task_struct 等待父进程读取退出状态(wait())。若父进程不调用,该结构体无法释放,进程表项残存。少量无影响,持续增多说明父进程有 bug。D 状态进程无法被 kill,通常需等待 I/O 完成或重启系统。
七、Init 系统:PID 1 的非绝对性
PID 1 是 init 进程,不一定是 systemd。具体实现因发行版而异:
| Init 系统 | 发行版 |
|---|---|
| systemd(主流) | RHEL、Debian、Ubuntu、Arch、Fedora、openSUSE |
| OpenRC | Gentoo、Alpine Linux |
| runit | Void Linux |
| s6 | 部分嵌入式或极简发行版 |
| SysV init | 传统 UNIX 风格,现代发行版已基本弃用 |
查看当前 init:pstree -p 1 或 cat /proc/1/comm。
systemd 的职责(若为 PID 1):
- 进程树根:所有用户态进程的终极祖先,收养孤儿进程。
- 服务编排(unit):定义启动顺序、依赖关系、失败重启策略。
- 资源控制(cgroup v2):通过
/sys/fs/cgroup限制进程组的 CPU/内存/IO——容器化(Docker)的资源隔离基座。 - 设备与登录(logind + udev):管理硬件设备权限移交和多座位切换。
- 日志(journald):结构化二进制日志。
八、扩展:资源控制(cgroups)
cgroups(control groups)是 Linux 内核提供的资源隔离机制,systemd 通过挂载到 /sys/fs/cgroup/ 的接口统一管理。/proc/1/fd/ 中出现的 io.pressure、cpu.pressure、memory.pressure 文件即对应 cgroup v2 的资源统计接口。容器(Docker)本质上是利用 cgroup + namespace 构建隔离环境,并受 systemd 的资源配额管理。
查看当前 cgroup 树:systemd-cgtop(实时按 cgroup 分组显示资源占用),或 ls /sys/fs/cgroup/ 查看内核接口文件。
docker 本身不直接与 cgroup 交互,而是在启动容器时向 systemd 申请创建子 cgroup(/sys/fs/cgroup/system.slice/docker-<container-id>.scope 或类似路径),由 systemd 代为应用资源限制。