[Перевод] Собираем и запускаем минимальное ядро Linux
Однажды на работе техлид порекомендовал мне проштудировать книгу Understanding the Linux Kernel Бове и Чезати. В ней рассмотрена версия Linux 2.6, сильно не дотягивающая до более современной версии 6.0. Но в ней явно ещё много ценной информации. Книга толстая, поэтому на её изучение мне потребовалось немало времени. Занимаясь по ней, я решил настроить такую среду разработки, в которой я мог бы просматривать и изменять новейшую версию ядра Linux — чтобы было ещё интереснее.
Есть и другие статьи, в которых рассказано, как собрать ядро Linux. Но в этой статье я немного иначе организую и подаю информацию.
❯ Этапы
Работа пойдёт в 2 основных этапа:
Собираем и запускаем Linux на qemu
Собираем и запускаем Linux на qemu с поддержкой пользовательского пространства Busybox
Кроме того, через qemu можно подключить отладчик прямо к действующему ядру Linux. Сначала я планировал изучить этот процесс и именно о нём написать статью, но передумал, увидев, что статья получается слишком длинной. Здесь можно почитать о kgdb. Можете сами убедиться — раньше я с ней не работал.
❯ Установка qemu
Будем работать с эмулятором Qemu, который имитирует железо. Именно на нём будет работать тот Linux, который bs собираем. Для установки выполните:
~ sudo apt install qemu qemu-systemСначала я пытался собрать qemu, взяв за основу исходный код, но у него очень много зависимостей, и вскоре я стал понимать, что напрасно теряю время.
❯ Клонируем Linux, настраиваем ветку на локальной машине
Сначала клонируем репозиторий с Linux.
~ git clone https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/Отличная задача, на которой можно протестировать скорость загрузки.
~ du --max-depth=1 --block-size=GB | grep linux
6GB ./linuxЗатем отметим галочкой ту версию, которая нас интересует. Я остановился на 5.19.
~ cd linux
# alias gco="git checkout"
~ gco v5.19
~ git branch -M 5.19❯ Собираем Linux
Как вы можете убедиться, весь процесс сборки документирован прямо в дереве исходников Linux, см. readme. Также можно ввести команду make help, и она выведет доступные опции. Ниже я пошагово опишу работу, которую проделал.
Очистка (на первый раз не нужна)
Избавьтесь от всех устаревших файлов .o, оставшихся предыдущих попыток. Нам это сейчас не нужно, поскольку мы в первый раз приступаем к сборке, однако хорошие привычки не повредит усваивать заблаговременно.
~ make mrproperисходник
Собираем образ ядра
У ядра множество возможностей — выбирайте те, что вам нравятся. Например, в ядре есть множество драйверов, вам же, вероятно, нужны лишь некоторые из них. Если скомпилировать ядро сразу со всеми драйверами, это даже может привести к отказу некоторых функций. Драйверы — это лишь один пример. Есть подобные возможности, связанные с виртуализацией, файловыми системами и т.д. Много чего конфигурировать! Следовательно, при сборке ядра делается отдельный шаг, на котором вы явно указываете все возможности, которые вам понадобятся, а лишь затем приступаете собственно к сборке.
Если вы выбрали для сборки ядра путь /home/$USER/linux-build, то укажите флаг O (каталог вывода) как показано ниже.
~ OUTPUT_DIR=/home/$USER/linux/build
# создать файл конфигурации сборки ядра. При этом максимально возможное количество значений
# устанавливается в no. В сущности, нужно отключить как можно больше фич,
# так у вас получится компактное ядро.
# Если хотите сделать минимальное ядро, воспользуйтесь tinyconfig вместо allnoconfig.
# Не представляю, чем они отличаются.
~ make O=$OUTPUT_DIR allnoconfig
# Здесь можно просматривать конфигурацию ядра через визуально приятный пользовательский интерфейс.
# Тут пока нечего включать.
~ make O=$OUTPUT_DIR menuconfig
# Собираем само ядро
# Заменяем 8 на столько процессов, сколько поддерживает ваш компьютер.
# cat /proc/cpuinfo | grep processor | wc -l.
~ make O=$OUTPUT_DIR -j8На выходе получаем образ ядра --файл bzImage—и убеждаемся, что его размер составляет всего 1,5 МБ.
❯ Этап второй: Запускаем Linux на qemu
Теперь давайте попробуем запустить при помощи qemu то ядро, которое у нас получилось.
В man-подобной справке по qemu достаточно хорошо объяснено, как работают разные флаги.
~ OUTPUT_DIR=/home/$USER/linux/build
# -nographic в сущности, означает, что мы обходимся одной лишь консолью для последовательного ввода и не нуждаемся в gui/устройстве с дисплеем.
# -append позволяет qemu передать следующую строку в качестве командной строки ядра.
# Так можно сконфигурировать ядро в процессе загрузки:
# - console=ttyS0 сообщает ядру, что нужно использовать последовательный порт.
# - earlyprintk=serial,ttyS0 сообщает ядру, что нужно отправлять через последовательный порт информацию логов, чтобы мы могли, опираясь на неё,
# отлаживать систему после отказов ещё до того, как инициализируется код консоли. Попробуйте от этого избавиться – и увидите, что получится!
# -kernel указывает, какой образ ядра использовать.
#
~ qemu-system-x86_64 -kernel $OUTPUT_DIR/arch/x86/boot/bzImage -nographic -append "earlyprintk=serial,ttyS0 console=ttyS0"Если нажать ctrl + a, а затем x, то мы покинем экран консоли. Если нажать ctrl + a, а затем h, то будет выведено меню справки и другие опции.
В любом случае, если запустить собранное ядро через qemu, в ядре возникнет паника:
Warning: unable to open an initial console.
List of all partitions:
No filesystem could mount root, tried:
Kernel panic - not syncing: VFS: Unable to mount root fs on unknown-block(0,0)На следующем этапе мы от этой паники избавимся.
❯ Обзаводимся Busybox
Теперь у нас есть ядро Linux, но нет ни пользовательского пространства, ни файловой системы. Воспользуемся файловой системой, в которой обеспечена поддержка памятью (initramfs, можете посмотреть эту ссылку Gentoo в качестве тизера). Что-то должно пойти в файловую систему, поскольку мы не хотим, чтобы наше пользовательское пространство пустовало.
Именно здесь нам пригодится Busybox. В нём предоставляются такие команды как ls, cd, cp, mv, vim, tar, grep, dhcp, mdev (события горячего подключения устройств к Linux), ifplugd (мониторинг сетевого канала/интерфейса) — всё через маленький двоичный файл. Пожалуй, эти команды не будут такими многофункциональными и разнообразно конфигурируемыми, как их альтернативы, применяемые вне Busybox, но их нам хватит.
Посмотрите файл README к исходному коду busybox после того, как скачаете его по ссылке ниже — там всё подробно написано.
Переходите по ссылке https://busybox.net/ и забирайте новейшую стабильную версию busybox.
❯ Конфигурируем и собираем Busybox
Процесс такой же, как и при работе с ядром Linux.
Посмотрите файл INSTALL file в исходниках к busybox, как только они скачаются.
Сначала выбираем желаемую конфигурацию Busybox, а затем приступаем к сборке.
~ cd busybox-1.33.2
~ mkdir -pv build
~ OUTPUT_DIR=/home/$USER/busybox-1.33.2/build
# создаём файл .config, в котором выставлено множество «yes». Получаем
# массу возможностей Busybox, может быть, даже больше, чем нам требуется.
# Я недостаточно разбираюсь в теме, чтобы начинать с allnoconfig
# поэтому включаю только абсолютный минимум функций – собственно, вот он.
# Возможно, сборка получится и крупнее, чем ядро на 1,5 МБ. Давайте посмотрим
~ make O=$OUTPUT_DIR defconfig
# Открываем конфигурационный UI
~ make O=$OUTPUT_DIR menuconfigКогда конфигурационный пользовательский интерфейс открыт, выбираем в нём «Settings» (Настройки) (клавишей ввода), а затем «Build Busybox as a static binary» (Собрать Busybox как статический двоичный файл) (клавишей пробела). Дело в том, что в файловой системе пользовательского пространства нашего пустого ядра не будет никаких разделяемых библиотек, поэтому мы можем сразу приступить к работе.
Теперь выходим из конфигурационного меню и сохраняем внесённые изменения.

