Linux内核分析之进程管理-09

韩乔落

第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; /* 当前父进程 (可能不同于 real_parent) */
struct list_head children; /* 子进程链表 */
struct list_head sibling; /* 兄弟进程链表 */
struct task_struct *group_leader; /* 线程组首领 */

// 进程组与会话
struct pid *thread_pid; /* 线程 PID */
struct pid *signal_struct; /* 信号结构 */
struct pid __rcu *pgrp; /* 进程组 ID (PGID) */
struct pid __rcu *session; /* 会话 ID (SID) */
};

9.1.2 real_parent 与 parent 的区别

1
2
3
4
5
6
7
/*
* real_parent: 创建当前进程的父进程
* parent: 当前进程的"逻辑"父进程,可能因为调试器等原因改变
*
* 正常情况下: parent == real_parent
* ptrace 时: parent == tracer (调试器), real_parent 保持不变
*/

示例:

1
2
3
4
5
6
7
8
9
10
11
// kernel/fork.c
static void fork_exit_core(struct task_struct *child)
{
// 如果没有调试器,parent 就是 real_parent
if (likely(!child->ptrace))
child->parent = child->real_parent;
else {
// 有调试器时,parent 是调试器
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
// include/linux/sched.h
struct task_struct {
struct pid *__rcu *pgrp; /* 进程组指针 */
// PGID 实际存储在 pid 结构中
};

// 获取进程组 ID
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
// kernel/sys.c
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);

// 分配新的 PID 结构
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
// kernel/sys.c
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
# Shell 创建的进程组
$ sleep 60 | sleep 60 | sleep 60
# ^Z
[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
// include/linux/sched.h
struct task_struct {
struct pid __rcu *session; /* 会话指针 */
struct signal_struct *signal; /* 信号结构,包含终端信息 */
};

// include/linux/signal_struct.h
struct signal_struct {
struct tty_struct *tty; /* 控制终端 */

// 会话 ID
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;

// 1. 检查是否已经是进程组首领
if (task_pgrp_vnr(tsk) == task_pid_vnr(tsk))
return -EPERM;

// 2. 检查是否已经是会话成员
if (tsk->signal->leader)
return -EPERM;

// 3. 创建新会话
// 设置进程组 ID 为自身 PID
tsk->signal->session = tsk->group_leader->pgrp;
tsk->signal->pgrp = tsk->group_leader->thread_pid;

// 4. 设置会话首领标志
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
// kernel/sys.c
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 /* Ctrl+C - 中断 */
#define SIGQUIT 3 /* Ctrl+\ - 退出 */
#define SIGTSTP 20 /* Ctrl+Z - 停止 */
#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
// include/linux/sched.h
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
# Ctrl+C 发送 SIGINT 到前台进程组
$ sleep 100
^C

# Ctrl+Z 发送 SIGTSTP,进程进入停止状态
$ sleep 100
^Z
[1]+ Stopped sleep 100

# bg 让进程在后台继续
$ bg
[1]+ sleep 100 &

# fg 将进程调到前台
$ 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
// include/linux/sched.h
struct task_struct {
struct task_struct *group_leader; /* 线程组首领 */
struct pid *thread_pid; /* 线程自己的 PID */

// 共享部分
struct mm_struct *mm; /* 地址空间 */
struct file_struct *files; /* 文件描述符 */
struct signal_struct *signal; /* 信号 disposition */
struct sighand_struct *sighand; /* 信号处理函数 */
};

// 线程组 ID (TGID)
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
// kernel/fork.c
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) {
// 处理子进程 task
printk("Child PID: %d\n", task_pid_vnr(task));
}

// 使用 for_each_process 宏
for_each_process(task) {
// 遍历所有进程
if (task->real_parent == parent) {
// task 是 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) {
// 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
// 通过 PID 查找进程
struct task_struct *find_task_by_vpid(pid_t vnr)
{
return pid_task(find_vpid(vnr), PIDTYPE_PID);
}

// 通过 TGID 查找进程组首领
struct task_struct *find_task_by_tgid(pid_t tgid)
{
return pid_task(find_vpid(tgid), PIDTYPE_TGID);
}

// 通过 PGID 查找进程组
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;

// 1. PR_SET_CHILD_SUBREAPER 设置的 subreaper
reaper = father->signal->child_reaper;
if (reaper)
return reaper;

// 2. PID namespace 的 init 进程
reaper = task_active_pid_ns(father)->child_reaper;
if (reaper)
return reaper;

// 3. 全局 init 进程
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 # 线程组 ID
Pid: 1235 # 线程 ID

9.8.3 /proc/[pid]/children

1
2
3
4
5
# 查看子进程列表
$ cat /proc/1000/children
1001 1002 1003

# 以空格分隔的子进程 PID 列表

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)

# 显示 PID
$ 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 进程关系与组管理:

  1. 进程树结构:real_parent、parent、children、sibling
  2. 进程组:PGID,用于作业控制,同一进程组接收相同信号
  3. 会话:SID,包含多个进程组,可有控制终端
  4. 控制终端:关联会话的终端,发送信号到前台进程组
  5. 线程组:TGID,共享地址空间的轻量级进程
  6. 遍历函数:for_each_process、for_each_thread
  7. 孤儿进程:被 init 或 subreaper 收养
  8. /proc 接口:status、task、children

下一章将介绍实时进程管理。

  • Title: Linux内核分析之进程管理-09
  • Author: 韩乔落
  • Created at : 2026-01-14 19:20:26
  • Updated at : 2026-01-19 13:40:18
  • Link: https://jelasin.github.io/2026/01/14/Linux内核分析之进程管理-09/
  • License: This work is licensed under CC BY-NC-SA 4.0.
Comments