kernel启动流程-start_kernel的执行_7.arch_call_rest_init

目录

  • 1. 前言
  • 2.arch_call_rest_init
    • |- - rcu_scheduler_starting
    • |- -kernel_thread(kernel_init, NULL, CLONE_FS)
    • |- -tsk = find_task_by_pid_ns(pid, &init_pid_ns);
    • |- -set_cpus_allowed_ptr(tsk, smp_processor_id())
    • |- -numa_default_policy
    • |- -kernel_thread(kthreadd, ...)
    • |- -find_task_by_pid_ns(pid, &init_pid_ns)
    • |- -complete(&kthreadd_done)
    • |--schedule_preempt_disabled
    • |- -cpu_startup_entry(CPUHP_ONLINE)
  • 3. kernel_init
    • |- -kernel_init_freeable
      • |- - -wait_for_completion(&kthreadd_done)
      • |- - -gfp_allowed_mask = __GFP_BITS_MASK
      • |- - -set_mems_allowed(node_states[N_MEMORY])
      • |- - -set_cpus_allowed_ptr(current, cpu_all_mask)
      • |- - -smp_prepare_cpus(setup_max_cpus)
      • |- - -workqueue_init
      • |- - -init_mm_internals
      • |- - -do_pre_smp_initcalls()
      • |- - -lockup_detector_init
      • |- - -smp_init
      • |- - -sched_init_smp
      • |- - -padata_init
      • |- - -page_alloc_init_late
      • |- - -page_ext_init()
      • |- - -do_basic_setup()
      • |- - -kunit_run_all_tests()
      • |- - -console_on_rootfs()
      • |- - -integrity_load_keys()
    • |- -async_synchronize_full()
    • |- -kprobe_free_init_mem()
    • |- -ftrace_free_init_mem()
    • |- -free_initmem()
    • |- -mark_readonly()
    • |- -pti_finalize()
    • |- -system_state = SYSTEM_RUNNING
    • |- -numa_default_policy()
    • |- -rcu_end_inkernel_boot()
    • |- -do_sysctl_args()
    • |- -run_init_process(...)
  • 4.总结

1. 前言

本专题文章承接之前《kernel启动流程_head.S的执行》专题文章,我们知道在head.S执行过程中保存了bootloader传递的启动参数、启动模式以及FDT地址等,创建了内核空间的页表,最后为init进程初始化好了堆栈,并跳转到start_kernel执行。
本文重点介绍start_kernel的arch_call_rest_init的主要流程.

kernel版本:5.10
平台:arm64

2.arch_call_rest_init

arch_call_rest_init->rest_init
    |--rcu_scheduler_starting()
    |--pid = kernel_thread(kernel_init, NULL, CLONE_FS)	
    |--tsk = find_task_by_pid_ns(pid, &init_pid_ns);
    |--set_cpus_allowed_ptr(tsk, cpumask_of(smp_processor_id()));
    |--numa_default_policy()
    |--pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES)
    |--kthreadd_task = find_task_by_pid_ns(pid, &init_pid_ns)
    |--complete(&kthreadd_done)
    |--schedule_preempt_disabled();
    |--cpu_startup_entry(CPUHP_ONLINE)

|- - rcu_scheduler_starting

主要设置rcu_scheduler_active = RCU_SCHEDULER_INIT,标记调度器被激活。
从rcu_scheduler_active 变量的注释可以看到:
rcu_scheduler_active最初的值为RCU_SCHEDULER_INACTIVE,在第一个task创建后转换为RCU_SCHEDULER_INIT(本函数),此时RCU做一些硬件初始化(?), 当初始化完毕后会将状态转换为RCU_SCHEDULER_RUNNING

|- -kernel_thread(kernel_init, NULL, CLONE_FS)

kernel_thread会调用do_fork来创建kernel_init进程也就是1号进程,此时kernel_init进程并未调度执行,直到schedule_preempt_disabled被执行。

|- -tsk = find_task_by_pid_ns(pid, &init_pid_ns);

|- -set_cpus_allowed_ptr(tsk, smp_processor_id())

设置此进程只允许运行在当前执行的cpu?

|- -numa_default_policy

Reset policy of current process to default

|- -kernel_thread(kthreadd, …)

创建kthreadd内核线程,它的任务就是管理和调度其他内核线程kernel_thread,进程号为2

|- -find_task_by_pid_ns(pid, &init_pid_ns)

|- -complete(&kthreadd_done)

