1. 总览

这里以ARM64为例。

在内核的起始阶段,还没有进程和线程的概念,在开启MMU之后,__primary_switched的第一步就是将init_task的地址写到sp_el0,这个时候就可以用get_current()或者curent获取到0号线程的task_struct了。在0号线程的上下文,完成调度器的相关初始化之后,创建1号和2号线程,然后开启调度器,init_task自此进入idle状态。

这里看几个概念:

  • 0号线程:对应init_task结构体,名字为swapper,在多处理器中,名称为swapper/<id>,负责内核的一些基础的初始化工作。
  • 1号线程:由0号线程创建,最初的函数为kernel_init,运行在内核态,kernel_init完成一些初始化工作后,通过kernel_execve进入用户态。具体的名字由用户态程序决定。
  • 2号线程:由0号线程创建,回调函数为kthreadd,名字也是kthreadd,所有内核线程的创建都通过2号线程完成。也就是说,所有内核线程的父线程,都是kthreadd。

在kernel_init中,smp_init会为每个处理器核心创建一个的swapper线程,其task_struct结构体,并将其绑定到对应的处理器上。 在kernel_init调用的一些初始化函数中,有些会通过kthread或kworker的接口来创建内核线程,这些都内核线程的创建都由kthreadd接管。 在kernel_init最后,通过kernel_execve进入用户态,用户态的init程序,如busybox或systemd等,再通过fork或者clone等系统调用创建新的进程或者线程。

关于0号线程,在init_task中,设置的名字为swapper,在sched_init时,才会通过init_idle将其设置为swapper/<id>,而这个id时bootcpu的id,大多数情况下,bootcpu的id是0,图中展示的就是这种情况。

2. 汇编阶段的初始化

使能MMU后,__primary_switched的第一步就是初始化init_task。

1/// arch/arm64/kernel/head.S
2SYM_FUNC_START_LOCAL(__primary_switched)
3    adr_l	x4, init_task
4    init_cpu_task x4, x5, x6

在ARM64中,内核使用sp_el0来记录当前的task_struct。init_cpu_task负责将init_task的地址写入到sp_el0中,设置sp和fp,这个sp是sp_el1或者sp_el2,由内核运行的异常等级决定。

 1/// arch/arm64/kernel/head.S
 2    /*
 3     * Initialize CPU registers with task-specific and cpu-specific context.
 4     *
 5     * Create a final frame record at task_pt_regs(current)->stackframe, so
 6     * that the unwinder can identify the final frame record of any task by
 7     * its location in the task stack. We reserve the entire pt_regs space
 8     * for consistency with user tasks and kthreads.
 9     */
