我并不喜欢打游戏,随着我对Linux的深入了解,Windows对我的吸引力越来越少,但是总免不了需要用到Office的时候,就是为了用一下Office或者Photoshop而占用了一个分区,实在是有点浪费空间,我的硬盘一般只分出来EFI和ext4两个分区,根本不必担心会有需要重装系统的问题,而Windows一般情况下都是打开Qemu虚拟机来临时用一下。天有不测之风云啊,系统没有发生故障,笔记本的固态硬盘坏了,幸好笔记本还在保修期内,可以免费更换一块新的硬盘,但是系统还是需要重做的,部分资料因为没有备份也丢失了,在笔记本送修的这段时间里,我用旧的笔记本先把系统安装好,回来再恢复一下就可以了,于是就狠狠地折腾了一把Linux各种安装方法。对于启动盘,我需要满足一下的要求:

  1. 可以同时支持BIOS和UEFI启动。
  2. 引导各种版本的Linux Live CD。
  3. 引导可读写的Debian Linux的img镜像。
  4. 直接加载Win PE镜像,免去每次制作Windows启动盘都需要重新烧录U盘。
  5. 引导Windows 10 LTSB的vhd镜像。
  6. Linux和Windows镜像既可以在真机上启动也可以在Qemu虚拟机启动。

这么复杂和奇葩的需求,当然就没有现成的可以下载,只能自己动手做啦。 写这篇文章花费了不少的时间,如果只是做一个可用的启动盘,是用不了多少时间的,但是为了把每一步都搞明白,使用虚拟机做了很多实验,Debian和Windows都重复安装了几遍,写这篇文章的过程中,送修的笔记本拿回来了,却看到远景的帖子上有人在我这个笔记本型号上成功安装了macOS,立刻就心动了,跑去折腾黑苹果的安装方法,所以之后我也会写一篇关于HP ENVY AS110TU安装macOS的文章。本文写作前期是使用Linux,测试启动盘主要是使用Qemu,也因此对Qemu有了深入的了解,后期是在macOS下完成的,但Qemu for macOS不能使用USB Passthrough,改用Parallels Desktop。安装macOS之后,笔记本的续航时间居然变长了,后来发现是Firefox惹的祸,相对于Chrome,用Firefox时CPU使用率长期偏高,改用chrome浏览器之后CPU发热变小了,续航也就变长了,续航时间短应该不是因为使用Linux的缘故。使用macOS一段时间之后,我决定不再换回Linux系统了,Linux下太缺乏应用了,而且用docker就可以满足基本的开发需求。 我是用SSD的移动硬盘制作启动盘的,感觉还是不够方便,就想把拿个大容量的u盘来试试,Linux的启动当然就没有什么问题了,但是windows的vhd镜像,就不好做了,Windows To Go的功能是对U盘是有要求的,Removable media类型是Fixed的才能够安装,虽然可以通过cfadisk驱动来改变Removable Media的类型,但需要对驱动进行签名,否则每次开机都提示你驱动没有签名,需要修改BCD启动文件关闭驱动签名才能启动,以下就是制作过程。

1 用Qemu测试启动盘

根据自己的测试需要打开和关闭相应的命令行参数就可以了,Qemu可以很方便地将主机上的某个插口直通到虚拟机中,有了这个它,就不需要通过重启来测试启动盘了。

