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

韩乔落

第3章 进程标识符与命名空间

基于 Linux 6.12.38 源码分析


3.1 进程 ID 类型

3.1.1 PID 类型定义

位置: include/linux/pid.h

1
2
3
4
5
6
7
enum pid_type {
PIDTYPE_PID, // 进程 ID (线程 ID)
PIDTYPE_TGID, // 线程组 ID (进程 ID)
PIDTYPE_PGID, // 进程组 ID
PIDTYPE_SID, // 会话 ID
PIDTYPE_MAX,
};

3.1.2 各类型用途

PIDTYPE_PID:

  • 标识单个线程(轻量级进程)
  • 每个线程都有唯一的 PID
  • 通过 getpid() 系统调用获取

PIDTYPE_TGID:

  • 线程组 ID,即传统意义的进程 ID
  • 同一进程内所有线程共享相同的 TGID
  • 主线程的 PID = TGID

PIDTYPE_PGID:

  • 进程组 ID
  • 用于作业控制
  • 通过 getpgid() 系统调用获取

PIDTYPE_SID:

  • 会话 ID
  • 进程登录会话标识
  • 通过 getsid() 系统调用获取

3.2 PID 管理数据结构

3.2.1 struct pid

位置: include/linux/pid.h

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
29
/*
* struct upid is used to get the id of the struct pid. This is used in the
* pidhash and in the pid itself to speed up the pid finding operations.
*/
struct upid {
int nr; // 该命名空间内的 PID 编号
struct pid_namespace *ns; // 所属的命名空间
};

struct pid {
refcount_t count; // 引用计数
unsigned int level; // 命名空间层级深度
spinlock_t lock; // 保护 pid 结构的锁
struct dentry *stashed; // pidfd 的 dentry
u64 ino; // inode 编号

/* lists of tasks that use this pid */
struct hlist_head tasks[PIDTYPE_MAX]; // 每种类型的任务哈希表

struct hlist_head inodes; // pidfd 相关的 inode

/* wait queue for pidfd notifications */
wait_queue_head_t wait_pidfd;

struct rcu_head rcu; // RCU 回收

/* PID 级数组,每个元素对应一个命名空间层级 */
struct upid numbers[]; // 柔性数组
};

3.2.2 struct pid_namespace

位置: include/linux/pid_namespace.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
struct pid_namespace {
struct idr idr; // PID 分配器 (IDR)
struct rcu_head rcu; // RCU 回收
unsigned int pid_allocated; // 已分配的 PID 数量
struct task_struct *child_reaper; // 该命名空间的 init 进程
struct kmem_cache *pid_cachep; // pid 结构的 slab 缓存
unsigned int level; // 命名空间层级深度
struct pid_namespace *parent; // 父命名空间
#ifdef CONFIG_BSD_PROCESS_ACCT
struct fs_pin *bacct; // BSD 进程会计
#endif
struct user_namespace *user_ns; // 用户命名空间
struct ucounts *ucounts; // ucounts 计数
int reboot; // 组退出码 (如果命名空间被重启)
struct ns_common ns; // 通用命名空间字段
#if defined(CONFIG_SYSCTL) && defined(CONFIG_MEMFD_CREATE)
int memfd_noexec_scope; // memfd noexec 范围
#endif
} __randomize_layout;

extern struct pid_namespace init_pid_ns;

3.2.3 PID 命名空间层级

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
29
30
31
32
33
34
35
36
37
┌────────────────────────────────────────────────────────────────┐
│ PID Namespace 层级 │
├────────────────────────────────────────────────────────────────┤
│ │
│ Level 0: 初始命名空间 (init_pid_ns) │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ level = 0 │ │
│ │ child_reaper = init (PID 1) │ │
│ │ │ │
│ │ PID 1: systemd/init │ │
│ │ PID 2: kthreadd │ │
│ │ PID 100: container-A │ │
│ └──────────────────────────────────────────────────────┘ │
│ │ │
│ │ clone(CLONE_NEWPID) │
│ ↓ │
│ Level 1: 容器 A 命名空间 │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ level = 1 │ │
│ │ parent = init_pid_ns │ │
│ │ child_reaper = container-A-init │ │
│ │ │ │
│ │ PID 1: container-A-init (外部: PID 100) │ │
│ │ PID 2: app-process (外部: PID 101) │ │
│ └──────────────────────────────────────────────────────┘ │
│ │ │
│ │ clone(CLONE_NEWPID) │
│ ↓ │
│ Level 2: 嵌套容器命名空间 │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ level = 2 │ │
│ │ parent = container-A-pid-ns │ │
│ │ │ │
│ │ PID 1: nested-init (外部: PID 101, 容器A: PID 2) │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
└────────────────────────────────────────────────────────────────┘

3.2.4 视图映射

不同命名空间视角:

进程 全局视角 Level 0 视角 Level 1 视角 Level 2 视角
init PID 1 PID 1 - -
container-A-init PID 100 PID 100 PID 1 -
app-process PID 101 PID 101 PID 2 PID 1

