Skip to content

07|进程管理:查看与控制

一台 Linux 上同时跑着几十上百个进程——ssh 登录后的 shell、Web 服务、数据库、监控 agent。运维要做的事,很大一部分就是看这些进程在不在、状态健不健康、占了多少资源,以及需要时把它们停掉或重启。

本篇讲进程管理的基础:怎么查看进程、怎么用信号控制进程、怎么用 systemd 管服务。学会这些,就能应付日常的进程操作。企业级的故障排查(OOM、进程卡死、CPU 飙高定位)是下一篇的内容。

一、进程是什么

进程是正在运行的程序实例。一个程序可以同时启动多个进程,每个进程有唯一的 PID。进程之间存在父子关系——父进程通过 fork 创建子进程,子进程通过 PPID 指向父进程。

查看当前 shell 自己的 PID:

bash
$ echo $$
3845
$ ps -p $$
    PID TTY          TIME CMD
   3845 pts/0    00:00:00 bash

查看进程树,能直观看到父子关系:

bash
yum install psmisc -y         # 安装 pstree
pstree -p
text
systemd(1)─┬─NetworkManager(823)
           ├─sshd(1024)───sshd(2150)───sshd(2155)───bash(2156)
           └─nginx(1500)─┬─nginx(1501)
                         └─nginx(1502)

可以清楚看到 SSH 登录后的进程链路:systemd → sshd(主进程) → sshd(连接进程) → bash → 当前命令。父子关系后面排查僵尸进程时要用。

进程的状态码

ps 输出的 STAT 列标记进程当前状态:

状态含义备注
R运行中或可运行正在使用 CPU 或在运行队列中
S可中断的睡眠在等待事件(网络、信号、定时器),最常见
D不可中断的睡眠通常等待磁盘 I/O,kill -9 也杀不掉
Z僵尸进程已退出但父进程未回收
T暂停收到 SIGSTOP 信号被暂停

R、S 是健康进程的常见状态。D、Z、T 是异常信号,看到要警觉——D 状态和僵尸进程的排查在下一篇故障排查里专门讲。

二、ps:查看进程快照

ps 是查看进程的主力工具,常用两种风格:

bash
ps aux           # BSD 风格,显示所有用户的所有进程
ps -ef           # Unix 风格,功能相似
ps aux | grep nginx

ps aux 输出列说明:

含义
USER进程所属用户
PID进程 ID
%CPUCPU 占用百分比
%MEM内存占用百分比(物理内存 RSS / 系统总内存)
VSZ虚拟内存大小(KB)
RSS常驻物理内存大小(KB)
TTY关联的终端,? 表示无终端(后台进程)
STAT进程状态
START启动时间
TIME累计 CPU 时间
COMMAND启动命令(可能被截断)

自定义输出列

bash
ps -p 1234 -o pid,ppid,user,stat,pcpu,pmem,cmd

-o 指定输出字段,排查时只看关心的列更聚焦。

看完整启动命令

ps 输出的 COMMAND 列经常被终端宽度截断。直接读 /proc/PID/cmdline 能拿到完整命令:

bash
$ tr '\0' ' ' </proc/1234/cmdline
/usr/bin/java -Xms2g -Xmx4g -Dspring.profiles.active=prod -jar /opt/app/app.jar

/proc/PID/cmdline 里各参数之间用 null 字符分隔,tr 转成空格就能读。

/proc/PID/ 下还有不少排查时常用的接口:

bash
ls -l /proc/1234/cwd                      # 进程的工作目录
ls -l /proc/1234/exe                      # 进程对应的可执行文件
cat /proc/1234/environ | tr '\0' '\n'     # 进程的环境变量
ls /proc/1234/fd/ | wc -l                 # 进程打开的文件描述符数量
cat /proc/1234/status                     # 进程的详细状态信息

这些接口不占磁盘空间,是内核实时生成的虚拟文件,执行 cat 时由内核返回当前状态。

三、top:实时观察

top 提供实时进程视图,进程状态会持续刷新:

bash
top

高频交互键:

按键作用
P按 CPU 占用降序
M按内存占用降序
1展开显示每个 CPU 核心的负载
c切换显示完整命令行
H切换线程级别显示
k终止进程(会提示输入 PID)
q退出

htoptop 的增强版,支持鼠标点击、彩色显示、内置进程树视图(F5),交互更友好:

bash
yum install htop -y      # RHEL 系
apt install htop -y      # Debian 系

