起因

  • 20年年末做了一个小项目,涉及到了linux系统的全盘加密,
  • 在这个过程中回头从鸟哥开始,了解到了系统启动的冰山一角
  • 引起了兴趣后决定深入挖掘一下:把从BIOS到linux启动的整个流程串起来
  • 也就是本文的主要内容:从UEFI到Systemd

UEFI和BIOS

  • 知乎大佬所述,BIOS与UEFI本质是一类东西
  • UEFI替代BIOS的过程,类似电动车替代燃油车的过程,是同类东西的更新换代
  • 而这更新换代背后的一系列推动逻辑还是很有意思的,比如为什么是intel芯片厂商主导推动,在文章中都有讲
  • 一致性:引用文中BIOS与UEFI的一致的统一流程如下:
    1. Rom Stage:在这个阶段没有内存,需要在ROM上运行代码。这时因为没有内存,没有C语言运行需要的栈空间,开始往往是汇编语言,直接在ROM空间上运行。在找到个临时空间(Cache空间用作RAM,Cache As Ram, CAR)后,C语言终于可以登场了,后期用C语言初始化内存和为这个目的需要做的一切服务。
    2. Ram Stage: 在经过 ROM阶段的困难情况后,我们终于有了可以大展拳脚的内存,很多额外需要大内存的东西可以开始运行了。在这时我们开始进行初始化芯片组、CPU、主板模块等等核心过程。
    3. Find something to boot Stage: 终于要进入正题了,需要启动,我们找到启动设备。就要枚举设备,发现启动设备,并把启动设备之前需要依赖的节点统统打通。然后开始移交工作,Windows或者Linux的时代开始。
  • 区别处:UEFI即统一可扩展固件接口,与BIOS即基本输入输出系统,的主要区别体现在上面所说的:
    • Ram Stage阶段:UEFI的的扩展性要比BIOS好得多,体现在如网卡厂商需要编写固件适配主板时,会舒服很多很多
    • Find something to boot Stage:BIOS与UEFI在寻找bootloader时约定有所不同,注意仅仅是一个约定,是需要我们去遵守的,但这并不是UEFI和BIOS的核心区别。尽管如此,我们在探索和对比使用时,接触到的大多数仅仅是本阶段的区别,所以接下来会着重介绍。

引导在哪里

  • 引导即bootloader,会按照使用者的意愿将目标操作系统拉起
    • 最初单系统的主机并不需要考虑复杂的bootloader行为,如提供菜单选择引导多个系统
  • 承上小节,接下来介绍BIOS和UEFI在从不同介质启动时,会去哪寻找引导

引导在哪里

  • SATA 硬盘
    • BIOS:MBR分区表的0-446字节
    • UEFI:EFI分区中的特定路径:EFI/BOOT/BOOTX64.EFI
  • USB 硬盘
    • 同SATA硬盘
  • CDROM 光盘
    • BIOS:根据El Torito格式(ISO 9660文件系统扩展)寻找
    • UEFI:将ISO本身当作ESP分区,从 EFI/BOOT/BOOTX64.EFI启动
  • PXE 网络
    • BIOS:根据dhcp返回包中的参数,请求指定的bootloader
    • UEFI:根据dhcp返回包中的参数,请求指定的bootloader
  • 引导的配置文件一般保存有多个系统启动方式,甚至多个系统的启动选项
  • 如centos的正常模式以及救援模式,如windows和linux双系统

引导配置文件在哪里

  • 引导的配置文件中一般存储着经常变化的信息,不适合硬编码在引导二进制代码中
    • 如引导提供的菜单的内容均由引导配置文件中的信息提供
  • 引导通过什么介质获取,一般引导配置文件也通过相同方式获取
    • 如pxe启动时,grub会在tftp目录中探测寻找grub.cfg
    • 如硬盘启动时,grub会挂载本地启动硬盘的boot分区,grub.cfg就在其中
  • 某个引导文件具体会选择去哪里寻找配置文件,是在引导文件编译时固定指定的

内核与临时文件系统在哪里

  • 展示并加载指定内核与临时文件系统是bootloader的责任,这些信息记录在配置文件中,因此以下均以grub2引导linux为例继续介绍
  • 内核,即linux的kernel,与kernel相配合的,还有rootfs,根目录,类似windows的c盘
  • 临时文件系统,顾名思义是临时的,其核心任务为找到rootfs并挂载
    • 根文件系统有任何骚操作时,基本都要在临时文件系统中进行相应的配置和支持
    • 如全盘加密(根文件系统加密,需在临时文件系统中对其进行解密后挂载至根)
    • 如挂载远程的nfs为根目录系统
    • 临时文件系统的存在,保持了内核的精简,将内核非必要的一些特殊文件系统识别挂载等模块集成在其中
  • 内核文件的名字一般为vmlinuz,大小一般10m以内
  • 临时文件系统一般命名为initramfs(rh系)、initrd(de系),大小根据集成模块多少在在50m左右浮动
  • 根据grub2的文档,中13.1条,为grub2指明设备时,可以使用tftp和http协议,也就为全流程从网络启动提供了可能性
  • 最后从grub2的配置文件可以看出,grub并不关心真正的根文件系统在哪,他只负责找到并加载内核与临时文件系统
    • 如我的工作站的grub.cfg,根文件系统通过内核启动参数的方式传递
    • 由内核和临时文件系统组成的小型操作系统根据参数去完成寻找与挂载

