本帖最后由 Misty 于 2021-3-21 07:00 编辑  
 
受到 @2011niumao 大佬的启发,近日研究了如何从vhd等虚拟磁盘启动Linux。可以注意到niumao大佬的kloop方案有诸多限制: 
1. 仅限VHD 
2. VHD必须固定大小 
而固定大小的vhd实际上和wubi所使用的raw原始格式并没有任何区别,只不过加了一个header。实际上并不需要kloop,可以手动获取到分区起始位置并使用-o loop, offset=XXXX。所以其实和wubi方案并没有太大差别,都是物理设备 → loop partition → ext4 
 
这明显丧失了虚拟磁盘的快照、 动态容量等各种特性,满足不了需求。经过一段时间的查找,找到国外网友使用vdfuse来支持多种虚拟磁盘读写的方案: 
https://unix.stackexchange.com/questions/309900/deploy-linux-into-and-boot-from-vhd/465215#465215 
 
思路(原理及调试方法见最下面): 
vdfuse借助开源项目virtualbox的内核模块读写virtualbox支持的格式(至少有vmdk、vhd、vdi),从而挂载出loop,并可以支持动态大小的虚拟磁盘文件。 
上面连接中的回答时间很新,所以操作基本都可以正常执行。经过实践,我总结出来了这些步骤 
 
编译安装vdfuse 
 
1. 安装依赖(注:还需要安装svn和git,请自行安装):sudo apt install libfuse-dev virtualbox pkg-config 
2. 下载vdfuse: vdfuse年久失修,利用github的fork network追踪功能,发现有一个人将其fork并适配了新版的vbox 6.0.2,仅有少量编译错误需要修改。我已经把编译错误修复:https://github.com/NyaMisty/vdfuse  
3. 编译并安装vdfuse:执行这些命令(注:可能需要挂代理) 
- ./autogen.sh
 - ./fetch_vbox_headers.sh
 - ./configure
 - make
 - sudo make install
 
 
  
dracut方案: 
dracut可以在systemd关机后返回initramfs从而得以让我们正确卸载vhd。 
 
(0. 安装dracut:sudo apt install dracut) 
1. 同上编译安装vdfuse,然后执行sudo cp /usr/local/bin/vdfuse /usr/bin/vdfuse 
2. 下载或克隆https://github.com/NyaMisty/dracut-vdfuseloop 
3. 将90vdfuseloop复制到/usr/lib/dracut/modules.d 
4. 修改/etc/dracut.conf.d/10-debian.conf,修改hostonly=no,在add_drivers一行前加#注释掉 
5. 执行sudo dracut -f生成initramfs 
6. 将生成好的/boot/initramfs-XXXXX.img和vmlinuz复制出来供引导 
7. 编辑/usr/lib/dracut/dracut-initramfs-restore,找到其中的[[ -f $IMG ]] || IMG="/boot/XXXXXXXXXX",将其对应修改为dracut生成的img的文件名格式,如[[ -f $IMG ]] || IMG="/boot/initramfs-${KERNEL_VERSION}.img" 
8. 配置grub参数 
- iftitle [ find --set-root --ignore-floppies --ignore-cd  /VMs/LinuxWorkspace/LinuxWorkspace.vmdk ] 启动Ubuntu
 
 - find --set-root --ignore-floppies --ignore-cd  /VMs/LinuxWorkspace/LinuxWorkspace.vmdk
 
 - uuid ()
 
 - kernel /ubuntuvm-vhd-helper/vmlinuz  rw rd.hostdev=UUID=%?% rd.vdisk=/VMs/LinuxWorkspace/LinuxWorkspace.vmdk rd.vdloop=/dev/vdhost/Partition1 rd.debug rd.shell verbose nomodeset
 
  复制代码 
 
initramfs-tool方案 (已废弃,仅作为记录保留): 
initramfs-tool方案难以解决关机Buffer IO问题,如有高手解决可以考虑共享 
 
(1-3接编译vdfuse) 
4. 修改hooks使得vdfuse工具被包含到initramfs中 
sudo gedit /usr/share/initramfs-tools/hooks/vdfuse  
- #!/bin/sh -e
 
 - PREREQ=""
 
 - prereqs()
 
 - {
 
 -     echo "$PREREQ"
 
 - }
 
  
- case "$1" in
 
 -     prereqs)
 
 -     prereqs
 
 -     exit 0
 
 -     ;;
 
 - esac
 
  
- . /usr/share/initramfs-tools/hook-functions
 
  
- copy_exec /bin/ntfs-3g /sbin
 
 - copy_exec /usr/local/bin/vdfuse /sbin
 
  复制代码 5. 修改init-top实现自定义文件系统挂载逻辑sudo gedit /usr/share/initramfs-tools/scripts/init-top/vdfuse  
- #!/bin/bash -e
 
 - PREREQ="udev"
 
 - prereqs()
 
 - {
 
 -     echo "$PREREQ"
 
 - }
 
  
- case "$1" in
 
 -     prereqs)
 
 -     prereqs
 
 -     exit 0
 
 -     ;;
 
 - esac
 
  
