4.2.3 进程组与会话管理:setsid、setpgid、pstree
进程组与会话管理:setsid、setpgid、pstree#
1. 核心概念与原理草图#
- 进程组(PGID):同一作业的一组进程,便于统一发送信号。
- 会话(SID):一组进程组,通常由登录 Shell 创建。
- 控制终端(TTY):会话与终端关联的设备,前台进程组可接收键盘信号。
关系要点:
- 一个会话包含多个进程组;一个进程组包含多个进程。
- 前台进程组拥有终端控制权,后台进程组不拥有。
- 控制终端会将 Ctrl+C/Ctrl+Z 等信号发送给前台进程组。
2. setsid:创建新会话与脱离终端#
命令解释:
- setsid <command>:让 command 成为新会话首进程,SID=PID,PGID=PID,并脱离控制终端。
安装(通常自带,来自 util-linux):
# Debian/Ubuntu
apt-get update && apt-get install -y util-linux
# RHEL/CentOS
yum install -y util-linux
示例:脱离终端运行长任务并记录日志
# 1) 启动一个每秒输出时间的任务
cat >/tmp/loop.sh <<'EOF'
#!/bin/bash
while true; do date >>/var/log/loop.log; sleep 1; done
EOF
chmod +x /tmp/loop.sh
# 2) 使用 setsid 脱离终端运行
setsid /tmp/loop.sh >/var/log/loop.out 2>&1 &
# 3) 验证:查看该进程是否无 TTY、是否有新 SID/PGID
ps -eo pid,ppid,pgid,sid,tty,cmd | grep /tmp/loop.sh | grep -v grep
预期效果:
- TTY 列为空(?)。
- PGID 与 SID 等于该进程 PID。
3. setpgid:设置进程组(系统调用示例)#
setpgid(pid, pgid) 是系统调用,可在 C 程序中设置进程组关系。
示例:父进程创建两个子进程加入同一进程组
// 文件:/tmp/setpgid_demo.c
#include <unistd.h>
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
int main() {
pid_t pid1 = fork();
if (pid1 == 0) { // 子进程1
setpgid(0, 0); // 以自身为PGID
execlp("sleep", "sleep", "60", NULL);
exit(0);
}
pid_t pid2 = fork();
if (pid2 == 0) { // 子进程2
setpgid(0, pid1); // 加入子进程1的进程组
execlp("sleep", "sleep", "60", NULL);
exit(0);
}
sleep(1);
system("ps -eo pid,ppid,pgid,sid,tty,cmd | grep sleep");
return 0;
}
编译与运行:
gcc /tmp/setpgid_demo.c -o /tmp/setpgid_demo
/tmp/setpgid_demo
预期效果:
- 两个 sleep 进程的 PGID 相同,便于统一信号管理:
# 向该进程组发送 SIGTERM
kill -TERM -<PGID>
4. pstree:查看进程树与组关系#
安装(来自 psmisc):
# Debian/Ubuntu
apt-get install -y psmisc
# RHEL/CentOS
yum install -y psmisc
常用命令与解释:
pstree # 查看系统整体进程树
pstree -p # 显示 PID
pstree -p <pid> # 查看某进程的子树
pstree -p -s <pid> # 显示该进程到根进程的链路
示例:分析 nginx 进程结构
# 启动 nginx 后
pstree -p | grep -A5 nginx
预期效果:
- 能看到 nginx master 与多个 worker 子进程。
5. 排错与实践建议#
- 后台任务仍占用终端
- 现象:退出终端后任务停止或输出仍锁定终端。
- 处理:用setsid或nohup,确保无控制终端。 - 信号无法统一控制
- 现象:kill PID只能杀掉单个子进程。
- 处理:确保进程组设置正确,使用kill -TERM -PGID。 - 进程树结构复杂
- 现象:难以定位谁启动了服务。
- 处理:pstree -p -s <pid>回溯启动链路。
6. 实操练习#
练习1:验证 setsid 脱离终端
setsid sleep 300 &
ps -eo pid,ppid,pgid,sid,tty,cmd | grep sleep | grep -v grep
目标: 确认 TTY 为空,SID=PGID=PID。
练习2:统一管理进程组
# 启动两个后台 sleep 并放在同一进程组
bash -c 'sleep 200 & sleep 200 & wait' &
# 查找 PGID,向进程组发送信号
ps -eo pid,pgid,cmd | grep sleep
kill -TERM -<PGID>
目标: 一次信号结束同组进程。
练习3:用 pstree 观察服务结构
pstree -p -s $(pgrep -o sshd)
目标: 理解 sshd 的父子关系链路。
7. 操作速查#
# 创建新会话并后台运行
setsid command >/var/log/command.log 2>&1 &
# 查看会话与进程组
ps -eo pid,ppid,pgid,sid,tty,cmd | grep <name>
# 查看进程树
pstree -p