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的代码段和数据段。
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_task
的comm
,所以开始阶段,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}