# 保存CD镜像路径的变量
ISO_Debian=debian-9.4.0-amd64-netinst.iso
ISO_LTSB=cn_windows_10_enterprise_2016_ltsb_x64_dvd_9060409.iso
# 数组保存qemu命令行参数,通过注释随时打开和关闭相应的功能
BOOT_USB=(
# 等同于-enable-kvm,macOS下accel=hvf
-machine accel=kvm
# 设置内存容量
-m 1G
# 设置cpu
-cpu host
# 打开启动菜单
-boot menu=on
# UEFI固件
#-drive if=pflash,format=raw,readonly,file=/usr/share/ovmf/OVMF.fd
#-drive file=$HOME/.config/qemu.nvram,if=pflash,format=raw,unit=1
# 硬件时间
-rtc clock=host,base=utc
# usb 2.0控制器
#-device usb-ehci,id=ehci
# windows系统启动用该选项
#-device usb-tablet,bus=usb-bus.0
# usb 3.0控制器
-device nec-usb-xhci,id=xhci
# 可以将某个插口直通给虚拟机,在虚拟机控制台通过info usbhost命令查看
-device usb-host,bus=xhci.0,hostbus=3,hostport=1,id=usb-driver
# 使用当前的标准输入和输出做为控制台
#-monitor stdio
# 使用telnet Server作为控制台
-monitor telnet:127.0.0.1:23,server,nowait
# 直接使用内核启动
#-kernel vmlinuz
#-initrd initrd.img
# 以下三个参数用于虚拟机接管当前console
#-append "'root=/dev/sda3 ro single console=ttyS0'"
#-nographic
#-serial mon:stdio
# ide控制器
#-drive if=none,file=/home/wing/machines/win10.vhd,format=vpc,id=system
-device ide-hd,drive=system,bus=ide.1,unit=0
# scsi热插拔
#-device scsi-hd,drive=system,bus=scsi0.0,scsi-id=0
#-device virtio-scsi-pci,id=scsi0,bus=pci.0,addr=0x8
-drive if=none,file=debian.img,format=raw,id=system
# virtio驱动
#-drive if=virtio,format=raw,file=debian.img,id=system
# 光驱
#-drive file=$ISO_LTSB,media=cdrom,index=0
-drive file=$ISO_Debian,media=cdrom,index=0
# 9p文件系统,把当前目录直通到虚拟机中
#-fsdev local,id=home_dev,path=/home/wing,security_model=passthrough
#-device virtio-9p-pci,fsdev=home_dev,mount_tag=home_mount
# 网卡使用virtio驱动
#-net nic,model=virtio
# 网络使用qemu内置模式,hostfwd设置端口映射
#-net user,hostfwd=tcp::2222-:22
# smb共享目录
#-netdev type=user,id=net0,smb=/home/wing
# 用于更改网卡型号
#-device e1000,netdev=net0
# UEFI启动需要使用qxl驱动,否则会花屏
#-vga qxl
# 使用vnc协议连接虚拟机
#-vnc :1
# 使用spice协议链接虚拟机
#-spice port=5900,addr=127.0.0.1,disable-ticketing
# 设置共享粘贴板
#-device virtio-serial-pci -device virtserialport,chardev=spicechannel0,name=com.redhat.spice.0 -chardev spicevmc,id=spicechannel0,name=vdagent
)
sudo qemu-system-x86_64 "${BOOT_USB[@]}"

2 分区和安装grub2

不是所有的qemu-img所支持的镜像格式qemu都能直接支持,但可以通过使用qemu-nbd命令挂载,提供间接的支持,vhd格式qemu能够直接支持所以就不选最新的vhdx格式,如果需要使用vhdx格式,可以先用qemu-nbd -t -k soket-file-path vhdx-file挂载,然后以qemu-system-x86_64 -hda -hda nbd:unix:/soket-file-path启动虚拟机。

# 创建vhd镜像文件
qemu-img create -f vpc -o subformat=dynamic win10.vhd 60G
# 默认情况下,nbd的分区功能是关闭的,需要max_part参数重新加载该模块
rmmod nbd
modprobe nbd max_part=8
#挂载vhd
qemu-nbd -c /dev/nbd0 win10.vhd
# -o, --clear 清除分区表
# -n, --new=partnum:start:end partnum=0 表示第一个可用的分区号 start=0或忽略 表示第一个可用的地址 end=0或忽略 表示最后一个可用的地址
# -h, --hybrid=partnum[:partnum...] 创建hybrid MBR
# hydrid MBR分区方案中可以将bootmgr安装到efi分区中,因为有更好的方案,不再对这个分案进行深入探索
#sgdisk --clear -n 0::+1m -t 1:EF02 -n 0::+100m -t 2:EF00 -n 0:: -t 3:0700 -h 2:3 -p /dev/sda
sgdisk --clear -n 0::+1m -t 1:EF02 -n 0::+100m -t 2:EF00 -n 0:: -t 3:0700 -p /dev/sdb
#-F fat-size
mkfs.fat -F32 /dev/sdb2
#-f, --fast or -Q, --quick 快速格式化
mkfs.ntfs -Q /dev/sdb3
#挂载efi分区/dev/sda1到/mnt,uid和gid参数是为了编辑文件不需要sudo
mount /dev/sdb2 /mnt -o uid=$UID,gid=$GID
mkdir -p /mnt/boot
apt-get install grub-pc grub-efi-amd64
# grub安装到MBR
grub-install --target=i386-pc --recheck --boot-directory=/mnt/boot /dev/sdb
# grub安装到ESP,特别注意--removable参数,安装到移动设备上一定要用这个参数
grub-install --target=x86_64-efi --efi-directory=/mnt --boot-directory=/mnt/boot --removable

bios_grub分区是用于存放core.img,特别强调bios_grub标记的分区必须存在,否则BIOS模式下无法使用,分区为1M容量,因为1M是保证扇区能够对齐的最低容量。

