CPU从bootloader跳转出来后会开始启动linux kernel,内核启动完成后会将控制权转交给用户空间。
1. _start (head.S)
加载内核运行是的个数据段寄存器,重新设置中断描述符表; 开启内核争创运行是的协处理器等资源
加载全局指针
失能浮点运算部件
选择某线程运行boot sequence
清除bss段
保存线程号和DTB信息
setup_vm:
设置内存管理的分页机制;
relocate:
重定位虚拟地址(relocate),包括返回地址、stvec的虚拟地址、kernel页表的satp等,随后切换到kernel的页表首地址
parse_dtb:
解析设备树(”setup.c” : parse_dtb)
2. 跳转到start_kernel(init/main.c: start_kernel)
2.1 关中断单线程阶段
setup_arch():根据体系结构进行初始化
trap_init():异常初始化,为每个CPU配置和建立异常向量表
init_IRQ():初始化设备树描述的中断控制器
time_init():初始化计时系统部分。
2.2 开中断单线程阶段
2.2.1 local_irq_enable()
2.2.2 kmem_cache_init_late()
2.2.3 console_init()
控制台初始化,选择VTconsole(鼠标键盘显示器)、SerialConsole(串口模式)、VGAConsole或者FBConsole模式
2.3 开中断多线程阶段
3. 跳转到rest_init
在调用该函数之前主要进行与操作系统核心层相关的操作,包括进程调度、内存管理和中断系统等主要模块的初始化,而该rest_init函数将创建kernel_init进程。
4. 跳转到kernel_init
该函数主要分为两步,一步是内核线程,接管之前初始化的工作,并开始启动多核进入多核阶段,进而调用外部设备的初始化函数;下一步是装载用户态的init进程,变身为一个用户进程,成为所有其他用户态进程的鼻祖。
4.1 kernel_init_freeable():
4.1.1 准备工作
4.1.2 smp_prepare_cpus()
为启动多核做准备
4.1.3 workqueue_init()
工作队列初始化
4.1.4 do_pre_smp_initcalls()
执行__initcall_start和__initcall0_start之间的.initcall*.init()函数,实际上就是执行所有用early_initcall()定义的initcall函数
实际上linux内核中有9个级别的do_initcalls()函数,该过程只执行优先级最高的函数eraly_initcall(),其余的均在do_basic_setup阶段执行。
4.1.5 smp_init()
多核启动阶段
4.1.6 sched_init_smp()
该函数执行完成后,linux将正式进入多处理器并行状态
4.1.7 do_basic_setup()
4.1.7.1 driver_init()
4.1.7.1.1 devtmpfs_init()
该函数调用kthread_run创建线程devtmpfsd,该线程创建/dev目录下的基本设备节点,如/dev/console、/dev/zero、/dev/null等
4.1.7.1.2 device_init()
创建/sys/devices以及下级节点
4.1.7.1.3 buses_init()
4.1.7.1.4 classes_init()
4.1.7.1.5 firmware_init()
4.1.7.1.6 hypervisor_init()
4.1.7.2 中断处理
4.1.7.3 do_initcalls()
该阶段需要执行剩余的8个级别initcall函数,执行的代码量非常大。 该函数采用__define_initcall宏的形式从外部接收参数并处理
参考链接:调用initcall的机制
4.1.7.3.1 pure_initcall()
4.1.7.3.2 core_initcall()
init_hpet_clocksource():注册HPET的ClockSource
4.1.7.3.3 postcore_initcall()
4.1.7.3.4 arch_initcall()
4.1.7.3.5 subsys_initcall()
4.1.7.3.6 rootfs_initcall()
4.1.7.3.7 device_initcall()/module_init()
该函数将调用__initcall6_start()到__initcall7_start()之间的函数。
载入驱动的函数会调用platform_drc_probe()进行设备树信息提取进而调用probe(dev)指向dev的驱动文件中的_probe函数,例如lowrisc开发的sd控制器的驱动就指向lowrisc_sd_probe(),从而执行该函数体里的驱动代码。
如果linux设备驱动程序采用built-in的方式则使用_define_initcall的方式加载驱动,如果是采用Module的方式,则采用device_initcall的方式加载。
驱动加载顺序由makefile确定,顺序会打印在根目录下的System,map中。
参考链接:linux 驱动module_init
4.1.7.3.8 late_initcall()
4.1.8 打开console控制台设备
4.1.8.1 ksys_open(“/dev/console”)
打开控制台设备,linux中一切皆为文件,/dev/console文件就代表控制台设备
4.1.8.2 ksys_dup(0)
此时kernel_init进程获得了该文件的文件描述符,此时调用两次ksys_dup(0)复制两次,则得到三个文件描述符,这三个文件描述符分别是0、1、2,这三个文件描述符就是所谓的:标准输入、标准输出、标准错误。
此后kernel_init所有的子进程都将继承这3个文件描述符,也就是后面所有的进程一生出来,就默认有标准输入、标准输出、标准错误的文件描述符。
4.1.9 prepare_namespace()
挂载根文件系统,此时若出错,可能是bootloader阶段传递的bootargs设置不对,导致传递了错误的参数给kernel
4.2 numa_default_policy()
将1号进程自己的NUMA内存分配策略改成MPOL_DEFAULT
4.3 run_init_process()
装入用户态的init程序,变身为普通进程
4.3.1 getname_kernel()
4.3.2 do_execve()
在根文件系统中寻找init程序,具体路径由bootloader设置的环境变量bootargs提供,一旦init程序被找到,就会启动init进程(该可执行程序的文件名不一定叫init),然后操作系统正运行。
6. init()
init程序需要读取配置文件/etc/inittab,以查看下一步做什么。inittab是一个不可执行的文本文件,它有若干行指令所组成,告诉 init 要进入什么运行级别,以及在哪里可以找到该运行级别的配置文件。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/82464.html