【Linux 驱动开发】Linux 内核启动过程详解

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 函数流中的关键函数)

内核启动流程:

内核启动过程

本文系统梳理了ARM架构下Linux内核的启动流程,分为四个关键阶段:引导加载(U-Boot加载内核镜像和设备树)、自解压(head.S调用解压函数)、汇编级准备(stext函数完成硬件初始化)和C语言初始化(start_kernel完成系统初始化)。重点解析了各阶段核心任务:U-Boot参数传递、内核解压、处理器验证、页表建立、命令行参数解析,直至最终启动init进程。文中还提供了mkimage工具的使用说明和关键函数调用关系图,为理解Linux内核启动机制提供了清晰的框架。通过结合源码与日志,可深入掌握从硬件初始化到用户空间创建的全过程。

按照时间轴全面梳理 Linux 内核(以 ARM 架构为例)的启动流程。整体上可分为四个阶段:引导加载 → 自解压 → 汇编级准备 → C 语言初始化。其中每个阶段都包含关键函数与数据结构,建议结合源代码与串口日志对照理解。

0. 运行前:引导加载阶段(Bootloader)

  1. U-Boot 读取镜像
    • 按照环境变量 bootcmd/bootm 指定的地址,从存储介质加载内核镜像(zImage/uImage/Image)与设备树 *.dtb、可选的 initramfs
    • 如果使用 uImage,其内部是 U-Boot 头(64 字节)+ zImage,头部包含加载地址、入口地址、校验等元数据,可通过 mkimage -l uImage 查看。
  2. 跳转到内核入口
    • 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 函数启动内核。

stextvmlinux 的真正入口函数(由 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 内核启动可以概括为:

  1. Bootloader 负责加载镜像和设备树,跳转到压缩内核入口。
  2. 自解压阶段 解包并准备跳转到真正内核入口。
  3. 汇编准备阶段 完成 CPU/内存最小设置,建立初始页表。
  4. C 语言初始化阶段 逐步初始化内核各子系统,创建内核线程,挂载根文件系统,启动用户态 init

掌握上述流程有助于定位启动失败的具体环节,针对性分析是出现在 Bootloader、解压、汇编阶段还是 C 初始化阶段,从而快速解决启动问题。

以上,欢迎有从事同行业的电子信息工程、互联网通信、嵌入式开发的朋友共同探讨与提问,我可以提供实战演示或模板库。希望内容能够对你产生帮助!

相关推荐
昵称只能一个月修改一次。。。2 小时前
Linux系统编程:网络编程
linux·服务器·网络
人工智能训练2 小时前
Qwen3.5 开源全解析:从 0.8B 到 397B,代际升级 + 全场景选型指南
linux·运维·服务器·人工智能·开源·ai编程
蜕变的小白2 小时前
Linux系统编程-->UDP编程:C/S模型实战详解
linux·运维·网络协议·udp
linuxxx1102 小时前
让openclaw使用系统命令:“rm“, “mkdir“, “touch“, “ls“, “cat“, “echo“
linux·服务器·windows
辰哥单片机设计2 小时前
STM32太阳能光伏板
stm32·单片机·嵌入式硬件
ldj20202 小时前
Linux系统开放端口命令
linux
草莓熊Lotso2 小时前
MySQL 数据库基础入门:从概念到实战
linux·运维·服务器·数据库·c++·人工智能·mysql
芒果披萨2 小时前
linux系统管理基本命令行
linux·运维·服务器
Will_11302 小时前
Linux运维自动化常用的Python库
linux·运维·自动化