四、信号与 kill

信号是 Linux 进程间通信的基础机制。运维场景下最常用的几个信号:

信号编号用途可否捕获
SIGTERM15请求进程正常退出
SIGKILL9强制终止不可
SIGHUP1挂起,常用于触发配置重载
SIGINT2终端中断(Ctrl+C)
SIGSTOP19暂停进程不可
SIGCONT18恢复被暂停的进程
bash
kill 1234              # 默认发送 SIGTERM(15)
kill -15 1234          # 同上,显式指定
kill -9 1234           # 强制终止
kill -HUP 1234         # 发送 SIGHUP,通常触发配置重载
kill -l                # 列出所有可用信号

SIGTERM 和 SIGKILL 的区别

这俩差别很大,不能随便用。

SIGTERM(15) 是通知进程"请你自己停掉"。进程接到信号后可以执行清理逻辑:关闭网络连接、刷新缓冲区、写完未完成的日志、释放锁,然后退出。这是优雅退出。

SIGKILL(9) 由内核直接终止进程,进程没有任何机会执行清理代码。可能造成的后果:半写状态的文件损坏、连接未正常关闭、临时文件残留。

标准的终止顺序,先礼后兵:

bash
# 第 1 步:先用 SIGTERM,给进程优雅退出的机会
kill 1234

# 第 2 步:等几秒,确认是否已退出
sleep 5
ps -p 1234

# 第 3 步:仍未退出,再用 SIGKILL
kill -9 1234

注意:SIGKILL 杀不掉 D 状态进程——D 状态的进程在内核态等待 I/O,信号要等它返回用户态才能被处理。这是 D 状态故障的核心特征,下一篇讲。

pkill 与 pgrep

按进程名而不是 PID 操作:

bash
pgrep nginx              # 列出所有匹配 nginx 的 PID
pgrep -a nginx           # 同时显示完整命令行
pkill nginx              # 向所有匹配的进程发送 SIGTERM
pkill -9 nginx           # 强制终止

pkill 的匹配范围可能超出预期pkill nginx 会匹配所有进程名里包含 nginx 的进程——nginxnginx-exporternginx-ingress-controllermy-nginx-wrapper 全中。执行前先用 pgrep -a 确认匹配范围:

bash
$ pgrep -a nginx
1500 nginx: master process /usr/sbin/nginx
1501 nginx: worker process
1502 nginx: worker process
8923 /usr/local/bin/nginx-exporter --nginx.scrape-uri=http://localhost/stub_status

更精确的匹配用 -x(严格等于)或 -f(匹配完整命令行):

bash
pkill -x nginx           # 只匹配进程名严格等于 "nginx"
pgrep -f "nginx-exporter"

五、前台与后台任务

shell 里的命令默认前台运行。命令末尾加 & 放到后台:

bash
sleep 100 &              # 后台运行
jobs                     # 列出当前 shell 的后台任务
fg %1                    # 1 号后台任务切到前台
Ctrl+z                   # 暂停当前前台任务
bg %1                    # 让暂停的任务在后台继续运行

& 这种后台方式只适合临时的长时间命令,不适合长期运行的服务——SSH 断开后进程可能收到 SIGHUP 被终止,输出也可能丢失。长期运行的服务要用 systemd 管,下一节讲。

六、systemd 服务管理

systemd 是绝大多数 Linux 发行版的服务管理器,负责系统启动时按依赖拉起服务、监控状态、异常退出时按策略重启、接管日志。

查看服务状态

bash
systemctl status nginx              # 查看状态、最近日志
systemctl is-active nginx           # 仅返回是否处于 active 状态
systemctl is-enabled nginx          # 仅返回是否设置了开机自启
systemctl list-units --type=service --state=running     # 所有运行中的服务
systemctl list-units --type=service --state=failed       # 所有 failed 的服务

systemctl status 输出示例:

text
● nginx.service - The nginx HTTP and reverse proxy server
   Loaded: loaded (/usr/lib/systemd/system/nginx.service; enabled; vendor preset: disabled)
   Active: active (running) since Tue 2026-06-23 09:00:00 CST; 1h 23min ago
   Main PID: 1500 (nginx)
    Tasks: 3 (limit: 4915)
   CGroup: /system.slice/nginx.service
           ├─1500 nginx: master process /usr/sbin/nginx
           ├─1501 nginx: worker process
           └─1502 nginx: worker process

