进程管理
问题
请详细介绍 Linux 进程管理,包括进程状态、systemd、信号机制和容器底层原理(namespace/cgroup)。
答案
进程基础概念
进程是程序的一个运行实例。每个进程有唯一的 PID,由父进程 fork() 创建。
进程状态
┌──────────────────────────────────────────────────┐
│ R (Running) ←→ S (Sleeping) │
│ ↓ ↓ │
│ T (Stopped) D (Uninterruptible Sleep) │
│ ↓ ↓ │
│ └──────→ Z (Zombie) → 回收 │
└──────────────────────────────────────────────────┘
| 状态 | 字符 | 说明 | 常见场景 |
|---|---|---|---|
| Running | R | 正在执行或等待 CPU | 计算密集型任务 |
| Sleeping | S | 等待事件(可中断) | 等待 I/O、网络连接 |
| Disk Sleep | D | 不可中断睡眠 | 等待磁盘 I/O、NFS |
| Stopped | T | 被信号暂停 | Ctrl+Z、调试 |
| Zombie | Z | 已退出但未被父进程回收 | 父进程未调用 wait() |
D 状态和 Z 状态进程
- D 状态(不可中断睡眠):无法被 kill 杀死,通常是磁盘 I/O 或 NFS 问题。大量 D 状态进程意味着存储系统可能出了问题
- Z 状态(僵尸进程):已退出但占位不释放。少量无害,大量需排查父进程。杀死父进程可让 init 领养并回收
进程查看与管理
# ps - 查看进程
ps aux # BSD 风格,所有进程
ps -ef # System V 风格
ps aux --sort=-%mem # 按内存排序
ps -eo pid,ppid,user,%cpu,%mem,stat,cmd --sort=-%cpu | head -20
# top/htop - 实时监控
top
# 常用快捷键:
# P - 按 CPU 排序
# M - 按内存排序
# k - kill 进程
# 1 - 显示每个 CPU 核心
# c - 显示完整命令
htop # 更友好的交互式进程查看器
# pgrep/pkill - 按名称查找/杀死进程
pgrep -la nginx # 列出所有 nginx 进程
pkill -f "python app.py" # 按完整命令行匹配杀死
# pstree - 进程树
pstree -p # 显示 PID
pstree -u # 显示用户切换
信号机制
信号是进程间通信的一种方式,也是管理进程的主要手段:
| 信号 | 编号 | 说明 | 用途 |
|---|---|---|---|
| SIGHUP | 1 | 挂起 | 重载配置(nginx、sshd) |
| SIGINT | 2 | 中断 | Ctrl+C |
| SIGQUIT | 3 | 退出并 core dump | Ctrl+\ |
| SIGKILL | 9 | 强制杀死(不可捕获) | 最后手段 |
| SIGTERM | 15 | 优雅终止(默认) | 推荐的终止方式 |
| SIGSTOP | 19 | 暂停(不可捕获) | 调试 |
| SIGCONT | 18 | 继续执行 | 恢复暂停的进程 |
| SIGUSR1/2 | 10/12 | 用户自定义 | 应用自定义行为 |
# kill 发送信号
kill <PID> # 默认发送 SIGTERM (15)
kill -9 <PID> # 强制杀死 SIGKILL
kill -HUP <PID> # 重载配置(如 nginx)
kill -USR1 <PID> # 用户自定义信号(如 nginx 重新打开日志)
# killall/pkill 按名称发信号
killall nginx
pkill -HUP nginx
# 常见操作组合
kill -TERM <PID> # 先优雅关闭
sleep 5
kill -9 <PID> # 不行再强杀
优雅停止 vs 强制杀死
生产环境应**先发 SIGTERM(15)**让进程优雅退出(关闭连接、刷新缓冲区、清理资源),等待一段时间后若仍未退出,再发 SIGKILL(9) 强制杀死。kill -9 不会给进程清理的机会,可能导致数据丢失。
systemd 服务管理
systemd 是现代 Linux 的初始化系统,管理服务(service)、挂载(mount)、定时器(timer)等:
# 服务管理
systemctl start nginx # 启动
systemctl stop nginx # 停止
systemctl restart nginx # 重启
systemctl reload nginx # 重载配置(不中断服务)
systemctl status nginx # 查看状态
systemctl enable nginx # 开机自启
systemctl disable nginx # 取消开机自启
# 查看服务日志
journalctl -u nginx # 某个服务的日志
journalctl -u nginx -f # 实时跟踪
journalctl -u nginx --since "1 hour ago"
journalctl -u nginx --since "2026-03-15" --until "2026-03-16"
# 查看启动耗时
systemd-analyze
systemd-analyze blame # 各服务启动耗时排序
systemd-analyze critical-chain # 关键路径
# 查看所有服务
systemctl list-units --type=service
systemctl list-units --type=service --state=failed
自定义 Service 文件
/etc/systemd/system/myapp.service
[Unit]
Description=My Application
# 在网络就绪后启动
After=network.target
# 依赖 redis
Requires=redis.service
After=redis.service
[Service]
Type=simple
User=app
Group=app
WorkingDirectory=/opt/myapp
ExecStart=/opt/myapp/bin/server --config /etc/myapp/config.yml
ExecReload=/bin/kill -HUP $MAINPID
Restart=on-failure
RestartSec=5
# 优雅停止超时
TimeoutStopSec=30
# 资源限制
LimitNOFILE=65535
# 环境变量
Environment=NODE_ENV=production
EnvironmentFile=/etc/myapp/env
[Install]
WantedBy=multi-user.target
# 修改 service 文件后需要重新加载
systemctl daemon-reload
systemctl restart myapp
Service Type 类型
| Type | 说明 | 适用场景 |
|---|---|---|
| simple | 默认,ExecStart 即主进程 | 前台运行的程序 |
| forking | 主进程 fork 后退出 | 传统 daemon(需配 PIDFile) |
| oneshot | 执行完就退出 | 初始化脚本 |
| notify | 通过 sd_notify 通知就绪 | systemd-aware 程序 |
| idle | 等其他任务完成后启动 | 控制台输出 |
namespace 与 cgroup(容器底层)
namespace 和 cgroup 是 Linux 容器技术(Docker、K8s)的底层基础:
namespace(隔离)
namespace 提供进程级别的资源隔离:
| Namespace | 隔离内容 | 系统调用标志 |
|---|---|---|
| PID | 进程 ID | CLONE_NEWPID |
| Network | 网络栈 | CLONE_NEWNET |
| Mount | 文件系统挂载 | CLONE_NEWNS |
| UTS | 主机名和域名 | CLONE_NEWUTS |
| IPC | 进程间通信 | CLONE_NEWIPC |
| User | 用户和组 | CLONE_NEWUSER |
| Cgroup | cgroup 根目录 | CLONE_NEWCGROUP |
# 查看进程的 namespace
ls -la /proc/<PID>/ns/
# 使用 unshare 创建新 namespace
# 在新的 PID + Mount namespace 中运行 bash
unshare --pid --mount --fork /bin/bash
# 使用 nsenter 进入容器的 namespace
nsenter -t <PID> -m -u -i -n -p -- /bin/bash
# 这就是 docker exec 的底层原理
cgroup(资源限制)
cgroup(Control Group)限制进程组可使用的资源:
| 资源 | 控制器 | 说明 |
|---|---|---|
| CPU | cpu/cpuset | 限制 CPU 使用时间/核心 |
| 内存 | memory | 限制内存使用上限 |
| I/O | blkio | 限制磁盘 I/O 带宽 |
| 网络 | net_cls | 标记网络包优先级 |
| 进程数 | pids | 限制进程数量 |
# cgroup v2(现代 Linux 默认)
# 查看 cgroup 层次
cat /proc/cgroups
ls /sys/fs/cgroup/
# 查看某个容器的资源限制
# Docker 容器的 cgroup 路径
cat /sys/fs/cgroup/system.slice/docker-<容器ID>.scope/memory.max
cat /sys/fs/cgroup/system.slice/docker-<容器ID>.scope/cpu.max
# 手动创建 cgroup 并限制资源(cgroup v2)
mkdir /sys/fs/cgroup/mygroup
echo "100000 100000" > /sys/fs/cgroup/mygroup/cpu.max # 限制 1 CPU
echo "536870912" > /sys/fs/cgroup/mygroup/memory.max # 限制 512MB
echo $$ > /sys/fs/cgroup/mygroup/cgroup.procs # 将当前 shell 加入
Docker 与 cgroup 的关系
Docker 使用 cgroup 实现 --memory、--cpus 等资源限制参数。当容器内存超过 cgroup 限制时,内核的 OOM Killer 会杀死容器内的进程。
前台/后台任务管理
# 后台运行
command & # 后台执行
nohup command & # 后台执行且忽略 SIGHUP(终端关闭不影响)
nohup command > /dev/null 2>&1 & # 丢弃所有输出
# 任务管理
jobs # 查看后台任务
fg %1 # 将任务 1 放到前台
bg %1 # 将暂停的任务 1 放到后台继续
Ctrl+Z # 暂停当前前台任务
# screen/tmux(推荐方式)
screen -S mysession # 创建会话
screen -r mysession # 恢复会话
tmux new -s mysession # tmux 创建会话
tmux attach -t mysession # tmux 恢复会话
常见面试问题
Q1: 如何排查僵尸进程?
答案:
# 1. 查找僵尸进程
ps aux | awk '$8=="Z" {print}'
# 或
ps -eo pid,ppid,stat,cmd | grep -w Z
# 2. 找到僵尸进程的父进程
ps -eo pid,ppid,stat,cmd | grep Z
# 然后根据 PPID 找父进程
# 3. 解决方案
# 方案 A:向父进程发 SIGCHLD,提醒它回收子进程
kill -SIGCHLD <PPID>
# 方案 B:杀掉父进程,让 init (PID 1) 接管并回收
kill <PPID>
# 4. 预防:代码中正确处理子进程退出
# - 调用 wait()/waitpid()
# - 注册 SIGCHLD 信号处理器
Q2: 如何查看系统的 CPU 和内存使用情况?
答案:
# CPU
top # 实时监控
mpstat -P ALL 1 # 每秒显示每个 CPU 核心
uptime # 负载均衡(1/5/15 分钟)
cat /proc/loadavg # 负载
# 内存
free -h # 内存概览
cat /proc/meminfo # 详细内存信息
vmstat 1 # 虚拟内存统计
# 综合
sar -u 1 5 # CPU 使用率(5 次,间隔 1 秒)
sar -r 1 5 # 内存使用率
Q3: systemd 的 Restart 策略有哪些?
答案:
| 值 | 说明 |
|---|---|
no | 不重启(默认) |
on-success | 正常退出(exit 0)时重启 |
on-failure | 异常退出时重启(非 0 退出码、信号杀死、超时等) |
on-abnormal | 被信号杀死或超时时重启 |
on-abort | 被未捕获信号杀死时重启 |
on-watchdog | 看门狗超时时重启 |
always | 任何退出情况都重启 |
生产推荐:Restart=on-failure + RestartSec=5(5 秒后重启,避免频繁重启风暴)。
Q4: Docker 容器是如何实现资源隔离的?
答案:
Docker 利用 Linux 内核的两大特性:
- namespace:提供 6 种隔离(PID、Network、Mount、UTS、IPC、User),让容器看到的是独立的进程空间、网络栈、文件系统
- cgroup:限制容器可使用的 CPU、内存、I/O 等资源上限
本质上容器就是一个有特殊隔离和限制的普通 Linux 进程,并非虚拟机。这也是容器比虚拟机轻量的根本原因。
Q5: 如何限制某个进程的 CPU 使用?
答案:
# 方法 1:cgroup v2
mkdir /sys/fs/cgroup/limit_cpu
echo "50000 100000" > /sys/fs/cgroup/limit_cpu/cpu.max # 限制 50% CPU
echo <PID> > /sys/fs/cgroup/limit_cpu/cgroup.procs
# 方法 2:cpulimit 工具
cpulimit -p <PID> -l 50 # 限制 50% CPU
# 方法 3:nice/renice 调整优先级
nice -n 19 command # 最低优先级运行
renice 19 -p <PID> # 调整已运行进程的优先级
# 方法 4:systemd service 中限制
# CPUQuota=50%