Skip to the content.

Linux kernel boot flow

1. 内核启动的基本流程

boot process

1.1 启动加载程序 (Bootloader)

启动加载程序(如GRUB、LILO、syslinux等)负责将内核映像从存储设备加载到内存中,并准备好内核启动所需的环境。

1.2 内核解压阶段

在内核映像的开头,有一个小的解压缩程序,它负责解压内核的主体部分。

1.3 内核启动(Kernel Startup)

解压后的内核代码会从一个固定的入口点开始执行,这个入口点是平台和架构相关的。对于x86架构,通常是startup_32或startup_64函数。

1.4 start_kernel函数

start_kernel函数位于init/main.c文件中,负责完成大部分内核的初始化工作。

1.5 启动初始进程

init进程是用户空间的第一个进程,负责进一步的系统初始化工作,包括启动系统服务和守护进程。

2. 内核文件加载及解压缩

2.1 为什么是压缩文件

Linux内核映像通常是一个压缩文件,主要有以下原因:

2.2 文件类型vmlinuxz和bzImage

在连接压缩映像文件之前,我们先来了解一下未经压缩的编译文件vmlinux

2.2.1 什么是vmlinux?

vmlinux内核编译过程中生成的一个包含所有内核代码和数据的二进制文件。它是未经压缩和未经过处理的内核映像,通常位于内核源码目录的根目录下,特性如下:

2.2.2 vmlinux的生成过程

编译Linux内核时,vmlinux是在链接阶段生成的。以下是一个简化的生成过程:

链接命令举例:ld -o vmlinux [object files] [linker scripts]

2.2.3 vmlinuxz和bzImage的生成过程

在获得编译文件vmlinux后,通常使用压缩工具做进一步处理。

压缩命令: gzip -c vmlinux > vmlinuz

生成命令:make bzImage

2.2.4 其他压缩格式

vmlinuzbzImagezImageuImage 都是不同的 Linux 内核映像文件格式,它们各自有不同的用途和特性。

2.2.5 Android系统文件

在 Android 系统中,内核的压缩文件格式通常是zImageImage.gz,具体取决于所使用的启动加载程序和设备的要求。

2.3 内核加载过程

kernel loading

2.3.1 内核映像加载到内存中

启动加载程序(Bootloader)负责将压缩的内核映像加载到内存中,并准备好启动内核的环境。

2.3.1.1 启动BootLoader

以BIOS为例:

2.3.1.2 加载内核文件

2.3.2 内核解压

以下以x86系统为例:

2.3.2.1 关键文件和代码路径

2.3.2.2 主要步骤

  1. 启动加载程序跳转到内核入口点:
  1. 解压缩程序的初始化:
ENTRY(startup_32)
// 设置 CPU 状态和内存环境
jmp decompress_kernel // 跳转到解压缩代码
  1. 解压缩代码执行:
ENTRY(decompress_kernel)
// 设置硬件环境
// 调用解压缩函数入口方法
jmp decompress_kernel_method
  1. 调用解压缩函数:
void decompress_kernel(...) {
    // 选择解压算法
    // 调用相应的解压函数
    decompress_method(); // 调用特定的解压算法,如 inflate()
}
  1. 解压缩完成后跳转到内核入口:
jmp *%eax

2.3.2.3 名词解释

  1. 跳转入口点和控制权转移

程序计数器或指令指针是一个特殊的寄存器,用于存储正在执行的指令的内存地址。当处理器执行一条指令时,程序计数器会自动递增到下一条指令的地址,从而控制执行流程。这样就实现了执行流程的转移,从而使得程序执行从一个代码段转移到另一个代码段。

  1. initrd/initramfs文件

加载 vmlinuz(Linux 内核映像)时,通常还会加载 initrd(initial ramdisk)或 initramfs(initial ram filesystem)文件。initrd 和 initramfs 文件的主要作用是在内核启动的早期阶段提供一个临时的根文件系统,帮助内核完成启动过程。

特性如下

加载过程

3. 内核启动(start_kernel)

start_kernel是Linux内核中非常重要的一个函数,它是整个内核初始化的核心函数,负责初始化内核的各个子系统、驱动程序以及其他关键组件,并最终将控制权转移到用户空间。

3.1 start_kernel方法介绍

3.1.1 第一个C函数的位置

start_kernel方法的定义通常位于init/main.c文件中,也是Linux启动过程中执行的第一个C函数。

