第9章 进程关系与组管理
基于 Linux 6.12.38 源码分析
9.1 进程树结构
9.1.1 task_struct 中的关系字段
位置: include/linux/sched.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| struct task_struct { struct task_struct __rcu *real_parent; struct task_struct __rcu *parent; struct list_head children; struct list_head sibling; struct task_struct *group_leader;
struct pid *thread_pid; struct pid *signal_struct; struct pid __rcu *pgrp; struct pid __rcu *session; };
|
9.1.2 real_parent 与 parent 的区别
示例:
1 2 3 4 5 6 7 8 9 10 11
| static void fork_exit_core(struct task_struct *child) { if (likely(!child->ptrace)) child->parent = child->real_parent; else { child->parent = child->parent; } }
|
9.1.3 进程树示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| init (PID 1, PPID 0) │ ├─ systemd (PID 1000, PPID 1) │ │ │ ├─ NetworkManager (PID 1001, PPID 1000) │ ├─ sshd (PID 1002, PPID 1000) │ │ │ │ │ └─ sshd (PID 2000, PPID 1002) │ │ │ │ │ └─ bash (PID 2001, PPID 2000, SID 2000, PGID 2000) │ │ │ │ │ ├─ command1 (PID 2002, PPID 2001) │ │ └─ command2 (PID 2003, PPID 2001) │ │ │ └─ cron (PID 1003, PPID 1000) │ └─ containerd (PID 500, PPID 1) │ └─ dockerd (PID 501, PPID 500) │ └─ container-init (PID 10000, PPID 501) │ └─ app (PID 10001, PPID 10000)
|
9.2 进程组 (Process Group)
9.2.1 进程组概念
定义: 进程组是一个或多个进程的集合,用于接收同一信号。
用途:
- 作业控制 (Job Control)
- 管道连接的进程属于同一进程组
- 终端信号可以发送到整个进程组
9.2.2 进程组数据结构
1 2 3 4 5 6 7 8 9 10 11
| struct task_struct { struct pid *__rcu *pgrp; };
static inline pid_t task_pgrp_vnr(struct task_struct *tsk) { return pid_vnr(tsk->group_leader->pgrp); }
|
9.2.3 进程组操作
设置进程组:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| SYSCALL_DEFINE2(setpgid, pid_t, pid, pid_t, pgid) { struct task_struct *p; struct task_struct *group_leader; int err;
p = find_task_by_vpid(pid); if (!p) return -ESRCH;
group_leader = p->group_leader;
if (!has_privs(current, p)) return -EPERM;
if (pgid == 0) pgid = task_pid_vnr(group_leader);
attach_pid(p, PIDTYPE_PGID, pid);
return 0; }
|
获取进程组 ID:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| SYSCALL_DEFINE0(getpgid) { return task_pgrp_vnr(current); }
SYSCALL_DEFINE1(getpgid, pid_t, pid) { struct task_struct *p;
p = find_task_by_vpid(pid); if (!p) return -ESRCH;
return task_pgrp_vnr(p); }
|
9.2.4 进程组示例
1 2 3 4 5 6 7 8 9 10 11 12
| $ sleep 60 | sleep 60 | sleep 60
[1]+ Stopped sleep 60 | sleep 60 | sleep 60
$ ps -o pid,ppid,pgid,sid,comm PID PPID PGID SID COMMAND 1000 999 1000 1000 bash 2001 1000 2000 1000 sleep 2002 1000 2000 1000 sleep 2003 1000 2000 1000 sleep
|
9.3 会话 (Session)
9.3.1 会话概念
定义: 会话是一个或多个进程组的集合。
特点:
- 每个会话有一个会话首领 (Session Leader)
- 会话首领创建时 setsid() 调用
- 会话可以有一个控制终端
9.3.2 会话数据结构
1 2 3 4 5 6 7 8 9 10 11 12 13
| struct task_struct { struct pid __rcu *session; struct signal_struct *signal; };
struct signal_struct { struct tty_struct *tty;
struct pid *pids[PIDTYPE_MAX]; };
|
9.3.3 创建会话
位置: kernel/sys.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| SYSCALL_DEFINE0(setsid) { struct task_struct *tsk = current;
if (task_pgrp_vnr(tsk) == task_pid_vnr(tsk)) return -EPERM;
if (tsk->signal->leader) return -EPERM;
tsk->signal->session = tsk->group_leader->pgrp; tsk->signal->pgrp = tsk->group_leader->thread_pid;
tsk->signal->leader = 1;
return task_pgrp_vnr(tsk); }
|
9.3.4 会话操作
获取会话 ID:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| SYSCALL_DEFINE0(getsid) { return task_session_vnr(current); }
SYSCALL_DEFINE1(getsid, pid_t, pid) { struct task_struct *p;
p = find_task_by_vpid(pid); if (!p) return -ESRCH;
return task_session_vnr(p); }
|
9.4 控制终端 (Controlling Terminal)
9.4.1 终端概念
控制终端: 与会话关联的终端设备
特点:
- 一个会话最多有一个控制终端
- 控制终端上的信号会发送到前台进程组
- 终端关闭时发送 SIGHUP
9.4.2 终端相关信号
1 2 3 4 5 6 7
| #define SIGINT 2 #define SIGQUIT 3 #define SIGTSTP 20 #define SIGTTIN 21 #define SIGTTOU 22 #define SIGHUP 1
|
9.4.3 前台与后台进程组
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| struct signal_struct { struct tty_struct *tty; pid_t pgrp; };
int tiocsctty(struct tty_struct *tty, int arg) { if (!arg) { tty->pgrp = NULL; return 0; }
tty->pgrp = current->signal->pgrp; return 0; }
|
9.4.4 终端信号处理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| $ sleep 100 ^C
$ sleep 100 ^Z [1]+ Stopped sleep 100
$ bg [1]+ sleep 100 &
$ fg sleep 100
|
9.5 线程组 (Thread Group)
9.5.1 线程组概念
定义: 共享同一内存空间的多个轻量级进程(线程)
特点:
- 共享地址空间 (mm)
- 共享文件描述符表 (files)
- 共享信号处理 (sighand)
- 共享信号 disposition (signal)
9.5.2 线程组数据结构
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| struct task_struct { struct task_struct *group_leader; struct pid *thread_pid;
struct mm_struct *mm; struct file_struct *files; struct signal_struct *signal; struct sighand_struct *sighand; };
static inline pid_t task_tgid_vnr(struct task_struct *tsk) { return pid_vnr(tsk->group_leader->thread_pid); }
|
9.5.3 CLONE_THREAD 标志
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| static struct task_struct *copy_process(unsigned long clone_flags, ...) {
if (clone_flags & CLONE_THREAD) { p->group_leader = current->group_leader; p->exit_signal = -1; } else { p->group_leader = p; p->exit_signal = SIGCHLD; }
}
|
9.5.4 多线程进程结构
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| 多线程进程 (TGID = 1000): ┌────────────────────────────────────────────────────────────────┐ │ 共享资源: │ │ - mm (地址空间) │ │ - files (文件描述符) │ │ - fs (文件系统信息) │ │ - signal (信号 disposition) │ │ - sighand (信号处理函数) │ └────────────────────────────────────────────────────────────────┘ │ ┌─────────────────────┼─────────────────────┐ │ │ │ ┌────┴────┐ ┌────┴────┐ ┌────┴────┐ │ thread1 │ │ thread2 │ │ thread3 │ │ PID:1000│ │ PID:1001│ │ PID:1002│ │ TGID:1000│ │ TGID:1000│ │ TGID:1000│ │ leader │ │ │ │ │ └─────────┘ └─────────┘ └─────────┘
task_struct 关系: thread1: group_leader = thread1, thread_pid = PID 1000 thread2: group_leader = thread1, thread_pid = PID 1001 thread3: group_leader = thread1, thread_pid = PID 1002
|
9.6 进程关系遍历
9.6.1 遍历子进程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| struct task_struct *task; struct task_struct *parent = current;
list_for_each_entry(task, &parent->children, sibling) { printk("Child PID: %d\n", task_pid_vnr(task)); }
for_each_process(task) { if (task->real_parent == parent) { } }
|
9.6.2 遍历线程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| struct task_struct *task; struct task_struct *group_leader = current->group_leader;
for_each_thread(group_leader, task) { printk("Thread PID: %d\n", task_pid_vnr(task)); }
static inline bool same_thread_group(struct task_struct *p1, struct task_struct *p2) { return p1->group_leader == p2->group_leader; }
|
9.6.3 查找进程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| struct task_struct *find_task_by_vpid(pid_t vnr) { return pid_task(find_vpid(vnr), PIDTYPE_PID); }
struct task_struct *find_task_by_tgid(pid_t tgid) { return pid_task(find_vpid(tgid), PIDTYPE_TGID); }
struct pid *find_vpid(int nr) { return find_pid_ns(nr, task_active_pid_ns(current)); }
|
9.7 孤儿进程与 re-parenting
9.7.1 孤儿进程
定义: 父进程先于子进程退出,子进程成为孤儿进程。
处理: 被 init 进程 (PID 1) 或子进程 reaper 收养。
9.7.2 forget_original_parent()
位置: kernel/exit.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| static void forget_original_parent(struct task_struct *father) { struct task_struct *p, *n; struct list_head *ptrace_dead = &father->ptrace_children;
reaper = find_child_reaper(father);
list_for_each_entry_safe(p, n, &father->children, sibling) { p->real_parent = reaper; p->parent = reaper;
list_del_init(&p->sibling);
list_add_tail(&p->sibling, &reaper->children); } }
|
9.7.3 find_child_reaper()
位置: kernel/exit.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| static struct task_struct *find_child_reaper(struct task_struct *father) { struct task_struct *reaper;
reaper = father->signal->child_reaper; if (reaper) return reaper;
reaper = task_active_pid_ns(father)->child_reaper; if (reaper) return reaper;
return &init_task; }
|
9.8 /proc 文件系统中的进程关系
9.8.1 /proc/[pid]/status
1 2 3 4 5 6 7 8 9 10
| $ cat /proc/1234/status Name: sleep State: S (sleeping) Tgid: 1234 Ngid: 0 Pid: 1234 PPid: 1000 TracerPid: 0 Uid: 1000 1000 1000 1000 Gid: 1000 1000 1000 1000
|
字段说明:
Tgid: 线程组 ID
Pid: 进程 ID (TID)
PPid: 父进程 ID
TracerPid: 调试器进程 ID
9.8.2 /proc/[pid]/task
1 2 3 4 5 6 7 8 9
| $ ls /proc/1234/task/ 1234 1235 1236 1237
$ cat /proc/1234/task/1235/status Name: mythread Tgid: 1234 Pid: 1235
|
9.8.3 /proc/[pid]/children
1 2 3 4 5
| $ cat /proc/1000/children 1001 1002 1003
|
9.9 进程关系工具
9.9.1 pstree
1 2 3 4 5 6 7 8 9 10 11 12
| $ pstree systemd─┬─NetworkManager───2*[{NetworkManager}] ├─cron ├─sshd───sshd───bash───sleep └─systemd───(2)
$ pstree -p systemd(1)─┬─NetworkManager(1001)───2*[{NetworkManager}] ├─cron(1003) └─sshd(1002)───ssd(2000)───bash(2001)───sleep(2002)
|
9.9.2 pgrep/pkill
1 2 3 4 5 6 7 8 9 10 11
| $ pgrep sleep 2002
$ pgrep -g 2000 2001 2002
$ pkill -g 2000 TERM
|
9.10 本章小结
本章介绍了 Linux 进程关系与组管理:
- 进程树结构:real_parent、parent、children、sibling
- 进程组:PGID,用于作业控制,同一进程组接收相同信号
- 会话:SID,包含多个进程组,可有控制终端
- 控制终端:关联会话的终端,发送信号到前台进程组
- 线程组:TGID,共享地址空间的轻量级进程
- 遍历函数:for_each_process、for_each_thread
- 孤儿进程:被 init 或 subreaper 收养
- /proc 接口:status、task、children
下一章将介绍实时进程管理。