Как стать автором
Обновить
1632.05
Timeweb Cloud
То самое облако

Собираем и запускаем минимальное ядро Linux

Уровень сложностиСложный
Время на прочтение9 мин
Количество просмотров13K
Автор оригинала: Subrat Mainali

Однажды на работе техлид порекомендовал мне проштудировать книгу Understanding the Linux Kernel Бове и Чезати. В ней рассмотрена версия Linux 2.6, сильно не дотягивающая до более современной версии 6.0. Но в ней явно ещё много ценной информации. Книга толстая, поэтому на её изучение мне потребовалось немало времени. Занимаясь по ней, я решил настроить такую среду разработки, в которой я мог бы просматривать и изменять новейшую версию ядра Linux — чтобы было ещё интереснее.

Есть и другие статьи, в которых рассказано, как собрать ядро Linux. Но в этой статье я немного иначе организую и подаю информацию.

❯ Этапы

Работа пойдёт в 2 основных этапа:

  1. Собираем и запускаем Linux на qemu

  2. Собираем и запускаем 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. В нём предоставляются такие команды как lscdcpmvvimtargrepdhcpmdev (события горячего подключения устройств к 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-канале 

Опробовать ↩

Источники

Теги:
Хабы:
Всего голосов 36: ↑32 и ↓4+41
Комментарии9

Публикации

Информация

Сайт
timeweb.cloud
Дата регистрации
Дата основания
Численность
201–500 человек
Местоположение
Россия
Представитель
Timeweb Cloud