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

韩乔落

第4章 进程创建

基于 Linux 6.12.38 源码分析


4.1 进程创建系统调用

4.1.1 系统调用概述

Linux 提供多个进程/线程创建系统调用:

系统调用 描述 内核实现
fork() 创建子进程 kernel_clone()
vfork() 创建共享地址空间的子进程 kernel_clone()
clone() 灵活创建进程/线程 kernel_clone()
clone3() 扩展的 clone (Linux 5.3+) kernel_clone()

4.1.2 系统调用入口

位置: kernel/fork.c

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
// fork 系统调用
#ifdef __ARCH_WANT_SYS_FORK
SYSCALL_DEFINE0(fork)
{
struct kernel_clone_args args = {
.exit_signal = SIGCHLD,
};

return kernel_clone(&args);
}
#endif

// vfork 系统调用
#ifdef __ARCH_WANT_SYS_VFORK
SYSCALL_DEFINE0(vfork)
{
struct kernel_clone_args args = {
.flags = CLONE_VFORK | CLONE_VM,
.exit_signal = SIGCHLD,
};

return kernel_clone(&args);
}
#endif

// clone 系统调用
#ifdef __ARCH_WANT_SYS_CLONE
SYSCALL_DEFINE5(clone, unsigned long, clone_flags, unsigned long, newsp,
int __user *, parent_tidptr,
int __user *, child_tidptr,
unsigned long, tls)
{
struct kernel_clone_args args = {
.flags = (lower_32_bits(clone_flags) & ~CSIGNAL),
.pidfd = parent_tidptr,
.child_tid = child_tidptr,
.parent_tid = parent_tidptr,
.exit_signal = (lower_32_bits(clone_flags) & CSIGNAL),
.stack = newsp,
.tls = tls,
};

return kernel_clone(&args);
}
#endif

// clone3 系统调用 (扩展版本)
SYSCALL_DEFINE2(clone3, struct clone_args __user *, uargs, size_t, size)
{
struct kernel_clone_args kargs;

// 从用户空间复制参数
err = copy_clone_args_from_user(&kargs, uargs, size);
if (err)
return err;

return kernel_clone(&kargs);
}

4.2 clone_flags 标志详解

4.2.1 标志定义

位置: include/uapi/linux/sched.hinclude/linux/sched.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
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 // 如果父进程被 ptrace,子进程也被 ptrace
#define CLONE_VFORK 0x00004000 // vfork - 父进程阻塞直到子进程 exec/exit
#define CLONE_PARENT 0x00008000 // 使用父进程的父进程作为新进程的父进程
#define CLONE_THREAD 0x00010000 // 加入父进程的线程组

// 命名空间标志
#define CLONE_NEWNS 0x00020000 // 新的 mount 命名空间
#define CLONE_NEWCGROUP 0x02000000 // 新的 cgroup 命名空间
#define CLONE_NEWUTS 0x04000000 // 新的 UTS 命名空间 (主机名/域名)
#define CLONE_NEWIPC 0x08000000 // 新的 IPC 命名空间
#define CLONE_NEWUSER 0x10000000 // 新的 user 命名空间
#define CLONE_NEWPID 0x20000000 // 新的 PID 命名空间
#define CLONE_NEWNET 0x40000000 // 新的 network 命名空间

// 其他标志
#define CLONE_SYSVSEM 0x00040000 // 共享 System V SEM_UNDO
#define CLONE_SETTLS 0x00080000 // 创建新的 TLS (线程本地存储)
#define CLONE_PARENT_SETTID 0x00100000 // 在父进程中设置子进程 TID
#define CLONE_CHILD_CLEARTID 0x00200000 // 在子进程中清除 TID
#define CLONE_DETACHED 0x00400000 // (废弃,无效果)
#define CLONE_UNTRACED 0x00800000 // 父进程被 ptrace 时,子进程不被 ptrace
#define CLONE_CHILD_SETTID 0x01000000 // 在子进程中设置 TID
#define CLONE_IO 0x80000000 // 克隆 I/O 上下文

4.2.2 标志组合

创建进程:

1
2
3
4
5
// fork() 等价于:
clone(SIGCHLD, 0, 0, 0, 0);

// vfork() 等价于:
clone(CLONE_VM | CLONE_VFORK | SIGCHLD, 0, 0, 0, 0);

创建线程:

