Создаем свой загрузочный диск Linux

В статье описывается, как создать собственный загрузочный диск Linux (оптический диск или флешку), добавить в него только нужные программы и убрать все лишнее. Полученный образ в экспериментах занял менее 25 Мб. Он позволяет быстро загружаться, работать в текстовом режиме, создавать, редактировать, удалять файлы на разных файловых системах, имеет поддержку русского языка. За основу взят Debian.

Для того, чтобы создать загрузочный диск, необходимо создать его образ, iso-файл, который затем можно записать на оптический диск или флешку. В общем случае порядок загрузки выглядит следующим образом:

Порядок загрузки LinuxПорядок загрузки Linux

Подготовка

Для начала необходимо создать пустой каталог livecd, в котором будет проводиться дальнейшая работа. В нем необходимо создать подкаталог iso, в котором будет формироваться образ диска.  Используемые в статье названия файлов и каталогов не являются обязательными. Если какое-то название является обязательным, об этом будет указано.

Добавление загрузчика UEFI

Для добавления загрузчика UEFI понадобятся файлы, которые могут отсутствовать на компьютере. Чтобы их получить, необходимо установить ряд пакетов:

apt install grub-efi-amd64-bin dosfstools mtools

Далее в каталоге livecd необходимо создать вспомогательные файлы:

grub.cfg:

set timeout=1
menuentry 'Live CD' {
    linux    /linux
}

grub-inst.cfg:

search --file --set=root /grub.cfg
if [ -e ($root)/grub.cfg ]; then
	set prefix=($root)
	configure $prefix/grub.cfg
else
	echo can't find grub.cfg
fi

Загрузчик добавляется командой:

cp grub.cfg iso/grub.cfg
mkdir -p EFI/BOOT
grub-mkimage --prefix '' --config "grub-inst.cfg" -O x86_64-efi -o 'EFI/BOOT/bootx64.efi' acpi appleldr boot configfile efi_gop efi_uga elf fat fixvideo font gettext gfxmenu gfxterm gfxterm_background gfxterm_menu iso9660 linux memdisk minicmd normal part_gpt part_msdos search sleep usb video video_bochs video_cirrus video_fb videotest
mkdosfs -F12 -n "EFI" -C iso/efiboot.img 2048
mcopy -s -i iso/efiboot.img EFI ::
rm -r EFI

В результате в каталоге iso появятся два файла: grub.cfg и efiboot.img.

Добавление загрузчика BIOS

На древних компьютерах UEFI отсутствует. Вместо этого там используется BIOS. Чтобы создаваемый диск мог загружаться на таких компьютерах тоже, необходимо добавить загрузчик BIOS. Для получения файлов необходимо установить ряд пакетов:

apt install isolinux syslinux-common

Далее в каталоге livecd необходимо создать вспомогательный файл (отступ не обязателен):

syslinux.cfg:

UI menu.c32
PROMPT 0
TIMEOUT 1
MENU TITLE Boot Menu
LABEL default
	MENU LABEL Live CD
	linux linux
	initrd init.ram

После этого нужно выполнить команды:

cp syslinux.cfg iso
cp /usr/lib/ISOLINUX/isolinux.bin iso
cp /usr/lib/syslinux/modules/bios/{ldlinux.c32,menu.c32,libutil.c32,libcom32.c32} iso

В результате в каталоге iso появится еще ряд файлов.

Создание образа и диска

Образ (файл livecd.iso) создается командой:

xorriso -as mkisofs -r -o livecd.iso -isohybrid-mbr /usr/lib/ISOLINUX/isohdpfx.bin -partition_offset 16 -J -l -joliet-long -c boot.cat -b isolinux.bin -no-emul-boot -boot-load-size 4 -boot-info-table -eltorito-alt-boot -e efiboot.img -no-emul-boot -isohybrid-gpt-basdat iso

Далее эта команда будет называться (1). Образ записывается на оптический диск командой:

wodim livecd.iso

Образ записывается на флешку командой (будем считать, флешка является устройством /dev/sdz):