- if [ ! -z $vdisk ]; then
 
 -     mkdir /vdhost
 
 -     mkdir /dev/vdhost
 
 -     exec -a @ntfs-3g ntfs-3g $host /vdhost/
 
 -     exec -a @vdfuse vdfuse -t VMDK -f /vdhost$vdisk /dev/vdhost
 
 -     mount -t ext4 $ROOT /root
 
 - fi
 
  复制代码 注1:vdfuse的-t参数为虚拟磁盘的格式,请根据自己的虚拟磁盘来配置,支持VMDK/VHD/VDI三个值 
注2:此处niumao前辈利用patch ntfs3g实现了防止buffer io的错误,但是实际上可以通过简单的exec传参来让argv[0][0] == "@"。方法来源于https://github.com/systemd/systemd/issues/13981 
注3:虽然配置了ntfs3g和vdfuse不被kill,但仍然会出现buffer I/O error,原因不明 
 
 
6. 修改init-bottom 
sudo gedit /usr/share/initramfs-tools/scripts/init-bottom/vdhost - #!/bin/sh -e
 
 - PREREQ=""
 
 - prereqs()
 
 - {
 
 -     echo "$PREREQ"
 
 - }
 
 - case "$1" in
 
 -     prereqs)
 
 -     prereqs
 
 -     exit 0
 
 -     ;;
 
 - esac
 
 - if [ -d ${rootmnt}/vdhost ]; then
 
 -     mount -n -o move /vdhost ${rootmnt}/vdhost
 
 - fi
 
  复制代码 
 
6. 设置权限 
- sudo chmod +x /usr/share/initramfs-tools/hooks/vdfuse
 
 - sudo chmod +x /usr/share/initramfs-tools/scripts/init-top/vdfuse
 
 - sudo chmod +x /usr/share/initramfs-tools/scripts/init-bottom/vdhost
 
  复制代码 
7. 生成initramfs,并拷贝initrd.img和vmlinuz 
具体方法参见niumao前辈的文章 
 
8.  设置grub参数 
这里仅列出grub4dos的参数设置(抄自niumao大佬)- iftitle [ find --set-root --ignore-floppies --ignore-cd  /VMs/LinuxWorkspace/LinuxWorkspace.vmdk ] 启动Ubuntu
 
 - find --set-root --ignore-floppies --ignore-cd  /VMs/LinuxWorkspace/LinuxWorkspace.vmdk
 
 - uuid ()
 
 - kernel /ubuntuvm-vhd-helper/vmlinuz  root=/dev/vdhost/Partition1 host=/dev/XXXX vdisk=/VMs/LinuxWorkspace/LinuxWorkspace.vmdk verbose nomodeset
 
 - initrd /ubuntuvm-vhd-helper/initrd.img
 
  复制代码 
其中host参数代指vhd所在物理磁盘的分区设备文件路径; 
root代指vhd挂载后linux rootfs所在分区(vdfuse挂载后会在/dev/vdhost下形成EntireDisk以及各个分区,如Partition1。一般均为Partition1,即第一个主分区) 
vdisk代指vhd所在分区中的路径 
kernel和initrd的路径设置为复制出来的路径 
 
此处参数设置错误时或不知道具体值时,可以随便填写,initramfs会自动出错,并弹出shell供诊断。相应的查看各个参数对应信息即可 
 
grub2配置可以根据niumao前辈的配置照葫芦画瓢 
 
 
 
原理: 
vdfuse与kpartx不同,会有进程驻留。vdfuse方案是物理设备 → vdfuse → loop partition → ext4,可以注意到比kpartx多了一级,这导致了systemd默认的卸载方式无法正常卸载掉vhd。 
initramfs-tool提供的结构并不能很好的控制systemd,所以我没能实现解决buffer io的问题 
dracut相比initramfs-tool与systemd结合更紧密,因而更容易控制systemd对我们挂载点的操作。所以我们得以借助dracut的强大功能解决buffer io error。 
 
1. 用exec -a @ntfs-3g ntfs-3g方法挂载hostdev到/dev/host,避免被systemd关机时杀掉。挂载到/dev下在关机流程会被systemd忽略,从而避免被remount为ro(见systemd/src/core/umount.c/umount.c) 
2. 用exec -a方法挂载vdfuse到/dev/vdhost。原理同上 
3. 不提供root参数,使用mount hook,从而避免dracut使用systemd的挂载和设备探测功能(systemd对于loop设备支持较差) 
4. 进行pre-shutdown hook,关机前按顺序卸载ext4、vdfuse、ntfs,从而避免buffer i/o error 
 
 
调试方法: 
1. initramfs-tool方案调试方法:添加参数: debug systemd.log_level=debug systemd.log_target=kmsg log_buf_len=1M printk.devkmsg=on enforcing=0 
2. dracut方案调试方法: 添加参数debug rd.break=pre-shutdown rd.break=shutdown debug systemd.log_level=debug systemd.log_target=kmsg printk.devkmsg=on enforcing=0 log_buf_len=10M verbose nomodeset rd.udev.debug rd.timeout=300 rd.break=initqueue rd.break=pre-mount rd.break=mount 
 
 
 
 
 
 
 |