根文件系统在哪里

  • 配置内核有参数三种方式,分别是编译时指定、启动时的命令行参数指定、启动后/sys目录配置
  • 根文件系统在哪里是由第二种的命令行参数指定的,其中小一部分参数会被内核读取,而其他的复杂的配置由位于临时文件系统中的dracut程序读取并执行
    • dracut是一个工具集,既可以按要求生成临时文件系统文件,也会在临时文件系统中负责根据参数寻找并挂载根目录
    • 这些内核启动的命令行参数都是在grub.cfg中指定的
  • 在硬盘
    • 内核+临时文件系统组成的临时操作系统,直接挂载硬盘然后chroot就好
  • 在网络
    • 根据dracut的文档,可以挂载nfs、cifs、iSCSI等网络上位于其他主机的文件系统至本机的根目录
  • 在内存
    • 根据dracut的文档,可以加载SquashFS文件至内存,并以此内存文件系统为根文件系统

Systemd

  • 在近年发布的linux发行版基本都以systemd取代init,成为linux的守护进程管理工具,也就是pid为1号的辣个人
    • systemd会负责为用户使用操作系统做好一切准备,如硬件准备、网络准备、登录准备等等
  • 在正常根目录系统中:
    • systemd会拉起/usr/lib/systemd/system/default.target所依赖的一系列服务
    • 此时default.target软连接到multi-user.target为命令行界面做准备
  • 在临时文件系统中:
    • systemd同样拉起default.target,只不过此时default指向了initrd.target
    • 参考此文拆包initrd,从而查看其内容
  • 在安装镜像的临时文件系统中:
    • systemd同样拉起的是initrd.target
    • 但是此时initrd会依赖于一系列系统安装相关的服务进程,如
      • redhat系的anaconda
      • debian系的debian-installer
  • 我们安装系统的时候所见到的安装GUI,就是anaconda这类服务在提供
  • 最后由于anaconda还是一个非常出名的科学计算套件,这里给出此场景下所指的anaconda的文档wiki
    • 文档中所列出的boot options,即kernel命令行参数,是pxe等场景下需要经常查阅的

应用实例

Hybrid ISO

  • 现在下载较新的linux镜像时,各种文档都会提醒,只需要dd if=xx.iso of=/dev/usb就可以刻录系统盘
  • 那么问题来了,为什么?为什么用于刻录在光盘的文件可以直接刻录在u盘中并生效
  • 经过一番搜索发现了isohybrid关键字,但是却没有找到详细介绍相关技术的文章
  • 最后还是在iso9600的wiki中顺藤摸瓜,还原出了技术细节:
  • 一个hybrid iso镜像具体构成如下:
    1. MBR分区表,位于iso9660未使用的前32kb空白区域(注意MBR只占用第一个扇区,即头512byte),分区表如下
      • 第一个分区打上了boot标志,但分区类型为0,即空,开始与结束指针指向了iso镜像的头与尾,即整个iso9660文件系统作为第一个分区
      • 第二个分区分区类型为ef,即EFI分区,开始与结束指针指向了下文提到的efiboot.img文件,其文件内容为一个vfat格式的文件系统,其中包含了EFI模式下的grub引导相关文件
        • 在学习hybrid iso时,最困惑的问题就是为什么iso9660文件系统中能够兼容一个vfat系统用作EFI引导,在这里得到了解答
  1. Volume Descriptors,iso9660文件系统信息结构,其中包含了:
    • 指向文件系统根目录结构的指针(Primary Volume Descriptor)
    • 指向引导相关的目录结构的指针(Boot Record),相关规范在El-Torito中定义
  1. 光盘文件系统(iso9660文件系统格式),也就是我们通过mount -o loop挂载后看到的内容,其中包含
    • linux系统的内核及临时文件系统,即centos镜像中的images/pxeboot文件夹
    • 提供efi分区内容的文件,即centos镜像中的images/efiboot.img文件
    • 提供传统BIOS模式下的引导,即centos镜像中的isolinux/文件夹,注意传统BIOS在光盘介质使用的是isolinux引导,而不是grub
  • 同一个二进制文件,BIOS与UEFI模式 * 写入光盘与U盘时,分别的启动流程:
    • BIOS + 光盘:读取光盘Boot Record信息,从而寻找到isolinux文件夹中的引导,引导寻找光盘中的内核与临时文件系统
    • BIOS + U盘:读取MBR头部的引导代码,从而寻找到isolinux中的引导,最后引导挂载boot分区(iso9660格式)找到内核与临时文件系统
    • UEFI + 光盘:读取光盘Boot Record信息,从而寻找到光盘中的EFI文件夹中的grub2引导,最后引导寻找光盘中的内核与临时文件系统
    • UEFI + U盘:读取MBR分区表,发现类型为ef的EFI分区,挂载后找到efiboot.img文件中的引导,最后引导寻找boot分区中的内核与临时文件系统
  • 最后总结一下:
    • 光盘Boot Record中的信息,指导光盘介质下,BIOS找到isolinux/ 与 UEFI找到EFI/
    • MBR分区表中的信息,指导U盘介质下,BIOS找到isolinux/ 与 UEFI找到images/efiboot.img
  • 最后的最后,在调用isoparser库解析iso镜像查询信息的时候,发现一个有意思的事情,即不同的文件路径有可能指向了同一个文件,如images/pxeboot/initrd.img与isolinux/initrd.img其实是同一个文件内容,这样节省了一些空间,代码如下
  1. #! /usr/bin/python3
  2. import isoparser
  3. a = isoparser.parse('CentOS-7-x86_64-Minimal-2009.iso')
  4. def foo(base, r):
  5. for i in r.children:
  6. if i.is_directory:
  7. foo(base + [i.name], i)
  8. else:
  9. print(i.location,'\t', b'/'.join(base + [i.name]))
  10. res.append((i.location, b'/'.join(base + [i.name])))
  11. res = []
  12. foo([], a.root)
  13. sorted(res, key=lambda x:x[0])