3.3 PID 分配与释放

3.3.1 PID 分配

位置: kernel/pid.c:alloc_pid()

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
struct pid *alloc_pid(struct pid_namespace *ns, pid_t *set_tid,
size_t set_tid_size)
{
struct pid *pid;
enum pid_type type;
int i, nr;
struct pid_namespace *tmp;
struct upid *upid;

// 1. 分配 pid 结构
pid = kmem_cache_alloc(ns->pid_cachep, GFP_KERNEL);
if (!pid)
return ERR_PTR(-ENOMEM);

tmp = ns;
pid->level = ns->level;

// 2. 为每个命名空间层级分配 PID
for (i = ns->level; i >= 0; i--) {
int tid = 0;

// 如果用户指定了特定 PID
if (set_tid && set_tid_size) {
tid = set_tid[ns->level - i];
// 验证 PID 的有效性
retval = -EINVAL;
if (tid < 1 || tid >= pid_max)
goto out_free;

// 分配指定的 PID
nr = idr_alloc(&tmp->idr, NULL, tid, tid + 1, GFP_ATOMIC);
} else {
// 自动分配下一个可用 PID
int pid_min = 1;
if (idr_get_cursor(&tmp->idr) > RESERVED_PIDS)
pid_min = RESERVED_PIDS;

nr = idr_alloc_cyclic(&tmp->idr, NULL, pid_min,
pid_max, GFP_ATOMIC);
}

if (nr < 0) {
retval = (nr == -ENOSPC) ? -EAGAIN : nr;
goto out_free;
}

// 保存该命名空间的 PID 编号
pid->numbers[i].nr = nr;
pid->numbers[i].ns = tmp;
tmp = tmp->parent;
}

// 3. 初始化 pid 结构
get_pid_ns(ns);
refcount_set(&pid->count, 1);
spin_lock_init(&pid->lock);
for (type = 0; type < PIDTYPE_MAX; ++type)
INIT_HLIST_HEAD(&pid->tasks[type]);

// 4. 将 PID 插入 IDR,使其可见
upid = pid->numbers + ns->level;
spin_lock_irq(&pidmap_lock);
for (; upid >= pid->numbers; --upid) {
idr_replace(&upid->ns->idr, pid, upid->nr);
upid->ns->pid_allocated++;
}
spin_unlock_irq(&pidmap_lock);

return pid;
}

3.3.2 PID 释放

位置: kernel/pid.c:free_pid()

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
29
30
31
void free_pid(struct pid *pid)
{
/* We can be called with write_lock_irq(&tasklist_lock) held */
int i;
unsigned long flags;

spin_lock_irqsave(&pidmap_lock, flags);
for (i = 0; i <= pid->level; i++) {
struct upid *upid = pid->numbers + i;
struct pid_namespace *ns = upid->ns;

// 减少 PID 分配计数
switch (--ns->pid_allocated) {
case 2:
case 1:
/* 当只剩 reaper 时,唤醒它 */
wake_up_process(ns->child_reaper);
break;
case 0:
/* 命名空间为空,可能需要清理 */
break;
}

// 从 IDR 中移除
idr_remove(&ns->idr, upid->nr);
}
spin_unlock_irqrestore(&pidmap_lock, flags);

// 延迟释放,等待 RCU
call_rcu(&pid->rcu, delayed_put_pid);
}

3.4 PID 查找与转换

3.4.1 查找 PID

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
29
30
31
32
33
34
35
36
// kernel/pid.c

/* 查找当前命名空间中的 PID */
struct pid *find_vpid(int nr)
{
return find_pid_ns(nr, task_active_pid_ns(current));
}

/* 在指定命名空间查找 PID */
struct pid *find_pid_ns(int nr, struct pid_namespace *ns)
{
return idr_find(&ns->idr, nr);
}

/* 通过 PID 编号获取 task_struct */
struct task_struct *find_task_by_vpid(pid_t vnr)
{
return find_task_by_pid_ns(vnr, task_active_pid_ns(current));
}

struct task_struct *find_task_by_pid_ns(pid_t nr, struct pid_namespace *ns)
{
struct pid *pid;
struct task_struct *result;

rcu_read_lock();
pid = find_pid_ns(nr, ns);
if (pid) {
result = pid_task(pid, PIDTYPE_PID);
} else {
result = NULL;
}
rcu_read_unlock();

return result;
}

3.4.2 PID 编号转换

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
// 获取当前命名空间的 PID 编号
static inline pid_t pid_nr(struct pid *pid)
{
pid_t nr = 0;
if (pid)
nr = pid->numbers[task_active_pid_ns(current)->level].nr;
return nr;
}

// 获取指定命名空间的 PID 编号
pid_t pid_nr_ns(struct pid *pid, struct pid_namespace *ns)
{
struct upid *upid;
pid_t nr = 0;

if (pid && ns->level <= pid->level) {
upid = &pid->numbers[ns->level];
if (upid->ns == ns)
nr = upid->nr;
}
return nr;
}

