内核启动流程
2026/9/7 22:54:05 网站建设 项目流程

内核启动流程

芯片上电之后会执行固化在芯片0地址处的Boot_ROM代码,这段代码:

  1. 根据BOOT_MODE引脚的电平决定启动方式

    10 : 从内部启动

  2. 根据BOOT_CFG引脚的电平决定从哪个存储介质中读取代码

    BOOT_CFG的引脚共有24个,其中的四个(BOOT_CFG1[7 : 4])决定设备类型,010X:SD; 011X:emmc; ......

  3. 然后读取前4K的uboot.imx放在OCRAM中(包含IVT、boot_data、DCD)

DCD:包含DDR内存的配置信息、PLL时钟初始化的信息

boot_data:包含uboot.bin的长度和目标地址等信息

IVT:包含entry地址(程序入口地址0X87800000)(start.S)

4. 然后Boot_ROM代码会根据DCD中的信息初始化DDR内存,根据boot_data中的信息读取uboot.bin全文放在DDR中,根据IVT跳转到程序入口地址0X87800000去执行start.S


至此,Boot_ROM将CPU的控制权交给uboot,uboot会从start.S开始进行下面的操作:

阶段代码位置主要任务
汇编初始化start.S设置向量表、关闭 MMU/Cache、初始化栈
C 早期初始化board_init_f初始化串口、规划内存布局
重定位relocate_code搬移自身、修正地址、清空 .bss
完整初始化board_init_r初始化驱动、加载环境变量、进入命令行
引导内核bootz/bootm加载内核与 DTB、跳转到 Linux 入口

第一阶段:汇编级初始化(start.S)

这是 U-Boot 的入口,全部由汇编代码完成,主要做三件关键事:

  1. 设置异常向量表:在0x87800000开头放置 ARM 中断向量表,并把 VBAR 寄存器指向这里,为后续中断处理做准备。

  2. 切换到 SVC(管理模式):确保 CPU 运行在最高特权级,并禁用 FIQ 和 IRQ 中断,防止早期阶段被意外打断。

  3. 关闭 MMU 和 Cache:此时 DDR 刚初始化完成,MMU 和 Cache 尚未建立,直接关闭它们是安全的。这一操作也确保后续代码执行在“平坦”物理地址上,避免缓存一致性问题。

  4. 初始化栈指针(SP):为 C 语言环境准备临时栈,通常指向0x87800000上方的一段空间。


第二阶段:C 语言环境初始化(board_init_f)

汇编完成后,跳转到 C 语言入口board_init_f,执行早期初始化

  • 初始化串口(UART),打印第一行启动信息(如U-Boot 2020.04 ...)。

  • 检测 DDR 大小,并规划内存布局,决定将 U-Boot 自身重定位到哪个地址(通常就是当前的0x87800000,或更高的0x8xxxxxxx)。

  • 初始化时钟、GPIO 等基础外设,但此时设备驱动模型尚未完全建立。


第三阶段:重定位(relocate_code)

这是 U-Boot 启动中最核心的一步,虽然我们已经在0x87800000运行,但 U-Boot 可能还会进行一次自我搬移(取决于配置):

  • 将 U-Boot 镜像从当前地址(0x87800000)复制到规划好的“最终运行地址”(可能是0x8FF00000等高端地址)。

  • 修正.rel.dyn段中的重定位表,将所有绝对地址跳转修正到新地址。

  • 清空.bss段(未初始化的全局变量区域)。

  • 跳转到新地址的入口,继续执行。


第四阶段:完整初始化与命令行(board_init_r)

重定位完成后,进入board_init_r,这是 U-Boot 的“完全体”

  • 初始化设备驱动模型(DM),扫描总线上的所有设备(如 eMMC、网络、USB 等)。

  • 加载环境变量(bootcmd),从存储介质(eMMC/SD)或默认配置中读取。

  • 初始化网络协议栈,支持 DHCP/TFTP 等网络引导方式。

  • 进入命令行交互=>提示符),等待用户输入命令;或者自动执行bootcmd中的指令,开始引导 Linux 内核。