Мы готовы приступать к сборке!
# введите make help, чтобы просмотреть доступные опции,
# но, в сущности, можно включить make all или make busybox.
# При первой опции также собирается документация, при второй - только busybox.
~ make O=$OUTPUT_DIR -j8 busyboxИ вот,
~ ls -la $OUTPUT_DIR --block-size=KB | grep busybox
-rw-r--r-- 1 yangwenli yangwenli 2kB Aug 23 15:33 .busybox_unstripped.cmd
-rwxr-xr-x 1 yangwenli yangwenli 2694kB Aug 23 15:33 busybox
-rwxr-xr-x 1 yangwenli yangwenli 2987kB Aug 23 15:33 busybox_unstripped
-rw-r--r-- 1 yangwenli yangwenli 2340kB Aug 23 15:33 busybox_unstripped.map
-rw-r--r-- 1 yangwenli yangwenli 105kB Aug 23 15:33 busybox_unstripped.outДвоичный файл busybox, который мы хотели получить, действительно оказался размером около 2,7 МБ, больше, чем собранное нами ядро. Вариант busybox_unstripped нас не интересует. Он немного крупнее и, очевидно, предназначен для изучения при помощи аналитических инструментов, так, как об этом рассказано в Busybox FAQ.
❯ Создаём исходную структуру каталогов
Следующие два раздела сильно вдохновлены вики-справкой по Gentoo, которая приводится в Custom Initramfs здесь.
Теперь нам предстоит собрать исходную структуру файлов для пользовательского пространства нашего Linux.
Нам потребуется убедиться наверняка, что двоичный файл busybox на своём месте. А также предусмотреть init-процесс/скрипт, чтобы настроить наше пользовательское пространство.
~ mkdir /home/$USER/initramfs && cd initramfs
# создаём ряд базовых каталогов, которые понадобятся нам в нашем пользовательском пространстве Linux
# dev, proc и sys нужны для хранения всякого материала, относящегося к работе ядра – в частности, procfs, sysfs и устройств.
# В etc будем хранить заготовки для конфигурации того материала, которым собираемся заняться в будущем.
# Из root мы будем действовать.
# В bin будут храниться исполняемые файлы.
~ mkdir {bin,dev,etc,proc,root,sys}
# busybox также рассчитывает, что в нём будут эти дополнительные каталоги,
# так что давайте создадим их для него
~ mkdir {usr/bin,usr/sbin,sbin}
# Мы хотим, чтобы busybox был включён в наш initramfs
~ cp /home/$USER/busybox-1.33.2/build/busybox bin/busybox❯ Создаём init-процесс
Теперь давайте создадим init-процесс. В каталоге initramfs создаём файл под названием init
~ touch init && chmod +x initИ заполняем его следующим материалом:
#!/bin/busybox sh
# Получаем busybox, чтобы создать нежёсткие ссылки на команды
/bin/busybox --install -s
# Монтируем файловые системы /proc и /sys.
# Можете пропустить этот шаг, если хотите. Просто мне показалось, что хорошо бы их иметь.
mount -t proc none /proc
mount -t sysfs none /sys
# Загружаем командную оболочку, которая теперь должна быть мягко связана с busybox
exec /bin/sh❯ Создаём initramfs cpio
Cpio — это инструмент-архиватор. В сущности, это означает, что он берёт набор файлов и каталогов и обратимо преобразует их в единственный файл. Примерно как tar. Не понимаю, почему, но initramfs указывается через cpio, поэтому и его мы должны обязательно использовать, чтобы всё упаковать. Для сжатия воспользуемся gzip.
~ find . -print0 | cpio --null --create --verbose --format=newc | gzip --best > ./custom-initramfs.cpio.gz
.
./etc
./root
./sys
./dev
./bin
./bin/busybox
./init
./proc
cpio: File ./custom-initramfs.cpio.gz grew, 1310720 new bytes not copied
./custom-initramfs.cpio.gz
7824 blocksВот мы и подготовили initramfs, которым собираемся пользоваться!
❯ Этап: выполняем Linux на qemu при помощи Busybox (с применением initramfs)
Теперь давайте запустим ядро Linux с включённым initramfs!
Возьмём команду qemu, приведённую выше, и добавим флаг command from above and add an initrd, указывая таким образом initramfs.
~ LINUX_BUILD_DIR=/home/$USER/linux/build
~ INITRAMFS_DIR=/home/$USER/initramfs/custom-initramfs.cpio.gz
# В прпинципе, флаг --initrd разрешает Linux использовать тот ram-диск, который мы собрали
~ qemu-system-x86_64 -kernel $LINUX_BUILD_DIR/arch/x86/boot/bzImage -nographic -append "earlyprintk=serial,ttyS0 console=ttyS0" --initrd $INITRAMFS_DIRВероятно, вас расстраивает, что паника ядра до сих пор возникает. Дело в том, что мы до сих пор не включили поддержку initramfs в ядре, а также не предусмотрели ещё пару деталей, необходимых для нормальной работы в нашем пользовательском пространстве.
Слегка затронем вопрос о том, как ядро запускает пользовательское пространство, и как оно узнаёт, где найти процесс init. Чтобы запустить работу пользовательского пространства, ядро ищет /init, а затем /sbin/init, /etc/init, /bin/init и, наконец, finally /bin/sh — именно в таком порядке. Я оставил ссылку на исходник. Кроме того, в командной строке здесь. Я разместил файл init по адресу /bin/init.
Теперь, наладив поддержку initramfs, давайте соберём ещё одно ядро. Повторите шаги, проделанные выше (когда мы его конфигурировали) и соберите ядро:
~ cd /home/$USER/linux
~ make O=$LINUX_BUILD_DIR menuconfigПерейдите в General Setup и найдите там файловую систему Initial RAM, а также диск с оперативной памятью (RAM), затем нажмите «пробел».
В самом верху конфигурационного файла также активируйте 64-битное ядро. Если при работе с двоичным файлом Busybox вы воспользуетесь командой file, то увидите, что он собран для архитектуры x86_64. Вы также убедитесь, что это файл в формате elf, поэтому мы должны будем предусмотреть в ядре поддержку и для этого формата. Поскольку мы используем в нашем init-файле нотацию !#, нам и для неё нужно будет обеспечить поддержку.


