Linux内核Kernel启动过程
在上一篇
计算机启动过程文章中介绍了计算机启动的基本流程,本篇文章主要介绍Linux内核Kernel的启动过程。
一、内核启动的基本流程
1. 启动加载程序 (Bootloader)
启动加载程序(如GRUB、LILO、syslinux等)负责将内核映像从存储设备加载到内存中,并准备好内核启动所需的环境。
- 加载内核映像:启动加载程序将压缩的内核映像(如vmlinuz)从硬盘加载到内存中。内核映像通常是一个gzip或其他格式压缩的二进制文件。
- 加载initrd/initramfs:如果使用initrd(初始RAM盘)或initramfs(初始RAM文件系统),启动加载程序也会将这些文件加载到内存中,以便内核在启动时使用。
2. 内核解压阶段
在内核映像的开头,有一个小的解压缩程序,它负责解压内核的主体部分。
- 解压内核:内核映像被加载到内存后,解压缩程序会运行并将压缩的内核映像解压到适当的内存位置。
- 跳转到解压后的内核:一旦解压完成,控制权会被移交给解压后的内核代码的入口点。
3. 内核启动(Kernel Startup)
解压后的内核代码会从一个固定的入口点开始执行,这个入口点是平台和架构相关的。对于x86架构,通常是startup_32或startup_64函数。
- 架构特定的初始化:根据具体的硬件架构,内核会执行一些必要的初始化步骤,比如设置CPU的运行模式,初始化分页机制,建立基本的内存映射等。
- 初始化内核堆栈:内核设置好自己的堆栈,以便后续的函数调用和操作。
- 调用
start_kernel函数:完成基础的硬件初始化后,内核会调用start_kernel函数,这是内核初始化的核心部分。
4. start_kernel函数
start_kernel函数位于init/main.c文件中,负责完成大部分内核的初始化工作。
- 初始化控制台:设置内核的打印机制,以便后续的输出可以显示出来。
- 初始化内存管理子系统:建立初始的内存管理结构,准备好内存分配机制。
- 检测和初始化硬件设备:内核会检测并初始化系统中的各种硬件设备和驱动程序。
- 启动中断处理机制:设置和启动中断处理机制,使得内核可以响应硬件中断。
- 初始化内核调度器:初始化内核调度器,以便管理进程调度。
- 加载初始进程:内核创建并启动第一个用户空间进程,通常是/sbin/init。
5. 启动初始进程
init进程是用户空间的第一个进程,负责进一步的系统初始化工作,包括启动系统服务和守护进程。
- init进程的初始化:init进程执行系统初始化脚本,设置各种系统参数和启动服务。
- 启动用户空间服务:最终,init进程启动配置的所有用户空间服务和守护进程,从而完成系统的启动过程。
二、内核文件加载及解压缩
1.为什么是压缩文件
Linux内核映像通常是一个压缩文件,主要有以下原因:
- 减少存储空间: 压缩内核映像可以显著减少其在存储设备上的占用空间。这对嵌入式系统、存储资源有限的设备以及需要快速分发和更新内核的环境尤其重要
- 加快加载速度: 压缩文件占用的空间更小,这意味着启动加载程序从磁盘读取文件到内存中的时间会更短。虽然解压缩内核映像需要一些时间,但现代处理器的解压缩速度非常快,通常解压缩的时间比从存储设备读取更多数据的时间要少。这会整体上加快启动过程。
- 提高传输效率: 在网络上传输内核映像时,压缩文件可以显著减少带宽使用量。这对于需要远程更新内核的系统(OTA)非常有利。
- 便于管理和分发: 压缩内核映像更便于在各种介质上分发,比如光盘、U盘等。一个较小的文件更容易管理、备份和分发。
- 标准化处理: 使用压缩内核映像是一种标准做法,启动加载程序(如GRUB)已经能够很好地支持这种格式,能自动识别并处理压缩的内核映像。这使得系统启动过程更简单可靠。
2.文件类型vmlinuxz和bzImage
在连接压缩映像文件之前,我们先来了解一下未经压缩的编译文件vmlinux。
2.1 什么是vmlinux?
vmlinux是内核编译过程中生成的一个包含所有内核代码和数据的二进制文件。它是未经压缩和未经过处理的内核映像,通常位于内核源码目录的根目录下,特性如下:
- 未压缩:vmlinux 是内核的未压缩映像。它包含所有内核代码、内核模块以及相关的数据结构。
- ELF 格式:vmlinux 通常是一个 ELF(Executable and Linkable Format)文件,这是一个标准的可执行文件格式,用于存储可执行文件、目标代码和共享库等。
- 符号信息:vmlinux 文件中包含调试符号和符号表信息,这些信息对内核调试和分析非常重要。
- 没有文件后缀:虽然 vmlinux 通常没有文件后缀,但它是一个标准的 ELF 文件,可以通过文件头信息识别其格式。
2.2 vmlinux的生成过程
编译Linux内核时,vmlinux是在链接阶段生成的。以下是一个简化的生成过程:
- 编译各个源文件:内核的各个源文件(
.c和.S文件)首先被编译为目标文件(.o 文件)。 - 链接目标文件:所有目标文件通过链接器(如
ld)链接在一起,生成一个完整的内核映像,这个映像就是 vmlinux。
链接命令举例:
ld -o vmlinux [object files] [linker scripts]
2.3 vmlinuxz和bzImage的生成过程
在获得编译文件vmlinux后,通常使用压缩工具做进一步处理。
- 压缩内核映像:将 vmlinux 压缩生成 vmlinuz。通常使用 gzip 或其他压缩工具。
压缩命令:
gzip -c vmlinux > vmlinuz
- 生成引导加载程序格式的内核映像:一些系统需要特定格式的内核映像,例如 bzImage(适用于 x86 架构)。
生成命令:
make bzImage
2.4 其他压缩格式
vmlinuz、bzImage、zImage 和 uImage 都是不同的 Linux 内核映像文件格式,它们各自有不同的用途和特性。
- vmlinuz:通用的压缩内核映像名称,主要用于各种 Linux 发行版。通常使用 gzip 压缩。
- bzImage:大内核映像,解决了早期 zImage 的内存限制问题。用于 x86 架构,支持较大的内核映像。
- zImage:较老的内核映像格式,适用于小内核映像,受限于低内存地址空间。
- uImage:U-Boot 使用的内核映像格式,广泛用于嵌入式系统。包含 U-Boot 头部信息,支持多种压缩算法。
2.5 Android系统文件
在 Android 系统中,内核的压缩文件格式通常是zImage或Image.gz,具体取决于所使用的启动加载程序和设备的要求。
- zImage:在一些早期的 Android 设备上,内核映像可能采用 zImage 格式。这种格式的内核映像通常会被启动加载程序直接加载并解压,然后启动内核。
- Image.gz:Image.gz 是指经过 gzip 压缩的内核映像。这种格式的内核映像通常是 Linux 内核编译过程中生成的 vmlinuz 文件,只是在 Android 系统中可能被重新命名为 Image.gz。启动加载程序会加载这个压缩的内核映像,并在加载到内存后解压缩,然后启动内核。
3.内核加载过程
3.1 内核映像加载到内存中
启动加载程序(Bootloader)负责将压缩的内核映像加载到内存中,并准备好启动内核的环境。
- 加载内核和initrd:GRUB 会根据配置文件(通常是 grub.cfg)加载压缩的内核映像和可选的 initrd/initramfs 文件。
- 设置内核参数:GRUB 会设置内核启动参数,这些参数可以通过命令行传递给内核。
- 跳转到内核入口点:GRUB 将控制权转移到内核映像的入口点。对于 x86 架构,这个入口点通常在内核映像的开头。
3.1.1 启动BootLoader
以BIOS为例
- CPU在重置后执行的第一条指令的内存地址
0xfffffff0,它包含一个 jump 指令,这个指令通常指向BIOS入口点。 - BIOS会进行一系列硬件初始化和自检,然后根据设置(例如启动顺序)选择一个启动设备(如硬盘、光盘、USB 等)
- 将控制权转移到启动设备的启动扇区代码。
3.1.2 加载内核文件
- 启动设备的启动扇区代码被执行,通常这段代码非常小,只占用一个扇区(512字节)。
- 启动扇区代码负责完成一些基本的初始化操作,然后跳转到更复杂的引导加载程序,如 GRUB 的核心映像(
core image)。 - 核心映像开始执行,它负责进一步的初始化操作,如加载GRUB的模块和配置文件(grub.cfg)。
- 根据
grub.cfg文件中的配置,GRUB加载压缩的内核映像(vmlinuz)和可选的initrd/initramfs文件。 - 内核映像加载完成后,GRUB 将控制权转移给内核的入口点代码,完成控制权从 BIOS 到内核的转移。
3.2 内核解压
以下以x86系统为例
3.2.1 关键文件和代码路径
- arch/x86/boot/header.S:启动代码的汇编部分,定义了内核入口点。
- arch/x86/boot/compressed/head_64.S 和 arch/x86/boot/compressed/misc.c:解压缩代码。
- arch/x86/kernel/head_64.S 和 arch/x86/kernel/head.c:解压后的内核启动代码。
3.2.2 主要步骤
3.2.2.1 启动加载程序跳转到内核入口点:
- BootLoader根据
grub.cfg文件中的配置加载内核映像(vmlinuz)到内存,并跳转到内核映像的入口点,即内核代码的起始地址。
3.2.2.2 解压缩程序的初始化:
- 内核入口点代码(在 header.S 中)会设置初始的 CPU 状态和内存环境,然后跳转到解压缩代码的入口。
- 32位方法
startup_32,64位方法startup_64。(长模式的32到64转换这里不做讨论,