EFI 系统分区
EFI 系统分区(也称为 ESP)是一个与操作系统无关的分区,其中存储了由 UEFI 固件启动的 UEFI 引导加载器、应用程序和驱动,是 UEFI 启动所必须的。
如果你正将 Arch Linux 安装到支持 UEFI 且已安装操作系统的计算机上,例如与 Windows 10 双启动,那么你很可能已有 EFI 系统分区。
要查看磁盘分区表和系统分区,以 root 的身份对你想要启动的磁盘使用 fdisk:
# fdisk -l /dev/sdx
命令将返回:
- 磁盘的分区表:如果分区表是 GPT,则会显示
Disklabel type: gpt;如果是 MBR,则会显示Disklabel type: dos。 - 磁盘上分区的列表:在列表中搜索 EFI 系统分区,它通常大小不小于 100 MiB,且类型为
EFI System或EFI (FAT-12/16/32)。要确认这个分区是 ESP,mount 它,然后看看是否包含一个名为EFI的目录,如果有,那这肯定就是 ESP。
如果你找到了现有的 EFI 系统分区,前往#挂载分区,否则,你将需要创建一个,前往#创建分区。
下两节介绍如何创建 EFI 系统分区(ESP)。
分区大小应足以储存启动加载器和启动所需要的其他文件。
推荐创建1 GiB大小的EFI系统分区以确保其提供足够的空间存放多个内核或统一内核镜像、引导加载器、固件升级文件以及任何其他操作系统或OEM文件。如果还有疑虑,4 GiB应该足够任何人使用,比如有些工具像Limine 启动引导器带有用于Btrfs的Snapper集成,支持创建多个可启动的快照。
GUID Partition Table 中 EFI 系统分区以分区类型 GUID C12A7328-F81F-11D2-BA4B-00A0C93EC93B 标识。
从以下方法中任选其一在 GPT 分区的磁盘上创建 ESP:
- fdisk:创建分区,然后使用命令
t并指定uefi别名将分区类型更改为EFI System。 - gdisk:创建分区类型为
EF00的分区。 - GNU Parted:创建文件系统类型为
fat32的分区,并设置esp标志。
创建分区之后,应当格式化为一种文件系统。前往#格式化分区。
- 某些固件可能不支持 UEFI/MBR 启动,因为它不受 Windows Setup 支持。
- bootctl 不支持将 systemd-boot 安装到MBR 分区的磁盘,参见 systemd issue 1125。
另请参阅 Partitioning#选择 GPT 还是 MBR 查看 MBR 的限制和 GPT 的优点。
主引导记录中 EFI 系统分区以分区类型 ID EF 标识。
从以下方法中任选其一在 MBR 分区的磁盘上创建 ESP:
- fdisk: 创建一个主分区,然后使用命令
t将分区类型更改为EFI (FAT-12/16/32)。 - GNU Parted: 创建文件系统类型为
fat32的分区,并设置esp标志。
创建分区之后,应当格式化为一种文件系统。前往#格式化分区。
UEFI 规范要求支持 FAT12、FAT16 和 FAT32 文件系统(参见 UEFI specification version 2.10, section 13.3.1.1),但任何合规的厂商都可以支持额外的文件系统。例如,Apple Mac 的固件支持 HFS+ 文件系统。
为避免与其他操作系统的潜在问题,同时既然 UEFI 规范声称 UEFI "encompasses the use of FAT32 for a system partition, and FAT12 or FAT16 for removable media"[specs/UEFI/2.10/13_Protocols_Media_Access.html#file-system-format],建议使用 FAT32。使用 dosfstools包 中的 mkfs.fat(8) 工具:
# mkfs.fat -F 32 /dev/sdxY
如果你收到消息 WARNING: Not enough clusters for a 32 bit FAT!并且不能创建更大的EFI系统分区,运行 mkfs.fat -s2 -F32 ... 或 -s1 减小簇大小。否则 UEFI 可能无法读取分区。参见 mkfs.fat(8) 查看支持的簇大小。
小于 32 MiB 的分区可能无法使用 FAT32。这种情况下,格式化为 FAT16 甚至是 FAT12。例如,2 MiB 的 ESP 只能支持 FAT12:
# mkfs.fat -F 12 /dev/sdxY
内核、initramfs 文件,在大多数情况下还有处理器的微码,都需要能被引导加载程序或 UEFI 本身访问才能成功启动系统。因此,如果想要设置简单,那引导加载程序的选择就会限制 EFI 系统分区可能的挂载点。
/boot,确保在升级内核时,没有使用systemd自动挂载机制(包括systemd-gpt-auto-generator)。每次系统或内核升级前都要手动挂载EFI系统分区,否则升级后可能会无法挂载,导致你的内核停留在当前版本并无法更新EFI系统分区中的内核文件。
或者在启动时预加载需要的内核模块,例如:
/etc/modules-load.d/vfat.conf
vfat nls_cp437 nls_ascii
有三种挂载EFI系统分区的典型情况:
- 挂载 EFI系统分区 到
/boot:
- 挂载 EFI系统分区到
/efi:- 当EFI系统分区包含其他系统的文件时最好和操作系统相关的文件分开,这确保了操作系统相关和EFI相关文件的分离。
- 只有EFI二进制文件(引导加载程序(和可选驱动))和(或)统一内核镜像会安装在EFI系统分区,避免了安装在
/boot中的文件对EFI系统分区的大小需求,节约了EFI系统分区的空间。 - 允许保留
/boot中文件的Linux特定的文件系统权限,避免了FAT的限制。 - 允许根据需求单独挂载EFI系统分区,例如需要升级引导加载程序时。
- 如果加密整个系统并且配置恰当,除少数需要文件没有被加密,
/boot中的文件能够被加密保护:内核及其他文件储存在加密分区,统一内核镜像或引导加载程序通过相应的文件系统驱动来访问这些文件。
- 挂载 EFI系统分区到
/efi, 然后再挂载一个“拓展引导加载器分区”(XBOOTLDR)分区到/boot。在以前创建的 ESP 太小而无法容纳多个引导加载程序以及内核但 ESP 又无法轻松调整大小时(例如在 Windows 之后将 Linux 安装到双引导(多引导)时),这可能非常有用。至少在 systemd-boot#使用XBOOTLDR安装时支持此方法。
如果不使用#典型挂载点中的方法,就需要将引导文件复制到 ESP(以下称为 esp)。
# mkdir -p esp/EFI/arch # cp -a /boot/vmlinuz-linux esp/EFI/arch/ # cp -a /boot/initramfs-linux.img esp/EFI/arch/ # cp -a /boot/initramfs-linux-fallback.img esp/EFI/arch/
此外,还需要使 ESP 中的文件在以后的内核更新中保持最新。否则可能会导致系统无法启动。以下部分讨论了几种自动化的机制。
除了将EFI系统分区挂载到/boot你也可以使用bind挂载将分区中的目录挂载到/boot(参考mount(8))。这样pacman就可以直接更新内核文件并保持EFI系统分区的规划。
按照#替代挂载点节内容,复制所有引导文件到你的EFI系统分区,分区挂载点在/boot外面。然后bind挂载目录:
# mount --bind esp/EFI/arch /boot
检查生效后,编辑Fstab使修改持续有效:
/etc/fstab
esp/EFI/arch /boot none defaults,bind 0 0
Systemd具备事件触发型任务能力。在本特定场景中,该系统利用路径监控功能来检测/boot/目录下EFISTUB内核与初始化内存盘文件的更新情况,并在文件被更新时执行同步操作。选择监视initramfs-linux-fallback.img文件是因为该文件由mkinitcpio最后生成,可确保所有文件构建完成后再启动复制流程。需要创建的systemd路径单元文件及服务单元文件包括:
/etc/systemd/system/efistub-update.path
[Unit] Description=Copy EFISTUB Kernel to EFI system partition [Path] PathChanged=/boot/initramfs-linux-fallback.img [Install] WantedBy=multi-user.target WantedBy=system-update.target
/etc/systemd/system/efistub-update.service
[Unit] Description=Copy EFISTUB Kernel to EFI system partition [Service] Type=oneshot ExecStart=/usr/bin/cp -af /boot/vmlinuz-linux esp/EFI/arch/ ExecStart=/usr/bin/cp -af /boot/initramfs-linux.img esp/EFI/arch/ ExecStart=/usr/bin/cp -af /boot/initramfs-linux-fallback.img esp/EFI/arch/
ExecStart=/usr/bin/sbsign --key /path/to/db.key --cert /path/to/db.crt --output esp/EFI/arch/vmlinuz-linux /boot/vmlinuz-linux
文件系统事件可用于在内核更新后运行脚本同步 EFISTUB 内核。下面是一个使用 incron 的示例。
/usr/local/bin/efistub-update
#!/bin/sh cp -af /boot/vmlinuz-linux esp/EFI/arch/ cp -af /boot/initramfs-linux.img esp/EFI/arch/ cp -af /boot/initramfs-linux-fallback.img esp/EFI/arch/
/boot/initramfs-linux-fallback.img 是要监视的文件。第二个参数 IN_CLOSE_WRITE 是要监视的动作。第三个参数 /usr/local/bin/efistub-update 是要执行的脚本。/etc/incron.d/efistub-update.conf
/boot/initramfs-linux-fallback.img IN_CLOSE_WRITE /usr/local/bin/efistub-update
要使用这个方法,启用 incrond.service。
因为/etc/mkinitcpio.d/中的preset支持shell脚本,编辑presets可以复制内核和initramfs到ESP。
编辑文件 /etc/mkinitcpio.d/linux.preset:
/etc/mkinitcpio.d/linux.preset
# mkinitcpio preset file for the 'linux' package
# Directory to install the kernel, the initramfs...
ESP_DIR="esp/EFI/arch"
#ALL_config="/etc/mkinitcpio.conf"
ALL_kver="${ESP_DIR}/vmlinuz-linux"
PRESETS=('default' 'fallback')
#default_config="/etc/mkinitcpio.conf"
default_image="${ESP_DIR}/initramfs-linux.img"
default_options=""
#fallback_config="/etc/mkinitcpio.conf"
fallback_image="${ESP_DIR}/initramfs-linux-fallback.img"
fallback_options="-S autodetect"
要测试它,只需运行:
# rm /boot/initramfs-linux-fallback.img /boot/initramfs-linux.img # mv /boot/vmlinuz-linux esp/EFI/arch/ # mkinitcpio -p linux
/etc/mkinitcpio.d/linux.preset
ESP_DIR="esp/EFI/arch"
#ALL_config="/etc/mkinitcpio.conf"
ALL_kver="$ESP_DIR/vmlinuz-linux$suffix"
PRESETS=('default')
default_config="/etc/mkinitcpio.conf"
default_image="$ESP_DIR/initramfs-linux$suffix.img"
/etc/mkinitcpio.d/linux-zen.preset
suffix='-zen' source /etc/mkinitcpio.d/linux.preset
mkinitcpio post 钩子能够在initramfs生成后,复制内核和initramfs镜像到所需的位置。
/etc/initcpio/post/copy-kernel-and-initramfs
#!/usr/bin/env bash
kernel="$1"
initrd="$2"
target_dir="esp/EFI/arch"
files_to_copy=()
for file in "$kernel" "$initrd"; do
if [[ -n "$file" ]] && ! cmp -s -- "$file" "${target_dir}/${file##*/}"; then
files_to_copy+=("$file")
fi
done
(( ! ${#files_to_copy[@]} )) && exit 0
cp -af -- "${files_to_copy[@]}" "${target_dir}/"
最后一个选项依赖于在事务结束时运行的 pacman 钩子。
第一个文件是一个监控相关文件的钩子,如果文件在前一个事务中被修改,钩子就会运行。
/etc/pacman.d/hooks/999-kernel-efi-copy.hook
[Trigger] Type = Path Operation = Install Operation = Upgrade Target = usr/lib/modules/*/vmlinuz Target = usr/lib/initcpio/* Target = boot/*-ucode.img [Action] Description = Copying linux and initramfs to EFI directory... When = PostTransaction Exec = /usr/local/bin/kernel-efi-copy.sh
第二个文件是脚本本身。创建文件并使其可执行:
/usr/local/bin/kernel-efi-copy.sh
#!/bin/sh
#
# Copy kernel and initramfs images to EFI directory
#
ESP_DIR="esp/EFI/arch"
for file in /boot/vmlinuz*
do
cp -af "$file" "$ESP_DIR/$(basename "$file").efi"
[ $? -ne 0 ] && exit 1
done
for file in /boot/initramfs*
do
cp -af "$file" "$ESP_DIR/"
[ $? -ne 0 ] && exit 1
done
[ -e /boot/intel-ucode.img ] && cp -af /boot/intel-ucode.img "$ESP_DIR/"
[ -e /boot/amd-ucode.img ] && cp -af /boot/amd-ucode.img "$ESP_DIR/"
exit 0
如果硬盘已经预装操作系统,那么EFI系统分区大小可能会比#创建分区中推荐的小。例如Windows安装程序会在一个非4Kn设备上创建一个很小的100 MiB EFI系统分区。
这种情况下最好新创建一个更大的EFI系统分区以避免存储空间耗尽。
在Windows下,使用磁盘管理(diskmgmt.msc)或在命令行使用diskpart.exe工具管理分区。
以管理员权限运行diskmgmt.msc。
- 右键C盘分区,然后选择压缩卷
- 输入
4096作为压缩的空间量,并点击压缩。
之后在C盘后方应该会出现4 GiB的未分配空间。
引导进入Arch Linux或Arch Linux安装介质环境为下一步创建新分区做准备。
首先确保备份好原来EFI系统分区的内容,若EFI系统分区的挂载点为esp:
# cp -a esp /esp_backup
卸载EFI系统分区:
# umount esp
运行blkid并记下旧分区的UUID和PARTUUID,下一步这些将值用于新分区上。
# blkid
/dev/sdxY: UUID="XXXX-XXXX" BLOCK_SIZE="512" TYPE="vfat" PARTLABEL="EFI system partition" PARTUUID="YYYYYYYY-YYYY-YYYY-YYYY-YYYYYYYYYYYY"
# sgdisk --delete=Y /dev/sdx
在最大的未分配空间上创建新分区并指定PARTLABEL和使用旧分区的PARTUUID:
# sgdisk --align-end --largest-new=0 --typecode=0:ef00 --change-name=0:'EFI system partition' --partition-guid=0:YYYYYYYY-YYYY-YYYY-YYYY-YYYYYYYYYYYY /dev/sdx
使用fdisk确认新创建的大小为4 GiB的EFI系统分区:
# fdisk -l /dev/sdx
... Device Start End Sectors Size Type /dev/sdx1 158099456 166488063 8388608 4G EFI System /dev/sdx2 206848 239615 32768 16M Microsoft reserved /dev/sdx3 239616 158099455 157859840 75.3G Microsoft basic data /dev/sdx4 166488064 167768063 1280000 625M Windows recovery environment /dev/sdx5 167768064 176156671 8388608 4G Linux swap /dev/sdx6 176156672 243265535 67108864 32G Linux root (x86-64) Partition table entries are not in disk order.
分区编号在删除和创建分区后没有重新排列,所以EFI系统分区编号应该和之前一样。
将新分区格式化为FAT32,并使用旧的UUID(需要删除UUID中的“-”横线符):
# mkfs.fat -F 32 -i XXXXXXXX /dev/sdxY
最后挂载新分区并恢复原有内容:
# mount /dev/sdxY esp # cp -a /esp_backup/. esp/
如果你先前停止了esp.automount,再次启动它。
如果swap正好在EFI系统分区后面,你可以牺牲swap空间用于扩大EFI系统分区。例如你的分区布局类似下面的例子:
# fdisk -l /dev/sdx
... Device Start End Sectors Size Type /dev/sdx1 2048 616447 614400 300M EFI System /dev/sdx2 616448 9005055 8388608 4G Linux swap /dev/sdx3 9005056 125827071 116822016 55.7G Linux root (x86-64)
使用fdisk删除swap分区并增大EFI系统分区。
- 运行:
# fdisk -l /dev/sdx
- 使用
d命令删除swap分区(在示例中swap分区的分区号是2)。 - 使用
e命令增大EFI系统分区(在示例中EFI系统分区的分区号是1)。使用给出的默认值作为新分区的大小并按下Enter确认。 - 通过{{ic|w}命令将修改落盘并退出fdisk。
分区大小修改后需要修改分区内的文件系统大小。因为fatresize(1) 存在问题并且libparted 不能修改FAT卷的大小为确切的值,唯一的办法就是备份文件系统的文件,然后创建新的分区来利用所有分区空间。
记下文件系统的UUID:
$ lsblk -dno UUID /dev/sdxY
XXXX-XXXX
备份好原来EFI系统分区的内容,若EFI系统分区的挂载点为esp:
# cp -a esp /esp_backup
卸载EFI系统分区:
# umount esp
从分区中擦除文件系统的signature以避免受到旧文件系统的影响:
# wipefs -af /dev/sdxY
将新分区格式化为FAT32,并使用旧的UUID(需要删除UUID中的“-”横线符):
# mkfs.fat -F 32 -i XXXXXXXX /dev/sdxY
最后挂载新分区并恢复原有内容:
# mount /dev/sdxY esp # cp -a /esp_backup/. esp/
如果你先前停止了esp.automount,再次启动它。
由于现在没有了swap分区,将swap放在一个交换文件上。
将ESP放在RAID1阵列上是可能的,但这么做也会带来数据损坏的风险,创建ESP时也要做额外的考虑,详情参见[8]和[9]还有UEFI booting and RAID1。
整个方案的关键点是使用--metadata 1.0将RAID metadata放在分区尾部,否则固件无法访问ESP:
# mdadm --create --verbose --level=1 --metadata=1.0 --raid-devices=2 /dev/md/ESP /dev/sdaX /dev/sdbY
或者如果你的ESP不会频繁修改,可以在进行相关更新时把主要ESP的修改复制到不同磁盘上的备用ESP上。备用ESP的引导启动项可以用efibootmgr手动添加。参见debian wiki。需要注意这个办法虽然避免了RAID方法的风险,但只在使用单系统时有用。
如果要给 FAT 文件系统一个卷名(即文件系统标签),请不要将其命名为 EFI。卷名和 EFI 目录名称相同可能会触发某些固件中的错误,导致固件表现得好像 EFI 目录不存在一样。