dd if=livecd.iso of=/dev/sdz

Указывать нужно саму флешку, а не раздел на ней, то есть /dev/sdz, но не /dev/sdz1.

Удалить linux с флешки и отформатировать ее обратно можно командой:

wipefs -a /dev/sdz
mkfs.exfat /dev/sdz

Проверка работоспособности созданного образа

Проверить созданный образ можно как на настоящем компьютере, так и с помощью виртуальной машины. Например, в VMware можно создать новую виртуальную машину без жесткого диска, но с CD-приводом, в качестве образа указать файл livecd.iso. Переключение между UEFI и BIOS осуществляется в настройках этой виртуальной машины в разделе Options — Advanced — Firmware type.

Переключение между UEFI и BIOS в VMwareПереключение между UEFI и BIOS в VMware

При загрузке в режиме UEFI отобразится меню загрузчика:

Загрузчик GRUBЗагрузчик GRUB

Загрузчик попытается загрузить Linux, но поскольку он еще не добавлен, отобразится сообщение об ошибке »file /linux not found».

При загрузке в режиме BIOS отобразится меню загрузчика:

Загрузчик SyslinuxЗагрузчик Syslinux

Поскольку Linux еще не добавлен, это меню уйдет в бесконечный цикл.

Добавление ядра Linux

Ядро Linux обычно хранится в каталоге /boot и представляет собой файл с названием »vmlinuz-XXX», где XXX — версия ядра. Добавить текущее ядро в создаваемый образ можно командой:

cp $(ls -t /boot/vmlinuz-$(uname -r) | head -n 1) iso/linux

После этого в каталоге iso появится файл linux. Далее необходимо пересобрать образ командой (1), указанной выше в разделе «Создание образа и диска».

