跳到主要内容

Linux 进程管理与信号机制


一、进程状态机

Linux 进程在生命周期中经历多种状态转换:

状态码名称说明
RRunning正在运行或在运行队列中等待 CPU
SSleeping可中断睡眠,等待事件(最常见)
DDisk Sleep不可中断睡眠,通常等待磁盘 I/O,无法 kill
TStopped被信号暂停(SIGSTOP/SIGTSTP)
ZZombie已退出但父进程未回收,占用进程表项
IIdle内核空闲线程(Kernel 5.x+)

D 状态(不可中断睡眠)的危害

进程卡在 D 状态通常意味着底层 I/O 长时间无响应(NFS 挂载失败、磁盘故障)。此时 kill -9 无效,只能等待 I/O 超时或重启系统。

# 查看 D 状态进程
ps aux | awk '$8 == "D" {print}'

# 查看进程系统调用栈(找卡点)
cat /proc/<pid>/wchan # 等待在哪个内核函数
cat /proc/<pid>/stack # 完整内核调用栈

二、fork / exec / wait 调用链

Copy-on-Write(CoW)fork() 后父子进程共享物理内存页,只有在某一方写入时才复制该页。大进程 fork 因此几乎是瞬时的。

僵尸进程产生原因:子进程已退出,内核保留其 PCB(进程控制块)等待父进程通过 wait() 读取退出状态。若父进程不调用 wait(),子进程成为僵尸,长期占用进程表项。

# 查看僵尸进程
ps aux | grep ' Z '

# 找出僵尸进程的父进程 PID
ps -eo pid,ppid,stat,cmd | awk '$3 ~ /Z/ {print}'

# 向父进程发送 SIGCHLD,触发其调用 wait()
kill -CHLD <父进程PID>
# 若父进程无法处理,只能终止父进程

三、信号机制

3.1 常用信号表

信号编号默认行为典型用途
SIGHUP1终止重新加载配置(nginx -s reload 等效)
SIGINT2终止Ctrl+C,用户中断
SIGQUIT3核心转储Ctrl+\,调试用
SIGKILL9终止(不可捕获强制终止,无法忽略
SIGTERM15终止优雅终止(可被捕获做清理)
SIGSTOP19暂停(不可捕获暂停进程,等待 SIGCONT
SIGCONT18继续恢复被暂停的进程
SIGCHLD17忽略子进程状态变化通知父进程
SIGUSR110终止用户自定义信号(常用于触发日志轮转)
SIGUSR212终止用户自定义信号

3.2 发送信号

# 优雅终止(进程可捕获并清理)
kill -15 <pid>
kill -TERM <pid>

# 强制终止(不可抗拒)
kill -9 <pid>
kill -KILL <pid>

# 按进程名发送信号
pkill -15 java
killall -9 nginx

# 向进程组发送信号(负号表示进程组)
kill -15 -<pgid>

# 发送信号给特定用户所有进程
pkill -u deploy -TERM

3.3 进程的信号处理

# 查看进程的信号掩码(阻塞、忽略、挂起的信号)
cat /proc/<pid>/status | grep -E "Sig(Blk|Ign|Cgt|Pnd)"

# SigCgt: 已捕获信号的位掩码(十六进制)
# 例:SigCgt: 0000000180014a03
# 转换:echo "ibase=16; 180014A03" | bc | python3 -c "import sys; n=int(sys.stdin.read()); [print(i) for i in range(64) if n & (1<<i)]"

3.4 trap 在脚本中捕获信号

#!/bin/bash
# 优雅退出处理
cleanup() {
echo "收到退出信号,清理临时文件..."
rm -f /tmp/my_lock_file
exit 0
}

trap cleanup SIGTERM SIGINT SIGHUP

# 长时间运行的任务
while true; do
do_work
sleep 5
done

四、进程监控与管理工具

4.1 ps 详细用法

# 显示所有进程(BSD 风格)
ps aux

# 显示所有进程(SysV 风格,更多字段)
ps -ef

# 自定义字段显示(PID、CPU、内存、命令)
ps -eo pid,pcpu,pmem,stat,cmd --sort=-pcpu | head -20

# 显示进程树
ps auxf
ps -ejH

# 查看特定进程的线程
ps -T -p <pid>
ps -eLf | grep <pid>

4.2 top / htop 实时监控

# top 关键快捷键
# P: 按 CPU 排序
# M: 按内存排序
# T: 按运行时间排序
# k: 向进程发送信号
# 1: 展开显示每个 CPU 核
# H: 显示线程(而非进程)
# u: 只显示特定用户进程

# 非交互式获取一次快照
top -bn 1 | head -30

# 获取特定进程状态
top -p <pid1>,<pid2>

4.3 systemd 服务管理

# 服务生命周期管理
systemctl start nginx
systemctl stop nginx
systemctl restart nginx
systemctl reload nginx # 重载配置(不中断现有连接)
systemctl status nginx

# 开机自启
systemctl enable nginx
systemctl disable nginx

# 查看所有失败的服务
systemctl --failed

# 查看服务依赖树
systemctl list-dependencies nginx

# 查看服务日志(实时跟踪)
journalctl -u nginx -f

# 查看最近 100 行日志
journalctl -u nginx -n 100

# 查看最近 1 小时日志
journalctl -u nginx --since "1 hour ago"

# 查看开机以来的日志
journalctl -b

4.4 自定义 systemd Unit 文件

# /etc/systemd/system/myapp.service
[Unit]
Description=My Java Application
After=network.target
Wants=network-online.target

[Service]
Type=simple
User=deploy
Group=deploy
WorkingDirectory=/opt/myapp
ExecStart=/usr/bin/java -Xmx2g -jar /opt/myapp/app.jar --spring.profiles.active=prod
ExecStop=/bin/kill -TERM $MAINPID
Restart=on-failure
RestartSec=10s
StandardOutput=journal
StandardError=journal
SyslogIdentifier=myapp

# 资源限制
LimitNOFILE=65536
LimitNPROC=4096

# 安全加固
NoNewPrivileges=yes
ProtectSystem=full
PrivateTmp=yes

[Install]
WantedBy=multi-user.target
# 重新加载 unit 文件(修改后必须执行)
systemctl daemon-reload
systemctl enable --now myapp

五、后台任务管理

# 在后台运行(&)
./long_running_script.sh &

# 查看后台任务
jobs

# 将后台任务切回前台
fg %1

# 将前台任务切到后台(先 Ctrl+Z 暂停,再 bg)
bg %1

# nohup:忽略 SIGHUP(终端关闭后继续运行)
nohup ./script.sh > /var/log/script.log 2>&1 &

# screen:终端复用器(SSH 断开后进程继续)
screen -S mysession # 新建命名会话
screen -ls # 列出会话
screen -r mysession # 重连会话
# Ctrl+A, D 分离会话

# tmux:更现代的终端复用器
tmux new -s deploy # 新建会话
tmux ls # 列出会话
tmux attach -t deploy # 重连
# Ctrl+B, D 分离会话