Linux内核启动流程

ARM 平台下的 Linux 内核启动流程大致分为 Bootloader 阶段内核启动阶段(汇编和 C 语言部分),最终进入 用户空间

一、Bootloader 阶段(以 U-Boot 为例)

U-Boot的主要功能如下:

  • 初始化基础硬件(如时钟、电源、串口等)
  • 加载 Linux 内核镜像(通常是 zImage/uImage/Image)
  • 设置内核启动参数(命令行、设备树等)
  • 跳转到内核入口地址执行

Bootloader 启动内核:

c
void (*kernel_entry)(int zero, int arch, void *params);
kernel_entry = (void *)kernel_load_address;
kernel_entry(0, mach_type, atags_or_fdt); // 通常传入的 3 个参数

二、Linux 内核汇编部分启动流程(arch/arm/boot/compressed/head.S)

1. 解压入口(zImage 情况)

内核的 zImage 其实是一个自解压的压缩包,执行时先运行解压代码(arch/arm/boot/compressed/head.S),解压后的内核代码放到指定地址后,跳转到内核真实入口。

asm
// head.S 中最后执行:
bl decompress_kernel
b start_kernel

三、Linux 内核真正的入口(arch/arm/kernel/head.S)

解压后的内核真正入口为 arch/arm/kernel/head.S 中的 _stext,通常位于 head.o 中,内核执行从这里开始。

Linux内核执行前要求:

/*
 * Kernel startup entry point.
 * ---------------------------
 *
 * This is normally called from the decompressor code.  The requirements
 * are: MMU = off, D-cache = off, I-cache = dont care, r0 = 0,
 * r1 = machine nr, r2 = atags or dtb pointer.
 *
...
 */
  • 关闭 MMU。
  • 关闭 D-cache。
  • I-Cache 无所谓。
  • r0=0。
  • r1=machine nr(也就是机器 ID)。
  • r2=atags 或者设备树(dtb)首地址。

2. _stextstart_kernel

_stext -> stext -> start_kernel

asm
ENTRY(stext)
    safe_svcmode_maskall r9  // 确保CPU处于SVC模式,且所有中断被屏蔽
    bl	__lookup_processor_type // 检查当前系统是否支持此 CPU
    bl __create_page_tables  // 创建初始页表
    bl __enable_mmu          // 开启 MMU

__enable_mmu:
    b	__turn_mmu_on   // 

__turn_mmu_on:
    执行 r13 里面保存的__mmap_switched 函数
  • 设置堆栈指针
  • 设置 CPU 模式(SVC 模式)
  • 建立初始页表
  • 开启 MMU
  • 跳转到 start_kernel(位于 init/main.c

四、Linux C 语言初始化流程(init/main.c)

3. start_kernel()

这是 C 语言部分的核心入口,主要完成:

c
asmlinkage __visible void __init start_kernel(void)
{
    // 初始化架构相关代码(setup_arch)
    // 初始化定时器、内存管理、页表
    // 初始化中断
    // 初始化内核线程支持
    // 调用 rest_init()
}

4. rest_init()

该函数进入内核线程模式,创建 init 进程。

c
static noinline void __ref rest_init(void)
{
    kernel_thread(kernel_init, NULL, CLONE_FS);
    kernel_thread(kthreadd, NULL, CLONE_FS);
    ...
    cpu_startup_entry(CPUHP_ONLINE); // 启动 idle 循环
}
  • 创建内核线程 kernel_init
  • 创建内核线程 kthreadd, 负责内核线程的关联
  • 主 CPU 进入 cpu_idle 循环等待

5. kernel_init()

这是内核线程,由 rest_init 创建。它会继续初始化用户空间相关内容。

c
static int __ref kernel_init(void *unused)
{
    kernel_init_freeable();
    ...
    // 尝试执行用户初始化程序,任意一个成功就退出
	if (ramdisk_execute_command) {
		ret = run_init_process(ramdisk_execute_command);
		if (!ret)
			return 0;
		pr_err("Failed to execute %s (error %d)\n",
		       ramdisk_execute_command, ret);
	}

	if (execute_command) {
		ret = run_init_process(execute_command);
		if (!ret)
			return 0;
		panic("Requested init %s failed (error %d).",
		      execute_command, ret);
	}
	if (!try_to_run_init_process("/sbin/init") ||
	    !try_to_run_init_process("/etc/init") ||
	    !try_to_run_init_process("/bin/init") ||
	    !try_to_run_init_process("/bin/sh"))
		return 0;

	panic("No working init found.  Try passing init= option to kernel. "
	      "See Linux Documentation/admin-guide/init.rst for guidance.");
}
  • kernel_init_freeable()中完成设备驱动初始化、创建/dev/console、挂载根文件系统等.kernel_init_freeable执行完后就已经进入用户模式了
  • 尝试加载用户空间第一个进程:/sbin/init , /sbin/init往往是一个链接,在zynq上指向/sbin/init -> /sbin/init.sysvinit,ubuntu上指向/sbin/init -> /lib/systemd/systemd

五、进入用户空间

6. 用户态 init 进程(PID 1)

  • 至此,内核启动完成
  • 系统正式进入 用户空间
  • init 进程负责启动其他服务(如 systemd、init、busybox 等)

其他

驱动初始化的位置

求职笔记 . 面经
C++ STL库 —— unordered_map