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 列为空(?)。
- PGIDSID 等于该进程 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. 排错与实践建议#

  1. 后台任务仍占用终端
    - 现象:退出终端后任务停止或输出仍锁定终端。
    - 处理:用 setsidnohup,确保无控制终端。
  2. 信号无法统一控制
    - 现象:kill PID 只能杀掉单个子进程。
    - 处理:确保进程组设置正确,使用 kill -TERM -PGID
  3. 进程树结构复杂
    - 现象:难以定位谁启动了服务。
    - 处理: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