3 配置启动菜单grub.cfg

把grub.cfg保存到/mnt/boot/grub/grub.cfg,就可以使用Qemu来测试移动硬盘了,看是否能看到菜单。 grub.cfg配置文件是主要是参考《How to create an Hybrid UEFI GPT + BIOS GPT/MBR boot USB disk》和《U盘引导多个linux镜像安装,同时支持BIOS和UEFI模式》两篇文章。

# path to the partition holding ISO images (using UUID)
#probe -u $root --set=rootuuid
set rootuuid='2DF7-3C9E'
set imgdevuuid='73790BC06D97FEEC'

# define globally (i.e outside any menuentry)
insmod search_fs_uuid
search --no-floppy --set=root --fs-uuid $rootuuid
search --no-floppy --set=imgdev --fs-uuid $imgdevuuid
set imgpath=($imgdev)/image # 定义iso文件存放的路径

# 定义各种要启动的iso镜像名
set ubuntu=ubuntu-16.04.1-desktop-amd64.iso # ubuntu桌面版镜像,支持12.04-16.04
set ubuntuserver=ubuntu-16.04.1-server-amd64.iso # ubuntu服务器版镜像,支持14.04-16.04
set debianlive=debian-live-8.6.0-amd64-gnome-desktop.iso # debian livecd镜像,支持debian8
set deepin15=deepin-15.3-amd64.iso # deepin15镜像
set linuxmint=linuxmint-18.1-cinnamon-64bit.iso # linuxmint镜像,支持17-18
set ubuntukylin=ubuntukylin-16.04.1-desktop-amd64.iso # ubuntukylin镜像,支持14.04-16.04
set centosdvd=CentOS-7-x86_64-Minimal-1511.iso # centos DVD版镜像,包括DVD/Everything/Minimal/Netinstall镜像,支持6-7
set centoslive=CentOS-7-x86_64-LiveGNOME-1511.iso # centos livecd镜像,包括livecd/livedvd,支持7(6似乎不支持直接引导ISO,期待PR解决)

# grub模块配置
set lang=zh_CN
insmod part_msdos
insmod xfs
insmod loopback
insmod iso9660
insmod ntfs

# Setup video
if [ "$grub_platform" = "efi" ]
then
insmod efi_gop
insmod efi_uga
fi

if [ "$grub_platform" = "pc" ]
then
insmod vbe
fi

# Setup font
insmod font

if loadfont ${prefix}/fonts/unicode.pf2
then
insmod gfxterm
set gfxmode=auto
set gfxpayload=keep
terminal_output gfxterm
fi

# Setup GRUB Screen
insmod jpeg
if background_image /boot/grub/grub_bg.jpg ; then
set color_normal=white/black
set color_highlight=white/green
else
set menu_color_normal=white/black
set menu_color_highlight=black/light-gray
fi

menuentry 'Boot From Local Disk' {
chainloader +1
}

menuentry 'Debian GNU/Linux In Image With LVM2' {
set root=$imgdev
insmod lvm
loopback loop /image/debian_lvm_vg_system.img
echo 'Loading Linux 4.9.0-6-amd64 ...'
linux (lvm/vg_system-lv_root)/vmlinuz root=/dev/mapper/vg_system-lv_root hostdev=$imgdevuuid ro quiet
echo 'Loading initial ramdisk ...'
initrd (lvm/vg_system-lv_root)/initrd.img
}

if [ "$grub_platform" = "efi" ]; then
menuentry 'Win 10 Enterprise In VHD (UEFI)' {
chainloader /EFI/Microsoft/Boot/bootmgfw.efi
}
fi

if [ "$grub_platform" = "pc" ]; then
menuentry "Win 10 Enterprise In VHD (Legacy)" {
# apt install syslinux; cp /usr/share/syslinux/memdisk ${efi_mount_point}/boot/syslinux/memdisk
linux16 /boot/syslinux/memdisk raw
initrd16 /boot/bootmgr.vhd
}
fi

menuentry 'Win10 pe' {
linux16 /boot/syslinux/memdisk iso
set root=$imgdev
initrd16 /image/win10pe.iso
}

if [ -f "$imgpath/$linuxmint" ]; then
menuentry "Linux Mint ISO" {
loopback loop "$imgpath/$linuxmint"
echo '载入Linux Mint ...'
linux (loop)/casper/vmlinuz file=/cdrom/preseed/linuxmint.seed boot=casper iso-scan/filename=$isofile noeject noprompt splash locale=zh_CN.UTF-8 --
echo '载入初始化内存盘 ...'
initrd (loop)/casper/initrd.lz
}
fi

