Linux 内核启动过程详解
- 内核启动过程
-
- [0. 运行前:引导加载阶段(Bootloader)](#0. 运行前:引导加载阶段(Bootloader))
- [1. Linux内核自解压过程(Compressed Kernel)](#1. Linux内核自解压过程(Compressed Kernel))
- [2. 内核汇编级启动准备阶段(Physical Kernel Entry)](#2. 内核汇编级启动准备阶段(Physical Kernel Entry))
- [3. Linux内核C 语言初始化阶段(start_kernel 及其子流程)](#3. Linux内核C 语言初始化阶段(start_kernel 及其子流程))
-
- [1> `start_kernel` 函数的主要工作](#1>
start_kernel函数的主要工作) - [2> `start_kernel` 函数流中的关键函数](#2>
start_kernel函数流中的关键函数)
- [1> `start_kernel` 函数的主要工作](#1>
内核启动流程:

内核启动过程
本文系统梳理了ARM架构下Linux内核的启动流程,分为四个关键阶段:引导加载(U-Boot加载内核镜像和设备树)、自解压(head.S调用解压函数)、汇编级准备(stext函数完成硬件初始化)和C语言初始化(start_kernel完成系统初始化)。重点解析了各阶段核心任务:U-Boot参数传递、内核解压、处理器验证、页表建立、命令行参数解析,直至最终启动init进程。文中还提供了mkimage工具的使用说明和关键函数调用关系图,为理解Linux内核启动机制提供了清晰的框架。通过结合源码与日志,可深入掌握从硬件初始化到用户空间创建的全过程。
按照时间轴全面梳理 Linux 内核(以 ARM 架构为例)的启动流程。整体上可分为四个阶段:引导加载 → 自解压 → 汇编级准备 → C 语言初始化。其中每个阶段都包含关键函数与数据结构,建议结合源代码与串口日志对照理解。
0. 运行前:引导加载阶段(Bootloader)
- U-Boot 读取镜像
- 按照环境变量
bootcmd/bootm指定的地址,从存储介质加载内核镜像(zImage/uImage/Image)与设备树*.dtb、可选的initramfs。 - 如果使用
uImage,其内部是 U-Boot 头(64 字节)+zImage,头部包含加载地址、入口地址、校验等元数据,可通过mkimage -l uImage查看。
- 按照环境变量
- 跳转到内核入口
do_bootm()根据镜像类型选择对应启动路径,最终跳转到内核镜像头部指定的入口地址。- 入口通常是压缩内核的起始汇编文件:
arch/arm/boot/compressed/head.S。
常用 mkimage 命令:
bash
mkimage -A arm -O linux -T kernel -C none -a 0x40008000 -e 0x20008000 -n "Linux5.4.31 Test" -d zImage uImage
1. Linux内核自解压过程(Compressed Kernel)
uboot 完成系统引导以后,执行环境变量 bootm 中的命令;即将 Linux内核 调入内存中并调用 do_bootm 函数启动内核,跳转至 kernel 的起始位置。如果内核没有被压缩,则直接启动;如果内核被压缩过,则需要进行解压,被压缩过的 kernel 头部有解压程序。压缩过的 kernel 入口第⼀个文件源码位置。
在 /kernel/arch/arm/boot/compressed/head.S , 它将调用 decompress_kernel() 函数进行解压,解压完成后打印出信息"Uncompressing Linux...done,booting the kernel", 解压缩完成后,调用 gunzip() 函数(或unlz4()、或 bunzip2()、或 unlz() )将内核放于指定位置开始启动内核。
2. 内核汇编级启动准备阶段(Physical Kernel Entry)
由内核链接脚本 /kernel/arch/arm/kernel/vmlinux.lds 可知,内核入口函数为 stext(/kernel/arch/arm/kernel/head.S), 内核解压完成后,解压缩代码调用 stext 函数启动内核。
stext 是 vmlinux 的真正入口函数(由 arch/arm/kernel/vmlinux.lds 链接脚本指定)。该阶段主要完成最基础的硬件和内存准备。
ps:内核链接脚本 vmlinux.lds 在内核配置过程中产⽣,由 /kernel/arch/arm/kernel/vmlinux.lds.S 文件⽣成。原因是,内核链接脚本为适应不同平台,有条件编译的需求,故由⼀个汇编文件来完成链接脚本的制作。
(1)关闭 IRQ、FIQ 中断,进入SVC模式。调用 setmode 宏实现;
(2)校验处理器ID,检验内核是否⽀持该处理器;若不⽀持则停止启动内核( __lookup_processor_type 函数)
(3)校验机器码,检验内核是否⽀持该机器;若不⽀持,则停止启动内核(__lookup_machine_type 函数)
(4)检查 uboot 向内核传参ATAGS格式 是否正确,调用 __vet_atars 函数实现;
(5)建立虚拟地址映射页表。此处建立的页表为粗页表,在内核启动前期使用。Linux 对内存管理有更精细的要求,随后会重新建立更精细的页表。调用 __create_page_tables 函数实现。
(6)跳转执行__switch_data函数,其中调用 __mmap_switched 完成最后的准备工作。
1> 复制数据段、清除 `bss` 段,⽬的是构建C语⾔运行环境;
2> 保存处理器ID号、机器码、`uboot` 向内核传参地址;
3> `start_kernel` 跳转至内核初始化阶段。
3. Linux内核C 语言初始化阶段(start_kernel 及其子流程)
此阶段从 start_kernel 函数开始。start_kernel 函数是所有 Linux 平台进入系统内核初始化的入口函数。它的主要工作是完成剩余与硬件平台相关的初始化工作,在进行⼀系列与内核相关的初始化之后,调用第⼀个用户进程 init 并等待其执行,至此整个内核启动完成。
1> start_kernel 函数的主要工作
bash
1)内核架构,通用配置相关初始化
2)内存管理相关初始化
3)进程管理相关初始化
4)进程调度相关初始化
5)⽹络⼦系统管理
6)虚拟文件系统
7)文件系统
2> start_kernel 函数流中的关键函数
bash
1> setup_arch(&command_line)函数内核架构相关的初始化函数,是⾮常重要的⼀个初始化步骤。
包含了处理器相关参数的初始化、内核启动参数(tagged list)的获取和前期处理、内存⼦系统的早期初始化。
command_line实质是uboot向内核传递的命令行启动参数,即uboot中环境变 量bootargs的值。若uboot中bootargs的值为空,command_line = default_command_line,即为内核中的默认命令行参数,其值在.config文件中 配置,对应CONFIG_CMDLINE配置项。
bash
2> setup_command_line、parse_early_param以及parse_args函数
这些函数都是在完成命令行参数的解析、保存
cmdline = console=ttySAC2,115200 root=/dev/mmcblk0p2 rw init=/linuxrc rootfstyp e=ext3;
解析为⼀下四个参数:
console=ttySAC2,115200 //指定控制台的串⼝设备号,及其波特率
root=/dev/mmcblk0p2 rw //指定根文件系统rootfs的路径
init=/linuxrc //指定第⼀个用户进程init的路径
rootfstype=ext3 //指定根文件系统rootfs的类型
bash
3> sched_init 函数
初始化进程调度器,创建运行队列,设置当前任务的空线程
bash
4> rest_init函数
rest_init函数的主要工作如下:
1> 调用kernel_thread函数启动了2个内核线程,分别是:kernel_init和kthreadd。
kernel_init线程中调用do_basic_setup()加载驱动程序,然后调用prepare_namespace函数挂载根文件系统rootfs;最后调用init_post函数,执行根文件系统rootfs下的第⼀个用户进程init。用户进程有4个备选⽅案,若command_line中init的路径错误,则会执行备用⽅案。第⼀备 用:/sbin/init,第⼆备用:/etc/init,第三备用:/bin/init,第四备用:/bin/sh。
2> 调用schedule函数开启内核调度系统;
3> 调用cpu_idle函数,启动空闲进程idle,完成内核启动