10    .macro  init_cpu_task tsk, tmp1, tmp2
11    msr sp_el0, \tsk                    /// 将init_task的地址写入sp_el0,内核运行于EL2或EL1,内核中会使用sp_el0来作为current
12
13    ldr \tmp1, [\tsk, #TSK_STACK]       /// 获取init_task的栈地址,offsetof(struct task_struct, stack)
14    add sp, \tmp1, #THREAD_SIZE         /// 栈是由高地址向下生长的,所以SP_ELx要加上THREAD_SIZE,init_task的栈是静态分配的,它指向init_stack这是一个数组。
15    sub sp, sp, #PT_REGS_SIZE           /// 为struct pt_regs留出空间
16
17    stp xzr, xzr, [sp, #S_STACKFRAME]   /// 将struct pt_regs的u64 stackframe[2]清零
18    add x29, sp, #S_STACKFRAME          /// x29(FP)指向栈中pt_regs的stackframe
19
20    scs_load \tsk                           /// 此处为空操作,详细信息可以参考arch/Kconfig中的SHADOW_CALL_STACK
21
22    adr_l   \tmp1, __per_cpu_offset         /// 读取__per_cpu_offset[NR_CPUS]数组基地址
23    ldr w\tmp2, [\tsk, #TSK_TI_CPU]     /// offsetof(struct task_struct, thread_info.cpu)
24    ldr \tmp1, [\tmp1, \tmp2, lsl #3]   /// tmp1 = __per_cpu_offset[init_task.cpu << 3],通常来说,bootcpu为0
25    set_this_cpu_offset \tmp1               /// 将当前cpu的per_cpu变量的offset值写入TPIDR_ELx
26    .endm

3. init_task结构体

内核规定,所有的线程必须由已存在的线程创建出来,也就是所有的task_struct都是在已有的task_struct基础上复制出来的。

内核的初始阶段,内存分配器还没有初始化,无法实现task_struct的动态创建,init_task结构体是在代码中静态声明的。下面展示的是静态声明时,init_task的和其他结构体的关系。

从这里可以看出来,init_mm指定代码段和数据段指向的就是vmlinux的代码段和数据段。

init_task

4. bootcpu 0号线程的工作

4.1. start_kernel

汇编结束后,就会进入著名的start_kernel。 这里主要实现调度器和定时器的初始化,以及一些相关变量和kmem_cache的初始化。

 1/// init/main.c
 2    /// ... ...
 3    local_irq_disable();        /// 禁用当前cpu(bootcpu)的中断
 4    early_boot_irqs_disabled = true;
 5    /// ... ...
 6
 7    /*
 8     * Set up the scheduler prior starting any interrupts (such as the
 9     * timer interrupt). Full topology setup happens at smp_init()
10     * time - but meanwhile we still have a functioning scheduler.
11     */
12    sched_init();               /// 调度器初始化
13    /// ... ...
14    time_init();                /// 定时器初始化,注册clockevent等
15    /// ... ...
16    early_boot_irqs_disabled = false;
17    local_irq_enable();         /// 使能当前cpu(bootcpu)的中断
18    /// ... ...
19    sched_clock_init();         /// 与CONFIG_HAVE_UNSTABLE_SCHED_CLOCK和CONFIG_GENERIC_SCHED_CLOCK相关
20    calibrate_delay();          /// 计算lpj
21    pid_idr_init();             /// 初始化pid_max和init_pid_ns等
22    /// ... ...
23    thread_stack_cache_init();  /// 创建thread_stack_cache kmem_cache
24    cred_init();                /// 创建cred_jar kmem_cache
25    fork_init();                /// task_struct_cachep、rlimit和一些其他初始化
26    proc_caches_init();         /// 一些kmem_cache的初始化
27    /// ... ...
28    /* Do the rest non-__init'ed, we're now alive */
29    arch_call_rest_init();      /// 调用rest_init
30
31    prevent_tail_call_optimization();   /// 实际为mb(),下边是内核给出的注释
32/*
33 * This is needed in functions which generate the stack canary, see
34 * arch/x86/kernel/smpboot.c::start_secondary() for an example.
35 */

4.2. 其他cpu的0号线程创建

在bootcpu使用的静态声明的init_task结构体中,comm为swapper,在shced_init时,调用init_idle将其设置为swapper/<id>。 在1号线程开始运行后,smp_init调用idle_threads_init创建非bootcpu的0号线程,并将其设置为swapper/<id>。 percpu变量idle_threads记录每个cpu的0号线程对应的task_struct。

4.3. 创建1号和2号线程

0号线程的最后,会创建1号和2号线程,开启完整的调度。

这里注意一点,1号和2号线程创建之初,直接复制了init_taskcomm,所以开始阶段,1号和2号线程的comm都是swapper/X

在1号进程通过kernel_exec执行用户程序后,进程名改为对应的用户程序名。2号线程则是进入线程回调函数kthreadd后,通过set_task_comm(tsk, "kthreadd")设置了comm

 1/// init/main.c
 2static __initdata DECLARE_COMPLETION(kthreadd_done);
 3
 4noinline void __ref rest_init(void)
 5{
 6    struct task_struct *tsk;
 7    int pid;
 8
 9    rcu_scheduler_starting();       /// 启动RCU(Read-Copy-Update)机制
10    /*
11     * We need to spawn init first so that it obtains pid 1, however
12     * the init task will end up wanting to create kthreads, which, if
13     * we schedule it before we create kthreadd, will OOPS.
14     */
15    pid = user_mode_thread(kernel_init, NULL, CLONE_FS);
16    /*
17     * Pin init on the boot CPU. Task migration is not properly working
18     * until sched_init_smp() has been run. It will set the allowed
19     * CPUs for init to the non isolated CPUs.
20     */
21    rcu_read_lock();
22    tsk = find_task_by_pid_ns(pid, &init_pid_ns);
23    tsk->flags |= PF_NO_SETAFFINITY;    /// 不允许用户程序操作cpus_mask
24    set_cpus_allowed_ptr(tsk, cpumask_of(smp_processor_id()));
25    rcu_read_unlock();
26
27    numa_default_policy();
28    pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);
29    rcu_read_lock();
30    kthreadd_task = find_task_by_pid_ns(pid, &init_pid_ns);
31    rcu_read_unlock();
32
33    /*
34     * Enable might_sleep() and smp_processor_id() checks.
35     * They cannot be enabled earlier because with CONFIG_PREEMPTION=y
36     * kernel_thread() would trigger might_sleep() splats. With
37     * CONFIG_PREEMPT_VOLUNTARY=y the init task might have scheduled
38     * already, but it's stuck on the kthreadd_done completion.
39     */
40    system_state = SYSTEM_SCHEDULING;
41
42    /// kernel_init可能会创建内核线程,kernel_init最开始会等待kthreadd线程创建完成
43    complete(&kthreadd_done);
44
45    /*
46     * The boot idle thread must execute schedule()
47     * at least once to get things moving:
48     */
49    /// bootcpu的idle程序需要调用schedule来使能调度,这样1号和2号线程才可以运行。
50    schedule_preempt_disabled();
51    /* Call into cpu_idle with preempt disabled */
52    cpu_startup_entry(CPUHP_ONLINE);
53}

4.4. schedule_preempt_disabled

init_task的thread_info的preempt_count初始化为INIT_PREEMPT_COUNT。

1/// include/linux/preempt.h
2/*
3 * Disable preemption until the scheduler is running -- use an unconditional
4 * value so that it also works on !PREEMPT_COUNT kernels.
5 *
6 * Reset by start_kernel()->sched_init()->init_idle()->init_idle_preempt_count().
7 */
8#define INIT_PREEMPT_COUNT	PREEMPT_OFFSET

这里在关闭抢占的情况下打开调度。实际上,idle进程一直都是禁止抢占的。

 1/// kernel/sched/core.c
 2/*
 3 * schedule_preempt_disabled - called with preemption disabled
 4 *
 5 * Returns with preemption disabled. Note: preempt_count must be 1
 6 */
 7void __sched schedule_preempt_disabled(void)
 8{
 9    sched_preempt_enable_no_resched();  /// 打开抢占,但是不进行调度
10    schedule();                         /// 主动调度,使1号和2号线程可以运行
11    /// 此时已经切换到其他进程,等到没有其他进程需要运行时,会再次回到这里
12    preempt_disable();                  /// 重新关闭抢占,swapper是最后被选择的进程,不会有其他进程抢占
13}

4.5. cpu_startup_entry

1/// kernel/sched/core.c
2void cpu_startup_entry(enum cpuhp_state state)
3{
4    arch_cpu_idle_prepare();    /// 默认为空
5    cpuhp_online_idle(state);   /// cpu hotplug相关
6    while (1)
7        do_idle();
8}

4.6. do_idle

do_idle的主要工作就是检查swapper是否需要重新调度,如果不需要,则一直循环,如果处理器支持,进入低功耗状态,等待处理器被唤醒。否则通过schedule_idle调度其他进程运行。

 1/// kernel/sched/idle.c
 2/*
 3 * Generic idle loop implementation
 4 *
 5 * Called with polling cleared.
 6 */
 7static void do_idle(void)
 8{
 9    int cpu = smp_processor_id();
10
11    /*
12     * Check if we need to update blocked load
13     */
14    nohz_run_idle_balance(cpu);
15
16    /*
17     * If the arch has a polling bit, we maintain an invariant:
18     *
19     * Our polling bit is clear if we're not scheduled (i.e. if rq->curr !=
20     * rq->idle). This means that, if rq->idle has the polling bit set,
21     * then setting need_resched is guaranteed to cause the CPU to
22     * reschedule.
23     */
24
25    __current_set_polling();
26    tick_nohz_idle_enter();
27
28    /// 如果不需要重新调度,一直循环,如果处理器支持,进入低功耗状态,等待处理器被唤醒
29    while (!need_resched()) {
30        rmb();
31
32        local_irq_disable();        /// 在cpu_idle_poll或cpuidle_idle_call会重新使能中断
33
34        if (cpu_is_offline(cpu)) {
35            tick_nohz_idle_stop_tick();
36            cpuhp_report_idle_dead();
37            arch_cpu_idle_dead();
38        }
39
40        arch_cpu_idle_enter();
41        rcu_nocb_flush_deferred_wakeup();
42
43        /*
44         * In poll mode we reenable interrupts and spin. Also if we
45         * detected in the wakeup from idle path that the tick
46         * broadcast device expired for us, we don't want to go deep
47         * idle as we know that the IPI is going to arrive right away.
48         */
49        if (cpu_idle_force_poll || tick_check_broadcast_expired()) {
50            tick_nohz_idle_restart_tick();
51            cpu_idle_poll();
52        } else {
53            cpuidle_idle_call();
54        }
55        arch_cpu_idle_exit();
56    }
57
58    /// 如果需要重新调度,则运行其他进程
59
60    /*
61     * Since we fell out of the loop above, we know TIF_NEED_RESCHED must
62     * be set, propagate it into PREEMPT_NEED_RESCHED.
63     *
64     * This is required because for polling idle loops we will not have had
65     * an IPI to fold the state for us.
66     */
67    preempt_set_need_resched();
68    tick_nohz_idle_exit();
69    __current_clr_polling();
70
71    /*
72     * We promise to call sched_ttwu_pending() and reschedule if
73     * need_resched() is set while polling is set. That means that clearing
74     * polling needs to be visible before doing these things.
75     */
76    smp_mb__after_atomic();
77
78    /*
79     * RCU relies on this call to be done outside of an RCU read-side
80     * critical section.
81     */
82    flush_smp_call_function_queue();
83    schedule_idle();
84
85    /// 重新进入idll进程时,检查是否有kernel livepatch
86    if (unlikely(klp_patch_pending(current)))
87        klp_update_patch_state(current);
88}