EFI 系统分区

来自 Arch Linux 中文维基

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 SystemEFI (FAT-12/16/32)。要确认这个分区是 ESP,mount 它,然后看看是否包含一个名为 EFI 的目录,如果有,那这肯定就是 ESP。
提示:要判断是 FAT12、FAT16 还是 FAT32 文件系统,参阅 FAT#检测FAT类型
警告: 双启动时不要重新格式化 ESP,因为它可能包含启动其他操作系统所需的文件。

如果你找到了现有的 EFI 系统分区,前往#挂载分区,否则,你将需要创建一个,前往#创建分区

创建分区[编辑 | 编辑源代码]

下两节介绍如何创建 EFI 系统分区(ESP)。

警告: EFI 系统分区必须是磁盘主要分区表上的物理分区,不能处于 LVM 或软件 RAID 等等之下。

分区大小应足以储存启动加载器和启动所需要的其他文件。

推荐创建1 GiB大小的EFI系统分区以确保其提供足够的空间存放多个内核或统一内核镜像、引导加载器、固件升级文件以及任何其他操作系统或OEM文件。如何还有疑虑,4 GiB应该足够任何人使用。

注意: 当然也可以使用一个较小的分区,但要注意潜在的兼容问题:
  • 对于早期和/或古怪的 UEFI 实现,可能最少需要 512 MiB。[1]
  • 如果你打算把分区挂载到/boot并且只安装一个内核,那么400 MiB足够使用。
  • 如果是Arch + Windows 双系统,逻辑扇区大小为4096(高级格式化4Kn 设备)的分区应该至少300 MiB[2],其他情况应该至少100 MiB。[3]
  • 为确保分区能够被格式化为FAT32,逻辑扇区大小512字节的分区大小应该至少 36 MiB,4096字节的分区至少 260 MiB。[4]
  • 如果与这些问题都无关,分区大小最小可达 2 MiB,此时只能放得下启动加载器。

GPT 分区磁盘[编辑 | 编辑源代码]

GUID Partition Table 中 EFI 系统分区以分区类型 GUID C12A7328-F81F-11D2-BA4B-00A0C93EC93B 标识。

从以下方法中任选其一在 GPT 分区的磁盘上创建 ESP:

创建分区之后,应当格式化为一种文件系统。前往#格式化分区

MBR 分区磁盘[编辑 | 编辑源代码]

警告: 强烈建议使用 GPT 而不是 MBR

另请参阅 Partitioning#选择 GPT 还是 MBR 查看 MBR 的限制和 GPT 的优点。

主引导记录中 EFI 系统分区以分区类型 ID EF 标识。

从以下方法中任选其一在 MBR 分区的磁盘上创建 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英语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 系统分区可能的挂载点。

注意: 如果EFI系统分区没有挂载到/boot,确保在升级内核时,没有使用systemd自动挂载机制(包括systemd-gpt-auto-generator)。每次系统或内核升级前都要手动挂载EFI系统分区,否则升级后可能会无法挂载,导致你的内核停留在当前版本并无法更新EFI系统分区中的内核文件。

或者在启动时预加载需要的内核模块,例如:

/etc/modules-load.d/vfat.conf
vfat
nls_cp437
nls_ascii

典型挂载点[编辑 | 编辑源代码]

有三种挂载EFI系统分区的典型情况:

  • 挂载 EFI系统分区 到 /boot
    • 便于系统维护和管理,/boot微码包安装CPU微码initramfs文件和mkinitcpio安装内核initramfs镜像的默认位置。
    • FAT在挂载时设置了全局属性,这会阻止设置文件特定的权限拓展属性
    • 通常安装在/boot中的文件与EFI相关文件共享EFI系统分区,提高了EFI系统分区的大小需求
    • 双启动的情况下,系统特定的启动文件会处在被其它系统修改操作的潜在危险中
    • 无法加密/boot,因为固件需要读取EFI相关文件
  • 挂载 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安装时支持此方法。
注意:
  • /efi/boot/efi的替代挂载点[5][6]/boot/efi在过去被使用但现在不推荐。
  • /efi在安装一开始时不存在,需要先用 mkdir(1) 创建再挂载EFI系统分区到该目录。

替代挂载点[编辑 | 编辑源代码]

