使用 BIOS 启动 GPT 分区表上的 Windows
本文又名:制作 UEFI + BIOS 双启动的 Windows 镜像
我偶尔会在虚拟机上安装一些 Windows 来用,当然也制作了一些常用的 Windows 虚拟硬盘文件。但一直有个问题困扰着我:有时候我希望我的虚拟机是 BIOS 启动的,而有时候是 UEFI;为此,我需要准备 MBR 和 GPT 两种分区表的硬盘,分别给两种启动方式的虚拟机用。这实在是太麻烦了,有没有办法合二为一呢?
答案是——有。wzyboy 的 BIOS + GPT + GRUB + Linux + Windows 折腾笔记一文给出了非常明确的方向。虽然原始需求和我的不同,但要做的事情很相似。
整体思路
按照 wzyboy 的文章中的思路,我们需要用 BIOS 启动 grub;再用 grub 加载一个 memdisk,启动 Windows 的 bootmgr;再用 bootmgr 启动 Windows。理由在 wzyboy 的文章里有说,这里不再赘述。
UEFI 启动只需要在 GPT 分区表里有 ESP 分区即可,这里保留微软做的 ESP 不动;另外需要两个额外分区,一个是放 grub 的 EF02 分区,沿用 parted 的命名叫它 bios_grub,BIOS 启动的时候会读取这个分区里的 grub 进行加载;另一个是放 grub 配置和 memdisk 的普通分区,这里沿用 Linux 的命名叫它 boot。
很巧,现代 Windows 安装器默认的分区结构是 16M 左右的 MSR + 200M 左右的 ESP + 130M 左右的 Recovery。MSR 本来也不放东西,这里我们就把它用作 bios_grub;Recovery 虽然里面有东西,但对我们来说并没什么用,这里就把它用作 boot 分区,很完美。
材料清单
- 一个可以运行的 Linux 系统,这里我用的是 Ubuntu 22.04 LTS;
- 要魔改的、已经用 UEFI + GPT 安装好的 Windows 镜像,这里叫
windows.qcow2
; - 一个 Windows PE iso,这里是网上随便下载的。
挂载硬盘
那么既然我们要在 Linux 上魔改,首先要把硬盘挂载上去。这里选择 qemu-nbd
,因为我们要操作分区表,而它可以挂载裸的硬盘设备。
modprobe nbd max_part=8
qemu-nbd --connect /dev/nbd0 windows.qcow2
挂载之后可以看到 /dev/nbd0
设备。用 parted /dev/nbd0 print
可以看到它的分区表:
...
Partition Table: gpt
Disk Flags:
Number Start End Size File system Name Flags
1 17.4kB 16.8MB 16.8MB Microsoft reserved partition msftres
2 16.8MB 226MB 210MB fat32 EFI system partition boot, esp
3 226MB 361MB 134MB Microsoft reserved partition msftres
4 361MB 42.9GB 42.6GB ntfs Basic data partition msftdata
修改分区表
正如刚刚所说,一切都符合我们的想象。1号分区改成 bios_grub,3 号分区改成 boot,完美。
用 parted 修改分区表:
parted /dev/nbd0 'set 1 bios_grub on'
parted /dev/nbd0 'name 1 grub'
parted /dev/nbd0 'set 3 msftres off'
parted /dev/nbd0 'name 3 boot'
partprobe
这就可以了。其实重点就是第一行把 bios_grub 开了,其它的不做也行。
安装 grub
因为原来的 Recovery 是 NTFS 的,Linux 下读写不便,我这里索性给它格了。按道理你直接用应该也是可以的。
mkfs.fat -n boot /dev/nbd0p3
mkdir /tmp/guestboot
mount /dev/nbd0p3 /tmp/guestboot
然后把 grub 配置写进去:
mkdir /tmp/guestboot/grub
cat << EOF > /tmp/guestboot/grub/grub.cfg
set root=(hd0,gpt3)
set timeout=2
menuentry "Windows" {
linux16 /syslinux/memdisk harddisk
initrd16 /bootmgr.vhd
}
EOF
然后安装 grub 到我们刚刚提到的那个 bios_grub
分区:
grub-install /dev/nbd0 --target=i386-pc --boot-directory=/tmp/guestboot
grub 安装时,会将 boot 所在的分区的位置嵌入 core.img 里,启动的时候再从这个位置读取配置。按说刚刚的 grub 配置里额外指定了一次 root 应该是不必要的,不过我也没有仔细研究,这里就给读者留作练习题吧。
准备 memdisk
刚才的 grub 配置里提到了两个文件,memdisk
和 bootmgr.vhd
,接下来我们就要来获取他们。
memdisk
很简单,安装 syslinux
包然后复制即可:
apt install -y syslinux
cp /usr/lib/syslinux/memdisk /tmp/guestboot/syslinux/memdisk
而 bootmgr.vhd
则麻烦一点,需要我们启动 Windows PE 来制作。
这个时候我们先要把已经挂载上的硬盘卸载掉:
umount /tmp/guestboot
qemu-nbd --disconnect /dev/nbd0
然后将虚拟机设置为 BIOS 模式,挂载硬盘和 Windows PE iso,从 iso 启动 PE。
将我们刚刚创建的 boot 盘分区放到 V:,然后将系统盘放到 C:。
接下来启动一个 cmd 窗口,运行 diskpart
,并输入以下命令(摘抄自文首 wzyboy 的博客),以创建一个 32M 的 vhd 文件作为 memdisk 使用:
create vdisk file=V:\bootmgr.vhd maximum=32 type=fixed
attach vdisk
create partition primary offset=1024
format label=bootmgr quick
active
assign letter=b
启动另一个 cmd 窗口,运行以下命令(同样摘抄自 wzyboy),将 boomgr 和 BCD 写入刚刚创建的 memdisk 里:
bootsect /nt60 b: /mbr
bcdboot c:\Windows /s b:
回到第一个 cmd 窗口的 diskpart 里,运行以下命令卸载 vhd:
detch vdisk
成果
至此,整个 boot 分区就准备好了。接下来可以重启虚拟机并设置为硬盘启动测试一下。应该可以看到 grub 启动的界面,然后是 memdisk 加载 bootmgr 的画面,再然后就是 Windows 启动的界面了。
由于自始至终我们都没有碰过 ESP 分区,UEFI 启动应该也不受影响。
另外由于分区格式是固定的,创建出来的 bootmgr.vhd
也许能在其它虚拟机镜像上直接使用也说不定。