内核启动流程芯片上电之后会执行固化在芯片0地址处的Boot_ROM代码这段代码根据BOOT_MODE引脚的电平决定启动方式10 从内部启动根据BOOT_CFG引脚的电平决定从哪个存储介质中读取代码BOOT_CFG的引脚共有24个其中的四个BOOT_CFG1[7 : 4]决定设备类型010X:SD; 011X:emmc; ......然后读取前4K的uboot.imx放在OCRAM中包含IVT、boot_data、DCDDCD包含DDR内存的配置信息、PLL时钟初始化的信息boot_data包含uboot.bin的长度和目标地址等信息IVT包含entry地址程序入口地址0X87800000start.S4. 然后Boot_ROM代码会根据DCD中的信息初始化DDR内存根据boot_data中的信息读取uboot.bin全文放在DDR中根据IVT跳转到程序入口地址0X87800000去执行start.S至此Boot_ROM将CPU的控制权交给ubootuboot会从start.S开始进行下面的操作阶段代码位置主要任务汇编初始化start.S设置向量表、关闭 MMU/Cache、初始化栈C 早期初始化board_init_f初始化串口、规划内存布局重定位relocate_code搬移自身、修正地址、清空 .bss完整初始化board_init_r初始化驱动、加载环境变量、进入命令行引导内核bootz/bootm加载内核与 DTB、跳转到 Linux 入口第一阶段汇编级初始化start.S这是 U-Boot 的入口全部由汇编代码完成主要做三件关键事设置异常向量表在0x87800000开头放置 ARM 中断向量表并把 VBAR 寄存器指向这里为后续中断处理做准备。切换到 SVC管理模式确保 CPU 运行在最高特权级并禁用 FIQ 和 IRQ 中断防止早期阶段被意外打断。关闭 MMU 和 Cache此时 DDR 刚初始化完成MMU 和 Cache 尚未建立直接关闭它们是安全的。这一操作也确保后续代码执行在“平坦”物理地址上避免缓存一致性问题。初始化栈指针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 内核当用户执行bootz或bootm命令时U-Boot 会从指定地址如0x80008000加载内核镜像zImage。从指定地址如0x83000000加载设备树DTB。设置内核启动参数r00, r1machine_id, r2dtb_addr。关闭 MMU 和 Cache跳转到内核入口0x80008000将控制权交给 Linux。此后U-Boot 的使命彻底完成Linux 内核开始启动。当U-Boot执行bootz或bootm跳转到0x80008000这个地址后Linux内核就正式接管了CPU的控制权。第一步内核入口与解压汇编阶段0x80008000这个地址是U-Boot和Linux内核约定的“碰头地点”。U-Boot在跳转前会确保内核镜像zImage或uImage已经被加载到这里。约定俗成的地址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线程PID1这个内核线程最终会执行/init程序或者由init参数指定的程序变成所有用户空间进程的祖先——init进程。创建kthreadd线程PID2这是内核的守护进程专门负责创建和管理其他的内核线程。进入空闲循环完成以上创建后最初的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拽进内存。