如果不使用#典型挂载点中的方法,就需要将引导文件复制到 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 中的文件在以后的内核更新中保持最新。否则可能会导致系统无法启动。以下部分讨论了几种自动化的机制。

使用bind挂载[编辑 | 编辑源代码]

除了将EFI系统分区挂载到/boot你也可以使用bind挂载将分区中的目录挂载到/boot(参考mount(8))。这样pacman就可以直接更新内核文件并保持EFI系统分区的规划。

注意: 这需要内核引导加载程序兼容FAT32。通常安装的Arch没有这个问题,但可能存在于其他发行版中(也就是说需要在 /boot中创建软连接,参见[7]

按照#替代挂载点节内容,复制所有引导文件到你的EFI系统分区,分区挂载点在/boot外面。然后bind挂载目录:

# mount --bind esp/EFI/arch /boot

检查生效后,编辑Fstab使修改持续有效:

/etc/fstab
esp/EFI/arch /boot none defaults,bind 0 0

使用 systemd[编辑 | 编辑源代码]

Systemd features event triggered tasks. In this particular case, the ability to detect a change in path is used to sync the EFISTUB kernel and initramfs files when they are updated in /boot/. The file watched for changes is initramfs-linux-fallback.img since this is the last file built by mkinitcpio, to make sure all files have been built before starting the copy. The systemd path and service files to be created are:

/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/

Then enable and start efistub-update.path.

提示:For Secure Boot with your own keys, you can set up the service to also sign the image using sbsigntools:
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英语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

使用 mkinitcpio preset[编辑 | 编辑源代码]

因为/etc/mkinitcpio.d/中的preset支持shell脚本,编辑presets可以复制内核和initramfs到ESP。

替换 mkinitcpio 钩子[编辑 | 编辑源代码]

编辑文件 /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 钩子[编辑 | 编辑源代码]

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 钩子[编辑 | 编辑源代码]

最后一个选项依赖于在事务结束时运行的 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里为新分区腾出空间[编辑 | 编辑源代码]

在Windows下,使用磁盘管理(diskmgmt.msc)或在命令行使用diskpart.exe工具管理分区。

以管理员权限运行diskmgmt.msc

  1. 右键C盘分区,然后选择压缩卷
  1. 输入4096作为压缩的空间量,并点击压缩

之后在C盘后方应该会出现4 GiB的未分配空间。

引导进入Arch Linux或Arch Linux安装介质环境为下一步创建新分区做准备。

删除旧的分区并创建一个新的ESP[编辑 | 编辑源代码]

首先确保备份好原来EFI系统分区的内容,若EFI系统分区的挂载点为esp

# cp -a esp /esp_backup

卸载EFI系统分区:

# umount esp
注意: 在本地安装的系统下你可能还需要停止 esp.mountesp.automount 单元以防止系统再次自动挂载它。

运行blkid并记下旧分区的UUID和PARTUUID,下一步这些将值用于新分区上。

# blkid
/dev/sdxY: UUID="XXXX-XXXX" BLOCK_SIZE="512" TYPE="vfat" PARTLABEL="EFI system partition" PARTUUID="YYYYYYYY-YYYY-YYYY-YYYY-YYYYYYYYYYYY"

使用gptfdisksgdisk删除旧分区:

# 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分区来扩大ESP[编辑 | 编辑源代码]

如果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)

首先停用swap分区,并把它从fstab里删除。

使用fdisk删除swap分区并增大EFI系统分区。

  1. 运行:
    # fdisk -l /dev/sdx
  2. 使用d命令删除swap分区(在示例中swap分区的分区号是2)。
  3. 使用e命令增大EFI系统分区(在示例中EFI系统分区的分区号是1)。使用给出的默认值作为新分区的大小并按下Enter确认。
  4. 通过{{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
注意: 在本地安装的系统下你可能还需要停止 esp.mountesp.automount 单元以防止系统再次自动挂载它。

从分区中擦除文件系统的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放在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方法的风险,但只在使用单系统时有用。

固件看不到 EFI 目录[编辑 | 编辑源代码]

如果要给 FAT 文件系统一个卷名(即文件系统标签),请不要将其命名为 EFI。卷名和 EFI 目录名称相同可能会触发某些固件中的错误,导致固件表现得好像 EFI 目录不存在一样。

参见[编辑 | 编辑源代码]