kernel 启动流程

时间:2022-12-12 19:08:33

之前学习了uboot的启动流程,现在接着学习uboot的启动流程,关于 kernel 的启动流程分析的大佬也是很多的,这里还是通过流程的图的方式进行记录,为了像我一样的新手,直观的了解 kernel 的启动流程。

在 kernel 启动之前已将完成了 uboot 的启动,看到此笔记的小伙伴应该都知道,还不了解的可以看我之前的笔记:UBOOT 启动流程

二、kernel 文件目录介绍

在了解 kernel 启动之前,先了解一下源码的目录,通过下表可以初步了解源码目录的作用,想要了解更细一点的,可以看着两位博友的笔记:

名称 描述
arch 架构相关目录
block 块设备相关目录
crypto 加密相关目录
Documentation 文档相关目录
drivers 驱动相关目录
fs 文件系统相关目录
include 头文件相关目录
init 初始化相关目录
ipc 进程间通信相关目录
kernel 内核相关目录
lib 库相关目录
LICENSES 许可相关目录
mm 内存管理相关目录
net 网络相关目录
samples 例程相关目录
scripts 脚本相关目录
security 安全相关目录
sound 音频处理相关目录
tools 工具相关目录
usr 与 initramfs 相关的目录,用于生成 initramfs
virt 提供虚拟机技术(KVM)
.config Linux 最终使用的配置文件
.gitignore git 工具相关文件
.mailmap 邮件列表
.version 与版本有关
.vmlinux.cmd cmd 文件,用于生成 vmlinux
COPYING 版权声明
CREDITS Linux 贡献者
Kbuild Makefile 会读取此文件
Kconfig 图形化配置界面的配置文件
MAINTAINERS 维护者名单
Makefile Linux 顶层 Makefile
Module.xx、modules.xx 一系列文件,和模块有关
README Linux 描述文件
System.map 符号表
vmlinux 编译出来的、未压缩的 ELF 格式 Linux 文件
vmlinux.o 编译出来的 vmlinux.o 文件

三、镜像文件

编译完成后,会生成 vmlinux、Image,zImage、uImage 文件,这里通过对不它们的区别,便可了解每个文件的作用

  1. vmlinux
    vmlinux 是编译出来的最原始的内核文件,没有经过压缩,所以文件比较大。

  2. Image
    Image 是 Linux 内核镜像文件,仅包含可执行的二进制数据。是使用 objcopy 取消掉 vmlinux 中的一些其他信息,比如符号表等,虽然也没有压缩,但是文件比vmlinux小了很多。

  3. zImage
    zImage 是经过 gzip 压缩后的 Image,一般烧写的都是 zImage 镜像文件

  4. uImage
    uImage 是老版本 uboot 专用的镜像文件,uImag 是在 zImage 前面加了一个长度为 64字节的“头”,这个头信息描述了该镜像文件的类型、加载位置、生成时间、大小等信息。

四、kernel 汇编启动阶段

kernel 启动流程

  1. vmlinux.lds
    vmlinux.lds 是链接脚本,通过分析 kernel 顶层 Makefile 文件可知,镜像文件的打包是从 vmlinux.lds 链接脚本开始的,vmlinux.lds 文件位置在 arch/arm/kernel 目录下,在文件中使用了ENTRY(stext) 指定了入口 为 stext。

  2. stext
    stext 在文件 arch/arm/kernel/head.S 中,主要完成了 kernel 的汇编启动阶段。

  3. safe_svcmode_maskall
    safe_svcmode_maskall 在文件arch/arm/include/asm/assembler.h 中,主要作用,确保cpu处于SVC模式,并且关闭所有终端

  4. __lookup_processor_type
    __lookup_processor_type 在文件 arch/arm/kernel/head-common.S 文件中,主要作用是检查当前系统是否支持此 CPU,如果支持的就获取procinfo信息。

    procinfo 是proc_info_list 类型的结构体 , proc_info_list 在文件arch/arm/include/asm/procinfo.h 中

  5. __lookup_machine_type
    __lookup_machine_type检测是否支持当前单板。

  6. __vet_atags
    __vet_atags 在文件 arch/arm/kernel/head-common.S 中,主要作用是验证 atags 或设备树(dtb)的合法性。

  7. __fixup_smp
    __fixup_smp 在当前文件中,主要作用是处理多核,通过宏CONFIG_SMP_ON_UP 开启。

  8. __create_page_tables
    __create_page_tables 在文件文件 arch/arm/kernel/head.S中,主要作用是创建页表。

  9. __mmap_switched
    __mmap_switched 在文件 arch/arm/kernel/head-common.S 中,主要作用是将函数__mmap_switched的地址保存到 r13 寄存器中,最终会调用start_kernel 函数

  10. __enable_mmu
    __enable_mmu 在文件 arch/arm/kernel/head.S 中,主要作用是通过调用 _turn_mmu_on 来打开 MMU, _turn_mmu_on 最后会执行 r13 里面保存的_mmap_switched 函数。

