1. kernel_clone
kernel_clone是内核创建进程/线程的核心函数,如下功能都是通过调用kernel_clone实现的。
- kernel_thread:创建内核进程
- user_mode_thread:创建1号进程,1号进程的回调函数先是内核态的kernel_init,之后通过execve切换到用户进程
- fork和clone系统调用:创建用户进程/线程
kernel_clone主要流程是copy_process函数实现task_struct结构的创建和初始化,而后通过wake_up_new_task将新进程/线程加入运行队列,并唤醒。详细流程见源码注释。
1/// kernel/sched/core.c
2/*
3 * Ok, this is the main fork-routine.
4 *
5 * It copies the process, and if successful kick-starts
6 * it and waits for it to finish using the VM if required.
7 *
8 * args->exit_signal is expected to be checked for sanity by the caller.
9 */
10pid_t kernel_clone(struct kernel_clone_args *args)
11{
12 u64 clone_flags = args->flags;
13 struct completion vfork;
14 struct pid *pid;
15 struct task_struct *p;
16 int trace = 0;
17 pid_t nr;
18
19 /// ... ..
20
21 /// 创建进程的主要流程
22 p = copy_process(NULL, trace, NUMA_NO_NODE, args);
23 add_latent_entropy();
24
25 if (IS_ERR(p))
26 return PTR_ERR(p);
27
28 /*
29 * Do this prior waking up the new thread - the thread pointer
30 * might get invalid after that point, if the thread exits quickly.
31 */
32 trace_sched_process_fork(current, p);
33
34 /// 读取当前struct pid,并增加引用计数,防止被销毁
35 pid = get_task_pid(p, PIDTYPE_PID);
36 /// 获取虚拟的pid,是父进程所在最后一级pid namespace的id
37 nr = pid_vnr(pid);
38
39 if (clone_flags & CLONE_PARENT_SETTID)
40 put_user(nr, args->parent_tid);
41
42 /// 如果是vfork,让父进程阻塞,直到子进程调用exit或者execve
43 if (clone_flags & CLONE_VFORK) {
44 p->vfork_done = &vfork;
45 init_completion(&vfork);
46 get_task_struct(p);
47 }
48
49 if (IS_ENABLED(CONFIG_LRU_GEN) && !(clone_flags & CLONE_VM)) {
50 /* lock the task to synchronize with memcg migration */
51 task_lock(p);
52 lru_gen_add_mm(p->mm);
53 task_unlock(p);
54 }
55
56 /// 唤醒新进程
57 wake_up_new_task(p);
58
59 /* forking complete and child started to run, tell ptracer */
60 if (unlikely(trace))
61 ptrace_event_pid(trace, pid);
62
63 if (clone_flags & CLONE_VFORK) {
64 /// 等待子进程完成,如果是正常等待完成,使用ptrace报告事件
65 if (!wait_for_vfork_done(p, &vfork))
66 ptrace_event_pid(PTRACE_EVENT_VFORK_DONE, pid);
67 }
68
69 put_pid(pid);
70 return nr;
71}
1.1. fork的返回值
fork的返回值是子进程的pid,父进程的pid是调用fork的进程的pid。如果fork失败,则返回一个负值。
从代码中可以看到,进程的pid是通过pid_vnr
获取的,这里获取的是current
,也就父进程所在的最后一级命名空间里分配的id。
在clone_flags
包含CLONE_NEWPID
是会为子进程创建新的命名空间,由于父进程需要知道子进程的id,所以这里取的是父进程的pid namespace内的id。
1/// kernel/pid.c
2pid_t pid_vnr(struct pid *pid)
3{
4 return pid_nr_ns(pid, task_active_pid_ns(current));
5}
6EXPORT_SYMBOL_GPL(pid_vnr);
这里看到父进程正确拿到了子进程的pid,那子进程的fork返回值又是在哪里处理的呢?
这个问题涉及到子进程的第一次运行,需要看copy_thread
函数,这是一个与体系结构体相关的函数,其基本工作是将子进程内核空间内记录的cpu context
进程初始化,以ARM64为例,会将x0设为0,而pc设为ret_form_fork
,新进程第一次运行时,是从ret_from_fork
开始的,返回到用户空间时,将内核栈记录的寄存器恢复到寄存器。子进程的fork函数调用结束,从x0拿到的返回值就是0,
2. copy_process
copy_process主要流程如下:
- 对传入的clone_flags进行一些检查,如果是不合法的组合,直接返回失败。
- 在进行实际的fork之前,将收到的信号强制分发出去。使用delayed收集在fork过程中发送给进程的信号,以便进行延迟处理。在新进程准备好后,将其记录到新进程的
p->signal->shared_pending.signal
,再进行处理。之后清除pending的信号。完成之后,继续检查是否收到信号,如果收到信号,则fork失败,直接退出。 - 基于current创建新的task_struct,并设置一些基本信息。
- 设置进程/线程名
- 共享或复制证书
- 将新创建的进程状态设置为TASK_NEW,设置调度策略和优先级,初始化负载
- 共享或创建新的files_struct和fs_struct
- 共享或创建新的sighand_struct和signal_struct
- 共享或创建新的mm_struct
- 共享或创建新的除user namespace之外的namespace
- 共享或创建新的io_context
- 初始化新进程的内核栈,设置fork返回时要执行的函数和栈
- 如果不是fork_idle,在新进程所属的每一级pid namespace中申请pid
- 共享或创建新的pidfd
- 设置新进程/线程的group_leader、tgid、real_parent
- 将新进程/线程关联到线程、进程、进程组、会话
- 根据情况增加nr_threads和total_forks计数
- 如果clone_flags包含CLONE_PIDFD,将pidfd加入到父进程的文件列表
- 一些收尾工作和OOM参数配置后,返回新创建的task_struct
1+-- copy_process
2| +-- dup_task_struct
3| | +-- alloc_task_struct_node
4| | +-- arch_dup_task_struct
5| | +-- alloc_thread_stack_node
6| | +-- setup_thread_stack
7| | +-- set_task_stack_end_magic
8| +-- rt_mutex_init_task
9| +-- copy_creds
10| +-- rcu_copy_process
11| +-- init_sigpending
12| +-- posix_cputimers_init
13| +-- cgroup_fork
14| +-- set_kthread_struct
15| +-- sched_fork
16| | +-- __sched_fork
17| +-- shm_init_task
18| +-- copy_semundo
19| +-- copy_files
20| | +-- dup_fd
21| +-- copy_fs
22| | +-- copy_fs_struct
23| +-- copy_sighand
24| +-- copy_signal
25| +-- copy_mm
26| | +-- vmacache_flush
27| | +-- mmget
28| | +-- dup_mm
29| +-- copy_namespaces
30| | +-- get_nsproxy
31| | +-- create_new_namespaces
32| | +-- timens_on_fork
33| +-- copy_io
34| +-- copy_thread
35| +-- stackleak_task_init // arch/arm64/kernel/process.c
36| +-- alloc_pid // pid != &init_struct_pid
37| +-- futex_init_task
38| +-- clear_posix_cputimers_work
39| +-- cgroup_can_fork
40| +-- sched_cgroup_fork
41| +-- sched_core_fork
42| | +-- sched_core_clone_cookie
43| +-- copy_seccomp
44| +-- rv_task_fork
45| +-- rseq_fork
46| +-- init_task_pid_links
47| +-- init_task_pid
48| +-- attach_pid // likely(p->pid)
49| +-- fd_install // if (pidfile)
50| +-- sched_post_fork
51| +-- cgroup_post_fork
52| +-- perf_event_fork
53| +-- uprobe_copy_process
54| +-- copy_oom_score_adj