1
2
3
4
5
// pthread_create 等价于:
clone(CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND |
CLONE_THREAD | CLONE_SYSVSEM | CLONE_SETTLS |
CLONE_PARENT_SETTID | CLONE_CHILD_CLEARTID,
child_stack, tls, parent_tidptr, child_tidptr);

4.3 kernel_clone() 核心流程

4.3.1 主函数

位置: kernel/fork.c: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
/*
* This is the entry point for the new process or thread.
* The core of the clone() system call.
*/
pid_t kernel_clone(struct kernel_clone_args *args)
{
struct task_struct *p;
int trace = 0;
pid_t nr;

/*
* Determine whether the child should be traced.
*/
trace = kernel_clone_trace(args);

/*
* 创建新进程的 task_struct 并复制父进程信息
*/
p = copy_process(NULL, trace, NUMA_NO_NODE, args);
if (IS_ERR(p))
return PTR_ERR(p);

/*
* Do this prior waking up the new thread - the thread
* pointer might get invalid after that.
*/
args->pid = get_task_pid(p, PIDTYPE_PID);

nr = pid_vnr(args->pid);

if (args->flags & CLONE_PARENT_SETTID)
put_user(nr, args->parent_tidptr);

if (args->flags & CLONE_VFORK) {
// vfork 特殊处理:父进程等待
p->vfork_done = &vfork;
init_completion(&vfork);
get_task_struct(p);
}

/*
* 唤醒新进程,使其进入就绪队列
*/
wake_up_new_task(p);

/*
* vfork 的父进程等待子进程 exec 或 exit
*/
if (args->flags & CLONE_VFORK) {
if (!wait_for_vfork_done(p, &vfork))
ptrace_event(PTRACE_EVENT_VFORK_DONE, nr);
}

put_pid(args->pid);
return nr;
}

4.3.2 流程图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
kernel_clone()

├─→ copy_process()
│ │
│ ├─→ dup_task_struct() 分配 task_struct
│ ├─→ copy_creds() 复制证书
│ ├─→ copy_thread() 复制线程上下文
│ ├─→ copy_semundo() 复制信号量
│ ├─→ copy_files() 复制文件描述符
│ ├─→ copy_fs() 复制文件系统信息
│ ├─→ copy_sighand() 复制信号处理函数
│ ├─→ copy_signal() 复制信号信息
│ ├─→ copy_mm() 复制地址空间
│ ├─→ copy_namespaces() 复制命名空间
│ ├─→ copy_io() 复制 I/O 上下文
│ ├─→ sched_fork() 调度器初始化
│ └─→ alloc_pid() 分配 PID

├─→ wake_up_new_task() 唤醒新进程

└─→ return child_pid 返回子进程 PID

4.4 copy_process() 详细流程

4.4.1 函数签名

位置: kernel/fork.c

1
2
3
4
5
6
7
8
9
10
11
static struct task_struct *copy_process(
struct pid *pid,
int trace,
int node,
struct kernel_clone_args *args)
{
int retval;
struct task_struct *p;

// ... 实现代码
}

4.4.2 参数验证

1
2
3
4
5
6
7
8
9
10
11
12
13
// 1. 检查 clone_flags 的有效性
if ((clone_flags & (CLONE_NEWNS | CLONE_FS)) ==
(CLONE_NEWNS | CLONE_FS))
return ERR_PTR(-EINVAL);

// 不允许同时共享和不共享信号处理
if ((clone_flags & (CLONE_SIGHAND | CLONE_CLEAR_SIGHAND)) ==
(CLONE_SIGHAND | CLONE_CLEAR_SIGHAND))
return ERR_PTR(-EINVAL);

// 新 PID 命名空间要求 CLONE_THREAD
if ((clone_flags & CLONE_THREAD) && (clone_flags & CLONE_NEWPID))
return ERR_PTR(-EINVAL);

4.4.3 检查资源限制

1
2
3
4
5
6
7
8
9
// 2. 检查进程数限制
retval = -EAGAIN;
if (atomic_read(&p->user->processes) >=
task_rlimit(p, RLIMIT_NPROC)) {
if (capable(CAP_SYS_ADMIN) ||
capable(CAP_SYS_RESOURCE) ||
is_global_init(current))
goto bad_fork_free;
}

4.4.4 分配 task_struct

1
2
3
4
5
6
7
8
9
// 3. 分配并复制 task_struct
p = dup_task_struct(current, node);
if (!p)
goto bad_fork_free;

// 清除一些标志
retval = arch_dup_task_struct(p);
if (retval)
goto bad_fork_free;

