针对于嵌入式软件杂乱的知识点总结起来,提供给读者学习复习对下述内容的强化。
目录
1.什么是bootloader?
2.Bootloader的两个阶段
3.uboot启动过程中做了哪些事?
4.uboot和内核kernel如何完成参数传递?
5.为什么要给内核传递参数?
6.如何给内核传递参数?
7.为什么uboot要关掉caches?
1.什么是bootloader?
Linux系统要启动就必须需要一个 bootloader程序,也就说芯片上电以后先运行一段bootloader程序这段 bootloader程序会先初始化时钟,看门狗,中断,SDRAM,等外设,然后将 Linux内核从flash(NAND,NOR FLASH,SD,MMC等)拷贝到SDRAM中,最后启动Linux内核。当然了bootloader的实际工作要复杂的多,但是它最主要的工作就是启动 Linux内核。
bootloader和 Linux内核的关系就跟PC上的BIOS和 Windows的关系一样,bootloader就相当于BIOS。总得来说,Bootloader就是一小段程序,它在系统上电时开始执行,初始化硬件设各、准备好软件环境,最后调用操作系统内核。
loader包含ATF UBOOT (设备树)FDT
对于复杂系统系统启动流程一般是:BootRom、Bootloader、Kernel、Filesystem(文件系统)、App。
BootLoader的主要功能包括:关闭看门狗,初始化中断和异常向量表,进行时钟和外设的初始化,提供Can、Uart、Flash读写驱动等等。 大多数的Bootloader分为两个阶段,第一阶段使用汇编来实现,它完成一些依赖于CPU体系结构的初始化并调用第二阶段的代码;第二阶段则通常使用C语言来实现,这样可以实现更复杂的功能,而且代码会有更好的可读性和可移植性。 SPL(Secondary Program Loader)。硬件设备初始化、初始化内存空间、初始化堆栈,随后将第二阶段的代码(uboot)复制到SRAM中,跳转到Uboot入口地址处。 Uboot第二阶段初始化本阶段要使用到的硬件设备,通常会初始化一个串口做命令行方便交互。随后可能检测系统内存映射(memory map),并将内核代码复制到DDR中,最后准备传递给内核的参数,并引导内核
2.Bootloader的两个阶段
(1)Bootloader第一阶段的功能 硬件设备初始化 为加载Bootloader 的第二阶段代码准备RAM空间 复制 Bootloader 的第二阶段代码到RAM空间中 设置好栈 跳转到第二阶段代码的C入口点
(2)Bootloader第二阶段的功能 初始化本阶段要使用到的硬件设备(如串口、Flash和网卡等) 检测系统内存映射( memory map ) 将内核映象和根文件系统映象从Flash 上读到RAM空间中 ·为内核设置启动参数 调用内核
3.uboot启动过程中做了哪些事?
硬件上电或复位。
执行 SPL(若存在),初始化基本硬件。
加载主 U-Boot 到 RAM 并执行。
初始化硬件和外设。
加载环境变量。
检测和选择启动设备。
加载内核和根文件系统。
跳转到内核,完成启动过程。
4.uboot和内核kernel如何完成参数传递?
U-Boot和Linux内核之间的参数传递通常通过环境变量和设备树来实现。以下是几种常见的参数传递方式:
1. 通过 U-Boot 环境变量传递参数
U-Boot 提供了一个环境变量机制,可以在启动时将一些参数传递给内核。这些环境变量通常用于传递系统配置、启动选项等。
在 U-Boot 中设置环境变量:
setenv bootargs "root=/dev/mmcblk0p2 console=ttyS0,115200"
saveenv
在启动内核时,U-Boot 会将这些环境变量传递给内核,通常是通过 bootargs
参数。内核启动时可以通过 bootargs
来获取启动时传递的参数。
U-Boot 中的环境变量可以通过 printenv
命令查看,修改后使用 saveenv
保存。
内核启动时,会通过 bootargs
参数接收到这些传递的参数。内核可以通过 getenv()
或 parse_bootargs()
函数来解析这些参数。
2. 通过设备树传递参数
设备树(Device Tree,DT)是一种硬件描述的标准格式,内核在启动时会加载设备树以获取硬件配置信息。你也可以通过设备树来传递一些启动时的配置参数。
在设备树文件中,你可以定义一个 chosen
节点,用于传递启动参数。例如:
/ {
chosen {
bootargs = "console=ttyS0,115200 root=/dev/mmcblk0p2";
};
};
在内核启动时,内核会解析设备树中的 bootargs
参数,通常它会覆盖 U-Boot 传递的 bootargs
环境变量,或者可以与之合并。
设备树中的参数可以通过 of_get_flat_dt_prop()
或 of_get_property()
函数读取。在内核代码中,通常会在初始化阶段读取这些信息。
有时,我们还会用到 U-Boot 的 bootcmd
命令来启动内核,这时可以在命令行中指定内核参数。
例如,在 U-Boot 中通过以下命令启动内核:
run bootcmd
在 bootcmd
脚本中,你可以设置 bootargs
,然后使用 bootm
或 bootz
等命令启动内核。
5.为什么要给内核传递参数?
在此之前,uboot已经完成了硬件的初始化,可以说已经"适应了“这块开发板。然而,内核并不是对于所有的开发板都能完美适配的(如果适配了,可想而知这个内核有多庞大,又或者有新技术发明了,可以完美的适配各种开发板),此时,对于开发板的环境一无所知。所以,要想启动Linux内核,uboot必须要给内核传递一些必要的信息来告诉内核当前所处的环境。
给内核传递参数是为了在系统启动时调整内核的行为或配置运行环境。这些参数会影响内核的初始化过程、硬件配置和功能启用。
参数 | 作用 |
---|---|
console=ttyS0,115200 | 指定串口为控制台,设置波特率为 115200。 |
root=/dev/mmcblk0p1 | 指定根文件系统所在设备。 |
rootfstype=ext4 | 指定根文件系统的类型为 ext4。 |
quiet | 禁止内核启动时打印日志信息。 |
loglevel=7 | 设置内核日志打印的详细级别。 |
mem=512M | 限制可用内存为 512 MB。 |
nohz=on | 启用动态时钟。 |
maxcpus=1 | 限制系统只使用一个 CPU 核心。 |
panic=10 | 系统崩溃后 10 秒重启。 |
6.如何给内核传递参数?
U-Boot 是嵌入式系统的常用引导加载程序,它支持通过环境变量传递参数给内核,特别是 bootargs
变量。
设置环境变量: 在 U-Boot 提示符下,设置 bootargs
环境变量,这个变量通常包含内核启动时的参数。
setenv bootargs "console=ttyS0,115200 root=/dev/mmcblk0p2 rw"
保存环境变量: 设置完成后,使用 saveenv
命令保存这些环境变量:
saveenv
启动内核: 启动内核时,U-Boot 会将 bootargs
环境变量传递给内核。例如,可以使用 bootm
或 bootz
命令启动内核:
bootm 0x80008000
内核获取参数: 内核会从 U-Boot 传递过来的 bootargs
中读取启动参数。内核启动时会将这些参数存储在 boot_args
中,你可以在内核代码中通过 getenv()
或 parse_bootargs()
等函数解析这些参数。
如果你在 U-Boot 中设置了 bootargs="console=ttyS0,115200 root=/dev/mmcblk0p2"
,内核启动时就会使用这些参数进行初始化,例如设置串口终端(console
)和挂载根文件系统(root=/dev/mmcblk0p2
)。
2. 通过设备树传递参数
设备树(Device Tree,DT)是描述硬件的一种标准方式,内核会读取设备树来配置硬件资源。有时你也可以在设备树中指定一些参数,尤其是与硬件配置相关的启动参数。
3. 通过内核启动命令行参数
有时,启动命令本身也可以直接在启动脚本中传递给内核。例如,如果你的引导加载器(如 GRUB)支持,你可以直接在启动命令中指定内核启动参数。
在启动命令中指定参数: 在引导加载器的配置文件中(如 GRUB 的 grub.cfg
),你可以为内核指定启动参数:
linux /vmlinuz-5.10.0 root=/dev/sda1 console=ttyS0,115200
内核获取参数: 内核启动时会从引导加载器传递的命令行中获取这些参数。
4. 通过内核命令行
内核启动时,通常会使用命令行参数(bootargs
)。这些参数在内核启动时由引导加载器(如 U-Boot、GRUB 等)传递。内核可以通过 command_line
全局变量或 getopt()
函数解析这些参数。
常用的内核命令行参数:
root=<root_device>
:指定根文件系统。console=<console_device>
:指定控制台设备。mem=<size>
:指定内存大小。debug
:启用调试模式。
7.为什么uboot要关掉caches?
caches是cpu内部的一个2级缓存,它的作用是将常用的数据和指令放在cpu内部。caches是通过CP15管理的,刚上电的时候,cpu还不能管理caches。上电的时候指令cache可关闭,也可不关闭,但数据cache一定要关闭,否则可能导致刚开始的代码里面,去取数据的时候,从cache里面取,而这时候RAM中数据还没有caches过来,导致数据预取异常