BootLoader这个玩意
前面我们完整的学习了UBoot,这里最后来从宏观上看看再看看BootLoader这个玩意。
内容来自《深入理解BootLoader》
什么是BootLoader?
BootLoader就是Boot加Loader。
Boot是引导,用于初始化各种硬件设备,比如存储控制器(内存控制和外围存储控制)、时钟和电源管理等,它是为了后面的Loader 做准备的;
Loader是加载器,Boot准备好了程序运行的环境后,那么Loader 就将要执行的程序从非易失性存储设备中加载到内存中运行。
我们一般讲的BootLoader是为了引导特定操作系统的,但是广义的 BootLoader可以是引导某个特定程序的。我们先以一个引导特定程序的BootLoader为例,分析下BootLoader的关键构成,再在这个基础上分析引导Linux的BootLoader有什么特别要注意的地方。
1、广义上上BootLoader—引导流水灯举个栗子
这里以一个流水灯代码当作内核来引导。
BootLoader依旧烧写在MMC卡8KB偏移处,而这个“伪内核”烧写在40KB偏移处。
我们首先依据目标做一下分析:目标是将位于MMC卡40KB偏移处的“伪内核”搬移到内存中,并使之运行。那么很显然我们的BootLoader必须要能够控制MMC接口和DRAM接口,这样才能操作这个搬移动作。
而要能控制MMC接口和DRAM接口,有一个前提条件:作为处理器控制核心的时钟和电源管理必须配置好。
在调试这些控制代码时,需要用到一些调试手段,这里最简单有效的调试手段就是串口了,所以串口的初始化也是很必要的。
于是总结下来
初始化boot 搬移loader—》执行
关键代码如下。在start.S中,有:
.text
.global _start
.global cpu_init_cp15
_start:ldr sp, =0x00007f00 /*the init RAM size if 0x8000*/mrs r0, cpsrand r1, r0, #0x1f @ mask mode bitsteq r1, #0x1a @ test for HYP modebicne r0, r0, #0x1f @ clear all mode bitsorrne r0, r0, #0x13 @ set SVC modeorr r0, r0, #0xc0 @ disable FIQ and IRQmsr cpsr,r0mrc p15, 0, r0, c1, c0, 0 @ Read CP15 SCTRL Registerbic r0, #8192 @ V = 0mcr p15, 0, r0, c1, c0, 0 @ Write CP15 SCTRL Register/* Set vector address in CP15 VBAR register */ldr r0, =_startmcr p15, 0, r0, c12, c0, 0 @Set VBARbl cpu_init_cp15bl watchdog_initbl clock_core_initbl led_initbl uart_cfgbl uart_initbl timer_initbl sunxi_dram_initldr sp, =0x7f000000b main
在汇编代码中,我们首先设置栈指针,然后调用watchdog_init函数初始化开门狗,调用clock_core_init函数初始化时钟,调用uart_cfg和uart_init函数初始化串口,调用sunxi_dram_init函数初始化内存。
在内存初始化以后,重新设定栈指针,然后跳转到main函数中。
main函数的代码如下。
{struct mmc *mmc;int i;int power_failed = 0;i2c_init(CONFIG_SYS_I2C_SPEED, CONFIG_SYS_I2C_SLAVE);power_failed = axp209_init();power_failed |= axp209_set_dcdc2(1400);power_failed |= axp209_set_dcdc3(1250);power_failed |= axp209_set_ldo2(3000);power_failed |= axp209_set_ldo3(2800);power_failed |= axp209_set_ldo4(2800);clock_set_pll1(912000000);uart_printf("power_failed:%d\n", power_failed);memset((void *)__bss_start, 0, __bss_end - __bss_start);mmc_initialize();mmc = find_mmc_device(0);mmc_init(mmc);unsigned long *dst = (unsigned long *)0x40000000;num = mmc_bread(0,80,4,dst);void (*func)(void);func = 0x40000000;func();
}
这里我们读取位于MMC卡40KB偏移处、大小为2KB的代码到0x40000000内存处,然后定义一个函数指针地址为0x40000000,最后直接跳转到该地址。这样“伪内核”就成功执行了。
2、引导linux
当“伪内核”变成了Linux内核,那么Linux是怎么看待Booting的?
内核文档Documentation/arm/Booting就描述了Booting ARM Linux的相关内容:
为了引导ARM Linux,需要在运行内核之前跑一小段代码,也就是一个BootLoader。
BootLoader应该可以初始化设备,向内核传递信息并调用Linux内核。本质上来说,BootLoader至少应该提供下面的功能:
- 1)初始化RAM。
- 2)初始化一个串口。
- 3)检测处理器类型。
- 4)建立内核标签列表。
- 5)调用内核映像。
1.初始化RAM
BootLoader要找到内核并初始化内核使用的所有用于存储非易失数据的RAM。这一步是必需的,因为Loader要将内核加载到RAM中运行。
2.初始化一个串口
其实这是可选的,但是推荐提供串口,以便于调试。
BootLoader最好为目标初始化并使用一个串口。
这样,内核串口驱动就能自动检测到哪个串口应该作为内核的控制台(通常用于调试,或者与目标板通信)。
另外,BootLoader也可以通过标签列表中的指定端口来传递相关的“console=”选项到内核。在Documentation/kernel-parameters.txt中可以找到串口的选项格式。
3.检测处理器类型
BootLoader应该通过某种方法来检测处理器类型,并最终向内核提供一个MACH_TYPE_xxx值(参考linux/arch/arm/tools/mach-types)。
4.建立Boot数据
BootLoader必须提供一个标签列表或者一个dtb(设备树)映像来向内核传递配置数据。
Boot数据的物理地址通过存放在寄存器R2中以向内核传递。
(1)建立内核标签列表
BootLoader必须创建并初始化内核标签列表。有效的标签列表以ATAG_CORE开始,以ATAG_NONE结束。
ATAG_CORE标签可能是空的或者非空的。空的ATAG_CORE标签的大小域设为’2’(0x00000002),ATAG_NONE的大小域必须设为0。
在列表中可以存放任意多的标签。BootLoader必须传递系统内存的大小和位置以及根文件系统的位置。因此,最小化的标签列表如下:
+-----------+base -> | ATAG_CORE | |+-----------+ || ATAG_MEM | | 增加的地址+-----------+ || ATAG_NONE | |+-----------+ v
(2)建立设备树(现在都用的这个)
BootLoader必须以64位地址对齐的方式将dtb加载到系统RAM中,并且使用引导数据将其初始化。
dtb的格式可以参见Documentation/devicetree/booting-without-of.txt。
内核会在dtb的物理地址处查找dtb的幻数(0xd00dfeed),以确定是dtb而不是tag数据。
BootLoader至少要传递下面几个参数:
- 系统内存的起始地址和大小、根文件系统的位置
dtb在内存中的位置不能被内核的解压过程覆盖。推荐存放的位置是RAM的最初16KB内,但是注意:不能将其放在物理地址0处,因为r2=0表示既不是tag数据,也不是dtb数据。
5.调用内核映像
调用内核映像有两种选择。
- 如果zImage存放在Flash中,并且链接正确,就可以从Flash中运行,那么BootLoader就可以直接调用Flash中的zImage。
- zImage也可以放在系统RAM中。注意内核使用其映像下面的16KB RAM来存放页表。
无论哪种情况,必须满足下面的条件:
- 1)必须禁用所有的DMA设备。
- 2)CPU寄存器设置:
+ R0=0;
+ R1=上面讨论的处理器类型MACH_TYPE_xxx;
+ R2=系统RAM中标签列表的物理地址或者系统RAM中dtb的物理地址。 - 3)CPU模式。禁止所有的中断(IRQ和FIQ),CPU必须为SVC模式。
- 4)Cache和MMU。MMU必须关闭,指令Cache可以关闭,数据Cache必须关闭。
- 5)BootLoader是通过直接跳转到内核的第一条指令的方式来调用内核的。在支持ARM指令集的CPU中,入口必须处于ARM态。在仅仅支持Thumb指令集的CPU中**(Cortex-M系列)**,入口必须处于Thumb状态。
这里我们就可以继续总结一波流程:
(完成跳转)
除了最后一步,前面几个步骤都和前面介绍的基本一致,在此就不再赘述了,关键看一下最后一步。
最后一步用boot_linux引导。
boot_linux函数如下。
void boot_linux(void)
{uart_printf("boot linux\n");void (*kernel_entry)(int zero, int arch, uint params);unsigned long r2;unsigned long machid;setup_linux_param(0x40000000 + 0x100);cleanup_before_linux();kernel_entry = (void (*)(int, int, uint))0x48000000;machid = 4283;r2= 0x40000100;kernel_entry(0, machid, r2);
}
该函数完成的工作就是前面文档中描述的内容:
- 建立tag参数列表,
- 设定中断和Cache,
- 然后定义函数指针为0x48000000,
- 设定第一个参数为0,第二个参数为4283,第三个参数为参数所在的地址0x40000100,
- 然后就直接跳转,到此就完成了Linux的引导工作。
那么BootLoader是如何引导linux的?
3、BootLoader引导linux总结
有了前面的概念和源码描述,这里进行最后的总结。
一个简单的BootLoader是如何引导linux的:
- 1)初始化看门狗。
- 2)初始化时钟。
- 3)初始化串口。
- 4)设置CPU模式的栈指针,进入SVC模式。
- 5)初始化RAM。
- 6)初始化MMC控制器,并将内核映像从MMC设备中读到0x48000000处的RAM地址中。
- 7)清除BSS段。
- 8)做好调用内核前的准备工作。
- 9)跳转到RAM中的内核的第一条指令,引导过程完成。
(SVC和SYS的区别知道吗?我们在UBoot的内容学习过)
(看门狗只要得电情况下,看门狗就起作用,因此不应该通过软件的方式去使能和禁止开门狗,默认只要得电看门狗就起作用。这样防止程序出现异常,刚好开门狗又被软件禁止了,造成死机。)
BootLoader这个玩意相关推荐
- Arduino通过USB转TTL无BootLoader烧录程序的两种办法
Arduino通过USB转TTL无BootLoader(引导程序)烧录程序的两种办法 注意 (这个实验室基于stm32duino的,avr单片机并不直接通用,不过如果你准备尝试使用串口来给avr单片机 ...
- 关于apm飞控烧bootloader
最近一直由于学校大创申请的的四轴,一直在做这玩意,哎,无奈这货实在不像想象的那样简单,自己写了N久飞控,也没把飞机飞起来,只能先用开源的apm飞控练练手呗,备份下apm烧bootloader的方法,以 ...
- U-BOOT小全(五):BootLoader源码(SPL-UBoot 2)
前面我们分析到了s_init函数,现在继续. 1.s_init函数 然后调用s_init来进行更多模块的初始化.函数s_init在arch/arm/cpu/armv7/sunxi/board.c中定义 ...
- ARM的位置无关程序设计在Bootloader中的应用
http://www.mcuol.com/tech/107/26052.htm 引言 基于位置无关代码PIC(PositionIndependent Code)的程序设计在嵌入式应用系统开发中具有重 ...
- 你管这破玩意叫 CPU ?
点击关注公众号,Java干货及时送达 每次回家开灯时你有没有想过,用你按的简单开关实际上能打造出复杂的 CPU 来,只不过需要的数量会比较多,也就几十亿个吧. 伟大的发明 过去200年人类最重要的发明 ...
- 这玩意比ThreadLocal叼多了,吓得我赶紧分享出来。
点击上方蓝色"方志朋",选择"设为星标" 回复"666"获取独家整理的学习资料! Dubbo的一次提交开始 故事得从前段时间翻阅 Dubbo ...
- 京东程序员被同事吐槽:需求排期长拖黄业务,都研发的一些啥玩意
在互联网职场社区一名京东职工发帖吐槽公司程序员"不作为"的帖子引发了不少京东职工及同行们的热议,该帖子称:京东研制不仅仅需要995,而且需要9106比较好,都是研制的一些啥玩意.动 ...
- BootLoader与MCU启动过程
STM32那点事(1)_STM32F40_41xx启动文件详解 STM32 官方为广大开发者提供一套统一开发固件,主要是屏蔽寄存器封装,提供初始化等功能,较少开发者负担.只需要调用相关模块封装,对相关 ...
- bundle包是什么意思_iOS开发里的Bundle是个啥玩意?!
初学iOS开发的同学,不管是自己写的,还是粘贴的代码,或多或少都写过下面的代码 [[NSBundle mainBundle] pathForResource:@"someFileName&q ...
最新文章
- Unlocker 解锁 VMware Workstation
- 云边协同 — 协同的类型
- 部署与管理ZooKeeper
- 【牛客 - 280C】约数(数论,GCD,数学,分解因子)
- mysql内存机制_MySQL内存管理机制
- Bootstrap HTML编码规范之减少标签的数量
- 前端怎么自我介绍_实习|猫眼前端面试经历,问问题很关键(已拿offer)
- 3DSmax里的nurms toggle命令中文版是什么意思
- FIT2CLOUD飞致云旗下开源项目DataEase成功进入GitHub趋势榜主榜
- Qt 常用类 (11)—— QLabel
- 手游内存辅助开发教程
- Python报错:IndentationError: unindent does not match any outer indentation level问题的解决办法及原因
- linux能力集机制,Linux能力(capability)机制的继承
- 毁人不倦 - 大公司小公司
- 【CVE-2021-1675】Windows Print Spooler RCE
- Ubuntu上安装QQ,无法显示图片和头像
- springBoot项目改名
- pug在vue中的序列写法
- 手把手教你使用ModelArts的自动学习识别毒蘑菇分类
- React之antd Form回显数据
热门文章
- Gram矩阵+Gram矩阵和协方差矩阵的关系
- Fiddler简介与Web抓包,远程抓包——小白入门
- python-pygame与pymunk-倒塌解压金字塔
- PS 考试案例 02笔记
- 基于html+js实现轮播图(自动轮播、左右按钮、小圆点点击及切换图片)
- php xmp,在jpeg中编写XMP元数据(使用PHP) – 使用单个或多个rdf:描述块
- hdu6194 string string string
- AMBA总线协议之AHB学习记录(1)—ahb_bus(附verilog代码)
- Coding Interview University学习
- python读取、保存图片的方法