- PVSM.RU - https://www.pvsm.ru -
Загрузка Linux с VHD [1] может пригодиться в различных сценариях, например, когда на компьютере установлена Windows и есть необходимость в Linux, но WSL или виртуальной машины с Linux недостаточно, а разбивать диск на разделы нет желания. Microsoft позволяет грузить Windows с VHD «из коробки» начиная со старших редакций Windows 7. Но что делать, если возникла необходимость загрузить таким способом Linux?
На форумах часто можно встретить мнение, что загрузить Linux с VHD либо нельзя [2], либо очень сложно [3]. Полезной информации в интернете на эту тему действительно мало. Базовая идея, как это осуществить, описана тут [4]. Суть в следующем:
Необходимо убедиться в поддержке NTFS на всех этапах.
Добавить в загрузочные скрипты ОС команду монтирования loop-устройства.
Перестроить initramfs.
Убедиться, что все необходимые утилиты добавлены в образ, обновить initramfs внутри VHD.
В случае legacy-зарузки (BIOS) и использования штатного загрузчика Windows добавить grub4dos в меню bootmgr, а в меню grub4dos добавить пункт для загрузки с VHD.
Практическое применение этой идеи для Arch Linux описано тут [7]. В этой статье я проведу аналогичный эксперимент с Debian [8]. Предполагается, что читатель имеет представление о работе с консолью в Windows и в Linux, умеет работать со стандартными системными утилитами, с ПО для виртуализации и т.п. — элементарные вещи подробно не расписаны.
Процесс загрузки будет выглядеть так: bootmgr -> grub4dos -> initramfs -> debian. Рассмотрим подготовку каждого этапа справа налево.
Для начала необходимо создать пустой образ VHD с фиксированным размером. Если нужно минимизировать размер образа, то для экспериментов с CLI достаточно создать диск объемом ~1,5 Гб. Для рабочей системы с GUI можно ограничиться объемом 10 Гб (с условием хранения пользовательских данных вне VHD).
Создадим VHD с помощью diskpart.exe:
create vdisk file=<path_to_vhd>debian.vhd type=fixed maximum=1500
Далее необходимо установить Debian на VHD. Я для этого воспользовался VirtualBox 6.1 [9], устанавливал debian-10.8.0-amd64-netinst.iso [8]. Параметры виртуальной машины — по умолчанию, новый диск создавать не надо, достаточно подключить ранее созданный debian.vhd.

Установка Debian стандартна, обращу внимание только на некоторые моменты.
При разметке диска я создал один загрузочный раздел ext4. Раздел подкачки на VHD я делать не стал, после установки можно разместить файл или раздел подкачки в удобном месте.