if [ -f "$imgpath/$ubuntu" ]; then
menuentry "Ubuntu Desktop ISO" {
loopback loop "$imgpath/$ubuntu"
echo '载入Ubuntu Desktop ...'
linux (loop)/casper/vmlinuz.efi file=/cdrom/preseed/ubuntu.seed boot=casper iso-scan/filename=$isofile noeject noprompt splash locale=zh_CN.UTF-8 --
echo '载入初始化内存盘 ...'
initrd (loop)/casper/initrd.lz
}
fi

if [ -f "$imgpath/$ubuntukylin" ]; then
menuentry "UbuntuKylin Desktop ISO" {
loopback loop "$imgpath/$ubuntukylin"
echo '载入UbuntuKylin Desktop ...'
linux (loop)/casper/vmlinuz.efi boot=casper iso-scan/filename=$isofile noeject noprompt splash locale=zh_CN.UTF-8 --
echo '载入初始化内存盘 ...'
initrd (loop)/casper/initrd.lz
}
fi

# 特别注意: Ubuntu Server会出现安装的时候检测不到光驱的现象
# 此时手工进入shell下,将iso镜像挂载在/cdrom继续即可
# mount /dev/sdb1 /media
# mount -o loop /media/boot/iso/ubuntu-*-server-*.iso /cdrom
if [ -f "$imgpath/$ubuntuserver" ]; then
menuentry "Ubuntu Server ISO" {
loopback loop "$imgpath/$ubuntuserver"
set gfxpayload=keep
echo '载入Ubuntu Server ...'
linux (loop)/install/vmlinuz file=/cdrom/preseed/ubuntu-server.seed iso-scan/filename=$isofile quiet ---
echo '载入初始化内存盘 ...'
initrd (loop)/install/initrd.gz
}
fi

if [ -f "$imgpath/$deepin15" ]; then
menuentry "Deepin 15 ISO" {
loopback loop "$imgpath/$deepin15"
echo '载入Deepin ...'
linux (loop)/live/vmlinuz.efi boot=live config findiso=$isofile noeject noprompt locales=zh_CN.UTF-8 --
echo '载入初始化内存盘 ...'
initrd (loop)/live/initrd.lz
}
fi

if [ -f "$imgpath/$debianlive" ]; then
menuentry "Debian LiveCD ISO" {
set isofile="$imgpath/$debianlive"
loopback loop ($imgdev)$isofile
echo '载入Debian LiveCD ...'
linux (loop)/live/vmlinuz boot=live config findiso=$isofile noeject noprompt locales=zh_CN.UTF-8 --
echo '载入初始化内存盘 ...'
initrd (loop)/live/initrd.img
}
fi

if [ -f "$imgpath/$centosdvd" ]; then
menuentry "CentOS DVD ISO" {
loopback loop "$imgpath/$centosdvd"
echo '载入CentOS DVD ...'
linux (loop)/isolinux/vmlinuz inst.stage2=hd:UUID=$rootuuid:/$isofile inst.lang=zh_CN.UTF-8 rhgb
echo '载入初始化内存盘 ...'
initrd (loop)/isolinux/initrd.img
}
fi

if [ -f "$imgpath/$centoslive" ]; then
menuentry "CentOS LiveCD ISO" {
set isofile="$imgpath/$centoslive"
loopback loop ($imgdev)$isofile
echo '载入CentOS LiveCD ...'
probe -l -s isolable (loop)
linux (loop)/isolinux/vmlinuz0 boot=isolinux root=live:LABEL="$isolable" iso-scan/filename=$isofile rd.live.image LANG=zh_CN rd.locale.LANG=zh_CN rhgb
echo '载入初始化内存盘 ...'
initrd (loop)/isolinux/initrd0.img
}
fi

menuentry 'Reboot' {
echo 'System reboot...'
reboot
}

menuentry 'Poweroff' {
echo 'System shutting down...'
halt
}

4 准备系统镜像

为了让镜像文件可以直接在Qemu中启动,我对镜像文件进行分区并添加了兼容UEFI和BIOS启动的Boot Loader,因为Qemu支持-kernel和-initrd参数加载内核,所以Linux镜像也可以不分区直接使用LVM管理整个镜像文件,关于Linux镜像的两种制作方法我都记录下来了。为了兼容Windows系统,系统镜像所在的分区采用的是NTFS文件系统格式,Debian需要安装ntfs-3g以支持ntfs文件系统格式,可以在安装过程中,切换到shell,chroot到安装好系统的目录,安装ntfs-3g以及修改Ramdisk的挂载脚本,使得内核可以在不同的执行环境中正确挂载分区和根目录。Debian镜像需要在虚拟机和真机上两种环境上运行的,不同的环境需要不同的挂载规则,需要修改Ramdisk中的挂载脚本以提供相应的支持。用虚拟机测试启动盘非常方便,可以把镜像文件作为虚拟机的虚拟机硬盘,先把系统安装和调试好,再放到移动硬盘中测试,Qemu也很容易配置USB Passthrough来测试启动盘,不需要在真机上不断重启来进行测试,请参考前文脚本。

