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主要流程如下:

  1. 对传入的clone_flags进行一些检查,如果是不合法的组合,直接返回失败。
  2. 在进行实际的fork之前,将收到的信号强制分发出去。使用delayed收集在fork过程中发送给进程的信号,以便进行延迟处理。在新进程准备好后,将其记录到新进程的p->signal->shared_pending.signal,再进行处理。之后清除pending的信号。完成之后,继续检查是否收到信号,如果收到信号,则fork失败,直接退出。
  3. 基于current创建新的task_struct,并设置一些基本信息。
  4. 设置进程/线程名
  5. 共享或复制证书
  6. 将新创建的进程状态设置为TASK_NEW,设置调度策略和优先级,初始化负载
  7. 共享或创建新的files_struct和fs_struct
  8. 共享或创建新的sighand_struct和signal_struct
  9. 共享或创建新的mm_struct
  10. 共享或创建新的除user namespace之外的namespace
  11. 共享或创建新的io_context
  12. 初始化新进程的内核栈,设置fork返回时要执行的函数和栈
  13. 如果不是fork_idle,在新进程所属的每一级pid namespace中申请pid
  14. 共享或创建新的pidfd
  15. 设置新进程/线程的group_leader、tgid、real_parent
  16. 将新进程/线程关联到线程、进程、进程组、会话
  17. 根据情况增加nr_threads和total_forks计数
  18. 如果clone_flags包含CLONE_PIDFD,将pidfd加入到父进程的文件列表
  19. 一些收尾工作和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