表示kthreadd已经创建完毕,唤醒阻塞的kernel_init进程,但是要等到schedule()调用时才能执行

|–schedule_preempt_disabled

schedule_preempt_disabled
    |--sched_preempt_enable_no_resched()
    |--schedule()
    \--preempt_disable()

调用schedule()函数切换当前进程,调用该函数1号进程(kernel_init)将被执行

|- -cpu_startup_entry(CPUHP_ONLINE)

调用cpu_idle(),当前cpu执行的0号进程进入idle函数的循环,在该循环中会周期性地检查

3. kernel_init

前面说过kernel_thread会调用do_fork来创建kernel_init进程也就是1号进程,此时kernel_init进程并未调度执行,直到schedule_preempt_disabled被执行。下面来看下kernel_init主要做了哪些工作?

kernel_init
    |--kernel_init_freeable()
    |--async_synchronize_full()
    |--kprobe_free_init_mem()
    |--ftrace_free_init_mem()
    |--free_initmem()
    |--mark_readonly()
    |--pti_finalize()
    |--system_state = SYSTEM_RUNNING
    |--numa_default_policy()
    |--rcu_end_inkernel_boot()
    |--do_sysctl_args()
    \--run_init_process(...)

|- -kernel_init_freeable

kernel_init_freeable
    |--wait_for_completion(&kthreadd_done)
    |--gfp_allowed_mask = __GFP_BITS_MASK
    |--set_mems_allowed(node_states[N_MEMORY])
    |--cad_pid = task_pid(current)
    |--smp_prepare_cpus(setup_max_cpus)
    |--workqueue_init()
    |--init_mm_internals()
    |--do_pre_smp_initcalls()
    |--lockup_detector_init()
    |--smp_init()
    |--sched_init_smp()
    |--padata_init()
    |--page_alloc_init_late()
    |--page_ext_init()
    |--do_basic_setup()
    |--kunit_run_all_tests()
    |--console_on_rootfs()
    \--integrity_load_keys()

|- - -wait_for_completion(&kthreadd_done)

等待2号进程kthreadd创建完毕

|- - -gfp_allowed_mask = __GFP_BITS_MASK

Now the scheduler is fully set up and can do blocking allocations

|- - -set_mems_allowed(node_states[N_MEMORY])

init can allocate pages on any node

|- - -set_cpus_allowed_ptr(current, cpu_all_mask)

init can run on any cpu

|- - -smp_prepare_cpus(setup_max_cpus)

cpu唤醒其它cpu的准备工作,如设置其它cpu的启动地址

参考:https://www.cnblogs.com/linhaostudy/p/9371562.html

|- - -workqueue_init

之前workqueue_init_early主要是遍历所有cpu的worker_pool,对其执行初始化,并链入unbound_pool_hash,创建一系列system workqueue,此处workqueue_init初始化了pool的相关node等, 同时也会遍历unbound_pool_hash哈希表,为每个pool创建worker

|- - -init_mm_internals

init_mm_internals
 |--mm_percpu_wq = alloc_workqueue("mm_percpu_wq", WQ_MEM_RECLAIM, 0)
 |--cpuhp_setup_state_nocalls(CPUHP_MM_VMSTAT_DEAD...)
 |--cpuhp_setup_state_nocalls(CPUHP_AP_ONLINE_DYN,...)
 |--init_cpu_node_state()
 |--start_shepherd_timer()
 |--proc_create_seq("buddyinfo", 0444, NULL, &fragmentation_op)
 |--proc_create_seq("pagetypeinfo", 0400, NULL, &pagetypeinfo_op)
 |--proc_create_seq("vmstat", 0444, NULL, &vmstat_op)
 |--proc_create_seq("zoneinfo", 0444, NULL, &zoneinfo_op)
 

start_shepherd_timer用于创建worker,关于worker的作用如下(没看懂):

vmstat workers are used for folding counter differentials into the zone, per node and global counters at certain time intervals.

|- - -do_pre_smp_initcalls()

遍历执行由early_initcall声明的函数。
从do_pre_smp_initcalls的代码可以看出,遍历执行位于__initcall_start和__initcall0_start之间的初始化函数。通过vmlinux.lds可以看到,这部分位于.initcallearly.init段:

__initcall_start = .; KEEP(*(.initcallearly.init)) __initcall0_start = .;

关于初始化函数包含如下的分类:

/*
 * Early initcalls run before initializing SMP.
 *
 * Only for built-in code, not modules.
 */