刚开始并没有打算安装Gnome 3作为桌面环境的,而是使用轻量的i3窗口管理器。 Gnome 3的好处是开箱即用,不需要花费太多的时间去配置系统,一条命令下去就有一个舒适的桌面环境,5G的镜像,装完Gnome 3就所剩无几,你需要增加镜像的容量,用LVM管理分区的好处就是扩展容量会方便一点,不用LVM管理分区,用图形化的gparted分区扩容也是件很容易的事情。

4.1 添加loop和nbd内核模块

raw格式的镜像可以用loop内核模块加载,也可以nbd模块加载,nbd模块支持很多种镜像格式,如常见的vhd(x)、vmdk、vdi、qcow2等等,所支持格式的详情参考qemu-nbd文档。

# 内核中添加nbd和loop内核模块
echo -e "nbd max_part=8\nloop max_part=8" | sudo tee -a /etc/initramfs-tools/modules
# 临时加载nbd和loop内核模块
sudo rmmod nbd loop
sudo modprobe nbd max_part=8
sudo modprobe loop max_part=8

4.2 制作LVM管理分区的Debian系统镜像(高级做法)

考虑到不是所有人都使用Linux作为主系统,使用macOS时,可以把创建逻辑卷过程放到Qemu虚拟机在Boot CD引导完成之后来进行。

4.2.1 先准备好内核Ramdisk中的挂载的脚本

安装ntfs-3g之后,内核当中就会添加ntfs-3g的内容,不需要手动添加ntfs-3g模块,内核挂载脚本执行顺序依次为/etc/initramfs-tools/scripts/{local-top,local-premount,local-bottom},这里需要在local-top和local-bottom分别添加一个脚本,前者用于挂载Debian系统镜像所在的数据分区和挂载系统镜像,后者用于在内核switch_root前,把数据分区以可读写的方式挂载到根文件系统中,否则switch_root后,是整个文件系统都是只读的。如果加载内核的过程中出现(initramfs),说明没有正确挂载系统镜像,在命令行中加载必要的内核模块、挂载数据分区和losetup系统镜像后,输入exit,就会内核就会继续执行启动步骤。需要调试脚本的话,可以参考lvm-loops-finalize,添加相应的log信息。这两个脚本的写法是参考buddy-linux项目的。 现在当前shell目录中准备好脚本lvm-loops-setup和lvm-loops-finalize,系统安装好之后,分别复制到/etc/initramfs-tools/scripts/{local-top,local-bottom}/目录中。

