使用 BIOS 启动 GPT 分区表上的 Windows

本文又名:制作 UEFI + BIOS 双启动的 Windows 镜像

我偶尔会在虚拟机上安装一些 Windows 来用,当然也制作了一些常用的 Windows 虚拟硬盘文件。但一直有个问题困扰着我:有时候我希望我的虚拟机是 BIOS 启动的,而有时候是 UEFI;为此,我需要准备 MBR 和 GPT 两种分区表的硬盘,分别给两种启动方式的虚拟机用。这实在是太麻烦了,有没有办法合二为一呢?

答案是——有。wzyboyBIOS + 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 配置里提到了两个文件,memdiskbootmgr.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 启动的界面了。

从截图中可以看到,这份 Windows 安装是以 BIOS 运行,并采用 GPT 分区表的。

由于自始至终我们都没有碰过 ESP 分区,UEFI 启动应该也不受影响。

另外由于分区格式是固定的,创建出来的 bootmgr.vhd 也许能在其它虚拟机镜像上直接使用也说不定。

评论

还没有评论。

发表评论

发表评论代表你授权本网站存储并在必要情况下使用你输入的邮箱地址、连接本站服务器使用的 IP 地址和用户代理字符串 (User Agent) 用于发送评论回复邮件,以及将上述信息分享给 Libravatar Akismet,用于显示头像和反垃圾。