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, cpsr
    and r1, r0, #0x1f                @ mask mode bits
    teq r1, #0x1a                    @ test for HYP mode
    bicne r0, r0, #0x1f              @ clear all mode bits
    orrne r0, r0, #0x13              @ set SVC mode
    orr r0, r0, #0xc0                @ disable FIQ and IRQ
    msr cpsr,r0
    mrc p15, 0, r0, c1, c0, 0        @ Read CP15 SCTRL Register
    bic r0, #8192                    @ V = 0
    mcr p15, 0, r0, c1, c0, 0        @ Write CP15 SCTRL Register
    /* Set vector address in CP15 VBAR register */
    ldr r0, =_start
    mcr p15, 0, r0, c12, c0, 0       @Set VBAR
    bl cpu_init_cp15
    bl watchdog_init
    bl clock_core_init
    bl led_init
    bl uart_cfg
    bl uart_init
    bl timer_init
    bl sunxi_dram_init
    ldr sp, =0x7f000000
    b 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的内容学习过)
(看门狗只要得电情况下,看门狗就起作用,因此不应该通过软件的方式去使能和禁止开门狗,默认只要得电看门狗就起作用。这样防止程序出现异常,刚好开门狗又被软件禁止了,造成死机。)

### Arch Linux 的详细安装教程 以下是关于如何在计算机上完成 Arch Linux 安装的详细指南: #### 制作启动 U 盘 为了制作一个能够引导系统的 USB 启动盘,可以选择多种工具来实现这一目标。推荐使用 **Ventoy** 工具,因为它支持多 ISO 文件管理而无需重复刻录[^1]。 对于 Windows 用户来说,可以直接访问 Ventoy 官方网站下载图形界面版本;而对于基于 Arch 的 Linux 发行版,则可以通过包管理器 `pacman` 来快速安装该软件: ```bash sudo pacman -S ventoy ``` #### 硬盘分区与格式化 进入 Live 环境后,首先需要对硬盘进行分区操作。可以借助 `fdisk` 或者更直观的 GUI 工具如 GParted 进行此过程。 假设已经完成了基本分区设置(EFI 分区 `/dev/sda1`, SWAP 分区 `/dev/sda2`, ROOT 分区 `/dev/sda3`),接下来执行相应的文件系统创建命令[^2]: ```bash mkfs.fat -F32 /dev/sda1 # EFI 分区, 使用 FAT32 格式 mkfs.ext4 /dev/sda3 # Root 分区, 推荐 EXT4 文件系统 mkswap /dev/sda2 # Swap 分区初始化 swapon /dev/sda2 # 开启交换空间 ``` 随后挂载这些新建立好的分区到指定目录下以便后续安装程序识别它们的位置关系: ```bash mount /dev/sda3 /mnt # 将根分区挂载至临时路径 '/mnt' mkdir /mnt/efi # 创建子目录用于存放 efi 数据 mount /dev/sda1 /mnt/efi # 把 efibootmgr 所需资源放置于此处供 bootloader 访问 ``` #### 设置镜像源 为提高软件包同步速度以及稳定性,在正式开始安装之前调整好合适的国内镜像站点是非常重要的一步。这里提供两种方法来进行更改: - 自动化脚本 Reflectors 可帮助我们筛选出最快的前十个服务器并将结果保存回默认配置文件当中去: ```bash reflector --verbose --latest 10 --sort rate --save /etc/pacman.d/mirrorlist ``` - 如果希望手动编辑也可以直接替换原有的内容为清华大学开源镜像服务地址或其他知名高校提供的链接列表之一即可满足需求: ```plaintext Server = http://mirrors.tuna.tsinghua.edu.cn/archlinux/$repo/os/$arch Server = http://mirrors.zju.edu.cn/archlinux/$repo/os/$arch Server = https://mirrors.ustc.edu.cn/archlinux/$repo/os/$arch ``` #### 基础环境部署 现在终于来到了最关键的环节——实际安装阶段!通过运行下面这条指令将会把一系列必要的核心组件拉取下来并安放到刚才准备完毕的目标磁盘上面去: ```bash pacstrap /mnt base linux linux-firmware net-tools networkmanager openssh vim nano ``` 这其中包括但不限于标准库集合(base group),最新的稳定内核(linux kernel),无线网络驱动(firmware),基础联网功能(net-tool & NetworkManager service daemon)还有远程连接安全性保障(OpenSSH server instance along with text editor utilities such as Vi Improved and Nano). #### 配置 fstab 表项 生成一份静态文件系统表记录所有已知设备及其对应的加载选项信息非常有必要,这样当机器重启之后仍然能正确找到各个逻辑卷位置而不至于丢失重要数据或者无法正常开机等情况发生。 ```bash genfstab -U /mnt >> /mnt/etc/fstab cat /mnt/etc/fstab # 查看生成的内容是否符合预期 ``` #### 初始化 chroot 环境 切换当前工作上下文进入到刚刚搭建起来的新环境中继续完善剩余部分设定直到完全独立运作为止. ```bash arch-chroot /mnt ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime # 设定时区为中国上海 hwclock --systohc # 更新硬件时钟 echo en_US.UTF-8 UTF-8 > /etc/locale.gen # 添加本地化语言支持条目 locale-gen # 应用修改后的区域参数定义 export LANG=en_US.UTF-8 # 导出变量生效即时影响shell session行为表现形式 echo myhostname > /etc/hostname # 给主机命名方便辨识区别开来 passwd # 修改 root 用户密码保护账户安全 systemctl enable NetworkManager # 开机自启网络管理服务进程单元 grub-install --target=x86_64-efi --bootloader-id=GRUB # 安装 GRUB 引导装载程序针对UEFI模式下的情况特别处理一下 grub-mkconfig -o /boot/grub/grub.cfg # 自动生成 grub.conf 脚本文档描述整个启动流程细节安排状况说明白纸黑字写清楚每一步骤动作含义解释得明明白白清清楚楚毫不含糊其里头到底干了些啥玩意儿出来呢? exit # 结束chroot会话返回原生状态 umount -R /mnt # 卸掉先前绑定关联上的全部子树结构层次体系架构布局规划图谱一览无余尽收眼底胸有成竹运筹帷幄决胜千里之外矣乎哉也夫! reboot # 最终重开电源按钮让一切改变立即显现眼前吧少年们加油啊!!!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

TrustZone_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值