#!/bin/sh
# /etc/initramfs-tools/scripts/local-top/lvm-loops-setup
PREREQS=""
# Output pre-requisites
prereqs()
{
echo "$PREREQ"
}
case "$1" in
prereqs)
prereqs
exit 0
;;
esac
HOSTDEV=""
ROOT=""
# 提取内核启动参数
for param in $(cat /proc/cmdline); do
case $param in
hostdev=*)
HOSTDEV=/dev/disk/by-uuid/${param#hostdev=}
;;
root=*)
ROOT=${param#root=}
;
esac
done
# 通过传递内核启动参数hostdev来判断镜像是真机上运行,否则是在虚拟机上运行
if [ "$HOSTDEV" == "" ]; then
# 虚拟机中通过-append参数传递root=/dev/vg_system/lv_root内核启动参数
mount -t ext4 $ROOT /root
else
# 加载必要的内核模块
modprobe usb_storage
modprobe sd_mod
modprobe fuse
modprobe loop
# 在Ramdisk根目录中添加host挂载点
mkdir -p /host && chmod 755 /host
# 等待文件系统中出现移动硬盘的设备文件
until [ -b $HOSTDEV ]
do
sleep 1
done
# 挂载保存镜像文件的分区到/host
mount -t ntfs-3g $HOSTDEV /host
# 设置系统镜像到循环设备
losetup -f /host/image/debian_lvm_vg_system.img
fi
#!/bin/sh 
# /etc/initramfs-tools/scripts/local-top/lvm-loops-finalize
PREREQS=""
# Output pre-requisites
prereqs()
{
echo "$PREREQ"
}
case "$1" in
prereqs)
prereqs
exit 0
;;
esac
HOSTDEV=""
# 提取内核启动参数
for param in $(cat /proc/cmdline); do
case $param in
hostdev=*)
HOSTDEV=/dev/disk/by-uuid/${param#hostdev=}
;;
esac
done
# 通过传递内核启动参数hostdev来判断镜像是真机上运行,否则是在虚拟机上运行
if [ ! -n "$HOSTDEV" ]; then
# 加载必要的内核模块
. /scripts/functions

MNT=/host
ROOT_MNT=${rootmnt}${MNT}

_log_msg "Moving LVM loops host device mount point from \"${MNT}\" to \"${ROOT_MNT}\"...\n"
mount -o remount,rw ${rootmnt} || panic "Cannot remount \"${rootmnt}\" R/W."

[ -d ${ROOT_MNT} ] || (mkdir -p ${ROOT_MNT} && chmod 755 ${ROOT_MNT}) || panic "Cannot create \"${ROOT_MNT}\" mount point."
mount -n -o move ${MNT} ${ROOT_MNT} || panic "Cannot move \"${MNT}\" to \"${ROOT_MNT}\"."
log_end_msg "Done"
fi

4.2.2 创建镜像、挂载分区和安装系统

sudo -s
LOOP_FILE=debian_lvm.img
# 创建raw格式磁盘镜像
qemu-img create -f raw ${LOOP_FILE} 5G
# --- Linux 环境下 ------
# 如果在macOS下可以把这一步放在启动虚拟机后
LOOP_DEV=`losetup -f`
LVM_VG=vg_system
LVM_LV_ROOT=lv_root
# 设置循环设备
losetup ${LOOP_DEV} ${LOOP_FILE}
# 初始化物理卷
pvcreate -v ${LOOP_DEV}
# 创建卷组
vgcreate -v ${LVM_VG} ${LOOP_DEV}
# 创建一个逻辑卷
lvcreate -v -l 100%FREE -n ${LVM_LV_ROOT} ${LVM_VG}
#------------------------
wget http://mirrors.163.com/debian-cd/9.4.0/amd64/iso-cd/debian-9.4.0-amd64-netinst.iso
# 内核Ramdisk的根目录挂载脚本
cat > bootup <<EOF
#!/bin/sh
PREREQ=""
prereqs()
{
echo "$PREREQ"
}

case $1 in
prereqs)
prereqs
exit 0
;;
esac
mount -t ext4 /dev/vg_system/lv_root /root
EOF
chmod +x bootup
# 为了提高虚拟机的性能需要添加参数-M accel=hvf(macOS) 或 accel=kvm(Linux)
# 如果macOS下的accel=hvf无效,可以自己编译qemu
qemu-system-x86_64 -m 512 -M accel=hvf -boot order=d -hda debian_lvm.img -cdrom debian-9.4.0-amd64-netinst.iso
# 当安装进度到Partition disks,按2次ESC返回Debian installer menu,选择Excute a shell,进入Busybox ash shell
DEV=/dev/sda
LVM_VG=vg_system
LVM_LV_ROOT=lv_root
pvcreate -v ${DEV}
vgcreate -v ${LVM_VG} ${DEV}
lvcreate -v -l 100%FREE -n ${LVM_LV_ROOT} ${LVM_VG}
# 当安装进度到Install the GRUB boot loader时,按2次ESC中断安装的进程,进入Debian installer main menu,选择Execute a shell,进入busybox ash shell
mount -t ext4 /dev/vg_system/lv_root /mnt
mount --bind /dev/pts /mnt/dev/pts
mount --bind /dev /mnt/dev
mount --bind /proc /mnt/proc
mount --bind /sys /mnt/sys
chroot /mnt /bin/bash
scp wing@10.0.2.2:bootup /etc/initramfs-tools/scripts/local-top/
mkinitramfs -o /initrd.new.img
scp /qemu.initrd.img wing@10.0.2.2:
scp /vmlinuz wing@10.0.2.2:
exit
poweroff
qemu-system-x86_64 -m 512 -M accel=hvf -hda debian_lvm.img -kernel vmlinuz -initrd qemu.initrd.img

mkdir initrd
cd initrd
cpio -ivmd < ../initrd.img
find . | cpio -o -H newc | gzip > ../initrd.new.img

4.2.3 调整镜像容量

以下命令需要在Linux环境下执行

sudo losetup -f debian.img
sudo pvresize /dev/loop0
sudo lvextend -l 100%FREE /dev/vg_system/lv_root
Sudo resize2fs /dev/vg_system/lv_root