4.4.5 复制各种资源

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
// 4. 复制执行域
retval = copy_thread_tls(clone_flags, args->stack, args->stack_size,
p, args->tls);
if (retval)
goto bad_fork_cleanup_io;

// 5. 复制证书
retval = copy_creds(p, clone_flags);
if (retval)
goto bad_fork_cleanup_thread;

// 6. 初始化内存管理
retval = copy_mm(clone_flags, p);
if (retval)
goto bad_fork_cleanup_signal;

// 7. 复制命名空间
retval = copy_namespaces(clone_flags, p);
if (retval)
goto bad_fork_cleanup_mm;

// 8. 复制文件描述符
retval = copy_files(clone_flags, p);
if (retval)
goto bad_fork_cleanup_namespaces;

// 9. 复制文件系统信息
retval = copy_fs(clone_flags, p);
if (retval)
goto bad_fork_cleanup_files;

// 10. 复制信号处理
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;

4.4.6 初始化调度相关

1
2
3
4
5
6
7
// 11. 初始化调度
retval = sched_fork(clone_flags, p);
if (retval)
goto bad_fork_cleanup_signal;

// 12. 初始化性能事件
perf_event_fork(p);

4.4.7 分配 PID

1
2
3
4
5
6
7
// 13. 分配 PID
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;
}

4.4.8 设置进程关系

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
// 14. 设置进程树关系
if (clone_flags & CLONE_THREAD) {
p->group_leader = current->group_leader;
p->tgid = current->tgid;
} else {
p->group_leader = p;
p->tgid = p->pid;
}

// 15. 设置父进程
if (clone_flags & (CLONE_PARENT | CLONE_THREAD)) {
p->real_parent = current->real_parent;
p->parent_exec_id = current->parent_exec_id;
} else {
p->real_parent = current;
p->parent_exec_id = current->self_exec_id;
}

// 16. 添加到进程树
write_lock_irq(&tasklist_lock);
if (clone_flags & CLONE_THREAD)
p->exit_signal = -1;
else
p->exit_signal = args->exit_signal;

list_add_tail_rcu(&p->tasks, &init_task.tasks);
write_unlock_irq(&tasklist_lock);

4.4.9 完成

1
2
3
4
5
6
7
8
9
10
11
    // 17. 设置进程名
set_task_comm(p, current->comm);

// 18. 返回新进程
return p;

// 错误处理路径
bad_fork_cleanup_thread:
// ...
return ERR_PTR(retval);
}

4.5 写时复制 (Copy-on-Write)

4.5.1 基本原理

写时复制是一种延迟分配策略,fork 时不立即复制物理内存,而是共享页表,并将页表设置为只读。当任一进程尝试写入时触发缺页异常,此时才真正分配新的物理页面。

4.5.2 fork 内存布局

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
fork() 之前 - 父进程内存:
┌─────────────────────────────────────────────────────────────┐
│ 用户空间 │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ 代码段 (只读) 物理页 A │ │
│ ├──────────────────────────────────────────────────────┤ │
│ │ 数据段 (可写) 物理页 B │ │
│ ├──────────────────────────────────────────────────────┤ │
│ │ 堆段 (可写) 物理页 C │ │
│ ├──────────────────────────────────────────────────────┤ │
│ │ 栈段 (可写) 物理页 D │ │
│ └──────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘

fork() 之后 - 页表共享 (COW):
┌─────────────────────────────┐ ┌─────────────────────────────┐
│ 父进程 │ │ 子进程 │
│ ┌────────────────────────┐ │ │ ┌────────────────────────┐ │
│ │ 代码段 → 物理页 A (只读)│◄┼───┼──►│ 代码段 → 物理页 A (只读)│ │
│ │ 数据段 → 物理页 B (只读)│ │ │ │ 数据段 → 物理页 B (只读)│ │
│ │ 堆段 → 物理页 C (只读)│ │ │ │ 堆段 → 物理页 C (只读)│ │
│ │ 栈段 → 物理页 D (只读)│ │ │ │ 栈段 → 物理页 D (只读)│ │
│ └────────────────────────┘ │ │ └────────────────────────┘ │
└─────────────────────────────┘ └─────────────────────────────┘

