目录
Uboot启动流程
BL0阶段 [运行在ROM]
疑问
BL1 [运行在soc内部SRAM] spl阶段
其他
BL2 [运行在外部DDR] 完整uboot阶段
NandFlash设备的分区方案
uboot整体编译流程
BL1与BL2阶段详述
BL1阶段
代码入口
疑问
BL2板级初始化
函数调用
疑问
为什么要将uboot进行重定向?
流程简单总结
uboot启动kernel相关的指令
vmlinuz、zimage和uimage的区别
bootcmd环境变量
bootargs环境变量
u-boot启动内核的过程
Linux内核启动流程
启动参数
Linux内核启动分为两个阶段
第一阶段
汇编函数中主要操作
第二阶段
setup_arch(&command_line)
mm_init()
fork_init(void)
rest_init()
1号进程init
kernel_init_freeable();
设备驱动初始化do_basic_setup
挂在根文件系统prepare_namespace();
疑问
2号进程kthreadd
0号进程idle
参考文献
1、上电后CPU会直接从IROM上开始执行引导程序(通常是汇编代码),对cpu的寄存器初始化,启动核0,等待核0在内核启动正常后通过中断或者事件将其他核唤醒
2、初始化时钟、关看门狗、根据 Bootstrap Pin 来确定启动设备,比如启动设备是flash
2、初始化flash和SRAM,为后面从flash加载bin文件,在DDR中运行程序提供基本环境
3、将flash中的uboot-spl镜像文件加载到SRAM
为什么不是直接将完整的uboot装载到外部内存DDR运行?
soc厂商一般支持多种类型DDR,SoC 厂家怎么可能知道用户 PCB 上到底用了哪种内存?直接将uboot装在到SRAM中又太大了装不下,比如SRAM大小8K,uboot大小200K。
1、将uboot截断,前面 8K 被加载进入 SRAM 执行,需要保证在 u-boot 的前 8KB 代码,把板载的 DDR 初始化好,把整个 u-boot 拷贝到 DDR
2、先做一个小的 u-boot ,这个 u-boot 就叫做 spl,它先被 rom中的引导程序加载到 SRAM 运行,其最主要的就是要初始化 DDR Controller,然后将真正的大 u-boot 从外部存储器读取到 DDR 中,然后跳转到大 u-boot
BL1阶段代码通常放在start.s文件中
1、SPL 初始化外部 DDR
2、SPL 使用驱动从外部存储器读取 u-boot 并放到 DDR
3、跳转到 DDR 中的 u-boot 执行
CONFIG_SPL
用于指定是否需要编译SPL,也就是是否需要编译出uboot-spl.bin文件
连接脚本的位置在
u-boot/arch/arm/cpu/u-boot-spl.lds
1、初始化大部分硬件
2、读取环境变量,执行用户命令
3、引导加载内核
uboot的BL1阶段代码通常放在start.s文件中,用汇编语言实现
arch级初始化是和spl完全一致
u-boot/arch/arm/cpu/u-boot.lds(u-boot-spl.lds好像和这差不多,猜测uboot和uboot-spl arch级初始化是一样的)
ENTRY(_start)
reset
SoC上电复位后运行的第一段代码就是reset
A、关闭IRQ、FIQ,并将处理器模式设置为SVC模式
B、 CPU关键寄存器的初始化cpu_init_crit:
关闭L2 cache
初始化L2 cache
开启L2 cache
关闭L1 cache
关闭MMU
读取OM启动引脚信息
确定从启动设备flash
设置SRAM中的栈为调用lowlevel_init做准备(lowlevel_init内部有嵌套调用)
调用lowlevel_init(主要初始化系统时钟、SDRAM初始化、串口初始化等)
设置SDRAM中的栈
从flash拷贝uboot到SDRAM
C、设置MMU,开启MMU
D、通过对SDRAM整体使用规划,在SDRAM中合适的地方设置栈
E、清除bss段,远跳转到start_armboot执行,BL1阶段执行完
在spl的阶段中已经对arch级进行了初始化了,为什么uboot里面还要对arch再初始化一遍?
spl对于启动uboot来说并不是必须的,在某些情况下,上电之后uboot可能在ROM上或者nor flash上开始执行而并没有使用spl。这些都是取决于平台的启动机制。因此uboot并不会考虑spl是否已经对arch进行了初始化操作,uboot-spl是uboot的子集,uboot会完整的做一遍初始化动作,以保证cpu处于所要求的状态下。
uboot和uboot-spl在启动过程的差异在哪里?
前期arch的初始化流程基本上是一致的,出现本质区别的是在board_init_f开始的。spl的board_init_f是由board自己实现相应的功能,其主要实现了复制uboot到ddr中,并且跳转到uboot的对应位置上。一般spl在这里就可以完成自己的工作了。uboot的board_init_f是在common下实现的,其主要实现uboot relocate前的板级初始化以及relocate的区域规划,其还需要往下走其他初始化流程
BL2板级初始化
感觉BL1是BL2的子集,其实这部分应该是囊括BL1阶段的,下面应该是BL1没有的部分
考虑以下问题
* 在某些情况下,uboot是在某些只读存储器上运行,比如ROM、nor flash或者BL1拷贝的仅仅是uboot的一部分。还需要将自身(uboot镜像)完整的拷贝到内存中
* 给kernel腾位置,一般会把kernel放在ddr的低端地址上,防止 Linux kernel 覆盖掉 uboot,将 DRAM 前面的区域完整的空出来
运行在ROM上的BL0阶段:上电后CPU会直接从IROM上开始执行引导程序,初始化flash和SRAM,然后将flash中的uboot-spl镜像文件加载到SRAM。
运行在SRAM上的BL1阶段:初始化外部 DDR,从flash中将完整的uboot装载到外部DDR中,sp跳转到 DDR 中的 u-boot 其实地址执行
运行在外部DDR上的BL2阶段:根据链接文件u-boot.lds,找到入口_start,先进行arch级初始化,会和spl阶段有重复的部分,然后到_main函数进行板级初始化,重定向前的区域规划,uboot将自己拷贝到 DRAM 最后面的内存区域中,为内核腾出空间,然后继续板级初始化各种还没初始化完的设备,然后命令行状态
uboot支持一堆指令有操作环境变量,操作内存、网络、nand flash,文件等,无非是读写IO与内存
通过网络启动Linux内核,用tftp将内核镜像、initrd(如果有)、设备树,传到内存指定位置,再使用bootz命令指定刚刚那内存地址启动linux内核
tftp 80800000 zImage
tftp 83000000 imx6ull-14x14-emmc-7-1024x600-c.dtb
bootz 80800000 - 83000000
bootz启动zImage镜像,bootu启动uimage镜像,其他都一样,问题是zimage和uimage有啥区别?
编译kernel时, 直接make 生成zImage,make uImage, 生成uImage, 但是会用到mkimage工具
Linux内核经过编译后也会生成一个elf格式的可执行程序,叫vmlinux或vmlinuz,这个是原始的未经任何处理加工的原版内核elf文件。嵌入式系统部署时烧录的一般不是这个vmlinux/vmlinuz,而是要用objcopy工具去制作成烧录镜像格式(就是u-boot.bin这种,但是内核没有.bin后缀),制作出来的镜像文件就叫Image(这个镜像就比elf格式的文件要小很多)。
原则上Image就可以直接烧录到启动介质中,但是实际上linux的开发者认为Image的大小还是太大,所以对Image进行了压缩,并且在Image压缩后的文件的前端附加了一部分解压缩代码。构成了压缩格式的镜像就叫zImage,uImage其实就是在zImage的前面加上64字节的uImage的头信息
有些uboot支持zImage启动,有些则不支持。但是所有的uboot肯定都支持uImage启动
98D板子,烧固件时用run upt,其实upt是一环境变量
也可以使用boot指令启动内核,其实是去指向bootcmd环境变量中的指令集,当然也可以只使用run mycmd,执行mycmd环境变量中的指令
实践情况中,如果 EMMC 或者 NAND 中没有保存 bootcmd 的值板子,第一次运行 uboot 的时候都会文件 include/env_default.h,中定义字符串,而这字符串使用到的宏在include/configs/中板级相关的头文件中
在 linux内核启动前, bootloader会将存储介质中的 initrd 文件加载到内存,内核启动时会在访问真正的根文件系统前先访问该内存中的 initrd 文件系统。在 bootloader 配置了 initrd 的情况下,内核启动被分成了两个阶段,第一阶段先执行 initrd 文件系统中的"某个文件",完成加载驱动模块等任务,第二阶段才会执行真正的根文件系统中的 /sbin/init 进程
head.S
arm和mips架构的Linux内核 的入口函数不一样
ARM:ENTRY(stext) arch/arm/kernel/head.S
mips:ENTRY(kernel_entry)
init/main.c
当前了解过的函数有以下:
内存相关的初始化,创建并初始化 slab allocator 体系,vmalloc初始化(还没研究),其中slab allocator 体系通过临时静态变量的方式创建第一个slab cache,然后创建创建kmalloc slab缓存(kmalloc 内存池的本质其实还是 slab 内存池,底层依赖于 slab alloactor 体系,在 kmalloc 体系的内部,管理了多个不同尺寸的 slab cache,kmalloc 只不过负责根据内核申请的内存块尺寸大小来选取一个最佳合适尺寸的 slab cache)
创建task_struct的slab缓存
reset_init函数共创建了3个进程,分别是idle、kernel_init和kthreadd,依次叫做0号进程,1号进程和2号进程
kernel_init主要任务是完成设备驱动初始化和挂载根文件系统,并读取根文件系统中init程序,将从内核态转变到用户态。
init程序来源于哪里呢?
根据uboot传来的参数rdinit=xxxx,init=xxxxx,在根文件系统找到要执行的init程序,如果 bootargs 设置 init=/linuxrc,那么 linuxrc 就是可以 作为用户空间的 init 程序,所以用户态空间的 init 程序是 busybox 来生成的,若参数 都为空,那么就依次查找:
/sbin/init、
/etc/init、
/bin/init和
/bin/sh,
这四个相当于备用 init 程序,如果这四个也不存在,那么 Linux 启动失败!
内核线程的守护进程,始终运行在内核态,负责所有内核线程的创建。原理是不断循环kthread_create_list全局链表,如果为空,则调度出去;否则则调用create_kthread接口来创建内核线程。
kthread_create_list全局链表的添加是调用kthread_create->kthread_create_on_node->__kthread_create_on_node()实现的,所以说,所有的内核线程都是间接以kthreadd进程为父进程
一口Linux_Cortex-A9 uboot启动代码详解
天山老妖S的博客_linux系统移植_51CTO博客
[uboot] (第五章)uboot流程——uboot启动流程_spi_boot_ooonebook的博客-CSDN博客