[TOC]

kernel/fork.c 进程创建(Process Creation) Unix/Linux的基石

历史与背景

这项技术是为了解决什么特定问题而诞生的?

kernel/fork.c 是Linux内核的心脏之一,它实现了进程创建的机制。这项技术是为了解决多任务操作系统中的一个根本问题:如何动态地创建新的、独立的执行流(即进程)

在单任务系统中,整个系统只有一个执行上下文。为了实现并发和多用户,系统必须有能力创建新的进程。Unix的设计者为此提出了一个极其优雅且强大的模型:fork()

  • 克隆与变形(Fork-and-Exec)fork()的核心思想不是从零开始创建一个空进程,而是克隆(Clone)当前进程。子进程在创建的瞬间,几乎是父进程的一个完美副本(拥有相同的内存映像、打开的文件等)。然后,子进程通常会通过exec()系统调用来加载并执行一个新的程序,从而“变形”为一个完全不同的进程。
  • 上下文继承:这种模型的强大之处在于子进程可以继承父进程的上下文(如环境变量、文件描述符),这使得像Shell中的管道(|)和I/O重定向(>)等功能实现起来非常简洁。

kernel/fork.c 就是这一模型的内核实现,它提供了一套机制来复制进程所需的所有资源,并管理这个过程。

它的发展经历了哪些重要的里程碑或版本迭代?

fork()的演进是Linux性能和功能发展的缩影。

  • 原始fork():最早的fork()实现非常“耿直”,它会完整地复制父进程的整个物理内存给子进程。对于大型进程来说,这个操作非常缓慢且浪费资源。
  • 写时复制(Copy-on-Write, COW):这是fork()发展史上最重要的里程碑。为了解决完整复制的性能问题,内核引入了COW机制。当fork()被调用时,内核不再复制物理内存页,而是让父子进程共享同一套物理内存页,同时将这些页的页表项标记为“只读”。fork()调用因此变得极快。只有当父进程或子进程尝试写入某个共享页时,内核才会捕获这个写保护异常,此时才真正为写入方分配一个新的物理页,并将旧页的内容复制过去。
  • clone()系统调用:Linux对fork()模型进行了重大扩展,引入了clone()系统调用。fork()可以看作是“全盘复制”,而clone()则像一个“瑞士军刀”,它允许调用者通过一系列标志(flags)精细地控制哪些资源被子进程共享,哪些被复制。这个里程碑式的创新使得在Linux上高效实现线程(共享地址空间、文件描述符等)和容器(共享或隔离命名空间)成为可能。fork()vfork()现在都只是clone()系统调用的一个特例封装。

目前该技术的社区活跃度和主流应用情况如何?

kernel/fork.c是内核最核心、最稳定的代码之一。它的代码不会像某些驱动那样频繁地进行颠覆性修改,但由于其中心地位,任何对内存管理、调度器、安全、命名空间的修改都可能需要与之协同,因此它始终处于持续的维护和优化中。它是所有Linux系统上每一个进程创建的必经之路,从启动init进程到用户在shell中执行的每一条命令,都依赖于此文件中的逻辑。

核心原理与设计

它的核心工作原理是什么?

kernel/fork.c的核心是_do_fork()函数(或其现代变体)。当用户空间调用fork(), vfork(), clone()时,最终都会汇集到这个内核函数,它按照一个精确的流程来创建一个新进程。

核心流程:

  1. 复制task_struct:内核首先调用dup_task_struct()来复制父进程的task_struct。这是内核中描述一个进程/线程所有信息的“总纲”数据结构。
  2. 检查限制:检查当前用户创建的进程数是否超过了RLIMIT_NPROC等资源限制。
  3. 资源复制或共享:这是最复杂的一步。_do_fork()会根据clone()传入的标志,决定如何处理各项资源:
    • copy_mm(): 处理内存地址空间。如果没有指定CLONE_VM,则调用dup_mm()复制父进程的mm_struct,这包括复制页表,并应用**写时复制(COW)**策略。如果指定了CLONE_VM(创建线程的典型情况),则子进程与父进程共享同一个mm_struct
    • copy_files(), copy_fs(), copy_sighand():类似地,根据CLONE_FILES, CLONE_FS, CLONE_SIGHAND等标志,决定是复制文件描述符表、文件系统信息、信号处理器等,还是与父进程共享。
    • copy_namespaces(): 如果指定了CLONE_NEW*系列标志,则为子进程创建新的命名空间,这是容器技术的基础。
  4. 分配PID:调用alloc_pid()为新进程分配一个唯一的PID(在相应的PID命名空间中)。
  5. 唤醒子进程:新创建的子进程处于不可运行状态。在所有复制工作完成后,内核会将其设置为可运行状态,并将其放入调度器的运行队列中。
  6. 返回fork()最奇特的特性之一是它“一次调用,两次返回”。在父进程中,它返回新创建子进程的PID;而在子进程中,它返回0。_do_fork()的末尾会处理这个逻辑。

它的主要优势体现在哪些方面?

  • 效率:得益于写时复制(COW),Linux的fork()调用非常快,其开销主要在于复制页表,而非整个内存。
  • 灵活性和通用性clone()系统调用提供了一个极其强大的统一接口,可以用它来创建传统的重型进程、轻量级的线程以及隔离的容器。
  • 简洁的编程模型fork-exec模型虽然在某些方面有其开销,但它允许子进程在继承父进程环境后进行精细的调整(如重定向I/O),然后再转变为新程序,这是一种非常强大且简洁的范式。

它存在哪些已知的劣势、局限性或在特定场景下的不适用性?

  • fork-exec的开销:在子进程被创建后立即调用exec()的常见场景中,为子进程复制页表等操作实际上是浪费的,因为这些数据马上就会被新程序覆盖。vfork()posix_spawn()就是为了优化这种情况而生的。
  • 内存过载(Overcommit)的风险:COW机制使得创建大量看似拥有海量内存的进程成为可能。但如果这些进程都开始写入它们各自的内存副本,物理内存可能会迅速耗尽,从而触发内核的OOM(Out-of-Memory) Killer。
  • vfork()的危险性vfork()是一种过时的优化。它让子进程在父进程的地址空间中运行,并阻塞父进程。子进程在调用exec()_exit()之前对内存的任何修改都会影响到父进程,这非常危险,应极力避免使用。

使用场景

在哪些具体的业务或技术场景下,它是首选解决方案?

它是Linux上创建进程的唯一内核级解决方案,因此所有需要创建新执行流的场景都是它的使用场景。

  • Shell执行命令:当你在bash中输入ls -l时,bash进程会fork()一个子进程,然后子进程调用execve("ls", ...)来执行ls程序。
  • 多进程服务:像Apache Web服务器(在prefork模式下)会预先fork()出多个子进程来处理并发的HTTP请求。
  • 创建线程:用户空间的pthread_create()库函数,其底层实现就是调用了clone()系统调用,并传入了CLONE_VM, CLONE_SIGHAND, CLONE_FILES等一系列标志。
  • 容器启动docker run等命令,其底层也是通过clone()并配合CLONE_NEW*系列标志来创建一个与宿主机隔离的新进程环境。

是否有不推荐使用该技术的场景?为什么?

如上所述,vfork()因其危险性而不被推荐。此外,对于fork-exec模式的性能敏感场景,可以考虑使用posix_spawn()posix_spawn()是一个库函数,它可以在内部进行优化,可能会选择比fork-exec更高效的方式来创建进程并加载新程序,从而避免不必要的页表复制。

对比分析

请将其 与 其他相似技术 进行详细对比。

kernel/fork.c的语境下,最有意义的对比是fork(), vfork()clone()这三个核心调用。

特性 fork() vfork() (不推荐使用) clone()
本质 创建一个传统的、独立的子进程。 fork()的一种不安全的、过时的优化。 创建进程/线程的通用、可配置的底层原语。
地址空间 (mm_struct) 复制 (使用写时复制COW策略)。父子进程有独立的地址空间。 共享。子进程在父进程的地址空间中运行。 可配置。通过CLONE_VM标志决定是复制还是共享。
父进程行为 父子进程并发执行(由调度器决定顺序)。 阻塞,直到子进程调用exec()_exit() 父子进程并发执行。
资源处理 复制文件描述符、信号处理器等。 共享。 可配置。通过CLONE_FILES, CLONE_SIGHAND等标志精细控制。
安全性 高。父子进程隔离良好。 极低。子进程对内存的修改会直接影响父进程,极易出错。 高。开发者明确知道哪些资源被共享。
主要用途 标准的进程创建。 仅用于优化fork()后立即exec()的场景,但已被COW和posix_spawn()取代。 创建线程、创建容器、以及其他需要精细控制资源共享的场景。

pthread_create()的对比
pthread_create()是一个用户空间的库函数,而不是系统调用。它为程序员提供了符合POSIX标准的线程创建接口。在Linux上,pthread_create()的实现(在glibc/NPTL中)最终会调用clone()系统调用,并传入一套预设的标志(如CLONE_VM | CLONE_FS | CLONE_FILES | ...)来创建一个与调用者共享大部分资源的“轻量级进程”,即内核眼中的线程。

include/uapi/linux/sched.h

cloning flags 进程或线程创建的标志

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
/*
* cloning flags:
*/
#define CSIGNAL 0x000000ff /*用于指定在子进程退出时发送给父进程的信号。低 8 位表示信号掩码*/
/* 资源共享相关标志 */
#define CLONE_VM 0x00000100 /* 子进程与父进程共享内存空间(虚拟内存)。适用于线程创建 */
#define CLONE_FS 0x00000200 /* 子进程与父进程共享文件系统信息(如当前工作目录) */
#define CLONE_FILES 0x00000400 /* 子进程与父进程共享打开的文件描述符表 */
#define CLONE_SIGHAND 0x00000800 /* 子进程与父进程共享信号处理程序和阻塞信号集 */
/* 进程管理相关标志 */
#define CLONE_PIDFD 0x00001000 /* 在父进程中创建一个 pidfd,用于管理子进程 */
#define CLONE_PTRACE 0x00002000 /* 允许子进程继续被调试器跟踪 */
#define CLONE_VFORK 0x00004000 /* 子进程阻塞父进程,直到子进程调用 execve() 或退出 */
#define CLONE_PARENT 0x00008000 /* 子进程与父进程共享同一个父进程 */
/* 线程相关标志 */
#define CLONE_THREAD 0x00010000 /* 子进程与父进程属于同一个线程组 */
#define CLONE_SETTLS 0x00080000 /* 为子进程创建新的线程本地存储(TLS) */
/* 命名空间相关标志 */
#define CLONE_NEWNS 0x00020000 /* 为子进程创建新的挂载命名空间 */

#define CLONE_NEWCGROUP 0x02000000 /* 为子进程创建新的 cgroup 命名空间 */
#define CLONE_NEWUTS 0x04000000 /* 为子进程创建新的 UTS 命名空间(主机名和域名 */
#define CLONE_NEWIPC 0x08000000 /* 为子进程创建新的 IPC 命名空间 */
#define CLONE_NEWUSER 0x10000000 /* 为子进程创建新的用户命名空间 */
#define CLONE_NEWPID 0x20000000 /* 为子进程创建新的 PID 命名空间 */
#define CLONE_NEWNET 0x40000000 /* 为子进程创建新的网络命名空间 */
/* 其他标志 */
#define CLONE_SYSVSEM 0x00040000 /* 子进程与父进程共享 System V 信号量的撤销语义 */
#define CLONE_PARENT_SETTID 0x00100000 /* 在父进程中设置子进程的线程 ID */
#define CLONE_CHILD_CLEARTID 0x00200000 /* 在子进程中清除线程 ID */
#define CLONE_DETACHED 0x00400000 /* 已废弃,不再使用 */
#define CLONE_UNTRACED 0x00800000 /* 禁止调试器强制对子进程启用 CLONE_PTRACE */
#define CLONE_CHILD_SETTID 0x01000000 /* 在子进程中设置线程 ID */
#define CLONE_IO 0x80000000 /*进程与父进程共享 IO 上下文 */