最终:引导 Linux 内核

当用户执行bootzbootm命令时,U-Boot 会:

  1. 从指定地址(如0x80008000)加载内核镜像(zImage)。

  2. 从指定地址(如0x83000000)加载设备树(DTB)。

  3. 设置内核启动参数(r0=0, r1=machine_id, r2=dtb_addr)。

  4. 关闭 MMU 和 Cache,跳转到内核入口(0x80008000),将控制权交给 Linux。

此后,U-Boot 的使命彻底完成,Linux 内核开始启动。


当U-Boot执行bootzbootm,跳转到0x80008000这个地址后,Linux内核就正式接管了CPU的控制权。

第一步:内核入口与解压(汇编阶段)

0x80008000这个地址,是U-Boot和Linux内核约定的“碰头地点”。U-Boot在跳转前,会确保内核镜像(zImageuImage)已经被加载到这里。

  • 约定俗成的地址0x80008000是ARM架构下非常常用的内核加载地址。这个值在制作内核镜像时,通过mkimage工具的-e(入口点)参数指定,并写入了镜像头部。U-Boot执行bootm命令时,会解析这个头部,最终跳转到这个地址。

  • 真正的入口:这个地址的代码通常是arch/arm/boot/compressed/head.S。这是一段位置无关的汇编代码,主要负责检查自身是否在正确位置,如果不是则进行自我搬运,然后解压真正的大内核映像(vmlinux),最后跳转到解压后的内核入口(通常叫stext)。

第二步:内核主体初始化(汇编转C,开启MMU)

跳转到解压后的stext(定义在arch/arm/kernel/head.S)后,内核开始执行自身的初始化。这部分任务主要是:

  • 验证硬件信息:检查U-Boot传入的设备树(DTB)或ATAGS是否合法。

  • 创建临时页表:为开启MMU(内存管理单元)做准备。

  • 开启MMU:使能虚拟地址,从此内核运行在虚拟地址空间。

  • 跳转到C世界:最后,它会跳转到__mmap_switched,这个函数会完成数据段和BSS段的初始化,然后执行最后一条汇编指令,跳转到start_kernel(),从此进入纯C语言的世界,并且不再返回

第三步:start_kernel()—— Linux的“大脑”开始运转

start_kernel()(在init/main.c中)是整个Linux内核初始化的核心,它像一个大总管,会按顺序调用数百个初始化函数,搭建起系统的骨架。

其中几个关键函数包括:

  • setup_arch():进行架构相关的特定设置,比如解析U-Boot传递过来的设备树,获取内存信息等。

  • mm_init():初始化内存管理子系统,这是内核最核心的模块之一。

  • sched_init():初始化进程调度器,从此内核有了管理多任务的能力。

  • init_IRQ():初始化中断处理系统。

  • console_init():初始化控制台,在此之前,所有的printk打印信息都只能缓存在内存里,这个函数执行后我们才能在串口上看到启动日志。

第四步:rest_init()—— 第一个“用户”进程诞生

start_kernel()的最后一步是调用rest_init(),它完成了从内核态到用户态的转变。

  • 创建kernel_init线程(PID=1):这个内核线程最终会执行/init程序(或者由init=参数指定的程序),变成所有用户空间进程的祖先——init进程

  • 创建kthreadd线程(PID=2):这是内核的守护进程,专门负责创建和管理其他的内核线程。

  • 进入空闲循环:完成以上创建后,最初的CPU核心会进入cpu_idle()函数,变成系统的空闲进程,在没有其他任务需要执行时运行。

至此,Linux内核的启动核心流程就完成了。init进程会依据配置文件(如/etc/inittab/etc/systemd/system/)启动系统服务,最终呈现一个可操作的控制台或图形界面。

需要注意的是,该流程就是一套完整的SPL->uboot->kernal的启动流程,因为,在NXP i.MX这类芯片中,Boot_ROM内部集成了SPL的功能。它直接通过读取uboot.imx头部的DCD(设备配置数据)来初始化DDR,并把整个U-Boot拽进内存。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询