关键字段:Loaded 显示 unit 文件路径与开机自启状态、Active 显示当前状态、Main PID 是主进程、CGroup 显示该服务管理的所有进程树。

启动、停止、重启、重载

bash
systemctl start nginx               # 启动
systemctl stop nginx                # 停止
systemctl restart nginx             # 重启(有短暂中断)
systemctl reload nginx              # 重载配置(平滑,不重启进程)

restartreload 的区别:restart 完整停止再启动有短暂中断;reload 发 SIGHUP 信号,进程不退出地重新读配置。但 reload 是否生效取决于程序自身有没有实现 SIGHUP 处理——不是所有服务都支持。

开机自启

bash
systemctl enable nginx              # 设置开机自启
systemctl disable nginx             # 取消开机自启
systemctl enable --now nginx        # 同时设置自启并立即启动

查看服务日志

systemd 的服务日志由 journald 统一管理:

bash
journalctl -u nginx                       # 查看 nginx 服务的所有日志
journalctl -u nginx -f                    # 持续跟踪(类似 tail -f)
journalctl -u nginx -n 100                # 最近 100 行
journalctl -u nginx --since "1 hour ago" # 最近 1 小时

journald 和 journalctl 的完整用法在第 11 讲日志管理里展开。

七、自定义 systemd unit

软件包安装的服务自带 unit 文件,但自己部署的服务要自己写 unit。

unit 文件放哪

路径用途
/usr/lib/systemd/system/软件包安装的 unit(系统升级时会被覆盖)
/etc/systemd/system/用户自定义 unit、drop-in 覆盖(不会被升级覆盖)

自定义服务放在 /etc/systemd/system/,不要直接编辑 /usr/lib/systemd/system/——下次软件包升级会覆盖修改。

最小可用的 service 文件

ini
# /etc/systemd/system/myapp.service
[Unit]
Description=My App
After=network.target

[Service]
Type=simple
WorkingDirectory=/opt/myapp
ExecStart=/opt/myapp/bin/app
Restart=always
RestartSec=5
User=app
Group=app

[Install]
WantedBy=multi-user.target

三个段的职责:

职责
[Unit]描述服务、定义依赖关系与启动顺序
[Service]定义进程怎么启动、停止、重启、以谁的身份运行
[Install]决定 systemctl enable 时把服务挂在哪个 target 下

几个容易踩坑的字段

ExecStart 必须用绝对路径。systemd 启动服务时用的 PATH 和登录 shell 不同,相对路径或简短命令名会找不到:

ini
# 错误:命令可能找不到
ExecStart=app

# 正确:绝对路径
ExecStart=/opt/myapp/bin/app

Restart=always 配合快速失败会无限重启。程序刚启动就崩溃,systemd 立刻重启又崩溃,陷入死循环,迅速把 CPU 和日志打满。应对:

ini
Restart=on-failure       # 只在非正常退出时重启
RestartSec=5             # 重启间隔至少 5 秒
StartLimitInterval=60    # 60 秒窗口内
StartLimitBurst=3        # 重启次数超过 3 次后停止重启

User 不指定时默认以 root 运行。除非需要绑定 1024 以下端口等 root 特权,否则应当显式指定低权限账号。

Type 字段

Type 决定 systemd 怎么判断"服务已经启动成功":

Type含义
simple程序前台运行,ExecStart 启动即视为就绪。最常用
forking程序自己 fork 到后台,适合老式守护进程
oneshot程序执行完成后退出,适合初始化脚本

新开发的服务推荐 Type=simple——把守护化交给 systemd,服务自身只关注业务逻辑、日志写到 stdout/stderr。

编写并启用

bash
vim /etc/systemd/system/myapp.service       # 创建 unit
systemctl daemon-reload                     # 让 systemd 重读 unit(必做)
systemctl start myapp                       # 启动
systemctl status myapp --no-pager           # 确认状态
journalctl -u myapp -n 50 --no-pager        # 查看日志
systemctl enable myapp                      # 开机自启

修改任何 unit 文件后,必须 systemctl daemon-reload——否则后续的 restart、reload 仍然使用旧配置。

学会了基础之后

到这里,日常的进程操作就够用了:ps/top 看进程、kill 控制进程、systemctl 管服务、自己写 unit 部署服务。

但生产环境里进程出问题,往往不是"在不在"这么简单——进程卡死杀不掉、CPU 突然飙满、内存涨个不停、进程被 OOM 杀掉。这些故障的排查需要更进阶的工具和思路,下一篇讲。