include/linux/sched/task.h

get_task_struct 获取任务结构体

1
2
3
4
5
static inline struct task_struct *get_task_struct(struct task_struct *t)
{
refcount_inc(&t->usage);
return t;
}

put_task_struct 释放任务结构体

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
static inline void put_task_struct(struct task_struct *t)
```c
{
/* 如果 t->usage 的引用计数减为 0,则需要释放 task_struct */
if (!refcount_dec_and_test(&t->usage))
return;

/*
* 在非 RT(实时)内核中,始终可以安全地调用 __put_task_struct()。
* 在 RT 内核下,只能在可抢占(preemptible)上下文中调用。
*/
if (!IS_ENABLED(CONFIG_PREEMPT_RT) || preemptible()) {
static DEFINE_WAIT_OVERRIDE_MAP(put_task_map, LD_WAIT_SLEEP);

lock_map_acquire_try(&put_task_map);
__put_task_struct(t);
lock_map_release(&put_task_map);
return;
}

/*
* 在 PREEMPT_RT 下,不能在原子上下文中调用 put_task_struct,
* 因为它会间接获取可能导致睡眠的锁。
*
* 此时通过 call_rcu() 延迟调用 __put_task_struct_rcu_cb(),
* 保证在进程上下文中释放资源。
*
* __put_task_struct() 只会在 refcount_dec_and_test(&t->usage) 成功时调用,
* 因此不会与 put_task_struct_rcu_user() 冲突(后者也使用 ->rcu 字段)。
* rcu_users 持有引用,因此在 rcu_users 从 1 变为 0 之后,task->usage 不会为 0。
*
* delayed_free_task() 也会用到 ->rcu,但它只在 fork 失败时调用,
* 因此不会与 put_task_struct() 冲突。
*/
call_rcu(&t->rcu, __put_task_struct_rcu_cb);
}

kernel/fork.c

fork 和 vfork 的区别

vforkfork 都是用于创建新进程的系统调用,但它们的行为和使用场景有所不同。以下是两者的主要区别:


1. 内存空间处理

  • fork

    • 父进程和子进程各自拥有独立的内存空间。
    • 使用 写时复制(Copy-On-Write, COW) 技术,父子进程共享内存页,直到其中一个进程尝试写入时才会复制内存页。
    • 适合需要独立运行的父子进程。
  • vfork

    • 子进程直接共享父进程的内存空间。
    • 在子进程调用 execveexit 之前,父进程会被阻塞,无法运行。
    • 不使用写时复制,效率更高,但子进程在修改内存时会直接影响父进程。

2. 性能

  • fork

    • 因为需要设置写时复制机制,fork 的性能相对较低,尤其是在父进程的内存空间较大时。
  • vfork

    • 不需要设置写时复制,性能更高。
    • 适合在子进程立即调用 execve 加载新程序的场景。

3. 父进程的行为

  • fork

    • 父进程和子进程可以并发运行,互不影响。
  • vfork

    • 父进程会被阻塞,直到子进程调用 execveexit
    • 子进程运行期间,父进程无法执行任何操作。

4. 安全性

  • fork

    • 父子进程的内存空间独立,安全性更高。
    • 子进程的操作不会影响父进程。
  • vfork

    • 子进程共享父进程的内存空间,可能会导致父进程的内存被意外修改。
    • 使用不当可能引发严重问题。

5. 使用场景

  • fork

    • 适用于需要创建独立进程的场景,例如多进程服务器或守护进程。
    • 父子进程可以独立运行,互不干扰。
  • vfork

    • 适用于子进程立即调用 execve 加载新程序的场景。
    • 更高效,但需要谨慎使用,避免子进程修改父进程的内存。

6. 系统调用的实现

  • fork

    • 在 Linux 内核中通过 do_forkcopy_process 实现。
    • 使用写时复制技术,减少内存复制的开销。
  • vfork

    • 在 Linux 内核中通过 do_fork 实现,但设置了特殊的标志(CLONE_VFORKCLONE_VM)。
    • 子进程共享父进程的内存空间,并阻塞父进程。

总结

特性 fork vfork
内存空间 独立,使用写时复制 共享父进程内存空间
性能 较低,适合复杂场景 较高,适合快速加载新程序
父进程行为 父子进程并发运行 父进程阻塞,等待子进程完成
安全性 高,子进程不会影响父进程 较低,子进程可能修改父进程内存
使用场景 创建独立进程,适合复杂任务 快速加载新程序,适合简单任务

fork 提供了更强的隔离性和灵活性,而 vfork 提供了更高的性能,但需要谨慎使用以避免潜在的安全问题。

set_task_stack_end_magic 在栈底设置magic,用于检测栈溢出

  1. 注意设置堆栈时特地预留了8字节用于填入magic
1
2
3
4
5
6
7
void set_task_stack_end_magic(struct task_struct *tsk)
{
unsigned long *stackend;

stackend = end_of_stack(tsk); //task->stack;
*stackend = STACK_END_MAGIC; /* for overflow detection */
}

mm_cache_init 创建一个内存缓存(kmem_cache)

  • 创建一个内存缓存(kmem_cache),专门用于分配和管理 mm_struct 结构体的内存
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/* SLAB cache for mm_struct structures (tsk->mm) */
static struct kmem_cache *mm_cachep;

void __init mm_cache_init(void)
{
unsigned int mm_size;

/*
* mm_cpumask位于 mm_struct 的末尾,并根据该系统可以具有的最大 CPU 数量动态调整大小,同时考虑热插拔 (nr_cpu_ids)。
*/
mm_size = sizeof(struct mm_struct) + cpumask_size() + mm_cid_size();

mm_cachep = kmem_cache_create_usercopy("mm_struct",
mm_size, ARCH_MIN_MMSTRUCT_ALIGN,
SLAB_HWCACHE_ALIGN|SLAB_PANIC|SLAB_ACCOUNT,
offsetof(struct mm_struct, saved_auxv),
sizeof_field(struct mm_struct, saved_auxv),
NULL);
}

task_struct_whitelist 任务结构体白名单

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//arch/arm/include/asm/processor.h
/*
* 用户复制到 thread_struct 或从 复制的所有内容都是静态大小的,因此不需要强化的 usercopy 白名单。
*/
static inline void arch_thread_struct_whitelist(unsigned long *offset,
unsigned long *size)
{
*offset = *size = 0;
}

static void __init task_struct_whitelist(unsigned long *offset, unsigned long *size)
{
/* 获取架构thread_struct白名单。 */
arch_thread_struct_whitelist(offset, size);

/*
* 处理零大小的白名单或空thread_struct,否则将偏移调整到thread_struct在 task_struct 中的位置。
*/
if (unlikely(*size == 0))
*offset = 0;
else
*offset += offsetof(struct task_struct, thread);
}

set_max_threads 计算和设置系统允许的最大线程数

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
/*
* 启动内核的最小线程数
*/
#define MIN_THREADS 20


/*
* set_max_threads
*/
static void __init set_max_threads(unsigned int max_threads_suggested)
{
u64 threads;
/* 估算系统中可用的内存页数 */
unsigned long nr_pages = memblock_estimated_nr_free_pages();

/*
* 应限制线程数,以便线程结构只能占用一小部分可用内存。
*/
/* 使用 fls64(找到最高有效位的索引) 检查 nr_pages 和 PAGE_SIZE 的位数之和是否超过 64 位*/
if (fls64(nr_pages) + fls64(PAGE_SIZE) > 64)
threads = MAX_THREADS;
else
/* 如果未超出 64 位限制,则根据以下公式计算线程数:
threads = (可用内存总量) / (每个线程结构占用的内存 * 8)
*/
threads = div64_u64((u64) nr_pages * (u64) PAGE_SIZE,
(u64) THREAD_SIZE * 8UL);

/* 如果计算出的线程数超过了建议的最大线程数(max_threads_suggested),则将其限制为建议值 */
if (threads > max_threads_suggested)
threads = max_threads_suggested;

/* 使用 clamp_t 函数将线程数限制在 MIN_THREADS 和 MAX_THREADS 之间,确保线程数不会低于最小值或超过最大值。 */
max_threads = clamp_t(u64, threads, MIN_THREADS, MAX_THREADS);
}

fork_init 设置与进程管理相关的核心数据结构和资源

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
void __init fork_init(void)
{
int i;
#ifndef ARCH_MIN_TASKALIGN
#define ARCH_MIN_TASKALIGN 0
#endif
int align = max_t(int, L1_CACHE_BYTES, ARCH_MIN_TASKALIGN);
unsigned long useroffset, usersize;

/* 创建可分配 task_structs 的 Slab*/
task_struct_whitelist(&useroffset, &usersize);
task_struct_cachep = kmem_cache_create_usercopy("task_struct",
arch_task_struct_size, align,
SLAB_PANIC|SLAB_ACCOUNT,
useroffset, usersize, NULL);

/* 执行 Arch 特定任务 caches init*/
arch_task_cache_init();

set_max_threads(MAX_THREADS);

/* 初始任务
RLIMIT_NPROC:每个用户允许的最大进程数,初始值为 max_threads / 2 */
init_task.signal->rlim[RLIMIT_NPROC].rlim_cur = max_threads/2;
init_task.signal->rlim[RLIMIT_NPROC].rlim_max = max_threads/2;
/* 初始任务
RLIMIT_SIGPENDING:每个用户允许的最大挂起信号数,与 RLIMIT_NPROC 相同 */
init_task.signal->rlim[RLIMIT_SIGPENDING] = init_task.signal->rlim[RLIMIT_NPROC];

/* 设置每种资源的最大值,初始为 max_threads / 2 */
for (i = 0; i < UCOUNT_COUNTS; i++)
init_user_ns.ucount_max[i] = max_threads/2;

/* 调用 set_userns_rlimit_max 设置特定资源的限制为无限制 */
/* static inline void set_userns_rlimit_max(struct user_namespace *ns,
enum rlimit_type type, unsigned long max)
{
ns->rlimit_max[type] = max <= LONG_MAX ? max : LONG_MAX;
}
*/
/* init_user_ns:
初始用户命名空间 (Initial User Namespace)。它是整个系统中默认的用户命名空间,所有进程在启动时都会默认处于这个命名空间中。
user_namespace 结构体用于管理 用户命名空间,其作用是提供权限隔离,使不同的进程可以拥有独立的用户 ID 映射,以实现容器或进程的权限隔离。
这是一种安全机制,使得某些进程能够运行在一个与其他进程隔离的环境中,避免影响系统的全局状态 */
set_userns_rlimit_max(&init_user_ns, UCOUNT_RLIMIT_NPROC, RLIM_INFINITY);
set_userns_rlimit_max(&init_user_ns, UCOUNT_RLIMIT_MSGQUEUE, RLIM_INFINITY);
set_userns_rlimit_max(&init_user_ns, UCOUNT_RLIMIT_SIGPENDING, RLIM_INFINITY);
set_userns_rlimit_max(&init_user_ns, UCOUNT_RLIMIT_MEMLOCK, RLIM_INFINITY);

#ifdef CONFIG_VMAP_STACK
cpuhp_setup_state(CPUHP_BP_PREPARE_DYN, "fork:vm_stack_cache",
NULL, free_vm_stack_cache);
#endif

/* 初始化内核的安全上下文堆栈(Shadow Call Stack) */
scs_init();

lockdep_init_task(&init_task);
/* 初始化用户态探针(uprobes),用于调试和性能分析 */
uprobes_init();
}

proc_caches_init 初始化与进程管理相关的内核缓存(slab caches)

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
void __init proc_caches_init(void)
{
struct kmem_cache_args args = {
.use_freeptr_offset = true,
.freeptr_offset = offsetof(struct vm_area_struct, vm_freeptr),
};

/* 用于分配和管理 sighand_struct(信号处理结构)。
该缓存启用了硬件缓存对齐(SLAB_HWCACHE_ALIGN)、RCU 安全(SLAB_TYPESAFE_BY_RCU)等特性,并指定了构造函数 sighand_ctor */
sighand_cachep = kmem_cache_create("sighand_cache",
sizeof(struct sighand_struct), 0,
SLAB_HWCACHE_ALIGN|SLAB_PANIC|SLAB_TYPESAFE_BY_RCU|
SLAB_ACCOUNT, sighand_ctor);
/* 用于分配和管理 signal_struct(信号结构) */
signal_cachep = kmem_cache_create("signal_cache",
sizeof(struct signal_struct), 0,
SLAB_HWCACHE_ALIGN|SLAB_PANIC|SLAB_ACCOUNT,
NULL);
/* 于分配和管理 files_struct(文件描述符表结构)和 fs_struct(文件系统上下文结构) */
files_cachep = kmem_cache_create("files_cache",
sizeof(struct files_struct), 0,
SLAB_HWCACHE_ALIGN|SLAB_PANIC|SLAB_ACCOUNT,
NULL);
fs_cachep = kmem_cache_create("fs_cache",
sizeof(struct fs_struct), 0,
SLAB_HWCACHE_ALIGN|SLAB_PANIC|SLAB_ACCOUNT,
NULL);
/* 分配和管理 vm_area_struct(虚拟内存区域结构)。 */
vm_area_cachep = kmem_cache_create("vm_area_struct",
sizeof(struct vm_area_struct), &args,
SLAB_HWCACHE_ALIGN|SLAB_PANIC|SLAB_TYPESAFE_BY_RCU|
SLAB_ACCOUNT);
mmap_init();
nsproxy_cache_init();
}

__put_task_struct 释放任务结构体

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void __put_task_struct(struct task_struct *tsk)
{
WARN_ON(!tsk->exit_state);
WARN_ON(refcount_read(&tsk->usage));
WARN_ON(tsk == current);

sched_ext_free(tsk);
io_uring_free(tsk);
cgroup_free(tsk);
task_numa_free(tsk, true);
security_task_free(tsk);
exit_creds(tsk);
delayacct_tsk_free(tsk);
put_signal_struct(tsk->signal);
sched_core_free(tsk);
free_task(tsk);
}
EXPORT_SYMBOL_GPL(__put_task_struct);

alloc_task_struct_node 分配一个新的 task_struct

1
2
3
4
5
6
static struct kmem_cache *task_struct_cachep;

static inline struct task_struct *alloc_task_struct_node(int node)
{
return kmem_cache_alloc_node(task_struct_cachep, GFP_KERNEL, node);
}

alloc_thread_stack_node 为任务分配线程栈

1
2
3
4
5
6
7
8
9
10
11
static int alloc_thread_stack_node(struct task_struct *tsk, int node)
{
struct page *page = alloc_pages_node(node, THREADINFO_GFP,
THREAD_SIZE_ORDER);

if (likely(page)) {
tsk->stack = kasan_reset_tag(page_address(page));
return 0;
}
return -ENOMEM;
}

account_kernel_stack 记录任务的内核栈使用情况

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
static void account_kernel_stack(struct task_struct *tsk, int account)
{
if (IS_ENABLED(CONFIG_VMAP_STACK)) {
struct vm_struct *vm = task_stack_vm_area(tsk);
int i;

for (i = 0; i < THREAD_SIZE / PAGE_SIZE; i++)
mod_lruvec_page_state(vm->pages[i], NR_KERNEL_STACK_KB,
account * (PAGE_SIZE / 1024));
} else {
void *stack = task_stack_page(tsk);

/* 所有堆栈页面都位于同一节点中.
更新每个页面的内存使用统计 */
mod_lruvec_kmem_state(stack, NR_KERNEL_STACK_KB,
account * (THREAD_SIZE / 1024));
}
}

dup_task_struct 为一个现有任务(task_struct)创建一个副本

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
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
int __weak arch_dup_task_struct(struct task_struct *dst,
struct task_struct *src)
{
*dst = *src;
return 0;
}

static struct task_struct *dup_task_struct(struct task_struct *orig, int node)
{
struct task_struct *tsk;
int err;

if (node == NUMA_NO_NODE)
/* NUMA_NO_NODE */
node = tsk_fork_get_node(orig);
/* 分配一个新的 task_struct */
tsk = alloc_task_struct_node(node);
if (!tsk)
return NULL;

/* 复制架构相关的任务信息(如寄存器状态) */
err = arch_dup_task_struct(tsk, orig);
if (err)
goto free_tsk;

/* 为任务分配线程栈 */
err = alloc_thread_stack_node(tsk, node);
if (err)
goto free_tsk;

#ifdef CONFIG_THREAD_INFO_IN_TASK
refcount_set(&tsk->stack_refcount, 1);
#endif
/* 记录内核栈的使用情况 */
account_kernel_stack(tsk, 1);

#ifdef CONFIG_SECCOMP
/*
* 我们必须在进入sighand锁后设置seccomp过滤器,
* 以防在此期间orig发生了变化。在此之前,
* filter必须为NULL,以避免在错误路径调用free_task时
* 弄乱使用计数。
*/
tsk->seccomp.filter = NULL;
#endif

/* 清除任务的调度标志 */
clear_tsk_need_resched(tsk);
set_task_stack_end_magic(tsk);

#ifdef CONFIG_STACKPROTECTOR
/* 为任务生成一个随机栈金丝雀(stack_canary),用于检测栈溢出 */
tsk->stack_canary = get_random_canary();
#endif
/* 复制任务的 CPU 亲和性设置,确保任务在正确的 CPU 上运行 */
if (orig->cpus_ptr == &orig->cpus_mask)
tsk->cpus_ptr = &tsk->cpus_mask;

/*
* 一个用于用户空间可见状态,在回收时消失。
* 一个用于调度器。
*/
refcount_set(&tsk->rcu_users, 2);
/* 一个用于 rcu 用户 */
refcount_set(&tsk->usage, 1);

tsk->splice_pipe = NULL;
tsk->task_frag.page = NULL;
tsk->wake_q.next = NULL;
tsk->worker_private = NULL;

/* 任务相关的覆盖率跟踪 */
kcov_task_init(tsk);
/* 内存安全性检查 */
kmsan_task_create(tsk);
/* 映射初始化 */
kmap_local_fork(tsk);

return tsk;

free_stack:
exit_task_stack_account(tsk);
free_thread_stack(tsk);
free_tsk:
free_task_struct(tsk);
return NULL;
}

rt_mutex_init_task 初始化实时互斥锁相关的任务结构体

1
2
3
4
5
6
7
8
9
static void rt_mutex_init_task(struct task_struct *p)
{
raw_spin_lock_init(&p->pi_lock);
#ifdef CONFIG_RT_MUTEXES
p->pi_waiters = RB_ROOT_CACHED;
p->pi_top_task = NULL;
p->pi_blocked_on = NULL;
#endif
}

rcu_copy_process 复制进程的 RCU 相关数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
static inline void rcu_copy_process(struct task_struct *p)
{
#ifdef CONFIG_PREEMPT_RCU
p->rcu_read_lock_nesting = 0;
p->rcu_read_unlock_special.s = 0;
p->rcu_blocked_node = NULL;
INIT_LIST_HEAD(&p->rcu_node_entry);
#endif /* #ifdef CONFIG_PREEMPT_RCU */
#ifdef CONFIG_TASKS_RCU
p->rcu_tasks_holdout = false;
INIT_LIST_HEAD(&p->rcu_tasks_holdout_list);
p->rcu_tasks_idle_cpu = -1;
INIT_LIST_HEAD(&p->rcu_tasks_exit_list);
#endif /* #ifdef CONFIG_TASKS_RCU */
#ifdef CONFIG_TASKS_TRACE_RCU
p->trc_reader_nesting = 0;
p->trc_reader_special.s = 0;
INIT_LIST_HEAD(&p->trc_holdout_list);
INIT_LIST_HEAD(&p->trc_blkd_node);
#endif /* #ifdef CONFIG_TASKS_TRACE_RCU */
}

copy_files 处理进程文件描述符复制

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
static int copy_files(unsigned long clone_flags, struct task_struct *tsk,
int no_files)
{
struct files_struct *oldf, *newf;

/*
* 后台进程可能没有任何文件...
*/
oldf = current->files;
if (!oldf)
return 0;

if (no_files) {
tsk->files = NULL;
return 0;
}

if (clone_flags & CLONE_FILES) {
atomic_inc(&oldf->count);
return 0;
}

/* 如果不共享文件描述符表,调用 dup_fd 函数复制父进程的文件描述符表 */
newf = dup_fd(oldf, NULL);
if (IS_ERR(newf))
return PTR_ERR(newf);

tsk->files = newf;
return 0;
}

copy_fs 处理进程文件系统信息的复制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
static int copy_fs(unsigned long clone_flags, struct task_struct *tsk)
{
struct fs_struct *fs = current->fs;
/* 包含 CLONE_FS 标志,表示新进程希望与父进程共享文件系统上下文 */
if (clone_flags & CLONE_FS) {
spin_lock(&fs->lock);
/* 如果当前文件系统上下文正在执行(in_exec 为真)*/
if (fs->in_exec) {
spin_unlock(&fs->lock);
return -EAGAIN;
}
/* 增加 fs->users 的引用计数,表示该文件系统上下文被多个进程共享 */
fs->users++;
spin_unlock(&fs->lock);
return 0;
}
/* 未设置 CLONE_FS 标志,调用 copy_fs_struct 函数创建父进程文件系统上下文的副本 */
tsk->fs = copy_fs_struct(fs);
if (!tsk->fs)
return -ENOMEM;
return 0;
}

copy_sighand 复制信号处理器

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
static int copy_sighand(unsigned long clone_flags, struct task_struct *tsk)
{
struct sighand_struct *sig;

/* 新进程希望与父进程共享信号处理程序 */
if (clone_flags & CLONE_SIGHAND) {
/* 增加父进程信号处理程序的引用计数(refcount_inc),
表示该信号处理程序被多个进程共享 */
refcount_inc(&current->sighand->count);
return 0;
}
sig = kmem_cache_alloc(sighand_cachep, GFP_KERNEL);
RCU_INIT_POINTER(tsk->sighand, sig);
if (!sig)
return -ENOMEM;

/* 设置新信号处理程序的引用计数为 1,表示当前只有一个进程使用该结构 */
refcount_set(&sig->count, 1);
spin_lock_irq(&current->sighand->siglock);
/* 复制父进程的信号处理程序动作(action)到新结构中 */
memcpy(sig->action, current->sighand->action, sizeof(sig->action));
spin_unlock_irq(&current->sighand->siglock);

/*将所有未设置为 SIG_IGN 的信号处理程序重置为 SIG_DFL。*/
if (clone_flags & CLONE_CLEAR_SIGHAND)
flush_signal_handlers(tsk, 0);

return 0;
}

copy_signal 复制信号结构体

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
static int copy_signal(unsigned long clone_flags, struct task_struct *tsk)
{
struct signal_struct *sig;

if (clone_flags & CLONE_THREAD)
return 0;

sig = kmem_cache_zalloc(signal_cachep, GFP_KERNEL);
tsk->signal = sig;
if (!sig)
return -ENOMEM;

/* 表示当前只有一个线程 */
sig->nr_threads = 1;
sig->quick_threads = 1;
/* 表示该信号结构处于活动状态 */
atomic_set(&sig->live, 1);
/* 表示当前只有一个进程使用该信号结构 */
refcount_set(&sig->sigcnt, 1);

/* 在未使用 INIT_LIST_HEAD() 的情况下,list_add(thread_node, thread_head)*/
sig->thread_head = (struct list_head)LIST_HEAD_INIT(tsk->thread_node);
tsk->thread_node = (struct list_head)LIST_HEAD_INIT(sig->thread_head);

init_waitqueue_head(&sig->wait_chldexit);
sig->curr_target = tsk;
init_sigpending(&sig->shared_pending);
INIT_HLIST_HEAD(&sig->multiprocess);
seqlock_init(&sig->stats_lock);
prev_cputime_init(&sig->prev_cputime);

#ifdef CONFIG_POSIX_TIMERS
INIT_HLIST_HEAD(&sig->posix_timers);
INIT_HLIST_HEAD(&sig->ignored_posix_timers);
hrtimer_setup(&sig->real_timer, it_real_fn, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
#endif

/* 使用锁保护父进程的资源限制(rlim),并将其复制到新信号结构 */
task_lock(current->group_leader);
memcpy(sig->rlim, current->signal->rlim, sizeof sig->rlim);
task_unlock(current->group_leader);

/* 初始化 CPU 计时器 */
// posix_cpu_timers_init_group(sig);

// tty_audit_fork(sig);
// sched_autogroup_fork(sig);

/* OOM(内存不足)评分调整 */
sig->oom_score_adj = current->signal->oom_score_adj;
sig->oom_score_adj_min = current->signal->oom_score_adj_min;

mutex_init(&sig->cred_guard_mutex);
init_rwsem(&sig->exec_update_lock);

return 0;
}

mm_init 初始化内存管理结构体(mm_struct)

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
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
static struct mm_struct *mm_init(struct mm_struct *mm, struct task_struct *p,
struct user_namespace *user_ns)
{
/* 初始化内存管理结构的多线程标志 */
mt_init_flags(&mm->mm_mt, MM_MT_FLAGS);
// mt_set_external_lock(&mm->mm_mt, &mm->mmap_lock);
atomic_set(&mm->mm_users, 1);
atomic_set(&mm->mm_count, 1);
seqcount_init(&mm->write_protect_seq);
mmap_init_lock(mm);
INIT_LIST_HEAD(&mm->mmlist);
// mm_pgtables_bytes_init(mm);
/* 当前没有任何内存映射。
* map_count 用于跟踪与该内存管理结构相关的内存映射数量
*/
mm->map_count = 0;
/* 锁定内存是指通过 mlock 系统调用固定在物理内存中的虚拟内存,避免被交换到磁盘 */
mm->locked_vm = 0;
/* 当前没有固定的虚拟内存 */
atomic64_set(&mm->pinned_vm, 0);
/* RSS 表示进程实际驻留在物理内存中的页面数量,是内存使用的重要指标 */
memset(&mm->rss_stat, 0, sizeof(mm->rss_stat));
spin_lock_init(&mm->page_table_lock);
spin_lock_init(&mm->arg_lock);
/* CPU 掩码用于指定该内存管理结构可以在哪些 CPU 上运行,优化多核系统的性能 */
mm_init_cpumask(mm);
/* 初始化与异步 I/O(AIO)相关的字段。
异步 I/O允许进程在不阻塞的情况下执行 I/O 操作,提高系统的吞吐量 */
// mm_init_aio(mm);
/* 将内存管理结构与指定的任务结构(task_struct)关联。
这确保内存管理结构的所有权归属于特定进程 */
// mm_init_owner(mm, p);
/* 初始化 PASID(进程地址空间标识符)。
PASID 是用于标识进程虚拟地址空间的唯一标识符,
通常用于硬件加速场景,例如 IOMMU(输入输出内存管理单元) */
// mm_pasid_init(mm);
/* exe_file 指向与该内存管理结构关联的可执行文件 */
RCU_INIT_POINTER(mm->exe_file, NULL);
// mmu_notifier_subscriptions_init(mm);
/* 初始化 TLB(翻译后备缓冲区)刷新状态。
TLB 刷新用于确保虚拟地址到物理地址的映射一致性 */
init_tlb_flush_pending(mm);
/* 初始化与 Futex(快速用户空间互斥锁)相关的字段。
Futex 是一种高效的同步机制,允许用户空间线程在内核支持下进行锁操作。 */
// futex_mm_init(mm);
#if defined(CONFIG_TRANSPARENT_HUGEPAGE) && !defined(CONFIG_SPLIT_PMD_PTLOCKS)
mm->pmd_huge_pte = NULL;
#endif
/* 初始化与用户空间探测(uprobes)相关的状态。
用户空间探测是一种调试机制,允许在用户空间代码中插入探测点 */
// mm_init_uprobes_state(mm);
/* 初始化与大页(huge pages)相关的计数器。
大页是一种优化机制,使用较大的内存页来减少 TLB(翻译后备缓冲区)刷新次数。 */
// hugetlb_count_init(mm);

/* 如果当前进程有内存管理结构,继承其标志(flags 和 def_flags)。
如果当前进程是内核线程(没有内存管理结构),使用默认标志。 */
if (current->mm) {
mm->flags = mmf_init_flags(current->mm->flags);
mm->def_flags = current->mm->def_flags & VM_INIT_DEF_MASK;
} else {
mm->flags = default_dump_filter;
mm->def_flags = 0;
}

/* 分配页全局目录(PGD) */
// if (mm_alloc_pgd(mm))
// goto fail_nopgd;

/* 内存管理 ID(MM ID) */
// if (mm_alloc_id(mm))
// goto fail_noid;

/* 上下文 */
// if (init_new_context(p, mm))
// goto fail_nocontext;

/* CID(上下文 ID) */
// if (mm_alloc_cid(mm, p))
// goto fail_cid;

/* 初始化 RSS(Resident Set Size)统计的每 CPU 计数器 */
if (percpu_counter_init_many(mm->rss_stat, 0, GFP_KERNEL_ACCOUNT,
NR_MM_COUNTERS))
goto fail_pcpu;

/* 调用 get_user_ns 设置用户命名空间(user_ns),用于权限管理 */
mm->user_ns = get_user_ns(user_ns);
/* 调用 lru_gen_init_mm 初始化与 LRU(最近最少使用)相关的字段。
LRU 是一种内存管理算法,用于决定哪些页面可以被回收 */
// lru_gen_init_mm(mm);
return mm;

fail_pcpu:
mm_destroy_cid(mm);
fail_cid:
destroy_context(mm);
fail_nocontext:
mm_free_id(mm);
fail_noid:
mm_free_pgd(mm);
fail_nopgd:
free_mm(mm);
return NULL;
}

dup_mm 复制一个现有的内存管理结构(mm_struct)

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
#define allocate_mm()	(kmem_cache_alloc(mm_cachep, GFP_KERNEL))
/**
* dup_mm() - 复制一个现有的 mm 结构
* @tsk: 与新 mm 关联的 task_struct。
* @oldmm: 要复制的 mm。
*
* 分配一个新的 mm 结构,并将提供的 @oldmm 结构内容复制到其中。
*
* 返回值: 复制的 mm 或失败时返回 NULL。
*/
static struct mm_struct *dup_mm(struct task_struct *tsk,
struct mm_struct *oldmm)
{
struct mm_struct *mm;
int err;

mm = allocate_mm();
if (!mm)
goto fail_nomem;

memcpy(mm, oldmm, sizeof(*mm));

if (!mm_init(mm, tsk, mm->user_ns))
goto fail_nomem;

// uprobe_start_dup_mmap();
err = dup_mmap(mm, oldmm);
if (err)
goto free_pt;
// uprobe_end_dup_mmap();

mm->hiwater_rss = get_mm_rss(mm);
mm->hiwater_vm = mm->total_vm;

if (mm->binfmt && !try_module_get(mm->binfmt->module))
goto free_pt;

return mm;

free_pt:
/* don't put binfmt in mmput, we haven't got module yet */
mm->binfmt = NULL;
mm_init_owner(mm, NULL);
mmput(mm);
if (err)
uprobe_end_dup_mmap();

fail_nomem:
return NULL;
}

copy_mm 复制内存管理结构体

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
static int copy_mm(unsigned long clone_flags, struct task_struct *tsk)
{
struct mm_struct *mm, *oldmm;

/* 初始化新进程的页面错误计数 */
tsk->min_flt = tsk->maj_flt = 0;
/* 初始化上下文切换计数 */
tsk->nvcsw = tsk->nivcsw = 0;

tsk->mm = NULL;
tsk->active_mm = NULL;

/*
* 我们是否正在克隆一个内核线程?
*
* 为此我们需要窃取一个活动的虚拟机..
*/
oldmm = current->mm;
if (!oldmm)
return 0;

/* 表示新进程希望与父进程共享内存管理结构 */
if (clone_flags & CLONE_VM) {
/* 增加父进程内存管理结构的引用计数。 */
mmget(oldmm);
mm = oldmm;
} else {
/* 为新进程创建父进程内存管理结构的副本 */
mm = dup_mm(tsk, current->mm);
if (!mm)
return -ENOMEM;
}

tsk->mm = mm;
tsk->active_mm = mm;
// sched_mm_cid_fork(tsk);
return 0;
}

pidfd_prepare 分配一个新的 pidfd_file 并保留一个 pidfd

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
/**
* pidfd_prepare - 分配一个新的 pidfd_file 并保留一个 pidfd
* @pid: 用于创建 pidfd 的 struct pid
* @flags: 新的 @pidfd 的标志
* @ret_file: 返回新的 pidfs 文件
*
* 分配一个新的文件以存储 @pid,并在调用者的文件描述符表中保留一个新的 pidfd 编号。
* pidfd 已保留但尚未安装。
*
* 辅助函数验证 @pid 是否仍在使用中,若没有 PIDFD_THREAD,则由 @pid 标识的任务必须是线程组的领导者。
*
* 如果此函数成功返回,调用者需负责调用 fd_install(),将返回的 pidfd 和 pidfd 文件作为参数传递,
* 以便将 pidfd 安装到其文件描述符表中,或者调用者必须分别对返回的 pidfd 和 pidfd 文件使用
* put_unused_fd() 和 fput()。
*
* 当需要提前保留 pidfd,但后续可能仍存在失败点时,此函数非常有用,调用者希望确保这一点。

* 确保没有 pidfd 泄漏到其文件描述符表中。
*
* 返回值:成功时,函数返回一个保留的 pidfd,并在函数的最后一个参数中返回一个新的 pidfd 文件。
* 出错时,函数返回一个负的错误代码,最后一个参数保持不变。
*/
int pidfd_prepare(struct pid *pid, unsigned int flags, struct file **ret_file)
{
struct file *pidfs_file;

/*
* 只有当调用者知道 @pid 已经在 pidfs 中注册,
* 并且可以保证 PIDFD_INFO_EXIT 信息可用时,
* 才允许传递 PIDFD_STALE。
*/
/* 如果未设置 PIDFD_STALE 标志,函数会验证 pid 是否仍在使用 */
if (!(flags & PIDFD_STALE)) {
/*
* 在持有 pidfd 等待队列锁的情况下,无法移除线程组组长 pid
* (PIDTYPE_TGID) 的任务链接。因此,如果 PIDTYPE_PID
* 仍然有任务链接,但 pid 没有线程组组长链接,
* 则意味着它一开始就不是线程组组长。
*/
guard(spinlock_irq)(&pid->wait_pidfd.lock);

/* 目标任务已被回收. */
if (!pid_has_task(pid, PIDTYPE_PID))
return -ESRCH;
/*
* 请求线程组领导者的 PID 文件描述符(未设置 PIDFD_THREAD),但 pid 不是线程组领导者
*/
if (!(flags & PIDFD_THREAD) && !pid_has_task(pid, PIDTYPE_TGID))
return -ENOENT;
}

CLASS(get_unused_fd, pidfd)(O_CLOEXEC);
if (pidfd < 0)
return pidfd;

pidfs_file = pidfs_alloc_file(pid, flags | O_RDWR);
if (IS_ERR(pidfs_file))
return PTR_ERR(pidfs_file);

*ret_file = pidfs_file;
return take_fd(pidfd);
}

copy_oom_score_adj 复制 OOM(Out of Memory)评分调整

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
static void copy_oom_score_adj(u64 clone_flags, struct task_struct *tsk)
{
/* 跳过内核线程 */
if (!tsk->mm)
return;

/* 跳过线程或 vfork 创建 */
/* 只有在创建独立进程(CLONE_VM)时才需要复制 OOM 分数调整值 */
if ((clone_flags & (CLONE_VM | CLONE_THREAD | CLONE_VFORK)) != CLONE_VM)
return;

/* 我们需要与 __set_oom_adj 同步 */
mutex_lock(&oom_adj_mutex);
/* 设置内存管理结构的 MMF_MULTIPROCESS 标志,表示该进程属于多进程环境 */
set_bit(MMF_MULTIPROCESS, &tsk->mm->flags);
/* 更新 值,以防它们在 copy_signal 后发生更改 */
tsk->signal->oom_score_adj = current->signal->oom_score_adj;
tsk->signal->oom_score_adj_min = current->signal->oom_score_adj_min;
mutex_unlock(&oom_adj_mutex);
}

copy_process 创建一个新进程作为旧进程的副本

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
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
/*
* 这会创建一个新进程作为旧进程的副本,
* 但实际上尚未启动它。
*
* 它会复制寄存器,以及进程环境中所有适当的部分
* (根据克隆标志)。实际启动由调用者完成。
*/
__latent_entropy struct task_struct *copy_process(
struct pid *pid,
int trace,
int node,
struct kernel_clone_args *args)
{
int pidfd = -1, retval;
struct task_struct *p;
struct multiprocess_signals delayed;
struct file *pidfile = NULL;
const u64 clone_flags = args->flags;
struct nsproxy *nsp = current->nsproxy;

/*
* 不允许与不同命名空间的进程共享根目录, 因为它们可能导致命名空间隔离性被破坏
*/
if ((clone_flags & (CLONE_NEWNS|CLONE_FS)) == (CLONE_NEWNS|CLONE_FS))
return ERR_PTR(-EINVAL);
if ((clone_flags & (CLONE_NEWUSER|CLONE_FS)) == (CLONE_NEWUSER|CLONE_FS))
return ERR_PTR(-EINVAL);

/*
* 线程组必须共享信号处理器,并且分离线程只能在同一个线程组内启动。
*/
if ((clone_flags & CLONE_THREAD) && !(clone_flags & CLONE_SIGHAND))
return ERR_PTR(-EINVAL);

/*
* 共享信号处理器意味着共享虚拟内存。通过上述逻辑,线程组也意味着共享虚拟内存。
* 阻止这种情况可以简化其他代码的实现。
*/
if ((clone_flags & CLONE_SIGHAND) && !(clone_flags & CLONE_VM))
return ERR_PTR(-EINVAL);

/*
* 检查是否设置了 CLONE_PARENT(共享父进程)且当前进程是全局初始化进程(不可杀死)
* 全局 init 的兄弟进程在退出后会一直处于僵尸状态,因为它们不会被父进程(swapper)回收。
* 为了解决这个问题并避免多根进程树,禁止全局或容器 init 创建兄弟进程。
*/
if ((clone_flags & CLONE_PARENT) &&
current->signal->flags & SIGNAL_UNKILLABLE)
return ERR_PTR(-EINVAL);

/*
* 如果新进程将处于不同的 pid 或 user 命名空间,则不允许其与当前进程共享线程组。
*/
if (clone_flags & CLONE_THREAD) {
if ((clone_flags & (CLONE_NEWUSER | CLONE_NEWPID)) ||
(task_active_pid_ns(current) != nsp->pid_ns_for_children))
return ERR_PTR(-EINVAL);
}

/* 同时设置了 CLONE_PIDFD(返回 PID 文件描述符)和 CLONE_DETACHED(分离线程)。
这种组合被禁止,以便未来可以重新利用 CLONE_DETACHED */
if (clone_flags & CLONE_PIDFD) {
/*
* - 禁止 CLONE_DETACHED,这样以后可以将其用于 CLONE_PIDFD。
*/
if (clone_flags & CLONE_DETACHED)
return ERR_PTR(-EINVAL);
}

/*
* 在此之前接收到的任何信号都强制传递,
* 在 fork 发生之前完成。收集在 fork 期间
* 发送到多个进程的信号,并延迟它们,
* 使它们看起来像是在 fork 之后发生。
*/
sigemptyset(&delayed.signal);
INIT_HLIST_NODE(&delayed.node);

spin_lock_irq(&current->sighand->siglock);
/* CLONE_THREAD 是一个标志,用于表示线程共享信号处理器(signal)和其他资源。如果该标志未设置,说明当前操作涉及的是进程而非线程 */
if (!(clone_flags & CLONE_THREAD))
hlist_add_head(&delayed.node, &current->signal->multiprocess);
/* 重新计算信号的挂起是否需要设置 */
recalc_sigpending();
spin_unlock_irq(&current->sighand->siglock);
retval = -ERESTARTNOINTR;
/* 线程被信号挂起执行fork_out */
if (task_sigpending(current))
goto fork_out;

retval = -ENOMEM;
p = dup_task_struct(current, node);
if (!p)
goto fork_out;
/* 清除任务结构中的 PF_KTHREAD 标志。
这确保任务默认不被标记为内核线程 */
p->flags &= ~PF_KTHREAD;
if (args->kthread)
p->flags |= PF_KTHREAD;
if (args->user_worker) {
/*
* 将我们标记为用户工作线程,并屏蔽任何非致命或非停止的信号
*/
p->flags |= PF_USER_WORKER;
/* 表示允许接收 SIGKILL 和 SIGSTOP 信号。
其他信号将被屏蔽,确保用户工作线程不会被非致命信号干扰 */
siginitsetinv(&p->blocked, sigmask(SIGKILL)|sigmask(SIGSTOP));
}
if (args->io_thread)
p->flags |= PF_IO_WORKER;

/* 内核线程创建过程中允许直接设置线程名称,而不是在线程创建后通过 set_task_comm 来设置名称 */
if (args->name)
strscpy_pad(p->comm, args->name, sizeof(p->comm));

/* CLONE_CHILD_SETTID 标志表示在新线程创建时,内核需要将新线程的 TID 写入到用户空间指定的地址(args->child_tid) */
p->set_child_tid = (clone_flags & CLONE_CHILD_SETTID) ? args->child_tid : NULL;
/*
* CLONE_CHILD_CLEARTID 标志表示在线程退出时,内核需要清除用户空间指定地址(args->child_tid)中的 TID,并唤醒等待该地址的线程
*/
p->clear_child_tid = (clone_flags & CLONE_CHILD_CLEARTID) ? args->child_tid : NULL;

// ftrace_graph_init_task(p);

rt_mutex_init_task(p);

lockdep_assert_irqs_enabled();

retval = copy_creds(p, clone_flags);
if (retval < 0)
goto bad_fork_free;

retval = -EAGAIN;
/* RLIMIT_NPROC 是一个资源限制,表示系统中每个用户允许创建的最大进程数
检查当前任务的进程数是否超过 RLIMIT_NPROC 限制 */
if (is_rlimit_overlimit(task_ucounts(p), UCOUNT_RLIMIT_NPROC, rlimit(RLIMIT_NPROC))) {
/* 如果进程数超出限制,进一步检查 */
/* 检查当前任务的用户是否是初始用户(INIT_USER)。初始用户通常具有特殊权限,不受 RLIMIT_NPROC 限制 */
if (p->real_cred->user != INIT_USER &&
!capable(CAP_SYS_RESOURCE) && !capable(CAP_SYS_ADMIN))
goto bad_fork_cleanup_count;
}
current->flags &= ~PF_NPROC_EXCEEDED;

/*
* 如果多个线程位于 copy_process() 中,那么此检查
* 触发得太晚。这并无影响,此检查仅用于
* 阻止 root 分叉炸弹。
* 用于限制系统中线程的总数量,以防止线程数量过多导致系统资源耗尽或性能下降
*/
retval = -EAGAIN;
if (data_race(nr_threads >= max_threads))
goto bad_fork_cleanup_count;

/* 初始化延迟账户统计信息,用于跟踪任务的延迟时间 */
// delayacct_tsk_init(p); /* 必须在 dup_task_struct() 之后保留*/
/*
PF_SUPERPRIV:表示任务具有超级用户权限。
PF_WQ_WORKER:表示任务是工作队列的工作线程。
PF_IDLE:表示任务是空闲线程。
PF_NO_SETAFFINITY:表示任务不能设置 CPU 亲和性
*/
p->flags &= ~(PF_SUPERPRIV | PF_WQ_WORKER | PF_IDLE | PF_NO_SETAFFINITY);
/* 表示任务是通过 fork 创建的,但尚未执行任何程序 */
p->flags |= PF_FORKNOEXEC;
INIT_LIST_HEAD(&p->children);
INIT_LIST_HEAD(&p->sibling);
/* 复制任务的 RCU(Read-Copy-Update)相关数据 */
rcu_copy_process(p);
p->vfork_done = NULL;
spin_lock_init(&p->alloc_lock);

init_sigpending(&p->pending);

/* utime:记录任务在用户态运行的时间。
stime:记录任务在内核态运行的时间。
gtime:记录任务的全局时间(可能用于特定的架构或统计需求)。
将这些字段初始化为零,表示新任务尚未消耗任何 CPU 时间。 */
p->utime = p->stime = p->gtime = 0;
prev_cputime_init(&p->prev_cputime);

/* 记录任务的默认定时器松弛值(以纳秒为单位)。
定时器松弛值用于控制任务的定时器精度,较大的松弛值可以减少定时器唤醒频率,从而提高系统的节能效果。
新任务继承当前任务的定时器松弛值,确保调度行为的一致性。 */
p->default_timer_slack_ns = current->timer_slack_ns;

/* 会计数据通常包括任务的资源使用统计,例如 CPU 时间、内存使用量等 */
// task_io_accounting_init(&p->ioac);
// acct_clear_integrals(p);

// posix_cputimers_init(&p->posix_cputimers);
// tick_dep_init_task(p);

// p->io_context = NULL;
// audit_set_context(p, NULL);
// cgroup_fork(p);
// if (args->kthread) {
// if (!set_kthread_struct(p))
// goto bad_fork_cleanup_delayacct;
// }

p->pagefault_disabled = 0;

/*执行与调度程序相关的设置。将此任务分配给一个CPU。*/
retval = sched_fork(clone_flags, p);
if (retval)
goto bad_fork_cleanup_policy;

// retval = perf_event_init_task(p, clone_flags);
// if (retval)
// goto bad_fork_sched_cancel_fork;
// retval = audit_alloc(p);
// if (retval)
// goto bad_fork_cleanup_perf;
// /* copy all the process information */
// shm_init_task(p);
// retval = security_task_alloc(p, clone_flags);
// if (retval)
// goto bad_fork_cleanup_audit;
// retval = copy_semundo(clone_flags, p);
// if (retval)
// goto bad_fork_cleanup_security;
retval = copy_files(clone_flags, p, args->no_files);
if (retval)
goto bad_fork_cleanup_semundo;
retval = copy_fs(clone_flags, p);
if (retval)
goto bad_fork_cleanup_files;
retval = copy_sighand(clone_flags, p);
if (retval)
goto bad_fork_cleanup_fs;
retval = copy_signal(clone_flags, p);
if (retval)
goto bad_fork_cleanup_sighand;
retval = copy_mm(clone_flags, p);
if (retval)
goto bad_fork_cleanup_signal;
retval = copy_namespaces(clone_flags, p);
if (retval)
goto bad_fork_cleanup_mm;
retval = copy_io(clone_flags, p);
if (retval)
goto bad_fork_cleanup_namespaces;
retval = copy_thread(p, args);
if (retval)
goto bad_fork_cleanup_io;

// stackleak_task_init(p);

/* init_struct_pid 是内核中用于初始化进程的特殊 PID。
如果当前进程是初始化进程(如 init),则无需分配新的 PID */
/* PID 命名空间允许多个进程拥有相同的 PID,但在不同的命名空间中互相隔离 */
if (pid != &init_struct_pid) {
/* p->nsproxy->pid_ns_for_children:指定 PID 命名空间,决定 PID 的范围和隔离性。
args->set_tid 和 args->set_tid_size:用于设置线程 ID(TID),通常在多线程环境中使用 */
pid = alloc_pid(p->nsproxy->pid_ns_for_children, args->set_tid,
args->set_tid_size);
if (IS_ERR(pid)) {
retval = PTR_ERR(pid);
goto bad_fork_cleanup_thread;
}
}

/*
* 这必须在我们可能取消共享文件描述符表之后发生
* (这样如果文件描述符表未共享,pidfd 就不会泄漏到子进程中)。
*/
/* 包含 CLONE_PIDFD 标志,表示需要为新进程创建一个 PID 文件描述符 */
if (clone_flags & CLONE_PIDFD) {
/* 包含 CLONE_THREAD 标志,设置 PIDFD_THREAD 标志。
PIDFD_THREAD 表示文件描述符与线程相关联,而不是整个进程 */
int flags = (clone_flags & CLONE_THREAD) ? PIDFD_THREAD : 0;

/*
* 请注意,目前尚未有任务附加到 @pid,
* 通过 CLONE_PIDFD 表示这一点。
*/
/* 为新进程准备一个 PID 文件描述符
PIDFD_STALE 标志表示当前文件描述符尚未附加到任务*/
retval = pidfd_prepare(pid, flags | PIDFD_STALE, &pidfile);
if (retval < 0)
goto bad_fork_free_pid;
pidfd = retval;

/* 将生成的 PID 文件描述符写入用户空间的地址 args->pidfd */
retval = put_user(pidfd, args->pidfd);
if (retval)
goto bad_fork_put_pidfd;
}

#ifdef CONFIG_BLOCK
p->plug = NULL;
#endif
// futex_init_task(p);

/*
* 共享同一 VM 时应清除 sigaltstack
* 新进程与父进程共享同一虚拟内存(CLONE_VM
*/
if ((clone_flags & (CLONE_VM|CLONE_VFORK)) == CLONE_VM)
sas_ss_reset(p);

/*
* 系统调用跟踪和单步执行应在
* 儿童,无论CLONE_PTRACE。
*/
// user_disable_single_step(p);
clear_task_syscall_work(p, SYSCALL_TRACE);

// clear_tsk_latency_tracing(p);

/* ok, now we should be set up.. */
/* 使用 pid_nr 获取新进程的 PID,并将其赋值给 p->pid */
p->pid = pid_nr(pid);
/* 设置了 CLONE_THREAD 标志,新进程属于父进程的线程组 */
if (clone_flags & CLONE_THREAD) {
p->group_leader = current->group_leader;
p->tgid = current->tgid;
} else {
/* 未设置 CLONE_THREAD 标志,新进程是独立进程 */
p->group_leader = p;
p->tgid = p->pid;
}

/* 初始化脏页计数器 */
p->nr_dirtied = 0; /* 初始化为 0,表示新进程尚未修改任何页面。 */
/* 设置为一个阈值,用于控制脏页的暂停行为 */
p->nr_dirtied_pause = 128 >> (PAGE_SHIFT - 10);
/* 表示脏页暂停的时间 */
p->dirty_paused_when = 0;
/* 初始化为 0,表示父进程终止时不会向子进程发送信号 */
p->pdeath_signal = 0;
/* 初始化为 NULL,表示新进程没有挂载任何任务工作 */
p->task_works = NULL;
// clear_posix_cputimers_work(p);

/*
* 确保 cgroup 子系统策略允许新进程
*分 叉。需要注意的是,新进程的css_set可以更改
* cgroup_post_fork如果组织作在
*进展。
*/
// retval = cgroup_can_fork(p, args);
// if (retval)
// goto bad_fork_put_pidfd;

/*
* 现在 cgroup 已固定,请重新克隆父 cgroup 并将
* 正确 RunQueue 上的新任务。所有这些都 * 在* 任务之前
* 变为可见。
*
* 这不是 ->can_fork() 的一部分,因为虽然重新克隆是
* cgroup 特定,它无条件地需要将任务放在
* runqueue 运行。
*/
/* 重新克隆父进程的 cgroup,并将新任务放入正确的运行队列 */
retval = sched_cgroup_fork(p, args);
if (retval)
goto bad_fork_cancel_cgroup;

/*
* 为用户进程分配一个默认的 futex 哈希,一旦第一个
* 线程生成。
*/
if (need_futex_hash_allocate_default(clone_flags)) {
/* 分配默认的 Futex 哈希表 */
retval = futex_hash_allocate_default();
if (retval)
goto bad_fork_core_free;
/*
* 如果代码在此之后失败,已分配的 Futex 哈希表不会被释放。
假设另一个线程会被创建并使用该哈希表。
哈希表的生命周期与主线程绑定,当主线程终止时,哈希表会被释放
*/
}
/*
* 从这一点开始,我们必须避免任何同步的用户空间
* 通信,直到我们获取 tasklist-lock。特别是,我们这样做
* 不希望用户空间能够通过以下方式预测进程启动时间
* 在我们记录 start_time 之后但在
* 对系统可见。
*/

/* 记录新进程的启动时间(start_time)和启动时的系统运行时间(start_boottime) */
p->start_time = ktime_get_ns();
p->start_boottime = ktime_get_boottime_ns();

/*
* 使其对系统的其余部分可见,但暂时不要唤醒它。
* 需要任务列表锁来处理父级等!
*/
write_lock_irq(&tasklist_lock);

/* CLONE_PARENT 重用旧父级 */
if (clone_flags & (CLONE_PARENT|CLONE_THREAD)) {
p->real_parent = current->real_parent;
p->parent_exec_id = current->parent_exec_id;
if (clone_flags & CLONE_THREAD)
p->exit_signal = -1;
else
p->exit_signal = current->group_leader->exit_signal;
} else {
p->real_parent = current;
p->parent_exec_id = current->self_exec_id;
p->exit_signal = args->exit_signal;
}

// klp_copy_process(p);

// sched_core_fork(p);

spin_lock(&current->sighand->siglock);

// rv_task_fork(p);

rseq_fork(p, clone_flags);

/* 不要在垂死的 pid 命名空间中启动 children */
if (unlikely(!(ns_of_pid(pid)->pid_allocated & PIDNS_ADDING))) {
retval = -ENOMEM;
goto bad_fork_core_free;
}

/* 让 kill 在中间终止 clone/fork */
if (fatal_signal_pending(current)) {
retval = -EINTR;
goto bad_fork_core_free;
}

/* 在此点之后不再有失败路径。 */

/*
* 在此处明确复制 seccomp 详细信息,以防它们被更改
* 在按住 sighand lock 之前。
*/
/* seccomp 配置包括允许的系统调用列表和相关的过滤规则 */
copy_seccomp(p);

init_task_pid_links(p);
if (likely(p->pid)) {
/* 初始化新进程的调试状态 */
ptrace_init_task(p, (clone_flags & CLONE_PTRACE) || trace);
/* 为新进程设置 PIDTYPE_PID 类型的 PID */
init_task_pid(p, PIDTYPE_PID, pid);
/* 判断进程 p 是否是线程组的领导者 */
if (thread_group_leader(p)) {
/* PIDTYPE_TGID:线程组 ID */
init_task_pid(p, PIDTYPE_TGID, pid);
/* PIDTYPE_PGID:进程组 ID */
init_task_pid(p, PIDTYPE_PGID, task_pgrp(current));
/* PIDTYPE_SID:会话 ID */
init_task_pid(p, PIDTYPE_SID, task_session(current));
/* 如果新进程是命名空间的子进程回收者(child_reaper),将其标记为不可杀死(SIGNAL_UNKILLABLE) */
if (is_child_reaper(pid)) {
ns_of_pid(pid)->child_reaper = p;
p->signal->flags |= SIGNAL_UNKILLABLE;
}
/* 继承父进程的挂起信号和 TTY 属性 */
p->signal->shared_pending.signal = delayed.signal;
p->signal->tty = tty_kref_get(current->signal->tty);
/*
* 继承同一has_child_subreaper标志
* tasklist_lock 将子项添加到进程树
* 用于propagate_has_child_subreaper优化。
*/
p->signal->has_child_subreaper = p->real_parent->signal->has_child_subreaper ||
p->real_parent->signal->is_child_subreaper;
list_add_tail(&p->sibling, &p->real_parent->children);
list_add_tail_rcu(&p->tasks, &init_task.tasks);
attach_pid(p, PIDTYPE_TGID);
attach_pid(p, PIDTYPE_PGID);
attach_pid(p, PIDTYPE_SID);
__this_cpu_inc(process_counts);
} else {
current->signal->nr_threads++;
current->signal->quick_threads++;
atomic_inc(&current->signal->live);
refcount_inc(&current->signal->sigcnt);
task_join_group_stop(p);
list_add_tail_rcu(&p->thread_node,
&p->signal->thread_head);
}
attach_pid(p, PIDTYPE_PID);
nr_threads++;
}
total_forks++;
hlist_del_init(&delayed.node);
spin_unlock(&current->sighand->siglock);
// syscall_tracepoint_update(p);
write_unlock_irq(&tasklist_lock);

if (pidfile)
/* /* 将 PID 文件描述符 pidfd 安装到进程的文件描述符表中 */ */
fd_install(pidfd, pidfile);

// proc_fork_connector(p);
// sched_post_fork(p);
// cgroup_post_fork(p, args);
// perf_event_fork(p);

// trace_task_newtask(p, clone_flags);
// uprobe_copy_process(p, clone_flags);
// user_events_fork(p, clone_flags);

/* 在进程创建过程中复制父进程的 OOM(Out-Of-Memory)分数调整值到新进程。OOM 分数调整值决定了进程在系统内存不足时的优先级,分数越高,进程越容易被内核选择进行终止 */
copy_oom_score_adj(clone_flags, p);

return p;

bad_fork_core_free:
sched_core_free(p);
spin_unlock(&current->sighand->siglock);
write_unlock_irq(&tasklist_lock);
bad_fork_cancel_cgroup:
cgroup_cancel_fork(p, args);
bad_fork_put_pidfd:
if (clone_flags & CLONE_PIDFD) {
fput(pidfile);
put_unused_fd(pidfd);
}
bad_fork_free_pid:
if (pid != &init_struct_pid)
free_pid(pid);
bad_fork_cleanup_thread:
exit_thread(p);
bad_fork_cleanup_io:
if (p->io_context)
exit_io_context(p);
bad_fork_cleanup_namespaces:
exit_task_namespaces(p);
bad_fork_cleanup_mm:
if (p->mm) {
mm_clear_owner(p->mm, p);
mmput(p->mm);
}
bad_fork_cleanup_signal:
if (!(clone_flags & CLONE_THREAD))
free_signal_struct(p->signal);
bad_fork_cleanup_sighand:
__cleanup_sighand(p->sighand);
bad_fork_cleanup_fs:
exit_fs(p); /* blocking */
bad_fork_cleanup_files:
exit_files(p); /* blocking */
bad_fork_cleanup_semundo:
exit_sem(p);
bad_fork_cleanup_security:
security_task_free(p);
bad_fork_cleanup_audit:
audit_free(p);
bad_fork_cleanup_perf:
perf_event_free_task(p);
bad_fork_sched_cancel_fork:
sched_cancel_fork(p);
bad_fork_cleanup_policy:
lockdep_free_task(p);
bad_fork_cleanup_delayacct:
delayacct_tsk_free(p);
bad_fork_cleanup_count:
dec_rlimit_ucounts(task_ucounts(p), UCOUNT_RLIMIT_NPROC, 1);
exit_creds(p);
bad_fork_free:
WRITE_ONCE(p->__state, TASK_DEAD);
exit_task_stack_account(p);
put_task_stack(p);
delayed_free_task(p);
fork_out:
spin_lock_irq(&current->sighand->siglock);
hlist_del_init(&delayed.node);
spin_unlock_irq(&current->sighand->siglock);
return ERR_PTR(retval);
}

kernel_clone 进程复制函数,用于实现进程或线程的创建

  • 将任务放入运行队列,并标记为可调度
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
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
/*
* fork 例程。
*
* 它复制进程,如果成功,则启动它,并在需要时使用 VM 等待其完成。
*
* args->exit_signal 预计由调用者检查其合理性。
*/
pid_t kernel_clone(struct kernel_clone_args *args)
{
u64 clone_flags = args->flags;
struct completion vfork;
struct pid *pid;
struct task_struct *p;
int trace = 0;
pid_t nr;

/*
* 对于传统的 clone() 调用,CLONE_PIDFD 都使用 parent_tid 参数,但它们的用途不同,因此不能同时设置。
* 如果两者同时设置,并且指向同一个内存位置,会导致逻辑冲突
*
* 使用 clone3() 时,CLONE_PIDFD 在 struct clone_args 中
* 增加了一个单独的字段,并且让它们都指向同一个内存位置仍然没有意义。
* 在这里执行此检查的好处是我们不需要单独的辅助函数来检查传统的 clone()。
*/
/* 检查 CLONE_PIDFD 和 CLONE_PARENT_SETTID 标志是否同时设置,并确保它们指向不同的内存位置 */
if ((clone_flags & CLONE_PIDFD) &&
(clone_flags & CLONE_PARENT_SETTID) &&
(args->pidfd == args->parent_tid))
return -EINVAL;

/*
* 确定是否以及向 ptracer 报告哪个事件。
* 当从 kernel_thread 调用或显式请求 CLONE_UNTRACED 时,不报告事件;
* 否则,如果启用了该类型分叉的事件,则报告。
*/
if (!(clone_flags & CLONE_UNTRACED)) {
if (clone_flags & CLONE_VFORK)
/* 表示子进程通过 vfork 创建 */
trace = PTRACE_EVENT_VFORK;
else if (args->exit_signal != SIGCHLD)
/* 表示子进程通过 clone 创建 */
trace = PTRACE_EVENT_CLONE;
else
/* 表示子进程通过普通的 fork 创建 */
trace = PTRACE_EVENT_FORK;

/* 检查当前进程是否启用了 ptrace 事件跟踪 */
if (likely(!ptrace_event_enabled(current, trace)))
/* 事件未启用,将 trace 设置为 0,表示不报告事件 */
trace = 0;
}

/* 执行进程复制,创建新的任务结构 */
p = copy_process(NULL, trace, NUMA_NO_NODE, args);
/* 用于向系统的熵池(entropy pool)添加潜在的熵值 */
add_latent_entropy();

if (IS_ERR(p))
return PTR_ERR(p);

/*
* 在唤醒新线程之前执行此操作 - 线程指针
* 在该点之后可能会变得无效,如果线程快速退出。
*/
/* 调用调度器跟踪函数,记录进程复制事件 */
trace_sched_process_fork(current, p);

/* 获取子进程的 PID*/
pid = get_task_pid(p, PIDTYPE_PID);
nr = pid_vnr(pid);

/* 将其写入父进程的内存 */
if (clone_flags & CLONE_PARENT_SETTID)
put_user(nr, args->parent_tid);

/* 初始化 vfork 完成机制,阻塞父进程直到子进程完成 */
if (clone_flags & CLONE_VFORK) {
p->vfork_done = &vfork;
init_completion(&vfork);
get_task_struct(p);
}

/* 执行内存管理相关操作以优化内存使用 */
if (IS_ENABLED(CONFIG_LRU_GEN_WALKS_MMU) && !(clone_flags & CLONE_VM)) {
/* lock the task to synchronize with memcg migration */
task_lock(p);
lru_gen_add_mm(p->mm);
task_unlock(p);
}

/* 唤醒新创建的进程,使其开始运行 */
wake_up_new_task(p);

/* 分叉完成,子进程开始运行,通知 ptracer */
if (unlikely(trace))
/* 通知调试器进程已启动或 vfork 已完成 */
ptrace_event_pid(trace, pid);

if (clone_flags & CLONE_VFORK) {
if (!wait_for_vfork_done(p, &vfork))
ptrace_event_pid(PTRACE_EVENT_VFORK_DONE, pid);
}

/* 释放 PID 资源并返回新进程的 PID */
put_pid(pid);
return nr;
}

kernel_thread 创建内核线程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/*
* Create a kernel thread.
*/
pid_t kernel_thread(int (*fn)(void *), void *arg, const char *name,
unsigned long flags)
{
struct kernel_clone_args args = {
.flags = ((lower_32_bits(flags) | CLONE_VM |
CLONE_UNTRACED) & ~CSIGNAL),
.exit_signal = (lower_32_bits(flags) & CSIGNAL),
.fn = fn,
.fn_arg = arg,
.name = name,
.kthread = 1,
};

return kernel_clone(&args);
}

user_mode_thread 创建用户模式线程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/*
* 创建用户模式线程。
*/
pid_t user_mode_thread(int (*fn)(void *), void *arg, unsigned long flags)
{
struct kernel_clone_args args = {
/* CLONE_VM 标志,表示线程与父进程共享内存空间。
CLONE_UNTRACED 标志,表示线程不会被调试器跟踪 */
.flags = ((lower_32_bits(flags) | CLONE_VM |
CLONE_UNTRACED) & ~CSIGNAL),
/* 从 flags 中提取信号相关标志(CSIGNAL),用于指定线程退出时发送的信号 */
.exit_signal = (lower_32_bits(flags) & CSIGNAL),
.fn = fn,
.fn_arg = arg,
};
/* 分配线程资源并启动线程
返回值为线程的进程 ID(pid_t),用于标识新创建的线程 */
return kernel_clone(&args);
}

__mmdrop 释放内存管理结构

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
#define free_mm(mm)	(kmem_cache_free(mm_cachep, (mm)))

/*
* 当对 mm 的最后一个引用被释放时调用:
* 要么由一个惰性线程,要么由 mmput。
* 释放页目录和 mm。
*/
void __mmdrop(struct mm_struct *mm)
{
BUG_ON(mm == &init_mm);
WARN_ON_ONCE(mm == current->mm);

/* 确保没有 CPU 将此用作其懒惰的 TLB mm */
// cleanup_lazy_tlbs(mm);

WARN_ON_ONCE(mm == current->active_mm);
// mm_free_pgd(mm);
// mm_free_id(mm);
// destroy_context(mm);
// mmu_notifier_subscriptions_destroy(mm);
// check_mm(mm);
// put_user_ns(mm->user_ns);
// mm_pasid_drop(mm);
// mm_destroy_cid(mm);
// percpu_counter_destroy_many(mm->rss_stat, NR_MM_COUNTERS);

free_mm(mm);
}
EXPORT_SYMBOL_GPL(__mmdrop);

put_task_stack 释放任务栈

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
static void thread_stack_free_rcu(struct rcu_head *rh)
{
__free_pages(virt_to_page(rh), THREAD_SIZE_ORDER);
}

static void thread_stack_delayed_free(struct task_struct *tsk)
{
struct rcu_head *rh = tsk->stack;
/* 并不立即释放内存,而是将释放操作“注册”给RCU子系统,由RCU在未来的一个安全时间点来执行真正的释放 */
call_rcu(rh, thread_stack_free_rcu);
}

static void free_thread_stack(struct task_struct *tsk)
{
thread_stack_delayed_free(tsk);
tsk->stack = NULL;
}

static void release_task_stack(struct task_struct *tsk)
{
if (WARN_ON(READ_ONCE(tsk->__state) != TASK_DEAD))
return; /* 泄露堆栈比过早释放更好 */

free_thread_stack(tsk);
}

#ifdef CONFIG_THREAD_INFO_IN_TASK
void put_task_stack(struct task_struct *tsk)
{
if (refcount_dec_and_test(&tsk->stack_refcount))
release_task_stack(tsk);
}
#endif

__put_task_struct 释放任务结构

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
void __put_task_struct_rcu_cb(struct rcu_head *rhp)
{
struct task_struct *task = container_of(rhp, struct task_struct, rcu);

__put_task_struct(task);
}
EXPORT_SYMBOL_GPL(__put_task_struct_rcu_cb);

void __put_task_struct(struct task_struct *tsk)
{
WARN_ON(!tsk->exit_state);
WARN_ON(refcount_read(&tsk->usage));
WARN_ON(tsk == current);

sched_ext_free(tsk);
io_uring_free(tsk);
cgroup_free(tsk);
task_numa_free(tsk, true);
security_task_free(tsk);
exit_creds(tsk);
delayacct_tsk_free(tsk);
put_signal_struct(tsk->signal);
sched_core_free(tsk);
free_task(tsk);
}
EXPORT_SYMBOL_GPL(__put_task_struct);

exit_mm_release 释放内存管理结构

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
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
static void complete_vfork_done(struct task_struct *tsk)
{
struct completion *vfork;

task_lock(tsk);
vfork = tsk->vfork_done;
if (likely(vfork)) {
tsk->vfork_done = NULL;
complete(vfork);
}
task_unlock(tsk);
}

/*
* 请注意mmput和mm_release之间的区别。
* mmput是在我们每次停止持有一个mm_struct时被调用,无论成功、失败或任何情况。
*
* mm_release是在一个mm_struct已经从当前进程中被移除之后被调用。
*
* 这个区别对于错误处理非常重要,例如当我们为一个新进程只设置了一半的
* mm_struct并需要恢复到旧的那个时。因为我们在恢复旧的mm_struct之前,
* 会先对新的mm_struct调用mmput...
* Eric Biederman 1998年1月10日
*/
/*
* @tsk: 指向即将脱离其地址空间的任务。
* @mm: 指向该任务正在脱离的内存描述符。
*/
static void mm_release(struct task_struct *tsk, struct mm_struct *mm)
{
/* 释放与该任务相关的、所有由用户探针(uprobe)分配的资源。*/
uprobe_free_utask(tsk);

/*
* 注释:清理掉任何缓存的寄存器状态。
*
* 原理:调用deactivate_mm,通知底层硬件上下文(如FPU)与该mm解绑。
* 这会使与该地址空间关联的硬件状态(如FPU寄存器)失效。
*/
deactivate_mm(tsk, mm);

/*
* 注释:如果我们不是因为coredump而退出,就通知用户空间,
* 因为我们想保留那个值以用于调试目的。
*
* 原理:clear_child_tid是clone系统调用中的一个参数,用于线程退出时的同步。
*/
/* 检查tsk->clear_child_tid指针是否被设置。*/
if (tsk->clear_child_tid) {
/* 检查是否还有其他线程正在使用这个地址空间。*/
if (atomic_read(&mm->mm_users) > 1) {
/*
* 注释:我们不检查错误码 - 如果用户空间没有设置一个
* 合适的指针,算他们倒霉。
*/
/* 安全地向用户空间的地址写入0。*/
put_user(0, tsk->clear_child_tid);
/* 通过futex机制,唤醒任何正在该用户空间地址上等待的线程。*/
do_futex(tsk->clear_child_tid, FUTEX_WAKE,
1, NULL, NULL, 0, 0);
}
/* 清理指针,防止重复操作。*/
tsk->clear_child_tid = NULL;
}

/*
* 注释:所有事情都完成了,最终我们可以唤醒父进程,并将这个mm交还给他。
* 同样,kthread_stop()也使用这个completion来进行同步。
*
* 修正与澄清:上面注释中关于kthread_stop的说法,在现代内核中已不准确。
* 如我们之前所分析,kthread_stop的同步点在exit_notify中。
* 此处的vfork_done是专门用于vfork的。
*/
/* 检查tsk->vfork_done指针是否被设置。*/
if (tsk->vfork_done)
/* 如果是,则调用complete_vfork_done,它会complete(tsk->vfork_done),
* 从而唤醒因为vfork而睡眠的父进程。*/
complete_vfork_done(tsk);
}

void exit_mm_release(struct task_struct *tsk, struct mm_struct *mm)
{
// futex_exit_release(tsk);
mm_release(tsk, mm);
}

ksys_unshare 实现进程上下文的分离

  • ksys_unshare 是一个内核函数,它允许一个正在运行的、活动的进程(由 current 指针表示)“解除共享”或分离其部分执行上下文。这些上下文原本是在进程创建时通过 clone 系统调用与父进程或同组进程共享的。此函数的核心原理是为当前进程创建并切换到新的、独立的资源实例(如命名空间、文件系统属性等),从而实现与原有环境的隔离。
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
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
/*
* 这里的注释解释了 unshare 和 clone 的一个关键区别。
* unshare 允许一个进程“解除共享”最初通过 clone 共享的部分进程上下文。
* kernel_clone() 中使用的 copy_* 函数不能在这里直接使用,因为它们修改的是
* 一个正在被构建的、非活动的 task_struct。而 unshare 修改的是当前正在运行的、
* 活动的 task_struct。
*/
int ksys_unshare(unsigned long unshare_flags)
{
/*
* fs: 指向当前进程的文件系统结构体 (fs_struct) 的指针。
* new_fs: 指向新创建的文件系统结构体的指针,初始为 NULL。
*/
struct fs_struct *fs, *new_fs = NULL;
/*
* new_fd: 指向新创建的文件描述符表结构体 (files_struct) 的指针,初始为 NULL。
*/
struct files_struct *new_fd = NULL;
/*
* new_cred: 指向新创建的凭证结构体 (cred) 的指针,用于用户命名空间,初始为 NULL。
*/
struct cred *new_cred = NULL;
/*
* new_nsproxy: 指向新创建的命名空间代理结构体 (nsproxy) 的指针,初始为 NULL。
*/
struct nsproxy *new_nsproxy = NULL;
/*
* do_sysvsem: 一个标志,指示是否需要处理 System V 信号量。
*/
int do_sysvsem = 0;
/*
* err: 用于存储整个操作过程中的错误码。
*/
int err;

/*
* --- 标志位依赖关系检查与补全 ---
* 内核要求某些 unshare 操作必须依赖于其他操作,以保证状态的一致性。
*/

/*
* 如果要解除共享用户命名空间(CLONE_NEWUSER),那么也必须解除共享线程组(CLONE_THREAD)
* 以及文件系统的根目录和工作目录(CLONE_FS)。
*/
if (unshare_flags & CLONE_NEWUSER)
unshare_flags |= CLONE_THREAD | CLONE_FS;
/*
* 如果要解除共享虚拟内存(CLONE_VM),那么也必须解除共享信号处理器(CLONE_SIGHAND)。
*/
if (unshare_flags & CLONE_VM)
unshare_flags |= CLONE_SIGHAND;
/*
* 如果要解除共享信号处理器(CLONE_SIGHAND),那么也必须解除共享信号队列(CLONE_THREAD)。
*/
if (unshare_flags & CLONE_SIGHAND)
unshare_flags |= CLONE_THREAD;
/*
* 如果要解除共享挂载命名空间(CLONE_NEWNS),那么也必须解除共享文件系统信息(CLONE_FS)。
* 这正是 devtmpfs_setup 调用此函数时的场景。
*/
if (unshare_flags & CLONE_NEWNS)
unshare_flags |= CLONE_FS;

/*
* 调用 check_unshare_flags 检查传入的标志组合是否合法,防止无效或危险的组合。
*/
err = check_unshare_flags(unshare_flags);
if (err)
goto bad_unshare_out; // 如果标志不合法,直接跳转到末尾的出口点。

/*
* --- 准备阶段 ---
* 在这个阶段,内核会根据标志位创建所需的新结构体的副本,但尚未应用到当前进程。
*/

/*
* 如果请求解除共享IPC命名空间(CLONE_NEWIPC)或System V信号量(CLONE_SYSVSEM),
* 就需要进行信号量的分离处理。
*/
if (unshare_flags & (CLONE_NEWIPC|CLONE_SYSVSEM))
do_sysvsem = 1;

/*
* 调用 unshare_fs,如果 unshare_flags 包含 CLONE_FS,它会分配一个新的 fs_struct
* 并将其地址存入 new_fs。否则什么也不做。
*/
err = unshare_fs(unshare_flags, &new_fs);
if (err)
goto bad_unshare_out;

/*
* 调用 unshare_fd,如果 unshare_flags 包含 CLONE_FILES,它会分配一个新的 files_struct
* 并将其地址存入 new_fd。
*/
err = unshare_fd(unshare_flags, &new_fd);
if (err)
goto bad_unshare_cleanup_fs; // 如果失败,需要先清理已创建的 new_fs。

/*
* 调用 unshare_userns,处理用户命名空间的分离,可能会创建新的凭证 new_cred。
*/
err = unshare_userns(unshare_flags, &new_cred);
if (err)
goto bad_unshare_cleanup_fd; // 如果失败,清理 new_fd 和 new_fs。

/*
* 调用 unshare_nsproxy_namespaces,这是处理所有命名空间的核心。
* 它会创建一个新的 nsproxy 结构体,并根据标志位填充新的命名空间(如 IPC, UTS, network, PID, cgroup, time)。
* 对于挂载命名空间(CLONE_NEWNS),它会在这里创建一个新的挂载命名空间实例。
*/
err = unshare_nsproxy_namespaces(unshare_flags, &new_nsproxy,
new_cred, new_fs);
if (err)
goto bad_unshare_cleanup_cred; // 如果失败,清理凭证、fd和fs。

/*
* 如果创建了新的凭证,需要为其设置用户计数值。
*/
if (new_cred) {
err = set_cred_ucounts(new_cred);
if (err)
goto bad_unshare_cleanup_cred;
}

/*
* --- 应用阶段 ---
* 只有当所有准备工作都成功后,才会进入这个阶段,将新创建的结构体应用到当前进程。
* 这是一个临界区,需要通过锁来保护。
*/
if (new_fs || new_fd || do_sysvsem || new_cred || new_nsproxy) {
if (do_sysvsem) {
/*
* CLONE_SYSVSEM 的效果类似于进程退出时对信号量的处理,需要清理旧的信号量状态。
*/
exit_sem(current);
}
if (unshare_flags & CLONE_NEWIPC) {
/* 与信号量类似,切换IPC命名空间前,需要清理旧命名空间中的共享内存段。*/
exit_shm(current);
shm_init_task(current);
}

/*
* 如果创建了新的 nsproxy,则调用 switch_task_namespaces 将当前进程切换到这个新的命名空间集合。
* 这是实现命名空间隔离的关键一步。
*/
if (new_nsproxy)
switch_task_namespaces(current, new_nsproxy);

/*
* 对当前进程的 task_struct 加锁,以防止在修改指针时发生竞争条件(例如被抢占)。
*/
task_lock(current);

/*
* 如果创建了新的 fs_struct (new_fs)...
*/
if (new_fs) {
fs = current->fs; // 备份旧的 fs_struct 指针
spin_lock(&fs->lock); // 锁住旧的 fs_struct
current->fs = new_fs; // 将当前进程的 fs 指针指向新的结构体
if (--fs->users) // 将旧 fs_struct 的用户计数减一
new_fs = NULL; // 如果还有其他用户,则不清空 new_fs 指针,防止后面被错误释放
else
new_fs = fs; // 如果没有其他用户,则将 new_fs 指向旧的 fs,以便后面统一释放
spin_unlock(&fs->lock); // 解锁
}

/*
* 如果创建了新的文件描述符表 (new_fd),则交换当前进程的 files 指针。
*/
if (new_fd)
swap(current->files, new_fd);

task_unlock(current); // 解锁 task_struct

if (new_cred) {
/* 安装新的用户凭证,这是应用新的用户命名空间的最后一步。 */
commit_creds(new_cred);
new_cred = NULL; // 设为 NULL 防止被重复释放
}
}
/* 触发 perf 事件,通知性能监控子系统命名空间可能已更改。 */
perf_event_namespaces(current);

/*
* --- 清理与返回 ---
* 使用 goto 标签实现了一个标准的错误处理和资源释放流程。
* 如果中间任何一步失败,程序会跳转到对应的标签,然后像瀑布一样执行后续所有的清理代码。
*/
bad_unshare_cleanup_cred:
if (new_cred)
put_cred(new_cred); // 释放凭证结构体
bad_unshare_cleanup_fd:
if (new_fd)
put_files_struct(new_fd); // 释放文件描述符表结构体

bad_unshare_cleanup_fs:
if (new_fs)
free_fs_struct(new_fs); // 释放文件系统结构体

bad_unshare_out:
return err; // 返回最终的错误码,0 表示成功
}