极简pxe

  • 上面已经说过无论BIOS还是UEFI都是根据dhcp响应包中的字段去寻找引导
  • pxe引导安装系统的过程可以简单总结如下
    1. BIOS/UEFI指定网卡并从pxe启动
    2. 主板广播dhcp请求
    3. 我们搭建的dhcp服务器响应dhcp请求,在响应包中通过dhcp option返回相关信息,如
      • option 3 - gateway
      • option 6 - dns
      • option 66 - tftp server ip
      • dhcp obot - 引导在tftp服务中的路径
  1. 主板从我们搭建的tftp服务器中下载引导
  2. 执行引导,引导从tftp服务器中通过遍历寻找配置文件,即grub.cfg
  3. 寻找到配置文件后,在屏幕显示引导菜单供用户选择
  4. 用户选择安装系统后,使用指定协议向执行服务器下载内核及临时文件系统
    • 常规情况会继续向我们搭建的tftp服务器下载
    • 但是grub也会支持从http服务器下载,因此如果能通公网的话,可以编写grub.cfg直接指定公网华为软件源中的对应内核与临时文件系统
    • grub支持什么协议,这里就可以用什么协议,这时候已经和主板的支持没有关系了
  1. 由内核与临时文件系统组成的操作系统,会根据传入的内核命令行参数,执行相应的操作,如安装操作系统至本地硬盘
    • 安装过程涉及几个文件下载的过程:
    • 一:自动化安装时需要指定自动化安装脚本(kickstart之于centos、preseed之于debian)
    • 相应的安装程序(anaconda之于centos、debian-install之于debian)读取通过内核参数传入的脚本地址,并下载
    • 这里不同的安装程序也支持不同的脚本来源,如debian-installer支持tftp,而anaconda就不支持,具体支持协议列表需查阅文档
    • 二:下载脚本后,开始了安装过程,其中涉及第二点下载的过程时软件包的安装
    • 一个操作系统可以理解为内核 + 临时文件系统 + 根目录(由很多很多软件包组成)
    • 而内核是直接由安装时期的内核拷贝过去,临时文件系统根据根目录的位置使用工具重新生成
    • 根目录中的软件包却是需要从网络或硬盘源中下载安装的,这也是我们在下载安装镜像的时候everything与netinstall版本的区别
    • 即everything或者minial中内置了软件源,而netinstall需要从网络下载,我们需要在镜像大小与网络速度之间权衡下载
  • 了解了pxe的启动过程,我们可以通过搭建cobbler、debian-fai等搭建pxe所需的dhcp、tftp服务,但往往比较复杂,适用于大规模pxe管理,因此也可以尝试everything under control的极简pxe
    • 通过阅读dnsmasq的文档发现dnsmasq不止支持作为dns、dhcp服务,还支持tftp服务
    • 而根据上述的pxe过程,只有dhcp与tftp服务是本地内网不可或缺的,内核与临时文件系统可以从网络下载,安装脚本也可以从网络下载,软件包更是可以从网络下载
    • 其中内核与临时文件系统和软件包都可以从公网的centos镜像源中下载,与我们下载的iso镜像中完全相同
    • 而安装脚本的位置,如果安装程序如debian-installer支持从tftp下载安装脚本,那么把脚本放在tftp服务即可
    • 如果不支持从tftp下载脚本,那么在公网的自己的服务器的web或者内网搭建一个web服务提供脚本下载均可
    • 综上,其实一个dnsmasq就能够支撑pxe引导,我当前的配置如下
  1. ##### dhcp
  2. listen-address=198.18.0.1,127.0.0.1
  3. dhcp-range=198.18.10.0,198.18.126.254,48h
  4. dhcp-option=3,198.18.0.1
  5. dhcp-option=6,114.114.114.114
  6. dhcp-option=66,198.18.0.1
  7. dhcp-boot=grubx64.efi
  8. dhcp-lease-max=1000
  9. dhcp-authoritative
  10. ##### tftp
  11. enable-tftp
  12. tftp-root=/mnt/
  • tftp提供服务的目录也非常简单,只有两个文件:grubx64.efi 和 grub.cfg
  • grubx64.efi是从centos镜像中拆出来的grub引导,grub.cfg内容如下
  1. set default="1"
  2. function load_video {
  3. insmod efi_gop
  4. insmod efi_uga
  5. insmod video_bochs
  6. insmod video_cirrus
  7. insmod all_video
  8. }
  9. load_video
  10. set gfxpayload=keep
  11. insmod gzio
  12. insmod part_gpt
  13. insmod ext2
  14. insmod net
  15. set timeout=60
  16. ### END /etc/grub.d/00_header ###
  17. search --no-floppy --set=root -l 'CentOS 7 x86_64'
  18. ### BEGIN /etc/grub.d/10_linux ###
  19. menuentry 'Install CentOS 7.6.1810' --class fedora --class gnu-linux --class gnu --class os {
  20. linuxefi (http,mirrors.huaweicloud.com)/centos-vault/7.6.1810/os/x86_64/images/pxeboot/vmlinuz ks=http://xxx/kickstart/centos.ks
  21. initrdefi (http,mirrors.huaweicloud.com)/centos-vault/7.6.1810/os/x86_64/images/pxeboot/initrd.img
  22. }
  23. menuentry 'Install Debian 10 buster' {
  24. linuxefi (http,mirrors.huaweicloud.com)/debian/dists/buster/main/installer-amd64/current/images/netboot/debian-installer/amd64/linux vga=788 debian-installer/allow_unauthenticated=true --- quiet auto=true priority=critical interface=auto preseed/url=http://xxx/kickstart/debian.seed
  25. initrdefi (http,mirrors.huaweicloud.com)/debian/dists/buster/main/installer-amd64/current/images/netboot/debian-installer/amd64/initrd.gz
  26. }
  • 内核、临时文件系统、软件包都从华为源下载,真滴很快乐
  • 唯一的缺点可能是不同发行版的软件源目录布局不同,需要好好找一找到底在哪
    • 比如centos7的软件源已经过时了迁移到了centos-vault
    • 比如debian提供了netboot/debian-installer文件夹专供pxe引导的文件

