Appearance
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 -ptext
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 nginxps aux 输出列说明:
| 列 | 含义 |
|---|---|
| USER | 进程所属用户 |
| PID | 进程 ID |
| %CPU | CPU 占用百分比 |
| %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 | 退出 |
htop 是 top 的增强版,支持鼠标点击、彩色显示、内置进程树视图(F5),交互更友好:
bash
yum install htop -y # RHEL 系
apt install htop -y # Debian 系四、信号与 kill
信号是 Linux 进程间通信的基础机制。运维场景下最常用的几个信号:
| 信号 | 编号 | 用途 | 可否捕获 |
|---|---|---|---|
| SIGTERM | 15 | 请求进程正常退出 | 可 |
| SIGKILL | 9 | 强制终止 | 不可 |
| SIGHUP | 1 | 挂起,常用于触发配置重载 | 可 |
| SIGINT | 2 | 终端中断(Ctrl+C) | 可 |
| SIGSTOP | 19 | 暂停进程 | 不可 |
| SIGCONT | 18 | 恢复被暂停的进程 | 可 |
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 的进程——nginx、nginx-exporter、nginx-ingress-controller、my-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 # 重载配置(平滑,不重启进程)restart 和 reload 的区别: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/appRestart=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 杀掉。这些故障的排查需要更进阶的工具和思路,下一篇讲。