#define early_initcall(fn)              __define_initcall(fn, early)

/*
 * A "pure" initcall has no dependencies on anything else, and purely
 * initializes variables that couldn't be statically initialized.
 *
 * This only exists for built-in code, not for modules.
 * Keep main.c:initcall_level_names[] in sync.
 */
#define pure_initcall(fn)               __define_initcall(fn, 0)

#define core_initcall(fn)               __define_initcall(fn, 1)
#define core_initcall_sync(fn)          __define_initcall(fn, 1s)
#define postcore_initcall(fn)           __define_initcall(fn, 2)
#define postcore_initcall_sync(fn)      __define_initcall(fn, 2s)
#define arch_initcall(fn)               __define_initcall(fn, 3)
#define arch_initcall_sync(fn)          __define_initcall(fn, 3s)
#define subsys_initcall(fn)             __define_initcall(fn, 4)
#define subsys_initcall_sync(fn)        __define_initcall(fn, 4s)
#define fs_initcall(fn)                 __define_initcall(fn, 5)
#define fs_initcall_sync(fn)            __define_initcall(fn, 5s)
#define rootfs_initcall(fn)             __define_initcall(fn, rootfs)
#define device_initcall(fn)             __define_initcall(fn, 6)
#define device_initcall_sync(fn)        __define_initcall(fn, 6s)
#define late_initcall(fn)               __define_initcall(fn, 7)
#define late_initcall_sync(fn)          __define_initcall(fn, 7s)

|- - -lockup_detector_init

lockup_detector_init
    |--watchdog_nmi_probe()
    |--lockup_detector_setup()

watchdog死锁检测初始化

  1. watchdog_nmi_probe:检测nmi是否可用
  2. lockup_detector_setup:Create the watchdog thread infrastructure and configure the detector(s)

|- - -smp_init

smp_init
    |--idle_threads_init()
    |--cpuhp_threads_init()
           |--smpboot_register_percpu_thread(&cpuhp_threads))
           |--kthread_unpark(this_cpu_read(cpuhp_state.thread))
    |--bringup_nonboot_cpus(setup_max_cpus)

1.idle_threads_init
为每个非boot cpu都各fork一个idle task,将获得的task_struct记录到per_cpu变量idle_threads中

2.cpuhp_threads_init
为每个core都创建一个"cpuhp/%u"内核线程,结果记录在per_cpu变量cpuhp_state.thread中,然后启动当前cpu的"cpuhp/%u"线程:“cpuhp/0”
线程处理函数为smpboot_thread_fn - percpu hotplug thread loop function

3.bringup_nonboot_cpus
bringup未启动的cpu core

bringup_nonboot_cpus
    |--for_each_present_cpu(cpu)
           if (!cpu_online(cpu))
               cpu_up(cpu, CPUHP_ONLINE)
                   |--_cpu_up(cpu, 0, target)
                         |--cpu_present(cpu)判断cpu是否present
                         |--if (st->state == CPUHP_OFFLINE)
                         |      idle = idle_thread_get(cpu)
                         |--cpuhp_tasks_frozen = tasks_frozen
                         |--cpuhp_set_state(st, target)
                         |--if (st->state > CPUHP_BRINGUP_CPU)
                         |      cpuhp_kick_ap_work(cpu)
                         |--cpuhp_up_callbacks(cpu, st, target)              

关于cpuhp的状态机如下图:
kernel启动流程-start_kernel的执行_7.arch_call_rest_init_第1张图片

参考:http://www.bubuko.com/infodetail-3303003.html
上图左边是cpu up时的状态切换:OFFLINE -> BRINGUP_CPU -> AP_OFFLINE -> AP_ONLINE -> AP_ACTIVE
cpu_up(cpu, CPUHP_ONLINE)第二个参数表示target status,表示需要将第一个参数表示的cpu,唤醒,并达到指定的状态,目前cpu0就是处于CPUHP_ONLINE,这是cpu正常工作时的状态,这个函数会继续调用_cpu_up(cpu, 0, target):
1、检查cpu_present(cpu),正常情况下,为1
2、如果待唤醒的cpu的cpuhp_state.state大于target表示的状态,那么直接返回。数值越大,表示cpu越接近CPUHP_ONLINE,既然是bring up,那么当然是将state变大为目标
3、如果待处理cpu的当前状态是CPUHP_OFFLINE,那么要先获取该cpu的idle task结构,由于该cpu目前还没有上电,自然属于这种
4、调用cpuhp_set_state(st, target),设置待唤醒cpu的目标状态为CPUHP_ONLINE,如果当前状态小于target,将bringup设置为1,表示要执行up操作,返回的是当前状态
5、如果待处理cpu的当前状态大于CPUHP_BRINGUP_CPU,那么会执行cpuhp_kick_ap_work(cpu)。
6、下面的唤醒步骤分两个阶段:
(1)首先从OFFLINE到CPUHP_BRINGUP_CPU, 会调用cpuhp_up_callbacks完成
(2)然后剩下从CPUHP_BRINGUP_CPU到CPUHP_ONLINE则通过AP hotplug thread进行,这个线程就是上面创建的"cpuhp/0", 它的task_struct存放在per_cpu变量cpuhp_state.thread中