ventoy

  • 一个神奇的启动盘制作工具,显著的优点是只需要把iso镜像copy到u盘里就可以引导了,还不会废了一个u盘,地址
  • ventoy在安装到u盘的过程中,会初始化MBR分区表(为了兼容传统BIOS)
    • 第一个大分区格式化成exfat用于存放文件
    • 第二个小分区格成vfat用于EFI引导以及ventoy相关的文件和配置
  • 除了各类插件能定制grub2让引导菜单更好看之外,ventoy提供了安装脚本的挂载,这个功能真的非常棒,小规模的几台机器想自动化装系统但又懒得搭pxe时,这是个非常好的替代方案,省去了很多鼠标操作
  • 特地为了ventoy买了一个64G的u盘豆,从此系统走到哪装到哪

Liveos

  • dracut的文档中如何指定live模式,以及指定root目录的SquashFS文件有明确说明
  • 至于引导以及内核和临时文件系统在哪,都可以的
  • 但一般live模式启动启动用于把必要的工具装里面从而现场维护设备
  • 所以大概率引导、内核、临时文件系统、SquashFS都在U盘里

瘦客户端

  • 引导通过pxe、内核临时文件系统通过http或tftp、根目录可以通过nfs挂载
  • 最终完全依靠网络引导一个操作系统,主机不需要硬盘或硬盘当作数据盘,有cpu和内存就够了