3.1.2 主要功能

3.2 start_kernel源码解析

asmlinkage __visible void __init start_kernel(void)  
{  
    char *command_line;  
    extern const struct kernel_param __start___param[], __stop___param[];  
  
    /* ... 其他初始化代码 ... */  
  
    /* 设置页表和内存管理 */  
    paging_init();  
    mem_init();  
    kmem_cache_init();  
  
    /* 设备和驱动程序初始化 */  
    driver_init();  
    init_irq_proc();  
    softirq_init();  
    time_init();  
    console_init();  
  
    /* 文件系统初始化 */  
    vfs_caches_init_early();  
    mnt_init();  
    init_rootfs();  
    init_mount_tree();  
  
    /* 初始化进程 */  
    pid_cache_init();  
    proc_caches_init();  
    /* 启动 init 进程 */
    rest_init();  
  
    /* ... 其他初始化代码 ... */  
  
    /* 调用内核参数解析函数 */  
    kernel_param_init(karg_strings, num_args);  
  
    /* ... 其他初始化代码 ... */  
  
    /* 永远不会返回 */  
    cpu_idle();  
}

4. 启动初始进程(init process)

4.1 进程概念介绍

4.1.1 内核进程(Kernel Thread)和用户进程(User Process)

4.1.1.1 内核进程(Kernel Thread)

内核进程是由内核创建和调度的线程,运行在内核态,用于处理内核的各类任务。与用户进程不同,内核进程不直接与用户空间交互,主要用于执行内核内部的工作,如处理中断、管理设备、调度任务等。

注意内核进程是独立的,与0、1、2号进程无关

4.1.1.2 用户进程(User Process)

用户进程是在用户空间中执行的进程,用户通过编写和执行应用程序来创建用户进程。用户进程通过系统调用与内核交互,进行资源分配、文件操作、网络通信等。

4.1.2 0号进程、1号进程、2号进程

4.1.2.1 0号进程(swapper/idle/空闲进程)

0号进程(idle进程)是在系统引导过程中,由内核初始化代码创建的。在x86架构中,这个过程发生在汇编启动代码(通常在arch/x86/kernel/head.S中),该代码会设置基本的CPU和内存环境,然后跳转到C语言的start_kernel函数。

4.1.2.2 1号进程(init进程)和2号进程(kthreadd进程)

4.2 rest_init函数-初始化入口

rest_init函数负责创建初始进程并进行一些进一步的初始化工作。其代码实现如下:

static noinline void __ref rest_init(void)
{
    // 通知RCU(Read-Copy Update)子系统,调度器即将开始。这是确保RCU在调度器开始运行前正确初始化的关键步骤。
    rcu_scheduler_starting();
    // 创建pid=1的1号进程
    pid = kernel_thread(kernel_init, NULL, CLONE_FS);
    /** 处理1号进程相关代码 **/
    
    // 创建pid=2的2号进程
    pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);
    /** 处理2号进程相关代码 **/
    /** 其他初始化代码 **/
}

4.2.1 kernel_thread函数

int kernel_thread(int (*fn)(void *), void *arg, unsigned long flags)
{
    return do_fork(flags | CLONE_VM | CLONE_UNTRACED, (unsigned long)fn,
                   (unsigned long)arg, NULL, NULL, 0);
}

4.2.2 kernel_init函数 - 初始化1号进程(init)

kernel_init负责启动初始用户空间进程(/sbin/init或指定的init进程)。

static int __ref kernel_init(void *unused)
{
    /** 其他初始化代码 **/
    // 启动用户空间进程
    if (ramdisk_execute_command) {
        run_init_process(ramdisk_execute_command);
    } else if (execute_command) {
        run_init_process(execute_command);
    } else {
        run_init_process("/sbin/init");
    }
 
    return 0;
} 

4.3 系统启动

init进程启动后,通过后续工作完成了操作系统的加载和启动。

4.3.1 系统初始化脚本

init进程读取系统的初始化脚本(如/etc/inittab、/etc/init.d/脚本)或systemd的单元文件(unit files),执行系统初始化任务。这包括设置系统环境、挂载文件系统、启动网络服务、启动守护进程等。

4.3.2 启动用户界面

4.3.3 图形界面启动流程(systemd示例)

5. 流程图总结

前面使用了大量文字来说明,这里使用一张流程图来做概要总结:

flow overview