Keep learning, keep living...

0%

ISO启动原理及启动盘制作

计算机加电后,首先会执行刷在ROM/NVRAM中的系统固件代码。系统固件(BIOS/UEFI)完成自身的一系列工作(如硬件自检(POST: Power-On Self-Test)后,需要引导操作系统启动。固件可以从NVRAM中读取启动设备列表,按设备顺序尝试进行引导。

BIOS

对于磁盘介质来说,BIOS会尝试读取启动设备的第一个扇区(512字节)。如果这个扇区的最后两个字节是0x550xAA, 则表明该设备可以用于引导,否则继续尝试列表中的下个设备。这个扇区叫做主引导记录(MBR: Master Boot Record)。这512字节分为三部分:

  • 可执行代码, 446字节
  • 分区表,每个表项16字节,共64字节
  • Magic number: 0x55和0xAA。

分区表项结构如下:

BI Starting CHS PT Ending CHS Starting Sector Partition Size
  • BI: Boot Indicator, 1字节,如果是80,标记该分区是否含有操作系统启动代码
  • Starting CHS: 3字节,指示分区起始位置,用柱片(Cylinder), 磁头(Head), 扇区(Sector)表示
  • Partition Type Descriptor: 1字节,文件系统类型,如,0x0B表示FAT320x07表示NTFS
  • Ending CHS: 3字节,指示分区结尾位置
  • Starting Sectro: 4字节,以逻辑区块地址(LBA: Logical Block Address)表示的起始扇区
  • Partition Size: 4字节,表示分区大小

MBR的代码空间只有446字节,主要完成从分区表中查找可引导分区,并加载分区中的扇区并执行(可以直接加载内核,也可以链式加载分阶段的bootloader),完成操作系统的加载。

UEFI

UEFI: United Extensible Fireware Interface的启动流程, 不依赖BIOS模式下固定的启动扇区(boot sector),而是由实现在固件中的boot manager来寻找相应的启动项。一个设备要想支持从UEFI启动,需要具备ESP: EFI system partition,用来存储UEFI固件需要读取和加载的文件。它支持MBR: Mater Boot Record, GPT: GUID Partition Table和光盘上的El Torito卷。而在ESP上,UEFI支持的文件系统为: FAT。计算机启动时,boot managerESP中根据计算机架构在FAT格式文件系统中寻找目录/EFI/BOOT/下的相应的启动文件(不区分大小写)并执行它,文件名如下:

架构 文件
x86(32位) bootia32.efi
x64(64位) bootx64.efi
ARM(32位) bootarm.efi
ARM(64位) bootaa64.efi

简单来说,UEFI固件本身中已经实现文件系统功能,它从支持的文件系统中直接加载相应的启动文件。

EL Torito

对于从CD/DVD等光学介质来说,和磁盘的启动分区结构有所不同。El ToritoISO 9660文件系统的一个扩展规范,用于支持从CD/DVD等光盘介质引导计算机。BIOS固件会根据El Torito规格,从光盘中的引导扇区寻找引导机器码。UEFI支持ISO 9660文件系统及扩展EL Torito, 会从中寻找ESP: EFI System Partition及文件系统。

了解了相应原理后,下面来从头制作一个支持从BIOSUEFI两种固件来启动Linux内核的ISO

因为CentOSISO分别使用isolinuxgrub两种bootloader,我们也使用它们来进行实验。

isolinux的官网:

GRUB的官网:

实验

首先,创建工作目录demoiso:

1
mkdir -p demoiso

BIOS的启动,我们使用isolinuxisolinux实现了对ISO9660EL Torito规范的支持。

从官网下载syslinux并解压:

1
wget https://mirrors.edge.kernel.org/pub/linux/utils/boot/syslinux/syslinux-6.03.tar.gz

将以下文件copyisolinux:

  • syslinux-6.03/bios/core/isolinux.bin
  • syslinux-6.03/bios/com32/elflink/ldlinux/ldlinux.c32
  • syslinux-6.03/bios/com32/libutil/libutil.c32
  • syslinux-6.03/bios/com32/lib/libcom32.c32
  • syslinux-6.03/bios/com32/menu/vesamenu.c32

我们可以自己编译内核和制作initrd,为了简单,我直接使用Slackware官方编译好的64位内核和initrd

isolinux目录下载内核和initrd:

1
2
wget --no-check-certificate -O vmlinuz https://mirrors.kernel.org/slackware/slackware64-current/kernels/huge.s/bzImage
wget --no-check-certificate -O initrd.img https://mirrors.kernel.org/slackware/slackware64-current/isolinux/initrd.img

isolinux下创建isolinux.cfg,内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
default vesamenu.c32
timeout 600

MENU clear
MENU title DEMO ISO
MENU vshift 2
MENU rows 10
MENU margin 8
MENU helpmsgrow 19
MENU tabmsgrow 15
MENU autoboot Booting default in #s

LABEL DEMO
menu label ^Slackware linux
KERNEL vmlinuz
INITRD initrd.img

文件准备完毕,开始制作ISO。在demoiso目录下执行:

1
xorriso -as mkisofs -o ../demoiso.iso -b isolinux/isolinux.bin -c isolinux/boot.cat -no-emul-boot -boot-load-size 4 -boot-info-table ./

我们可以使用dumpet命令来查看ISO文件的启动信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[root@dev01v ~]# dumpet -i ../demoiso-bios.iso
Validation Entry:
Header Indicator: 0x01 (Validation Entry)
PlatformId: 0x00 (80x86)
ID: ""
Checksum: 0x55aa
Key bytes: 0x55aa
Boot Catalog Default Entry:
Entry is bootable
Boot Media emulation type: no emulation
Media load segment: 0x0 (0000:7c00)
System type: 0 (0x00)
Load Sectors: 4 (0x0004)
Load LBA: 34 (0x00000022)

可以看到存在一条启动记录。

BIOS固件虚拟机上启动界面如图:

启动后成功进入Slackware Linux,如图:

接下来,处理UEFI支持。在demoiso目录下创建目录EFI/BOOT:

1
mkdir -p EFI/BOOT/

EFI/BOOT目录下创建文件grub.cfg,内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
set default="0"

set timeout=60

set root=(cd0)

menuentry 'Slackware Linux' {
echo 'Loading Slackware kernel ...'
linuxefi /isolinux/vmlinuz
initrdefi /isolinux/initrd.img
}

menuentry 'Reboot' {
echo 'System rebooting ...'
reboot
}

menuentry 'Halt' {
echo 'Halting system ...'
halt
}

demoiso目录下执行命令,生成BOOTX64.EFI文件:

1
grub2-mkstandalone -o EFI/BOOT/BOOTX64.EFI -O x86_64-efi "boot/grub/grub.cfg=EFI/BOOT/grub.cfg"

demoiso目录下创建目录images:

1
mkdir images

查看EFI目录大小:

1
2
[root@dev03v demoiso]# du -sh EFI/
8.4M EFI/

我们制作一个能包含下这个大小的UEFI能够识别的ESP分区镜像, 并将文件BOOTX64.efi复制到镜像中相应位置:

1
2
3
4
dd if=/dev/zero of=images/efiboot.img bs=1M count=12
mkfs.vfat images/efiboot.img
mmd -i images/efiboot.img EFI EFI/BOOT
mcopy -i images/efiboot.img EFI/BOOT/BOOTX64.EFI ::EFI/BOOT/

接下来生成BIOSUEFI都支持的ISO文件:

1
xorriso -as mkisofs -o ../demoiso-both.iso -b isolinux/isolinux.bin -c isolinux/boot.cat -no-emul-boot -boot-load-size 4 -boot-info-table -eltorito-alt-boot -e images/efiboot.img -no-emul-boot ./

使用dumpet查看生成的ISO文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
[root@dev03v demoiso]# dumpet -i ../demoiso-both.iso
Validation Entry:
Header Indicator: 0x01 (Validation Entry)
PlatformId: 0x00 (80x86)
ID: ""
Checksum: 0x55aa
Key bytes: 0x55aa
Boot Catalog Default Entry:
Entry is bootable
Boot Media emulation type: no emulation
Media load segment: 0x0 (0000:7c00)
System type: 0 (0x00)
Load Sectors: 4 (0x0004)
Load LBA: 6178 (0x00001822)
Section Header Entry:
Header Indicator: 0x91 (Final Section Header Entry)
PlatformId: 0xef (EFI)
Section Entries: 1
ID: ""
Boot Catalog Section Entry:
Entry is bootable
Boot Media emulation type: no emulation
Media load address: 0 (0x0000)
System type: 0 (0x00)
Load Sectors: 24576 (0x6000)
Load LBA: 34 (0x00000022)

可以看到有两个启动条目了。

使用UEFI固件的虚拟机启动后,界面如下:

内核启动后可以从sysfs中读取到UEFI相关信息:

实际上,因为ESP分区是从efiboot.img来生成的,EFI目录可以忽略掉,为了令生成的ISO文件更小,可以排除EFI目录:

1
xorriso -as mkisofs -o ../demoiso-both.iso -x EFI/ -b isolinux/isolinux.bin -c isolinux/boot.cat -no-emul-boot -boot-load-size 4 -boot-info-table -eltorito-alt-boot -e images/efiboot.img -no-emul-boot ./

参考