4.smp_init小结
smp_init为每个非boot cpu都各fork一个idle task,为每个core创建了"cpuhp/%u"内核线程,同时bringup未启动的cpu core

|- - -sched_init_smp

sched_init_smp
 |--sched_init_numa
 |--sched_init_domains(cpu_active_mask)
 |--sched_init_granularity()
 |--init_sched_rt_class()
 |--init_sched_dl_class()
 |--sched_smp_initialized = true
  1. sched_init_domains

from:https://blog.csdn.net/u013836909/article/details/94206035
遍历cpu_active_mask中的所有CPU,对每个CPU遍历所有的SDTL,相当于每个CPU都有自己的一套SDTL对应的调度域,为每个CPU都初始化一整套SDTL对应的调度域和调度组。遍历cpu_active_mask中的所有CPU的调度域,创建并初始化调度组。
注:内核有一个数据结构struct sched_domain_topology_level来描述CPU的层次关系,简称SDTL,请见[/include/linux/sched/topology.h:186]

|- - -padata_init

创建padata_works

Padata是一种机制,通过这种机制,内核可以将工作外包出去,以在在多个CPU上并行,同时可以选择保留它们的排序。它最初是为IPsec开发的,因为IPsec需要对大量的数据包进行加密和解密,而不需要对这些数据包重新排序。 这是目前padata的序列化作业支持的唯一用户。Padata还支持多线程作业,将作业平均拆分,同时对线程之间进行负载均衡和协调。
参考:Documentation/core-api/padata.rst

|- - -page_alloc_init_late

|- - -page_ext_init()

|- - -do_basic_setup()

do_basic_setup
    |--cpuset_init_smp();
    |--driver_init(); 
    |--init_irq_proc();       
    |--do_ctors(); 
    |--usermodehelper_enable();  
    |--do_initcalls();
           |....
           |--rootfs_initcall(populate_rootfs)
           |....

do_basic_setup最主要的是会执行init段的函数,其中会调用到rootfs_initcall,它会将initrd释放到rootfs的/目录,这里可参考《start_kernel之rootfs挂载及cpio initrd解包》

|- - -kunit_run_all_tests()

|- - -console_on_rootfs()

通过filp_open("/dev/console", O_RDWR, 0)得到 struct file *file,然后又使用init_dup(file)执行了3次,一共得到了3个文件描述符。
init_dup(file)实际执行了如下操作:
设置current->files->fdt->fd[fd]为file,这里fd分别为0,1,2,就是所谓的:标准输入、标准输出、标准错误

|- - -integrity_load_keys()

|- -async_synchronize_full()

等待所有的init函数调用完毕,为释放init段做准备

|- -kprobe_free_init_mem()

|- -ftrace_free_init_mem()

|- -free_initmem()

释放__init_begin~__init_end的区域

|- -mark_readonly()

|- -pti_finalize()

|- -system_state = SYSTEM_RUNNING

|- -numa_default_policy()

|- -rcu_end_inkernel_boot()

|- -do_sysctl_args()

解析command line中sysctl参数,并对齐执行相应的回调process_sysctl_arg

|- -run_init_process(…)

启动init进程

4.总结

rest_init最重要的就是创建kernel_init进程也就是1号进程 和 kthreadd进程也就是2号进程,并执行1号进程和2号进程的处理函数。

1号进程主要负责启动secondary core, 执行init段的初始化函数,这期间会将initrd释放到根文件系统中,并执行其中的init进程,1号进程的启动时机是2号进程创建完毕;

2号进程用于管理调度其它进程

0号进程在启动完1号和2号进程,加入到idle调度类,自身退化为idle进程

你可能感兴趣的:(#,Kernel,Start)