// 获取虚拟 PID 编号 (当前线程命名空间)
static inline pid_t pid_vnr(struct pid *pid)
{
return pid_nr_ns(pid, task_active_pid_ns(current));
}

3.5 进程组与会话

3.5.1 进程组

定义: 进程组是一个或多个进程的集合,用于作业控制。

1
2
3
4
5
6
// task_struct 中的进程组字段
struct task_struct {
struct pid *pgrp; // 进程组 ID (PGID)
struct pid *session; // 会话 ID (SID)
// ...
};

进程组操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// kernel/sys.c

SYSCALL_DEFINE2(setpgid, pid_t, pid, pid_t, pgid)
{
struct task_struct *p;
struct task_struct *group_leader;
struct pid_namespace *ns;
int err;

// 查找目标进程
p = find_vpid(pid);
if (!p)
return -ESRCH;

// 权限检查
ns = task_active_pid_ns(p);
if (!check_pergroup_permission(p, ns, pid, pgid))
return -EPERM;

// 设置进程组
err = __setpgid(p, pgid);

return err;
}

3.5.2 会话

定义: 会话是进程组的集合,通常与登录会话关联。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 创建新会话
SYSCALL_DEFINE0(setsid)
{
struct task_struct *p = current;

// 检查是否已是会话首领
if (p->signal->leader)
return -EPERM;

// 创建新会话
if (!process_session(p))
return -ENOMEM;

// 成为进程组首领
set_signal_session(p->signal, process_session(p));

// 设置为会话首领
p->signal->leader = 1;

return process_session(p);
}

3.5.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
init (PID 1, SID 1, PGID 1)

├─ systemd (PID 1000, SID 1, PGID 1000)
│ │ session: 1
│ │ pgrp: 1000
│ │
│ ├─ NetworkManager (PID 1001, SID 1, PGID 1000)
│ ├─ sshd (PID 1002, SID 1, PGID 1000)
│ │
│ └─ cron (PID 1003, SID 1, PGID 1000)

├─ login (PID 2000, SID 2000, PGID 2000)
│ │ session: 2000 (新会话)
│ │ pgrp: 2000
│ │
│ └─ bash (PID 2001, SID 2000, PGID 2000)
│ │ session: 2000
│ │ pgrp: 2000
│ │
│ └─ command (PID 2002, SID 2000, PGID 2002)
│ │ session: 2000
│ │ pgrp: 2002 (新进程组)

└─ containerd (PID 3000, SID 3000, PGID 3000)
│ session: 3000 (新会话)
│ pgrp: 3000

└─ dockerd (PID 3001, SID 3000, PGID 3000)

3.6 PID Namespace 操作

3.6.1 创建新 PID 命名空间

1
2
3
4
5
// 通过 clone 创建
pid = clone(CLONE_NEWPID | SIGCHLD, NULL);

// 通过 unshare 创建
unshare(CLONE_NEWPID);

3.6.2 进入已有 PID 命名空间

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
// 通过 setns 系统调用进入
SYSCALL_DEFINE2(setns, int, fd, int, nstype)
{
struct file *file;
struct ns_common *ns;
int err;

// 获取 namespace 文件描述符
file = fget(fd);
if (!file)
return -EBADF;

// 获取 namespace
ns = get_ns_from_fd(fd);
if (IS_ERR(ns)) {
err = PTR_ERR(ns);
goto out;
}

// 切换到新 namespace
err = switch_task_namespaces(current, ns);

out:
fput(file);
return err;
}

3.7 /proc 文件系统中的 PID 信息

3.7.1 /proc/[pid]/status

1
2
3
4
5
6
7
8
9
$ cat /proc/1234/status
Name: systemd
State: S (sleeping)
Tgid: 1234
Pid: 1234
PPid: 1
TracerPid: 0
Uid: 0 0 0 0
Gid: 0 0 0 0

3.7.2 进程树查看

1
2
3
4
# pstree -p
systemd(1)─┬─NetworkManager(1000)
├─sshd(1002)───bash(2000)───vim(2001)
└─cron(1003)

3.7.3 命名空间查看

1
2
3
4
5
# 查看进程所属的 PID 命名空间
ls -la /proc/1234/ns/pid

# 查看所有 PID 命名空间
ls /proc/[pid]/ns/pid | xargs -I{} readlink {}

3.8 本章小结

本章介绍了 Linux 进程标识符与命名空间的管理:

  1. PID 类型:PID、TGID、PGID、SID 四种类型,各有不同用途
  2. 数据结构struct pid 管理编号,struct pid_namespace 管理命名空间
  3. 命名空间层级:支持嵌套的 PID 命名空间,实现容器隔离
  4. PID 分配:通过 IDR 机制分配和释放 PID
  5. PID 查找:支持跨命名空间的 PID 查找和转换
  6. 进程组与会话:支持作业控制和登录会话管理
  7. 容器支持:PID 命名空间是实现容器隔离的关键技术

下一章将介绍进程的创建机制。

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