X

使用 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 启动的界面了。

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

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

分类: M$ 大法
三三:
相关文章