При выборе дополнительного ПО для установки я оставил только SSH-сервер и стандартные системные утилиты. Всё остальное можно поставить потом, по необходимости. GRUB установлен в MBR. Если при установке была выбрана русская локаль, то после установки можно добавить локаль en_US командой dpkg-reconfigure locales.
В установленную систему необходимо добавить поддержку NTFS и утилиту partprobe, которая позволяет сообщить [10] ядру ОС о необходимости повторного чтения таблицы разделов жёсткого диска.
apt-get install ntfs-3g parted
Затем надо подготовить скрипты для initramfs.
initramfs [11] — это начальная файловая система в оперативной памяти, которая содержит утилиты и скрипты, требуемые для монтирования файловых систем перед вызовом init, располагающегося в корневой файловой системе.
Скрипты для initramfs созданы на основе документации [12]. Наши дополнения для initramfs мы будем размещать в следующих каталогах.
/etc/initramfs-tools/hooks/ — здесь размещаются скрипты, которые запускаются при генерации initramfs-образа. Тут мы разместим скрипт для добавления в initramfs утилиты partprobe с необходимыми библиотеками.
/etc/initramfs-tools/scripts/local-top/ — после выполнения этих скриптов загрузчик считает, что root-устройство смонтировано. Т.е. здесь будет скрипт для монтирования VHD.
Скрипт для добавления partprobe в initramfs возьмем из этой статьи [13] с добавлением еще одной библиотеки. Надо создать файл partcopy и сделать его исполняемым:
#!/bin/sh
cp -p /sbin/partprobe "$DESTDIR/bin/partprobe"
cp -p /lib/x86_64-linux-gnu/libparted.so.2 "$DESTDIR/lib/x86_64-linux-gnu/libparted.so.2"
cp -p /lib/x86_64-linux-gnu/libreadline.so.7 "$DESTDIR/lib/x86_64-linux-gnu/libreadline.so.7"
cp -p /lib/x86_64-linux-gnu/libtinfo.so.6 "$DESTDIR/lib/x86_64-linux-gnu/libtinfo.so.6"
Скрипт для монтирования VHD сделан на основе скрипта для Arch Linux [7] с учетом особенностей выбранного дистрибутива Linux. Скрипт необходимо сохранить под именем loop_boot_vhd и сделать исполняемым:
#!/bin/sh
PREREQ=""
prereqs()
{
echo "$PREREQ"
}
case $1 in
get pre-requisites
prereqs)
prereqs
exit 0
;;
esac
cmdline=$(cat /proc/cmdline)
#cmdline="root=/dev/loop0p1 loop_file_path=/debtst.vhd loop_dev_path=/dev/sda2"
for x in $cmdline; do
value=${x#=}
case ${x} in
root=) value_loop_check=${value#loop}
if [ x$value_loop_check != x$value ]
then loop_dev=${value%p}
loop_part_num=${value##p}
else echo "Root device is not a loop device. loopboot hook will be terminated."; return
fi ;;
loop_file_path=) loop_file_path=$value ;;
loop_dev_path=*) loop_dev_path=$value ;;
esac
done
if [ -z $loop_file_path ] || [ -z $loop_dev_path ]
then echo "Either loop_file_path or loop_dev_path parameter does not specified. loopboot hook will be terminated."; return
fi
#modprobe fuse
modprobe loop max_part=64 max_loop=8
loop_dev_type=$(blkid -s TYPE -o value $loop_dev_path)
if [ ! -d /host ]; then
mkdir /host
fi
if [ "$loop_dev_type" != "ntfs" ]
then mount -t $loop_dev_type $loop_dev_path /host; echo "mount -t $loop_dev_type $loop_dev_path /host"; echo "mounted using mount";
else ntfs-3g $loop_dev_path /host; echo "mounted using ntfs-3g";
fi
losetup $loop_dev /host$loop_file_path
partprobe $loop_dev
Немного подробнее поясню логику работы скрипта. Обработка prereqs рекомендована в документации [12]. В переменную cmdline попадает строка инициализации из grub4dos, например, root=/dev/loop0p1 loop_file_path=/debian.vhd loop_dev_path=/dev/sda2. Далее идет разбор этой строки и из нее определяется номер партиции на loop-устройстве, а в переменные loop_dev_path и loop_file_path сохраняются путь к устройству, на котором хранится VHD-файл, и путь к VHD-файлу на устройстве. Если данные для этих переменных не переданы, то скрипт прекращает работу и система пытается загрузиться в обычном режиме. Если переменные определены, то загружается модуль ядра для подержки loop-устройств с указанием в параметрах максимального количества loop-устройств и максимального количества таблиц разделов на loop-устройстве. Затем командой blkid определяется тип файловой системы диска, на котором хранится VHD-файл. Если VHD лежит на NTFS, то монтирование производится с помощью команды ntfs-3g, иначе — командой mount. Монтирование производится в каталог /host (который при необходимости предварительно создается). После этого VHD подключается в систему командой losetup, а затем partprobe сообщает ядру о новом диске.
После размещения скриптов в нужные каталоги (/etc/initramfs-tools/scripts/local-top/loop_boot_vhd и /etc/initramfs-tools/hooks/partcopy) необходимо пересобрать initramfs командой:
update-initramfs -c -k all
Для дальнейшей настройки надо запомнить номер версии ядра: /boot/initrd.img-4.19.0-14-amd64 и /boot/vmlinuz-4.19.0-14-amd64.
На этом образ готов к запуску на реальном железе, можно выключать виртуальную машину и приступать к подготовке загрузчика. Готовый образ debian.vhd надо скопировать в корень диска C:, дальнейшие скрипты написаны исходя из предположения, что VHD находится в корне NTFS-раздела.
Для начала надо скачать актуальную версию grub4dos [14]. Работа с этой утилитой в различных источниках описана достаточно подробно [15]. Настройка сводится к следующему:
необходимо найти раздел, в корне которого лежит VHD-файл, и сделать его корневым для всех команд в текущем пункте меню (команда find --set-root);
затем загрузить образ жесткого диска (команды map ...vhd и map --hook);
далее подключенный образ указать как корневое устройство (команда root);
и указать параметры запуска Linux (kernel и initrd).
Получается файл menu.lst с таким содержимым:
title debian
find --set-root --ignore-floppies --ignore-cd /debian.vhd
map /debian.vhd (hd3)
map --hook
root (hd3,0)
kernel /boot/vmlinuz-4.19.0-14-amd64 root=/dev/loop0p1 rw loop_file_path=/debian.vhd loop_dev_path=/dev/sda2
initrd /boot/initrd.img-4.19.0-14-amd64
Тут надо обратить внимание на один момент: в команде kernel инициализируются переменные, которые передаются в initramfs и используются в ранее созданном скрипте loop_boot_vhd.
В моем примере переменные заполнены исходя из моей конфигурации компьютера: один диск с Windows, разбитый на два раздела (загрузочный "System Reserved" и основной NTFS), а внутри VHD — один раздел ext4.
Обратите внимание: в зависимости от версии Windows и особенностей установки ОС возможны незначительные отличия.
Первое, что надо сделать, — подключить скрытый раздел с bootmgr, в примере ниже я подключаю скрытый раздел "System Reserved" в каталог C:mnt (каталог должен быть предварительно создан). Команды выполняются в diskpart.exe:
list volume
Volume 1 System Rese NTFS Partition 549 MB Healthy System
Volume 2 C NTFS Partition 49 GB Healthy Boot
select volume 1
assign mount=C:mnt
После этого надо распаковать в каталог C:mnt файлы из архива с grub4dos [14]: grldr и grldr.mbr. В этот же каталог надо скопировать файл menu.lst, созданный на предыдущем шаге. После этого раздел можно отключить в diskpart.exe:
remove mount=C:mnt
Чтобы настроить отображение пункта меню при загрузке Windows, надо сделать следующее:
bcdedit.exe -create -d grub4dos -application bootsector
*The entry {GUID} was successfully created.
В ответ будет сообщен GUID нового пункта меню. Полученный GUID используется в следующих командах:
bcdedit.exe -set {GUID} device boot
bcdedit.exe -set {GUID} path grldr.mbr
bcdedit.exe -displayorder {GUID} -addlast
Тут подробно не останавливаюсь, все команды очевидны и хорошо описаны в документации. Ну, и чтобы не переключаться лишний раз между графическим и текстовым режимами:
bcdedit.exe /set {default} bootmenupolicy legacy
На этом всё: можно перезагрузить компьютер, выбрать в меню загрузки grub4dos, затем Debian, после чего должен загрузиться Linux.
В этом случае, скорее всего, неверно указаны параметры с путями к устройству, на котором находится VHD-файл, или раздел на loop-устройстве. Если загрузка останавливается на уровне grub4dos, то в консоли надо последовательно вводить команды, перечисленные в menu.lst, и смотреть на результаты, в зависимости от которых правильно указать параметры для загрузки Linux. Если загрузка останавливается в initramfs, то надо проверить доступность необходимых устройств на этом этапе. Проверить можно, последовательно вводя команды из скрипта loop_boot_vhd (основное: смонтировать нужные разделы, найти VHD, подключить его, проверить присвоенный номер партиции с Linux, в моем примере — loop0p1).
Это немного другая история, надеюсь, позже найду время и проведу аналогичный эксперимент с UEFI.
Автор: sorcodiv
Источник [16]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/linux/362490
Ссылки в тексте:
[1] VHD: https://ru.wikipedia.org/wiki/VHD
[2] нельзя: https://forum.ubuntu.ru/index.php?topic=284154.0
[3] очень сложно: https://forum.ru-board.com/topic.cgi?forum=5&topic=37297&start=340#2
[4] тут: http://reboot.pro/index.php?showtopic=20603&page=5#entry210378
[5] loop: https://help.ubuntu.ru/wiki/loop
[6] устройств: https://en.wikipedia.org/wiki/Loop_device
[7] тут: http://reboot.pro/index.php?showtopic=20603&page=2#entry200203
[8] Debian: https://www.debian.org/
[9] VirtualBox 6.1: https://www.virtualbox.org/
[10] позволяет сообщить: http://rus-linux.net/MyLDP/BOOKS/LSA/ch05.html
[11] initramfs: https://wiki.gentoo.org/wiki/Initramfs/Guide/ru
[12] документации: http://manpages.ubuntu.com/manpages/xenial/man8/initramfs-tools.8.html
[13] этой статьи: https://habr.com/ru/post/457260/
[14] grub4dos: https://github.com/chenall/grub4dos/releases
[15] достаточно подробно: http://greenflash.su/Grub4Dos/Grub4dos.htm
[16] Источник: https://habr.com/ru/post/547150/?utm_source=habrahabr&utm_medium=rss&utm_campaign=547150
Нажмите здесь для печати.