进程与服务¶
本文你会学到:
- 程序与进程的根本区别,以及进程的关键属性(PID、PPID、UID、NI、TTY)
- 进程的生命周期与状态转移(Running、Sleep、Stopped、Zombie)
- Fork-and-exec 的底层机制与父子进程关系
- 孤儿进程与僵尸进程的成因及消灭方法
- 用
ps、top、jobs等工具查看、管理进程 - 前台进程与后台进程的控制(
Ctrl+Z、fg、bg) - 进程的信号处理与优先级调整(
kill、nice、renice) - 从进程树追踪程序的执行关系
进程基础概念¶
程序 vs 进程¶
程序(program)**是存放在磁盘上的静态二进制文件,如 /bin/bash;当它被执行、载入内存并开始运行时,就成为了**进程(process)。同一个程序可以被多次执行,产生多个独立的进程,彼此互不干扰。
| 概念 | 说明 |
|---|---|
| 程序(program) | 磁盘上的静态可执行文件 |
| 进程(process) | 运行中的程序实例,拥有独立的内存空间和 PID |
进程的关键属性¶
Linux 内核用以下属性来管理每个进程:
| 属性 | 含义 |
|---|---|
PID |
进程 ID,系统唯一标识 |
PPID |
父进程 ID |
UID / GID |
实际运行者的用户/组 ID,决定进程的权限 |
NI |
Nice 值,影响 CPU 调度优先级 |
TTY |
关联的终端(无终端的后台进程显示 ?) |
进程状态¶
ps 和 top 的 STAT 列会显示进程当前状态:
| 状态码 | 含义 |
|---|---|
R |
Running — 正在 CPU 上运行或在运行队列中等待 |
S |
Sleep — 可中断睡眠,等待某个事件(信号可唤醒) |
D |
不可中断睡眠 — 通常在等待 I/O,不响应信号 |
T |
Stopped — 已被暂停(Ctrl+Z 或 SIGSTOP) |
Z |
Zombie — 已结束但父进程未回收其退出状态 |
I |
Idle — 空闲内核线程 |
状态码后还可附加修饰符,如 s(会话领导者)、+(前台进程组)、<(高优先级)、N(低优先级)。
父子进程与 fork/exec¶
当 bash 执行 ls 时,底层发生的是 fork-and-exec 流程:
- 父进程(bash)调用
fork(),复制出一个与自身几乎完全相同的子进程 - 子进程调用
exec(),用ls的程序码替换自身 - bash 等待子进程结束,然后回收其退出状态
子进程可以继承父进程的环境变量。通过 PPID 字段可以追踪任何进程的父进程。
孤儿进程与僵尸进程¶
- 孤儿进程:父进程先于子进程退出,子进程会被
systemd(PID 1)收养 - 僵尸进程:子进程已退出,但父进程没有调用
wait()来回收退出状态,进程条目卡在内核中
ps aux 中看到 <defunct> 标记的进程就是僵尸进程。僵尸进程不占用 CPU,但占用一个进程表槽。如果数量过多,说明父进程存在 bug。
消灭僵尸进程的正确姿势
不能直接 kill -9 僵尸进程(它已经"死"了)。应该找到其父进程(ps -o ppid= -p <zombie_pid>),然后终止或修复父进程,由内核自动清理僵尸。
查看进程¶
ps — 进程快照¶
ps 在某一时刻对系统进程状态做快照。有两种常用风格:
ps aux 各列含义:
| 列名 | 含义 |
|---|---|
USER |
进程所属用户 |
PID |
进程 ID |
%CPU |
CPU 使用率 |
%MEM |
物理内存占用百分比 |
VSZ |
虚拟内存大小(KB) |
RSS |
常驻内存大小,实际占用的物理内存(KB) |
TTY |
关联终端,? 表示无终端 |
STAT |
进程状态 |
START |
启动时间 |
TIME |
累计占用 CPU 时间 |
COMMAND |
启动命令 |
pstree — 进程树¶
当你需要快速找出某个进程是谁启动的,pstree 最直观:
top — 动态实时监控¶
top 持续刷新显示系统状态,默认每 5 秒更新一次:
top 头部信息解读:
- 第 1 行:当前时间、运行时间、在线用户数、负载均值(⅕/15 分钟)
- 负载均值 > CPU 核心数时需要关注,表示进程在排队等待
- 第 2 行:进程总数及各状态数量,
zombie不为 0 时需排查 - 第 3 行:CPU 使用率细分,
wa(I/O wait)高时表示存在磁盘瓶颈 - 第 ⅘ 行:物理内存与 Swap 使用量
top 交互快捷键:
| 键 | 操作 |
|---|---|
q |
退出 |
1 |
展开/折叠各 CPU 核心使用率 |
P |
按 CPU 使用率排序 |
M |
按内存使用率排序 |
k |
向指定 PID 发送信号 |
r |
修改指定进程的 nice 值 |
< / > |
切换排序列 |
htop — 增强版交互监控¶
htop 支持鼠标操作和彩色显示,交互体验更好(需单独安装):
pgrep / pidof — 按名查找 PID¶
当你只需要获取进程的 PID,而不是完整列表:
发送信号¶
进程之间通过**信号(signal)**通信。kill 命令并不只是"杀进程",而是向进程发送任意信号。
常用信号速查¶
| 信号名 | 编号 | 触发方式 | 含义 |
|---|---|---|---|
SIGHUP |
1 | kill -1 |
挂起信号,常用于让进程**重载配置文件** |
SIGINT |
2 | Ctrl+C |
键盘中断,通知进程优雅退出 |
SIGQUIT |
3 | Ctrl+\ |
键盘退出,类似 SIGINT 但会产生 core dump |
SIGKILL |
9 | kill -9 |
强制终止,不可被进程捕获或忽略 |
SIGTERM |
15 | kill(默认) |
请求进程**优雅终止**,进程可以捕获并做清理 |
SIGSTOP |
19 | Ctrl+Z / kill -19 |
暂停进程,不可被捕获或忽略 |
SIGCONT |
18 | kill -18 / bg |
继续被 SIGSTOP 暂停的进程 |
SIGUSR1 |
10 | kill -10 |
用户自定义信号(各程序含义不同) |
SIGUSR2 |
12 | kill -12 |
用户自定义信号 |
kill — 向 PID 发送信号¶
SIGKILL 的副作用
-9 是强制终止,进程来不及做任何清理工作。vim 被 -9 杀死后会留下 .swp 锁文件;数据库进程被强杀可能导致数据文件损坏。优先使用 SIGTERM,只有进程无响应时才用 -9。
pkill — 按名称发送信号¶
killall — 按命令名批量发送信号¶
kill vs pkill vs killall
kill精准,需要知道 PID;kill %1操作的是工作号,kill 1操作的是 PID(systemd!)pkill按正则匹配进程名,适合"杀掉所有包含某关键字的进程"killall精确匹配完整命令名(不含参数),适合"杀掉某个服务的所有实例"
进程优先级¶
Nice 值与 PRI¶
Linux 用 Nice 值(NI)来影响 CPU 调度优先级。实际执行优先级 PRI 由内核动态计算(PRI = PRI基础 + NI),用户只能调整 NI。
- Nice 值范围:-20(最高优先级)到 19(最低优先级)
- **普通用户**只能将 nice 值调高(降低优先级),范围
0~19 - root 可以自由设置
-20~19,包括调高优先级
启动时指定 nice 值¶
调整运行中进程的 nice 值¶
也可以在 top 中按 r 键,然后输入 PID 和新的 nice 值来交互式调整。
调度策略¶
Linux 内核支持多种调度策略,决定就绪进程被 CPU 执行的顺序和时间片长度。策略分为两类:
| 策略 | 调度算法 | 优先级范围 | 适用场景 |
|---|---|---|---|
SCHED_OTHER(默认) |
完全公平调度(CFS) | nice -20~19 | 普通进程 |
SCHED_FIFO |
先进先出(实时) | 1~99(静态优先级) | 实时进程,需防止被其他进程抢占 |
SCHED_RR |
时间片轮转(实时) | 1~99(静态优先级) | 实时进程,允许同优先级轮转切换 |
SCHED_FIFO:一旦获得 CPU,除非自愿阻塞或被更高优先级的实时进程抢占,否则一直运行。同优先级下先进先出SCHED_RR:与SCHED_FIFO类似,但同优先级进程按时间片轮转SCHED_OTHER:默认策略,CFS 根据 nice 值分配 CPU 时间比例
| 设置实时调度策略(需要 root 或 CAP_SYS_NICE) | |
|---|---|
实时策略的注意事项
实时策略(SCHED_FIFO / SCHED_RR)的优先级高于所有普通进程。若实时进程进入死循环且没有休眠或释放 CPU,将**锁死系统**,连 bash 和 SSH 都无法响应。生产环境中务必谨慎使用,配合 sched_setaffinity 绑定特定核心。
工作控制(Job Control)¶
工作控制(Job Control)是 bash 提供的机制,允许在单个终端内同时管理多个任务。每个任务都是当前 bash 的子进程,与其他终端的 bash 互相独立。
前台与后台¶
- 前台(foreground):占据终端输入/输出,可以用
Ctrl+C中断 - 后台(background):在幕后运行,不占终端,可以继续输入其他命令
jobs — 查看当前后台任务¶
任务状态说明:
| 状态 | 含义 |
|---|---|
Running |
在后台正常运行中 |
Stopped |
被暂停(Ctrl+Z 或 SIGSTOP) |
Done |
已完成(下次调用 jobs 时消失) |
jobs 输出中,+ 标记的是"默认任务"(fg/bg 不加参数时操作的目标),- 是次新的任务。
fg / bg — 调度任务¶
kill 工作号¶
kill %1 与 kill 1 的区别
kill %1 操作的是**工作号 1**,kill 1 操作的是 PID=1 的进程(systemd)。千万别搞混!
脱离终端运行¶
bash 的后台任务依然与终端绑定——关闭终端或断开 SSH 后,SIGHUP 信号会发给该 shell 的所有子进程,导致后台任务被终止。如果你需要任务在退出登录后继续运行,有以下几种方式:
nohup — 忽略 SIGHUP¶
nohup 只是让进程忽略 SIGHUP 信号,进程仍然是当前 shell 的子进程,所以在 jobs 中还能看到。
disown — 从 jobs 列表中分离¶
将 nohup 与 disown 结合使用是在不依赖 tmux/screen 时最常见的"后台化"方式:
screen / tmux — 持久会话(推荐)¶
screen 和 tmux 提供"持久化终端会话",断线后可以随时恢复,是长时间任务的最佳选择:
系统资源监控¶
free — 内存概览¶
内存「用光」是正常的
Linux 会将闲置的物理内存用于文件系统缓存(buff/cache)以提升性能。available 列才是实际可用内存——它包含了可以被释放的缓存。真正需要关注的是 Swap 使用量,Swap 用量过多(>20%)说明物理内存不足。
vmstat — 系统整体状态¶
iostat — 磁盘 I/O 统计¶
%util 接近 100% 时说明该磁盘是性能瓶颈;await(平均等待时间)高则说明 I/O 延迟大。
sar — 历史数据分析¶
lsof — 进程打开的文件¶
lsof 是排查"端口被占用"、"文件被锁定"、"文件系统无法卸载"等问题的利器。
ss / netstat — 网络连接状态¶
uptime / lscpu¶
发行版差异¶
/proc/PID 进程目录¶
/proc 是 Linux 内核暴露进程状态的虚拟文件系统(Virtual Filesystem)。它不占磁盘空间,所有文件均由内核实时生成,读取时直接从内核数据结构中取值。每个运行中的进程都对应 /proc/<PID>/ 目录,通过读取其中的伪文件,可以在无需任何调试工具的情况下深入了解进程内部状态。
重要子路径速查¶
| 路径 | 内容说明 |
|---|---|
/proc/PID/status |
进程状态、内存使用量、UID/GID 凭证信息 |
/proc/PID/cmdline |
启动命令行参数,各参数以 \0(null 字节)分隔 |
/proc/PID/environ |
进程启动时的环境变量,以 \0 分隔 |
/proc/PID/exe |
指向可执行文件的符号链接 |
/proc/PID/cwd |
指向当前工作目录的符号链接 |
/proc/PID/fd/ |
所有已打开的文件描述符(每个描述符为一个符号链接) |
/proc/PID/maps |
内存映射布局(代码段/堆/栈/mmap 匿名区域等) |
/proc/PID/limits |
当前资源限制(等同于 ulimit -a 的输出) |
/proc/PID/net/ |
进程所在网络命名空间的接口、连接等信息 |
/proc/PID/task/ |
进程内各线程(TID)的独立目录 |
常用命令示例¶
| 查看进程文件描述符 | |
|---|---|
| 查看进程内存映射布局 | |
|---|---|
VmRSS 与 VmSize 的区别
VmSize:进程的**虚拟地址空间**总大小,包括代码、数据、共享库、mmap 区域等,数值通常远大于实际物理内存使用量。VmRSS(Resident Set Size):实际占用的物理内存,即当前在 RAM 中的页面总量,是衡量进程真实内存开销的关键指标。Threads:进程当前的线程数。多线程程序(如 Java 应用)此值会远大于 1。
进程凭证¶
每个进程都持有一组用户/组标识符,称为**进程凭证(Process Credentials)**,内核依据这些凭证决定进程能访问哪些资源、能向哪些进程发送信号。
四类用户 ID¶
Linux 进程实际上维护着**四个**用户 ID,而非普通认知中的一个:
| 标识符 | 全称 | 说明 |
|---|---|---|
RUID |
实际用户 ID(Real UID) | 进程真正属于谁,继承自登录用户,通常不变 |
EUID |
有效用户 ID(Effective UID) | 内核进行权限检查时**实际使用**的 ID |
SSUID |
保存的 set-UID(Saved Set-UID) | 用于在特权/非特权状态间安全切换的"备份值" |
FSUID |
文件系统用户 ID(Filesystem UID) | 仅用于文件系统操作的权限检查(Linux 特有) |
正常进程:RUID = EUID¶
对于普通进程,三个 ID 通常相等:
内核在检查文件访问权限时只看 EUID,所以在通常情况下,进程的权限就等于其登录用户的权限。
setuid 程序:EUID 的变化¶
当执行设置了 set-user-ID 位的程序时,内核会将 EUID 切换为**可执行文件的属主**:
| passwd 是典型的 setuid-root 程序 | |
|---|---|
为什么需要 SSUID¶
SSUID 解决了"临时放弃再恢复权限"的问题:
这种机制确保 setuid 程序在不需要特权时以非特权身份运行,减少攻击面。
查看进程凭证¶
| 读取 /proc/PID/status 中的凭证信息 | |
|---|---|
GID 同理
组 ID 也有四个对应值:RGID(实际组 ID)/ EGID(有效组 ID)/ SSGID(保存的 set-GID)/ FSGID(文件系统组 ID)。/proc/PID/status 中的 Gid: 行按相同顺序展示这四个值。
Linux Capabilities¶
问题:root 的全有或全无困境¶
传统 UNIX 权限模型只有两种状态:普通用户(受限)和 root(无限制)。这导致了一个问题——一个只需要绑定 80 端口的 Web 服务器,必须以 root 身份运行,进而获得**所有** root 权限,包括读取任意文件、杀死任意进程等。
Linux Capabilities 机制将超级用户权限**细化为 40+ 个独立单元**,程序只需申请真正需要的能力,而无需完整的 root 权限。
常用能力速查¶
| Capability | 作用 |
|---|---|
CAP_NET_BIND_SERVICE |
绑定 1024 以下的特权端口(如 80/443) |
CAP_NET_ADMIN |
配置网络接口、修改路由表、设置防火墙规则 |
CAP_SYS_ADMIN |
mount/umount、修改主机名、操作 namespace 等(权限极广,需谨慎) |
CAP_KILL |
向任意进程发送信号(忽略 UID 限制) |
CAP_CHOWN |
修改任意文件的属主(owner) |
CAP_DAC_OVERRIDE |
绕过文件的读/写/执行权限检查(DAC = Discretionary Access Control) |
CAP_SYS_PTRACE |
使用 ptrace() 调试任意进程 |
三个能力集¶
每个进程维护三组能力集:
- Permitted(许可集):进程**可以拥有**的能力上限,是 Effective 的超集
- Effective(有效集):内核进行权限检查时**实际使用**的能力集
- Inheritable(可继承集):通过
exec()执行新程序时可以传递给子进程的能力
查看进程能力¶
| 读取并解码进程能力 | |
|---|---|
设置与管理文件能力¶
| 为可执行文件设置能力(以 nginx 为例) | |
|---|---|
实践意义
通过文件能力机制,可以让 nginx、Node.js 等服务以普通用户身份运行并监听 80/443 端口,避免以 root 身份运行带来的安全风险。这是容器安全和最小权限原则的重要实践手段。