Чтобы не зависеть от версии ядра, указанной здесь командой »cp $(ls -t …» ядро не просто копируется, а переименовывается в linux без указания версии. Именно это имя файла указано вторым элементом в файлах grub.cfg и syslinux.cfg в строке »linux linux». Если в каталоге /boot имеется несколько ядер, можно использовать любое, главное, запомнить его версию. Далее в статье будет рассматриваться использование текущего ядра.

После добавления ядра загрузчики UEFI и BIOS перестанут ругаться на его отсутствие и запустят его выполнение. В свою очередь ядро успешно запустится и, в условиях отсутствия файловой системы, выдаст сообщение об ошибке »Kernel Panic. Unable to mount root fs».

Добавление файловой системы

В данной статье в качестве корневой файловой системы рассматривается использование временной файловой системы, которая размещается в оперативной памяти (initramfs). Для ее создания необходимо создать в каталоге livecd подкаталог initramfs, в нем пустой файл с названием init. Файл с таким названием почему-то обязательно должен быть и находиться в корне файловой системы, иначе ядро проигнорирует такую initramfs. Далее необходимо выполнить команды:

cd initramfs
find . | cpio -o -H newc --owner=root.root | gzip -9 > ../iso/init.ram
cd ..

В результате в подкаталоге iso появится файл init.ram. Его необходимо указать в файле grub.cfg, добавив перед закрывающей фигурной скобкой строку »initrd /init.ram».

grub.cfg:

set timeout=1
menuentry 'Live CD' {
    linux    /linux
    initrd   /init.ram
}

В конец файла syslinux.cfg нужно добавить строку »initrd init.ram».

syslinux.cfg:

UI menu.c32
PROMPT 0
TIMEOUT 1
MENU TITLE Boot Menu
LABEL default
MENU LABEL LiveCD
linux linux
initrd init.ram

После этого необходимо пересобрать образ командой (1), указанной выше в разделе «Создание образа и диска». Теперь при загрузке Linux будет сообщать об ошибке »Kernel Panic. No working init found». В данном случае это сообщение означает, что ядро успешно запустилось и даже признало файловую систему initramfs, нашло в нем файл init и попыталось его запустить, но не получилось, потому что этой пустой файл.

Минимальный работающий init

В каталоге initramfs нужно создать следующую структуру файлов:

├─lib (каталог с двумя файлами)
│ ├─ ld-linux-x86-64.so.2 (из /lib/x86_64-linux-gnu/ld-linux-x86-64.so.2)
│ └─ libc.so.6 (из /lib/x86_64-linux-gnu/libc.so.6)
├─lib64 (символическая ссылка на lib)
└─init (из /bin/dash)

В initramfs должен получиться один подкаталог lib с двумя файлами в нем, одна символическая ссылка lib64 и один файл init. В скобках указано, какие файлы нужно взять из текущего работающего компьютера. Файлы должны быть исполняемыми. Далее необходимо пересобрать образ командами:

cd initramfs
find . | cpio -o -H newc --owner=root.root | gzip -9 > ../iso/init.ram
cd ..
xorriso -as mkisofs -r -o livecd.iso -isohybrid-mbr /usr/lib/ISOLINUX/isohdpfx.bin -partition_offset 16 -J -l -joliet-long -c boot.cat -b isolinux.bin -no-emul-boot -boot-load-size 4 -boot-info-table -eltorito-alt-boot -e efiboot.img -no-emul-boot -isohybrid-gpt-basdat iso

Далее эти команды будут называться командой (2).

Если все сделать правильно, получится минимальный работающий образ с командной строкой. В моем случае его размер составил чуть более 12 Мб. В командной строке не работают никакие команды, они будут добавлены позже. Если по-прежнему отображается сообщение об ошибке »No working init found», необходимо проверить:

  1. Названия файлов, каталога и символической ссылки.

  2. Права доступа: в каталог можно зайти, файлы должны быть исполняемыми.

  3. Файлы предназначены для одной архитектуры x86_64. На некоторых компьютерах могут одновременно быть установлены 32 и 64 битные версии программ и библиотек. Узнать архитектуру файла можно командой objdump -p <имя файла>. В начале вывода должна быть строка типа »формат файла elf64-x86–64».

Если завершить работу командной строки командой exit, ядро выдаст ошибку »Kernel Panic. Attempted to kill init». Командная строка была первым и единственным процессом (PID=1) и после его завершения ядро не знает, что делать дальше.

Наполнение initfamfs простыми программами

Простой программой здесь называется программа, состоящая из одного исполняемого файла, например dash, mkdir, mount. В противоположность сложные программы содержат большое количество обязательных дополнительных файлов, например: текстовые, аудио и видеоредакторы, браузеры и т.п.

По сложившейся традиции программы размещаются в каталоге bin. В каталоге initramfs нужно создать подкаталог bin и скопировать туда файл ls из одноименного каталога работающей системы.

Многие программы в своей работе используют вспомогательные so-файлы (программные библиотеки), без которых они не запустятся. Узнать, какие so-файлы нужны программе, можно с помощью уже упоминавшейся команды objdump -p <имя файла>.

Вывод команды »objdump -p ls»:

…
Динамический раздел:
NEEDED libselinux.so.1
NEEDED libc.so.6
…

Файл libc.so.6 в initfamfs уже есть, а вот файл libselinux.so.1 нужно найти на работающем компьютере и скопировать в каталог initramfs/lib к другим so-файлам. Список каталогов, в которых нужно искать требуемый so-файл, можно взять из файлов /etc/ld.so.conf и /etc/ ld.so.conf.d/*. Библиотека libselinux.so.1 в свою очередь также зависит от других библиотек. С помощью команды objdump -p libselinux.so.1 можно узнать, что кроме уже имеющихся файлов libc.so.6 и libc.so.6 дополнительно требуется libpcre2–8.so.0. На работающей системе этот файл представляет собой символическую ссылку на файл libpcre2–8.so.0.11.0. При копировании в initramfs можно оставить имеющуюся структуру, т.е. скопировать libpcre2–8.so.0 как символическую ссылку и далее скопировать файл libpcre2–8.so.0.11.0. Можно поступить по-другому и скопировать файл libpcre2–8.so.0.11.0, переименовав его в libpcre2–8.so.0. Команда objdump -p libpcre2-8.so.0 показывает, что каких-либо других so-файлов, отсутствующих в initramfs, больше не требуется. В результате должна получиться следующая структура каталога initramfs:

├─bin
│ └─ ls
├─lib
│ ├─ ld-linux-x86-64.so.2
│ ├─ libc.so.6
│ ├─ libpcre2-8.so.0
│ └─ libselinux.so.1
├─lib64 (=> lib)
└─init

В скобках указана символьная ссылка.

При копировании so-файлов нужно не забывать проверять их архитектуру, если на компьютере есть файлы разных архитектур.

С помощью команды (2) из раздела «Минимальный работающий init» можно пересоздать образ, запустить и убедиться, что команда ls работает.

Автоматизация наполнения initfamfs простыми программами

В предыдущем разделе показано, что добавление простых программ требует рекурсивного поиска и копирования дополнительных so-файлов. Эту задачу можно автоматизировать, написав свой скрипт, либо используя программу copyso. При ее использовании достаточно указать требуемые для копирования программы, после чего выполнить еще несколько команд, чтобы сформировать оставшуюся часть структуры каталогов. В каталоге livecd необходимо заново создать пустой подкаталог initramfs и выполнить следующие команды:

copyso -p dash ls ln mkdir cat mount initramfs
ln -s lib initramfs/lib64
ln -s x86_64-linux-gnu/ld-linux-x86-64.so.2 initramfs/lib64/ld-linux-x86-64.so.2
ln -s usr/bin/dash initramfs/init

Первая команда копирует программы dash, ls, ln, mkdir, cat, mount. Остальные нужны, чтобы правильно сформировать структуру каталогов.

Получившаяся структура каталогов

├─lib
│ ├─x86_64-linux-gnu
│ │ ├─ ld-linux-x86-64.so.2
│ │ ├─ libblkid.so.1 (=> libblkid.so.1.1.0)
│ │ ├─ libblkid.so.1.1.0
│ │ ├─ libc.so.6
│ │ ├─ libmount.so.1 (=> libmount.so.1.1.0)
│ │ ├─ libmount.so.1.1.0
│ │ ├─ libpcre2-8.so.0 (=> libpcre2-8.so.0.11.0)
│ │ ├─ libpcre2-8.so.0.11.0
│ │ └─ libselinux.so.1
│ └─ ld-linux-x86-64.so.2 (=> x86_64-linux-gnu/ld-linux-x86-64.so.2)
├─ lib64 (=> lib)
├─usr
│ └─bin
│   ├─ cat
│   ├─ dash
│   ├─ ln
│   ├─ ls
│   ├─ mkdir
│   └─ mount
└─ init (=> usr/bin/dash)

В скобках указаны символьные ссылки.

После этого можно командой (2) из раздела «Минимальный работающий init» пересоздать образ, запустить и убедиться, что добавленные команды работают.

Добавление программ посложнее

Универсальный способ копирования сложных программ в initramfs предложить сложно. В самый неожиданный момент времени такая программа может обратиться к какому-нибудь файлу, который нигде не указан, но подразумевается автором программы и даже создается при штатной установке. Описываемый в статье способ подразумевает копирование существующих программ, а не их полноценную установку. Однако можно предложить способ, основанный на особенностях deb-пакетов в операционной системе Debian. В каталоге /var/lib/dpkg/info есть list-файлы для каждого установленного пакета. В этих list-файлах содержится перечень файлов, созданных при установке пакета. Не все из них являются обязательными, но, как сказано выше, с каждым нужно разбираться индивидуально либо копировать все, что есть. Для копирования всего пакета можно использовать следующий скрипт:

DEBS="e2fsprogs mc"
copy_deb()
{
	for F in $(cat /var/lib/dpkg/info/$1.list); do
		if [ -d $F ]; then
			mkdir -p initramfs$F
		elif [ -f $F ]; then
			copyso $copy_params /$F initramfs
		fi
	done
}
for DEB in $DEBS; do
	copy_deb $DEB
done
rm -rf initramfs/usr/share/{applications,doc,doc-base,info,man,lintian,libc-bin,locale,menu,pixmaps}

В первой строке перечисляются нужные пакеты (в примере их два), дальнейшие команды копируют файлы. Последняя команда удаляет файлы, которые скорее всего не понадобятся.

Если добавить таким образом Midnight Commander (команда mc), можно убедиться, что он запустится (условно). Скорее всего, таким вы его еще не видели.

Добавление модулей ядра

Модули ядра это файлы с расширением «ko», которые расположены в подкаталогах каталога /lib/modules/<версия ядра>/. Есть модули для работы с жесткими дисками, с разными файловыми системами, с устройствами USB и т.д. Составление перечня модулей, необходимых для каждого конкретного устройства, в данной статье не рассматривается. Предполагается, читатель знает, какие именно модули ему нужны.

Доя добавления нужных модулей в initramfs нужно скопировать их в формируемую файловую структуру с сохранением пути. Например, модуль для работы с жесткими дисками /lib/modules/6.0.0–6-amd64/kernel/drivers/scsi/sd_mod.ko нужно скопировать в initramfs/lib/modules/6.0.0–6-amd64/kernel/drivers/scsi/sd_mod.ko, создав недостающие каталоги (на момент написания статьи использовалось ядро версии 6.0.0–6-amd64).

Как и so-файлы, ko-файлы могут требовать для своей работы наличия других ko-файлов. Эти зависимости указаны в файле /lib/modules//modules.dep. В частности, sd_mod.ko зависит от scsi_mod.ko, scsi_common.ko, crc64.ko и других. Их также необходимо скопировать с сохранением пути и учетом того, что они, в свою очередь, могут зависеть от других модулей и так далее рекурсивно.

Эту задачу, как и предыдущую, можно автоматизировать, написав свой скрипт, либо используя программу copyko. При ее использовании для добавления модулей ядра необходимо выполнить команды:

copyko ahci sd_mod initramfs/lib/modules/$(uname -r)
cp /lib/modules/$(uname -r)/modules.builtin* initramfs/lib/modules/$(uname -r)
depmod -b initramfs

Первая команда копирует модули ahci и sd_mod в initramfs. Вторая копирует информацию о модулях (функционале ядра), которые уже встроены в ядро. Последняя команда создает информацию о зависимостях модулей друг от друга. Стоит отметить, что речь идет только о дисках SATA. При работе в VMware, если жесткий диск представлен как SCSI, дополнительно необходим модуль mptspi. При работе с другими дисками, например NVMe, нужны свои модули.  Также понадобится программа modprobe:

copyso -p modprobe initramfs

После обновления образа командой (2) можно загрузиться с него и проверить, что появился доступ к жесткому диску компьютера. Для этого в командной строке созданного образа необходимо выполнить команды:

mount -nt devtmpfs none /dev
modprobe ahci
modprobe sd_mod

При использовании других типов дисков, отличных от SATA, необходимо загрузить соответствующие им модули. После выполнения указанных команд в каталоге /dev/ появятся устройства типа sda, sda1, sda2, обозначающие найденные жесткие диски и разделы на них. Для автоматической загрузки всех имеющихся модулей можно использовать команду:

for m in $(find /lib/modules -name '*.ko'); do modprobe $(basename -s .ko $m); done

Использующиеся здесь программы find и basename нужно заранее добавить в образ. Чтобы эта команда выполнялась автоматически, нужно создать загрузочный скрипт:

#!/usr/bin/dash
mount -nt devtmpfs none /dev
for m in $(find /lib/modules -name '*.ko'); do modprobe $(basename -s .ko $m); done
/usr/bin/dash

Этот скрипт нужно сохранить в файл initramfs/init вместо созданной ранее символической ссылки, и сделать его исполняемым.

Помимо указанных двух модулей скорее всего понадобится множество других. Для автоматизации их добавления можно предложить скрипт:

Скрипт добавления популярных модулей

KERVER=$(uname -r)
MODS="sr_mod sd_mod ahci mptspi"

# Для оптических и жестких дисков:
MODS="$MODS sg evdev ata_generic ata_piix libsas uas mptsas"
for m in $(find /lib/modules/$KERVER/kernel/drivers/ata/ -name 'sata*.ko'); do MODS="$MODS $(basename -s .ko $m)"; done

# Для SSD дисков:
for m in $(find /lib/modules/$KERVER/kernel/drivers/nvme/ -name '*.ko'); do MODS="$MODS $(basename -s .ko $m)"; done

# Файловые системы:
MODS="$MODS squashfs overlay ext4 vfat exfat fuse udf isofs loop hfsplus libcrc32c crc32c-intel crc32c_generic"

# Языковые кодировки файловых систем:
for m in $(find /lib/modules/$KERVER/kernel/fs/nls/ -name '*.ko'); do MODS="$MODS $(basename -s .ko $m)"; done

# USB:
MODS="$MODS ehci-pci ohci-pci uhci-hcd xhci-pci usbhid i2c-hid psmouse hid-generic"

# EFI:
MODS="$MODS efivarfs"

copyko $MODS initramfs/lib/modules/$KERVER
cp -uf /lib/modules/$KERVER/modules.builtin* initramfs/lib/modules/$KERVER
depmod -b initramfs

Настройка консоли

Далее приводится несколько рекомендаций, как настроить консоль образа, сделать ее более удобной. Для этого требуется программа настройки консоли:

apt install console-setup

Чтобы в Midnight Commander заработали клавиши со стрелками, необходимо добавить в образ нужный файл командами:

mkdir -p initramfs/lib/terminfo
cp -r /lib/terminfo/l initramfs/lib/terminfo

Чтобы Midnight Commander стал цветным, необходимо добавить цвета в образ:

cp -r /usr/share/mc/{skins,syntax,mc.charsets,mc.lib} initramfs/usr/share/mc

Чтобы Midnight Commander не выдавал сообщение «Pipe failed» при попытке редактирования файлов:

mkdir initramfs/bin
ln -s /usr/bin/dash initramfs/bin/sh

Чтобы отображался текст на русском языке, нужно добавить русский шрифт:

cp $(find /etc/console-setup -name '*.psf.gz' | head -n 1) initramfs/etc/font.psf.gz
gunzip initramfs/etc/font.psf.gz

а в скрипт «init» добавить команду загрузки шрифта:

setfont /etc/font.psf

Чтобы можно было вводить с клавиатуры текст на русском языке, нужно добавить русскую раскладку клавиатуры:

cp $(find /etc/console-setup -name '*.kmap.gz' | head -n 1) initramfs/etc/keys.kmap.gz
gunzip initramfs/etc/keys.kmap.gz

а в скрипт «init» добавить команду загрузки этой раскладки:

loadkeys /etc/keys.kmap

Чтобы в командной строке работали клавиши со стрелками, нужно вместо dash использовать bash.

Чтобы отображались имена файлов на русском языке, нужно добавить в образ локаль:

mkdir -p initramfs/usr/lib/locale
cp /usr/lib/locale/locale-archive initramfs/usr/lib/locale

а в скрипт «init» добавить команду загрузки локали:

export LANG=ru_RU.UTF-8

Итого

На основе представленной информации создан небольшой набор скриптов (2 шт.) для создания загрузочного диска. Для работы с ними необходимо:

1. Установить требуемые deb пакеты

apt install grub-efi-amd64-bin dosfstools mtools isolinux syslinux-common console-setup

2. Установить программы copyso и copyko.

3. Загрузить сами скрипты и вспомогательные файлы.

4. Запустить скрипт 1.mkinitramfs.sh. В результате создастся подкаталог initramfs.

5. Изменить содержимое initramfs по своему желанию.

6. Запустить скрипт 2.mkiso.sh. В результате создастся файл-образ livecd.iso.

7. Записать образ на флешку или диск.

Чтобы вернуть initramfs в исходное состояние, необходимо заново запустить 1.mkinitramfs.sh.

Надеюсь, эта статья будет полезной тем, кто изучает основы Linux.

© Habrahabr.ru