4.3 制作普通分区的Debian系统镜像(一般做法)

4.3.1 先准备好内核Ramdisk中的挂载的脚本

安装ntfs-3g之后,内核当中就会添加ntfs-3g的内容,不需要手动添加ntfs-3g模块,内核挂载脚本执行顺序依次为/etc/initramfs-tools/scripts/{local-top,local-premount,local-bottom},这里需要在local-top和local-bottom分别添加一个脚本,前者用于挂载Debian系统镜像所在的数据分区和挂载系统镜像,后者用于在内核switch_root前,把数据分区以可读写的方式挂载到根文件系统中,否则switch_root后,是整个文件系统都是只读的。如果加载内核的过程中出现(initramfs),说明没有正确挂载系统镜像,在命令行中加载必要的内核模块、挂载数据分区和losetup系统镜像后,输入exit,就会内核就会继续执行启动步骤。需要调试脚本的话,可以参考lvm-loops-finalize,添加相应的log信息。这两个脚本的写法是参考buddy-linux项目的。 现在当前shell目录中准备好脚本lvm-loops-setup和lvm-loops-finalize,系统安装好之后,分别复制到/etc/initramfs-tools/scripts/{local-top,local-bottom}/目录中。

#!/bin/sh
# /etc/initramfs-tools/scripts/local-top/lvm-loops-setup
PREREQS=""
# Output pre-requisites
prereqs()
{
echo "$PREREQ"
}
case "$1" in
prereqs)
prereqs
exit 0
;;
esac
HOSTDEV=""
ROOT=""
# 提取内核启动参数
for param in $(cat /proc/cmdline); do
case $param in
hostdev=*)
HOSTDEV=/dev/disk/by-uuid/${param#hostdev=}
;;
root=*)
ROOT=${param#root=}
;
esac
done
# 通过传递内核启动参数hostdev来判断镜像是真机上运行,否则是在虚拟机上运行
if [ "$HOSTDEV" == "" ]; then
# 虚拟机中通过-append参数传递root=/dev/vg_system/lv_root内核启动参数
mount -t ext4 $ROOT /root
else
# 加载必要的内核模块
modprobe usb_storage
modprobe sd_mod
modprobe fuse
modprobe loop
# 在Ramdisk根目录中添加host挂载点
mkdir -p /host && chmod 755 /host
# 等待文件系统中出现移动硬盘的设备文件
until [ -b $HOSTDEV ]
do
sleep 1
done
# 挂载保存镜像文件的分区到/host
mount -t ntfs-3g $HOSTDEV /host
# 设置系统镜像到循环设备
LOOPDEV=`losetup -f`
losetup -f /host/image/debian.img
mount -t ext4 ${LOOPDEV}p3 /root
fi
#!/bin/sh 
# /etc/initramfs-tools/scripts/local-top/lvm-loops-finalize
PREREQS=""
# Output pre-requisites
prereqs()
{
echo "$PREREQ"
}
case "$1" in
prereqs)
prereqs
exit 0
;;
esac
HOSTDEV=""
# 提取内核启动参数
for param in $(cat /proc/cmdline); do
case $param in
hostdev=*)
HOSTDEV=/dev/disk/by-uuid/${param#hostdev=}
;;
esac
done
# 通过传递内核启动参数hostdev来判断镜像是真机上运行,否则是在虚拟机上运行
if [ ! -n "$HOSTDEV" ]; then
# 加载必要的内核模块
. /scripts/functions

MNT=/host
ROOT_MNT=${rootmnt}${MNT}

_log_msg "Moving LVM loops host device mount point from \"${MNT}\" to \"${ROOT_MNT}\"...\n"
mount -o remount,rw ${rootmnt} || panic "Cannot remount \"${rootmnt}\" R/W."

[ -d ${ROOT_MNT} ] || (mkdir -p ${ROOT_MNT} && chmod 755 ${ROOT_MNT}) || panic "Cannot create \"${ROOT_MNT}\" mount point."
mount -n -o move ${MNT} ${ROOT_MNT} || panic "Cannot move \"${MNT}\" to \"${ROOT_MNT}\"."
log_end_msg "Done"
fi

4.3.2 创建镜像、分区、挂载分区和安装系统

qemu-img create -f raw debian.img 5g
sudo -s
sudo losetup -f debian.img
# 分区1 grub_bios 分区2 ESP 分区3 ext4
sgdisk --clear -n 0::+1m -t 1:ef02 -n 0::+100m -t 2:ef00 -n 0:: -t 3:8300 -p /dev/loop0
sudo mkfs.fat -F32 /dev/loop0p2
sudo mkfs.ext4 /dev/loop0p3
sudo mount /dev/loop0p3 /mnt
sudo mkdir -p /mnt/boot
sudo mount /dev/loop0p2 /mnt/boot
# grub2 的安装过程也可以放到chroot之后
# grub安装到MBR
sudo grub-install --target=i386-pc --recheck --boot-directory=/mnt/boot /dev/loop0
# grub安装到ESP,特别注意--removable参数,安装到移动设备上一定要用这个参数
sudo grub-install --target=x86_64-efi --efi-directory=/mnt/boot --boot-directory=/mnt/boot
sudo apt-get install debootstrap
# 安装base system到/mnt
sudo debootstrap --verbose --components=main,contrib,non-free \
--include=linux-image-amd64,grub-pc-bin,grub-efi-amd64,ssh,vim \
--exclude=nano \
--arch amd64 stable /mnt http://mirrors.ustc.edu.cn/debian

sudo mount --bind /dev/pts /mnt/dev/pts
sudo mount --bind /dev /mnt/dev
sudo mount --bind /proc /mnt/proc
sudo mount --bind /sys /mnt/sys
sudo chroot /mnt bash
# 如果上面步骤中已经安装grub,这里就不需要重复安装了
# grub安装到MBR
grub-install --target=i386-pc --recheck --boot-directory=/boot /dev/loop0
# grub安装到ESP,特别注意--removable参数,安装到移动设备上一定要用这个参数
grub-install --target=x86_64-efi --efi-directory=/boot --boot-directory=/boot
# 生成启动菜单
grub-mkconfig -o /boot/grub/grub.cfg
passwd root
# Qemu中使用UEFI启动,需要添加以下自动启动脚本
echo "\EFI\debian\grubx64.efi" > /boot/startup.nsh
# 添加fstab
blkid | awk '$1 ~ /loop0p2/{print $2" /boot vfat defaults,noatime 0 2"} $1 ~ /loop0p3/{print $2" / ext4 defaults 0 1"}' > /etc/fstab
# 添加网络配置文件
cat > /etc/network/interfaces <<EOF
auto lo
iface lo inet loopback

allow-hotplug ens3
iface ens3 inet dhcp
EOF
# 更新内核Ramdisk
update-initramfs -uvt
exit
sudo umount /mnt/dev
sudo umount /mnt/dev/pts
sudo umount /mnt/proc
sudo umount /mnt/sys
sudo umount /mnt/boot
sudo umount /mnt
sudo losetup -d /dev/loop0

4.3.3 调整镜像容量

qemu-img resize -f raw debian.img +1G
sudo losetup -f debian.img
sudo gparted /dev/loop0

4.4 制作兼容UEFI和BIOS启动的Windows 10系统镜像

Windows 10的制作过程相对简单,却是花了最多的时间的才总结出来的,Windows所有的工具都是闭源的,只能效仿别人博客当中的用法。

diskpart
# 挂载win10镜像
select vdisk file=e:\image\win10.vhd
attach vdisk
# 创建并挂载bootmgr镜像
create vdisk file=c:\bootmgr.vhd maximum=32 type=fixed
attach vdisk
# 创建主分区
create partition primary offset=102410
active
format label=bootmgr quick
assign letter=b
exit
# 添加nt60 mbr引导
bootsect /nt60 b: /mbr
bcdboot g:\windows /s b: /f BIOS

# 用bootice 修复bcd文件或者用下一步产生的BCD文件(s:/EFI/Microsoft/Boot/BCD)覆盖b:/Boot/BCD
@echo off
Setlocal
Set BCDEDIT=C:\Windows\System32\bcdedit.exe
Set STORE=C:\Boot\BCD
for /f "tokens=3" %%A in ('%BCDEDIT% /store %STORE% /create /d "Start GRUB2" /application bootsector') do set guid=%%A
%BCDEDIT% /store %STORE% /set %guid% device boot
%BCDEDIT% /store %STORE% /set %guid% path \Boot\grub\i386-pc\boot.img
%BCDEDIT% /store %STORE% /displayorder %guid% /addlast
endlocal
# win10.vhd镜像可以直接添加到bootmgr中
diskpart
select vdisk file=D:\image\win10.vhd
attach vdisk
select partition 3
assign letter=v
# 添加BIOS启动项
# win7中的bcdboot 没有/f参数
bcdboot /s v:\windows c: /l zh-cn /f BIOS
# 添加EFI启动项
bcdboot /s v:\windows c: /l zh-cn /f UEFI
Last Updated 2018-10-14 日 23:38.
Created by Emacs 25.1.1 (Org mode 9.1.14)