子进程写入后:
┌─────────────────────────────┐ ┌─────────────────────────────┐
│ 父进程 │ │ 子进程 │
│ ┌────────────────────────┐ │ │ ┌────────────────────────┐ │
│ │ 代码段 → 物理页 A │ │ │ │ 代码段 → 物理页 A │ │
│ │ 数据段 → 物理页 B │ │ │ │ 数据段 → 物理页 B' (新) │ │
│ │ 堆段 → 物理页 C │ │ │ │ 堆段 → 物理页 C' (新) │ │
│ │ 栈段 → 物理页 D │ │ │ │ 栈段 → 物理页 D' (新) │ │
│ └────────────────────────┘ │ │ └────────────────────────┘ │
└─────────────────────────────┘ └─────────────────────────────┘

4.5.3 实现代码

位置: mm/memory.c:copy_page_range()

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
int copy_page_range(struct mm_struct *dst_mm, struct mm_struct *src_mm,
struct vm_area_struct *vma)
{
pgd_t *src_pgd, *dst_pgd;
unsigned long next;
unsigned long addr = vma->vm_start;
unsigned long end = vma->vm_end;
struct mmu_notifier_range range;

// 复制页表项,设置为只读 (COW)
src_pgd = pgd_offset(src_mm, addr);
dst_pgd = pgd_offset(dst_mm, addr);
do {
next = pgd_addr_end(addr, end);

if (pgd_none_or_clear_bad(src_pgd))
continue;

if (copy_p4d_range(dst_mm, src_mm, dst_pgd, src_pgd,
vma, addr, next))
return -ENOMEM;
} while (dst_pgd++, src_pgd++, addr = next, addr != end);

return 0;
}

写保护处理: mm/memory.c:wp_page_copy()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
static vm_fault_t wp_page_copy(struct vm_fault *vmf)
{
struct page *old_page = vmf->page;
struct page *new_page;

// 分配新的物理页面
new_page = alloc_page_vma(GFP_HIGHUSER_MOVABLE, vmf->vma, vmf->address);
if (!new_page)
return VM_FAULT_OOM;

// 复制页面内容
copy_user_highpage(new_page, old_page, vmf->address);

// 更新页表,指向新页面
flush_cache_page(vma, vmf->address, pte);
entry = mk_pte(new_page, vma->vm_page_prot);
set_pte_at(vma->vm_mm, vmf->address, vmf->pte, entry);

return VM_FAULT_WRITE;
}

4.6 execve() 系统调用

4.6.1 概述

execve() 用新程序替换当前进程的内存映像,保持 PID 不变。

1
2
3
4
5
6
7
8
// fs/exec.c
SYSCALL_DEFINE3(execve,
const char __user *, filename,
const char __user *const __user *, argv,
const char __user *const __user *, envp)
{
return do_execveat(AT_FDCWD, filename, argv, envp, 0);
}

4.6.2 执行流程

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
do_execveat_common()

├─→ open_exec() 打开可执行文件

├─→ sched_exec() 调度器 exec 处理

├─→ bprm_execve() 准备执行

├─→ prepare_binprm() 检查文件格式

├─→ copy_string_kernel() 复制参数

├─→ load_elf_binary() 加载 ELF 格式
│ 或
│ load_script_binary() 加载脚本格式

├─→ setup_new_exec() 设置新执行环境

├─→ flush_old_exec() 释放旧资源

├─→ setup_arg_pages() 设置参数栈

├─→ setup_binprm() 设置 binprm

├─→ load_binary() 加载二进制文件

└─→ start_thread() 启动新程序

4.7 fork vs vfork vs clone

4.7.1 对比

特性 fork() vfork() clone()
地址空间 复制 (COW) 共享 可选
页表 复制 不复制 可选
父进程阻塞 可选 (CLONE_VFORK)
用途 创建进程 创建后立即 exec 灵活创建进程/线程
性能 较低
POSIX

4.7.2 使用场景

fork():

  • 通用的进程创建
  • 父子进程独立执行

vfork():

  • 创建后立即执行 exec
  • 避免不必要的内存复制
  • 性能敏感场景

clone():

  • 创建线程
  • 细粒度控制资源共享
  • 容器/命名空间操作

4.8 本章小结

本章介绍了 Linux 进程创建机制:

  1. 系统调用:fork、vfork、clone、clone3 四种创建方式
  2. clone_flags:通过标志控制资源共享程度
  3. **kernel_clone()**:统一的创建入口
  4. **copy_process()**:核心复制逻辑,包含多个资源复制步骤
  5. 写时复制:延迟分配策略,提高 fork 性能
  6. **execve()**:替换进程内存映像
  7. 创建方式对比:fork、vfork、clone 各有适用场景

下一章将介绍进程调度机制。

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