要深入分析从开机到 Linux 内核执行的完整过程中的代码,我们将重点分析 引导加载程序(Bootloader),内核启动流程,以及 设备树(Device Tree) 和 内核初始化 过程中涉及的关键代码。以下是从硬件上电到内核执行过程中的详细代码分析。
1. 开机到 Bootloader:硬件初始化
1.1 Boot ROM
当设备通电时,处理器的程序计数器(PC)会跳转到 Boot ROM。Boot ROM 是固定存储在硬件中的引导程序,它的功能非常简洁:
- 初始化最低限度的硬件。
- 从存储介质中加载引导加载程序(Bootloader),例如 U-Boot。
Boot ROM 代码通常不会涉及复杂逻辑,主要是启动和加载的第一步。
2. 引导加载程序(Bootloader)
在引导加载程序阶段,Bootloader 负责初始化硬件,并将操作系统内核加载到内存中。这里我们以 U-Boot 为例进行分析。
2.1 U-Boot 启动代码
U-Boot 是常用的引导加载程序之一,在启动过程中会执行一系列硬件初始化,并加载内核和设备树。
硬件初始化:U-Boot 中的代码
U-Boot 在启动时会调用硬件初始化函数来配置处理器、内存和外设。以下是一个典型的初始化过程:
// U-Boot 初始化硬件
void board_init(void) {
// 初始化时钟
init_clocks();
// 初始化内存控制器
init_memory();
// 初始化存储设备(如SD卡、eMMC等)
init_storage();
// 初始化其他硬件,如网络、串口等
init_network();
}
这段代码实现了:
- init_clocks():初始化时钟系统。
- init_memory():设置内存控制器,初始化 RAM。
- init_storage():配置存储设备(例如 SD 卡、eMMC),确保操作系统内核镜像能够加载。
- init_network():为网络设备初始化配置,若有远程启动需求,可以通过网络加载内核镜像。
2.2 加载内核镜像和设备树
U-Boot 会根据存储介质(如 SD 卡、eMMC)中的配置加载 Linux 内核镜像、设备树文件(DTB 文件)及初始化 RAM 磁盘(initrd)。
// 从存储介质加载内核镜像和设备树
void load_kernel_and_dt(void) {
// 加载内核镜像
if (load_image("kernel.img", KERNEL_LOAD_ADDRESS) != 0) {
printf("Failed to load kernel image\n");
return;
}
// 加载设备树
if (load_image("devicetree.dtb", DEVICE_TREE_LOAD_ADDRESS) != 0) {
printf("Failed to load device tree\n");
return;
}
// 将控制权转交给内核
jump_to_kernel(KERNEL_LOAD_ADDRESS, DEVICE_TREE_LOAD_ADDRESS);
}
在这段代码中:
- load_image():从存储介质中加载内核镜像或设备树文件到内存中。
- jump_to_kernel():一旦内核和设备树加载完毕,跳转到内核的入口点,开始内核执行。
3. Linux 内核启动
当引导加载程序完成对内核和设备树的加载后,内核开始执行。以下是内核启动的代码分析。
3.1 内核入口点:start_kernel()
Linux 内核从 start_kernel()
函数开始执行。start_kernel()
是内核的第一个执行函数,负责初始化大部分内核组件。
// 内核入口点
void __init start_kernel(void) {
// 设置内核命令行参数
setup_cmdline();
// 初始化体系结构(CPU、内存等)
setup_arch();
// 初始化调度器
init_scheduler();
// 初始化内存管理
paging_init();
// 初始化中断系统
init_IRQ();
// 启动第一个用户进程(init进程)
kernel_thread(init, NULL, CLONE_FS | CLONE_SIGHAND | CLONE_VM);
}
分析:
- setup_cmdline():设置内核启动参数,通常由 Bootloader 传递。
- setup_arch():初始化平台架构相关的内容,如 CPU 配置、内存布局等。
- init_scheduler():初始化调度器,准备调度用户进程。
- paging_init():初始化内存分页,将虚拟内存映射到物理内存。
- init_IRQ():初始化中断处理程序,为 CPU 配置中断向量。
- kernel_thread(init, NULL, ...):内核启动后,创建并启动第一个用户进程
init
。
3.2 内核初始化:内存管理、硬件初始化
在内核启动过程中,start_kernel()
会依次初始化系统所需的各项功能,包括:
- 内存管理:设置内存分配、虚拟内存、分页机制等。
- 硬件初始化:通过设备树来初始化外设,加载设备驱动程序。
// 初始化内存管理
void __init paging_init(void) {
// 设置分页机制
setup_paging();
// 进行虚拟内存映射
create_initial_page_tables();
}
// 初始化硬件
void __init setup_arch(void) {
// 配置CPU
setup_cpu();
// 配置内存
setup_memory();
// 配置外设
setup_devices();
}
在这段代码中,paging_init()
负责内存分页的初始化,setup_arch()
则根据设备树信息初始化硬件设备。
4. 设备树(Device Tree)
设备树是内核识别和管理硬件的关键。设备树文件(.dtb)包含了硬件平台的配置信息。内核通过设备树来初始化硬件设备。
4.1 设备树加载
在 Linux 内核中,设备树文件由引导加载程序加载并传递给内核。内核使用该文件来配置硬件设备。
// 内核加载设备树
void __init setup_device_tree(void) {
// 解析设备树
if (of_scan_flat_dt()) {
printk("Failed to scan device tree\n");
}
// 配置硬件设备
of_bus_init();
of_platform_populate();
}
setup_device_tree()
函数中,of_scan_flat_dt()
负责解析设备树文件,of_platform_populate()
负责根据设备树的信息初始化硬件设备。
4.2 驱动程序加载
内核会根据设备树的信息加载和配置相应的设备驱动。例如,针对网络接口、存储设备、显示设备等,内核会加载对应的驱动程序并初始化外设。
// 初始化设备驱动
void __init driver_init(void) {
// 加载设备驱动
driver_register_all();
}
5. 系统服务和用户空间进程的启动
5.1 用户空间进程的启动
内核初始化完成后,会启动第一个用户空间进程(通常是 init
进程),并开始挂载根文件系统。
// 启动第一个用户进程
void kernel_init(void) {
// 挂载根文件系统
mount_root();
// 启动init进程
execve("/sbin/init", NULL, NULL);
}
execve()
会启动 /sbin/init
作为第一个用户空间进程,之后的系统服务会由 init
进程管理。
总结
从开机到 Linux 内核执行的过程涉及多个重要步骤,包括硬件初始化、引导加载程序的工作、内核的加载与初始化、设备树的使用以及用户空间进程的启动。每个阶段都有相应的代码和流程来保证系统的顺利启动和硬件的正确配置。通过对这些代码的分析,可以帮助我们更好地理解操作系统的底层工作原理,并为嵌入式系统开发提供基础知识。