请注意，本文中的方法已经不再被建议使用。因为在 2021 年 9 月 5 日，随着 OpenWrt 21.02 的释出，官方已经正式开始提供支持 EFI 的镜像，只需要下载带 -efi 后缀的即可，如 generic-ext4-combined-efi.img.gz。 可惜这篇文章只活了半年就结束了它的使命（主要还是我咕，不然早就可以出现了）。 在 Hyper-V 上第一代和第二代虚拟机有着一些差别，最显著的就是旧式 BIOS 启动和 UEFI 启动的区别，但还有其他的例如热插拔硬盘和网卡的区别。我一直以为是 OpenWrt 对 UEFI 启动支持不良，官方才一直没有推出 EFI 的磁盘映像的。因此，之前我的做法都是创建好一个 OpenWrt 虚拟机后都一次性加一堆网卡，以解决临时要加一个口的问题。昨天准备给家里的 OpenWrt 升级版本，就又想到了这个事情，就想试一试如果用 UEFI 启动能不能正常使用。 OpenWrt x86 的启动本身就是 GRUB 了，按理说如果内核支持，那么只要整一个 ESP 分区，然后整个 EFI 版本的 GRUB 进去，grub.cfg 复制过来以后按照老样子启动就可以了。试了之后，确实就这么简单就能启动并正常使用了，暂时也没发现什么问题，就让它冒烟测试吧。因为在调研的时候似乎没看到介绍这么做的教程，我就来水一篇博客了。 为了方便使用 GRUB 的工具，本文所有操作均在 Debian bullseye 上完成。那么接下来我将介绍如何构建同时支持 MBR 启动和 UEFI 启动的 OpenWrt 磁盘映像。 因为我也担心遇到 UEFI 下的问题，为了能够回滚的考虑，就选择了构建一个支持 MBR 和 UEFI 启动的版本，可以在保证旧式 MBR 启动的模式兼容的前提下，支持 UEFI 启动。注意，你同时需要 MBR 和 EFI 两种 GRUB 的文件，Debian 中对应的是 grub-efi-amd64-bin 和 grub-pc-bin 两个包。 首先先从 https://downloads.openwrt.org/ 上下载得到最新的磁盘映像，目前为 openwrt-19.07.7-x86-64-combined-ext4.img.gz，并解压得到磁盘映像。 接着使用 losetup -f，获得一个可以让我们挂载磁盘映像的 loop 设备，并用 losetup -P /dev/loop0 openwrt-19.07.7-x86-64-combined-ext4.img 将磁盘映像关联到这个 loop 设备。 然后我们将 boot 分区（即第一个分区）挂载到一个文件夹，直接把里面的 boot 分区里面的 boot 文件夹复制出来，之后我们会将它塞回新的 ESP 分区。 复制出来以后，先用 umount 卸载，然后使用 fdisk /dev/loop0，将第一个原来的启动分区更改类型为 EFI。先输入“t”（修改分区类型），然后输入“1”（第一个分区），然后输入“uefi”或者“ef”，再使用“w”将改动写入分区表。 ESP 分区本质是一个 FAT12/16/32 的分区，而官方磁盘映像中第一个分区仅 16M，因此我们需要使用 mkfs.fat -n EFI -F16 /dev/loop0p1 将第一个分区格式化为 FAT16。 改造好 ESP 分区后，重新将这个分区挂载上，并将之前复制出来的 boot 文件夹，复制回这个分区，并建立 EFI/BOOT（大小写不敏感）文件夹，还要将 configfile ${cmdpath}/../../boot/grub/grub.cfg 这句话写入 image.cfg 文件中。 这些准备就绪后，就是创建 GRUB 的 EFI 程序了。使用 grub-mkstandalone -d /usr/lib/grub/x86_64-efi/ -O x86_64-efi --locales="" --themes="" --fonts="" --modules="part_msdos" -o esp/EFI/BOOT/BOOTX64.EFI "boot/grub/grub.cfg=image.cfg" 这句话就可以生成 GRUB 的 EFI 文件了。 因为 EFI 版本的 GRUB 中，会先将 GRUB 所用到的文件，例如 modules、locales、themes 之类的文件塞到 EFI 程序里，在运行的时候将其挂载为一个内存盘，所以第一时间使用的 grub.cfg 也来自这个内存盘。这里最后一个参数的意思是将“image.cfg”这个文件作为这个内存盘里面的 grub.cfg 来写入。而在 image.cfg 中，我们使用了“configfile”再次将配置文件指向 ESP 分区中的 boot/grub/grub.cfg。cmdpath 代表 GRUB 的 EFI 文件所在的目录。而 modules 那一个参数的意思则是让 GRUB preload 了 part_msdos 模块，不然它无法正常读取 MBR 分区。你可以查看文档，了解更多例如 locales、themes、fonts 之类的参数（这些这里都设为空了，默认的 all 会使 EFI 文件巨大）的用法，并随你的想法增减。 UEFI 模式的 GRUB 完成了，因为我们修改了分区类型，接下来还要重新安装 MBR 的 GRUB。我们使用 grub-install --target=i386-pc --locales="" --themes="" --fonts="" --boot-directory=esp/boot /dev/loop0 来完成这个工作。 最后再使用 umount 卸载挂载的分区，使用 losetup -d /dev/loop0 卸载磁盘映像。这样就可以啦，直接将我们刚才修改好的 img 文件拿去启动就行了。 在这个版本中，只需要修改一下分区，重新安装一下 GRUB，工作就顺利完成了。这也让脚本的编写十分容易了，因此我还为它专门编写了一个自动处理的脚本。 点此展开 mbr.sh #!/bin/sh if [ "$#" -ne 2 ] || ! [ -f "$1" ]; then echo "Usage: $0 old.img.gz new.img.gz" >&2 exit 1 fi set -e WORKDIR=$(mktemp -d) IMGPATH=$WORKDIR/disk.img ESPPATH=$WORKDIR/esp mkdir -p $ESPPATH echo 'working directory is set to '$WORKDIR echo '\e[1;34m## decompressing gzipped disk image\e[0m' gzip -ckd $1 > $IMGPATH echo echo '\e[1;34m## changing working directory to '$WORKDIR'\e[0m' cd $WORKDIR echo echo '\e[1;34m## mounting disk image to loop device\e[0m' LODEV=$(losetup -f) losetup -P $LODEV $IMGPATH echo echo '\e[1;34m## copying old boot folder\e[0m' mount ${LODEV}p1 $ESPPATH cp -vr $ESPPATH/boot . umount $ESPPATH echo echo '\e[1;34m## changing partition type to EFI\e[0m' sfdisk --part-type $LODEV 1 ef echo echo '\e[1;34m## formatting boot partition with FAT16\e[0m' mkfs.fat -n EFI -F16 ${LODEV}p1 echo echo '\e[1;34m## mounting boot partition\e[0m' mount ${LODEV}p1 $ESPPATH mkdir -p $ESPPATH/EFI/BOOT echo echo '\e[1;34m## copying old boot directory to new partition\e[0m' cp -vr boot $ESPPATH echo echo '\e[1;34m## generating GRUB file for UEFI\e[0m' echo 'configfile ${cmdpath}/../../boot/grub/grub.cfg' > image.cfg grub-mkstandalone -d /usr/lib/grub/x86_64-efi/ -O x86_64-efi --locales="" --themes="" --fonts="" --modules="part_msdos" -o $ESPPATH/EFI/BOOT/BOOTX64.EFI "boot/grub/grub.cfg=image.cfg" echo echo '\e[1;34m## generating GRUB file for MBR\e[0m' grub-install --target=i386-pc --locales="" --themes="" --fonts="" --boot-directory=$ESPPATH/boot $LODEV echo echo '\e[1;34m## unmounting\e[0m' umount $ESPPATH losetup -d $LODEV echo echo '\e[1;34m## re-compressing disk image\e[0m' gzip -ckn $IMGPATH > $2 echo echo '\e[1;34m## cleaning up '$WORKDIR'\e[0m' rm -rfv $WORKDIR 关于纯 UEFI 版 可能出于兼容性、洁癖或者什么其他原因，你想要一个纯粹的 UEFI 启动/GPT 分区的 OpenWrt，在这里我这里简要提一下操作过程。 首先用类似 dd if=/dev/zero of=openwrt-efi.img bs=1M count=400 的方法创建一个空白的磁盘映像，或者使用真实的磁盘。接着使用你所喜欢的分区工具，将这个磁盘分为 ESP 和系统两个分区。分为 ESP 分区和系统分区后，挂载 ESP 分区，做与上文提到的 grub-mkstandalone 相同的操作在 ESP 分区上来创建一个 GRUB 的 EFI 文件。 下载官网的 rootfs-ext4.img.gz（分区大小 256M）用 dd 部署到想要装系统的分区，你也可以用 rootfs-generic.tar.gz 解压的方式部署到这个分区里，这样分区类型就不局限于 ext4 了（但是系统里面可能没有带上相应的内核模块）。并使用官网上下载的 vmlinuz 部署到 ESP 分区内，再使用 blkid 得到系统分区的 UUID 之后自己写一个 grub.cfg 就好了。