bash
/mkimage -l
./mkimage [-x] -A arch -O os -T type -C comp -a addr -e ep -n name -d data_file[:data_file...] image
-A ==> set architecture to 'arch' //设定架构 arm mips
-O ==> set operating system to 'os' //操作系统类型:Linux vxworks
-T ==> set image type to 'type' //镜像类型:kernel ramdisk
-C ==> set compression type 'comp' //是否压缩:gzip2 bzip2 none
-a ==> set load address to 'addr' (hex) //加载地址是什么
-e ==> set entry point to 'ep' (hex) //执行地址是什么
-n ==> set image name to 'name'
-d ==> use image data from 'datafile'
-x ==> set XIP (execute in place)
//使用以下命令可以把zImage转化成uImage
mkimage -A arm -O linux -T kernel -C none -a 0x40008000 -e 0x20008000 -n "Linux5.4.31 Test" -d zImage uImage
内核启动 c 代码阶段:
c
/init/main.c
|
asmlinkage __visible void __init start_kernel(void)
|
setup_arch(&command_line);
|
mdesc = setup_machine_fdt(__atags_pointer);
|
mdesc = of_flat_dt_match_machine(mdesc_best, arch_get_next_mach);
early_init_dt_scan_nodes();
unflatten_device_tree();
|
//解析设备树
__unflatten_device_tree(initial_boot_params, NULL, &of_root,early_init_dt_alloc_memory_arch, false);
|
unflatten_dt_nodes(blob, mem, dad, mynodes);
|
if (!populate_node(blob, offset, &mem, nps[depth], &nps[depth+1], dryrun))
|
struct device_node *np =unflatten_dt_alloc(mem, sizeof(struct device_node) + allocl,
__alignof__(struct device_node));
arch_call_rest_init();
|
rest_init();
|
pid = kernel_thread(kernel_init, NULL, CLONE_FS);
|
1,kernel_init_freeable();
|
do_basic_setup(); //加载内核中的驱动
prepare_namespace(); //挂载根文件系统
|
mount_root();
|
if (ROOT_DEV == Root_NFS) {
if (mount_nfs_root())
return;
printk(KERN_ERR "VFS: Unable to mount root fs via NFS, trying floppy.\n");
ROOT_DEV = Root_FD0;
}
2,执行祖先进程
if (execute_command) { // bootargs init= /linuxrc
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.");
Linux 内核启动可以概括为:
Bootloader负责加载镜像和设备树,跳转到压缩内核入口。- 自解压阶段 解包并准备跳转到真正内核入口。
- 汇编准备阶段 完成 CPU/内存最小设置,建立初始页表。
- C 语言初始化阶段 逐步初始化内核各子系统,创建内核线程,挂载根文件系统,启动用户态
init。
掌握上述流程有助于定位启动失败的具体环节,针对性分析是出现在 Bootloader、解压、汇编阶段还是 C 初始化阶段,从而快速解决启动问题。
以上,欢迎有从事同行业的电子信息工程、互联网通信、嵌入式开发的朋友共同探讨与提问,我可以提供实战演示或模板库。希望内容能够对你产生帮助!