五、kernel 初始化阶段

kernel 启动流程

start_kernel 函数通过调用众多的子函数来完成 Linux 启动之前的一些初始化工作,由于start_kernel 函数里面调用的子函数太多,而这些子函数又很复杂,因此我们简单的来看一下一些重要的子函数。start_kernel 函数如下所示:

asmlinkage __visible void __init start_kernel(void)
{
	char *command_line;
	char *after_dashes;

	/* 设置任务栈结束魔术数,用于栈溢出检测 */
	set_task_stack_end_magic(&init_task);
	
	/* 跟 SMP 有关(多核处理器),设置处理器 ID。
	 * 有很多资料说 ARM 架构下此函数为空函数,
	 * 是因为那时候 ARM 还没有多核处理器。
	*/
	smp_setup_processor_id();
	
	/* 做一些和 debug 有关的初始化 */
	debug_objects_early_init();

	/* Set up the the initial canary ASAP: */
	boot_init_stack_canary();

	/* cgroup 初始化,cgroup 用于控制 Linux 系统资源*/
	cgroup_init_early();

	/* 关闭当前 CPU 中断 */
	local_irq_disable();
	early_boot_irqs_disabled = true;

        /****** 中断关闭期间做一些重要的操作,然后打开中断 ******/

	/* 跟 CPU 有关的初始化 */
	boot_cpu_init();
	/* 页地址相关的初始化 */
	page_address_init();
	/* 打印 Linux 版本号、编译时间等信息 */
	pr_notice("%s", linux_banner);
	/* 架构相关的初始化,此函数会解析传递进来的
	 * ATAGS 或者设备树(DTB)文件。会根据设备树里面
	 * 的 model 和 compatible 这两个属性值来查找
	 * Linux 是否支持这个单板。此函数也会获取设备树
	 * 中 chosen 节点下的 bootargs 属性值来得到命令
	 * 行参数,也就是 uboot 中的 bootargs 环境变量的
	 * 值,获取到的命令行参数会保存到 command_line 中。
	*/
	setup_arch(&command_line);
#ifdef CONFIG_SS_PROFILING_TIME
//    recode_timestamp_ext(0, "start_kernel+", t1);
    recode_timestamp_init();
    recode_timestamp(__LINE__, "setup_arch-");
#endif
	/* 应该是和内存有关的初始化 */
	mm_init_cpumask(&init_mm);
	/* 好像是存储命令行参数 */
	setup_command_line(command_line);
	/* 如果只是 SMP(多核 CPU)的话,此函数用于获取
	 * CPU 核心数量,CPU 数量保存在变量 nr_cpu_ids 中。
	*/
	setup_nr_cpu_ids();
	/* 在 SMP 系统中有用,设置每个 CPU 的 per-cpu 数据 */
	setup_per_cpu_areas();
	
	boot_cpu_state_init();
	smp_prepare_boot_cpu();	/* arch-specific boot-cpu hooks */

	/* 建立系统内存页区(zone)链表 */
	build_all_zonelists(NULL, NULL);
	/* 处理用于热插拔 CPU 的页 */
	page_alloc_init();

	/* 打印命令行信息 */
	pr_notice("Kernel command line: %s\n", boot_command_line);
	/* 解析命令行中的 console 参数 */
	parse_early_param();
	after_dashes = parse_args("Booting kernel",
				  static_command_line, __start___param,
				  __stop___param - __start___param,
				  -1, -1, NULL, &unknown_bootoption);
	if (!IS_ERR_OR_NULL(after_dashes))
		parse_args("Setting init args", after_dashes, NULL, 0, -1, -1,
			   NULL, set_init_arg);

	jump_label_init();

	/*
	 * These use large bootmem allocations and must precede
	 * kmem_cache_init()
	 */
	 
	/* 设置 log 使用的缓冲区*/
	setup_log_buf(0);
	/* 构建 PID 哈希表,Linux 中每个进程都有一个 ID,
	 * 这个 ID 叫做 PID。通过构建哈希表可以快速搜索进程
	 * 信息结构体。
	*/
	pidhash_init();
	/* 预先初始化 vfs(虚拟文件系统)的目录项和索引节点缓存*/
	vfs_caches_init_early();
	/* 定义内核异常列表 */
	sort_main_extable();
	/* 完成对系统保留中断向量的初始化 */
	trap_init();
	/* 内存管理初始化 */
	mm_init();

	/* 初始化调度器,主要是初始化一些结构体 */
	sched_init();
	/* 关闭优先级抢占 */
	preempt_disable();
	/* 检查中断是否关闭,如果没有的话就关闭中断 */
	if (WARN(!irqs_disabled(),
		 "Interrupts were enabled *very* early, fixing it\n"))
		local_irq_disable();
	/*允许及早创建工作队列和工作项排队/取消。工作项的
	 * 执行取决于 kthread,并在 workqueue_init()之后开始。
	*/
	idr_init_cache();
	/* 初始化 RCU,RCU 全称为 Read Copy Update(读-拷贝修改) */
	rcu_init();

	/* 跟踪调试相关初始化 */
	trace_init();

	context_tracking_init();
	/* 基数树相关数据结构初始化 */
	radix_tree_init();
	/* 初始中断相关初始化,主要是注册 irq_desc 结构体变
	 * 量,因为 Linux 内核使用 irq_desc 来描述一个中断。
	*/
	early_irq_init();
	/* 中断初始化 */
	init_IRQ();
	/* tick 初始化 */
	tick_init();
	rcu_init_nohz();
	/* 初始化定时器 */
	init_timers();
	/* 初始化高精度定时器 */
	hrtimers_init();
	/* 软中断初始化 */
	softirq_init();
	timekeeping_init();
	/* 初始化系统时间 */
	time_init();
	sched_clock_postinit();
	printk_nmi_init();
	perf_event_init();
	profile_init();
	call_function_init();
	WARN(!irqs_disabled(), "Interrupts were enabled early\n");
	early_boot_irqs_disabled = false;
	/* 使能中断 */
	local_irq_enable();

	/* slab 初始化,slab 是 Linux 内存分配器 */
	kmem_cache_init_late();

	/* 初始化控制台,之前 printk 打印的信息都存放
	 * 缓冲区中,并没有打印出来。只有调用此函数
	 * 初始化控制台以后才能在控制台上打印信息。
	*/
	console_init();
	if (panic_later)
		panic("Too many boot %s vars at `%s'", panic_later,
		      panic_param);
	/* 如果定义了宏 CONFIG_LOCKDEP,那么此函数打印一些信息。*/
	lockdep_info();

	/* 锁自测 */
	locking_selftest();

#ifdef CONFIG_BLK_DEV_INITRD
	if (initrd_start && !initrd_below_start_ok &&
	    page_to_pfn(virt_to_page((void *)initrd_start)) < min_low_pfn) {
		pr_crit("initrd overwritten (0x%08lx < 0x%08lx) - disabling it.\n",
		    page_to_pfn(virt_to_page((void *)initrd_start)),
		    min_low_pfn);
		initrd_start = 0;
	}
#endif
	page_ext_init();
	debug_objects_mem_init();
	/* kmemleak 初始化,kmemleak 用于检查内存泄漏 */
	kmemleak_init();
	setup_per_cpu_pageset();
	numa_policy_init();
	if (late_time_init)
		late_time_init();
	sched_clock_init();
	/* 测定 BogoMIPS 值,可以通过 BogoMIPS 来判断 CPU 的性能
	 * BogoMIPS 设置越大,说明 CPU 性能越好。
	*/
	calibrate_delay();
	/* PID 位图初始化 */
	pidmap_init();
	/* 生成 anon_vma slab 缓存 */
	anon_vma_init();
	acpi_early_init();
#ifdef CONFIG_X86
	if (efi_enabled(EFI_RUNTIME_SERVICES))
		efi_enter_virtual_mode();
#endif
#ifdef CONFIG_X86_ESPFIX64
	/* Should be run before the first non-init thread is created */
	init_espfix_bsp();
#endif
	thread_stack_cache_init();
	/* 为对象的每个用于赋予资格(凭证) */
	cred_init();
	/* 初始化一些结构体以使用 fork 函数 */
	fork_init();
	/* 给各种资源管理结构分配缓存 */
	proc_caches_init();
	/* 初始化缓冲缓存 */
	buffer_init();
	/* 初始化密钥 */
	key_init();
	/* 安全相关初始化 */
	security_init();
	dbg_late_init();
	/* 为 VFS 创建缓存 */
	vfs_caches_init();
	/* 初始化信号 */
	signals_init();
	/* 注册并挂载 proc 文件系统 */
	page_writeback_init();
	proc_root_init();
	nsfs_init();
	/* 初始化 cpuset,cpuset 是将 CPU 和内存资源以逻辑性
	 * 和层次性集成的一种机制,是 cgroup 使用的子系统之一
	*/
	cpuset_init();
	/* 初始化 cgroup */
	cgroup_init();
	/* 进程状态初始化 */
	taskstats_init_early();
	delayacct_init();

	/* 检查写缓冲一致性 */
	check_bugs();

	acpi_subsystem_init();
	sfi_init_late();

	if (efi_enabled(EFI_RUNTIME_SERVICES)) {
		efi_late_init();
		efi_free_boot_services();
	}

	ftrace_init();

	/* rest_init 函数 */
	rest_init();
}
  1. rcu_scheduler_starting
    rcu_scheduler_starting 主要作用是启动 RCU 锁调度器

  2. kernel_thread
    函数 kernel_thread 创建 kernel_init 进程,也就是 init 内核进程。init 进程的 PID 为 1。init 进程一开始是内核进程(也就是运行在内核态),后面 init 进程会在根文件系统中查找名为“init”这个程序,这个“init”程序处于用户态,通过运行这个“init”程序,init 进程就会实现从内核态到用户态的转变

  3. kernel_thread
    kernel_thread 创建 kthreadd 内核进程,此内核进程的 PID 为 2。kthreadd
    进程负责所有内核进程的调度和管理。

  4. cpu_startup_entry
    cpu_startup_entry 进入 idle 进程,cpu_startup_entry 会调用 cpu_idle_loop,cpu_idle_loop 是个 while 循环,也就是 idle 进程代码。idle 进程的 PID 为 0,idle 进程叫做空闲进程。

  5. kernel_init_freeable
    kernel_init_freeable 函数用于完成 init 进程的一些其他初始化工作。

  6. do_basic_setup
    do_basic_setup 函数用于完成 Linux 下设备驱动初始化工作!非常重要。do_basic_setup 会调用 driver_init 函数完成 Linux 下驱动模型子系统的初始化。

  7. prepare_namespace
    prepare_namespace 是挂载根文件系统。根文件系统也是由命令行参数指定的,也就是 uboot 的 bootargs 环境变量。比如“root=/dev/mmcblk1p3 rootwait rw”就表示根文件系统在/dev/mmcblk1p3 中,也就是 EMMC 的分区 3 中。

参考链接

kernel目录介绍:https://www.jianshu.com/p/c9053d396fcb
kernel 目录 解析:https://www.cnblogs.com/yuanfang/p/1920895.html