Однажды на работе техлид порекомендовал мне проштудировать книгу 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-канале ↩
Источники
ссылка на оригинал статьи https://habr.com/ru/articles/899312/
Добавить комментарий