Наконец спуститесь из начала файла к Device Drivers > Character devices > Serial drivers и 8250/16550 и compatible serial support и Console on 8250/16550 и compatible serial port. Эти конфигурационные настройки нужны для того, чтобы использовать последовательный порт в качестве консоли. Подробнее об этом в документации. Если не внести эти изменения, init работать не сможет. Думаю, именно поэтому и нужна последняя строка exec /bin/sh.

Теперь соберём ядро:
~ make O=$LINUX_BUILD_DIR -j8А потом снова запустим qemu:
~ qemu-system-x86_64 -kernel $LINUX_BUILD_DIR/arch/x86/boot/bzImage -nographic -append "earlyprintk=serial,ttyS0 console=ttyS0 debug" --initrd $INITRAMFS_DIRИтак, мы сделали себе рабочий Linux. Если у вас достаточно свободного времени, то можете продолжить этот опыт и выстроить на основе проделанной здесь работы ваш собственный дистрибутив.
Новости, обзоры продуктов и конкурсы от команды Timeweb.Cloud — в нашем Telegram-канале ↩

Источники
Build the Linux kernel and busybox and run them on qemu
Prepare the environment for developing linux kernel with qemu
Kernel parameter guide
Stack Overflow answer about kernel serial console command line
Linux source tree > Documentation > admin-guide > README
qemu man page
Display devices in qemu
Gentoo wiki > Custom Initramfs
Linux source tree > Documentation > admin-guide > init.rst
Kernel.org > Linux Serial Consol
Habrahabr.ru прочитано 20688 раз
