Build operating system linux

Buildroot — часть 1. Общие сведения, сборка минимальной системы, настройка через меню

Введение

В данной серии статей я хочу рассмотреть систему сборки дистрибутива buildroot и поделиться опытом её кастомизации. Здесь будет практический опыт создания небольшой ОС с графическим интерфейсом и минимальным функционалом.

Прежде всего, не следует путать систему сборки и дистрибутив. Buildroot может собрать систему из набора пакетов, которые ему предложили. Buildroot построен на make-файлах и поэтому имеет огромные возможности по кастомизации. Заменить пакет на другую версию, добавить свой пакет, поменять правила сборки пакета, кастомизировать файловую систему после установки всех пакетов? Всё это умеет buildroot.

В России buildroot используется, но на мой взгляд мало русскоязычной информации для новичков.

Цель работы — собрать дистрибутив с live-загрузкой, интерфейсом icewm и браузером. Целевая платформа — virtualbox.

Зачем собирать свой дистрибутив? Зачастую нужен ограниченный функционал при ограниченных ресурсах. Ещё чаще в автоматизации нужно создавать прошивки. Приспосабливать дистрибутив общего назначения, вычищая лишние пакеты и превращать его в прошивку путь более трудоёмкий, чем собрать новый дистриб. Использование Gentoo тоже имеет свои ограничения.

Buildroot система очень мощная, но она ничего не сделает за вас. Она может лишь дать возможности и автоматизировать процесс сборки.

Альтернативные системы сборки (yocto, open build system и прочие) не рассматриваются и не сравниваются.

Где взять и как начать

Сайт проекта — buildroot.org. Здесь можно скачать актуальную версию и прочитать руководство. Там же можно обратиться к сообществу, есть багтрекер, mail-lists и irc-канал.

Buildroot оперирует defconfig’aми для целевой платы сборки. Defconfig — это конфигурационный файл, хранящий в себе только опции, не имеющими значения по умолчанию. Именно он определяет, что и как будет собрано. При этом можно отдельно настроить конфиги busybox, linux-kernel, uClibc, загрузчиков u-boot и barebox, но все они будут привязаны к целевой плате.
После распаковки скачанного архива или клонировании из git получаем готовый к работе buildroot. Подробно о структуре каталогов можно прочитать в руководстве, расскажу о самых важных:

board — каталог с файлами, специфичными для каждой платы. Это могут быть скрипты формирования образов системы(iso, sdcart, cpio и прочие), каталог overlay, конфиг ядер и прочее
configs — собственно defconfig платы. Defconfig — это неполная конфигурация платы. В нем хранится только отличные от дефолтных настроек параметры
dl — каталог со скачанными исходными кодами/файлами для сборки
output/target — собранная файловая система полученной ОС. В дальнейшем из нее создаются образы для загрузки/установки
output/host — host-утилиты для сборки
output/build — собранные пакеты

Конфигурирование сборки осуществляется через KConfig. Эта же система используется для сборки ядра linux. Список самых часто используемых команд (выполнять в каталоге buildroot):

  • make menuconfig — вызвать настройку сборки. Так же можно с использование графического интерфейса (make nconfig,make xconfig,make gconfig)
  • make linux-menuconfig — вызвать конфигурацию ядра.
  • make clean — очистить результаты сборки (всё что храниться в output)
  • make — собрать систему. При этом не выполняется пересборка уже собранных процессов
  • make defconfig_name — переключить конфигурацию на определенный defconfig
  • make list-defconfigs — показать список defconfig’ов
  • make source — только скачать установочный файлы, без сборки.
  • make help — вывести список возможных команд

Важные замечания и полезные советы

Buildroot не пересобирает уже собранные пакеты! Поэтому может создаться ситуация, когда потребуется полная пересборка.

Можно пересобрать отдельный пакет командой make packagename-rebuild. Например, можно пересобрать ядро linux:

Buildroot хранит состояние любого пакета созданием .stamp-файлов в каталоге output/build/$packagename:

Следовательно, можно пересобрать root-fs и образы без пересборки пакетов:

Полезные переменные

В buildroot есть набор переменных для удобного конфигурирования

  • $TOPDIR — корневой каталог buildroot
  • $BASEDIR — каталог OUTPUT
  • $HOST_DIR, $STAGING_DIR, $TARGET_DIR — каталоги сборки host fs, staging fs, target fs.
  • $BUILD_DIR — каталог c распакованными и собранными пакетами

Визуализация

В buildroot есть возможность по визуализации.Можно построить схему зависимостей, график времени сборки, график размера пакетов в итоговой системе. Результаты в виде pdf файлов( на выбор есть svn,png) в каталоге output/graph.

Примеры команд визуализации:

  • make graph-depends построить дерево зависимостей
  • make

-graph-depends построить дерево зависимостей конкретного пакета

  • BR2_GRAPH_OUT=png make graph-build построить график времени сборки с выводом в PNG
  • make graph-size построить график размера пакетов
  • Полезные скрипты

    В каталоге buildroot есть подкаталог utils c полезными скриптами. Например, там есть скрипт, проверяющий корректность описания пакетов. Это может быть полезно при добавлении своих пакетов (я это сделаю позже). В файле utils/readme.txt есть описание этих скриптов.

    Соберем cтоковый дистрибутив

    Важно напомнить, что все операции ведутся от лица обычного пользователя, не root.
    Все команды выполняются в корне buildroot. В поставке buildroot уже есть набор конфигураций под многие распространённые платы и виртуализацию.

    Смотрим список конфигураций:

    Переключаемся на конфиг qemu_x86_64_defconfig

    И запускаем сборку

    Сборка завершается успешно, смотрим на результаты:

    Buildroot собрал образы, которые можно запустить в Qemu и убедиться, что они работают.

    Результат — запущенная в qemu система:

    Создание конфигурации собственной платы

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

    Смотрим список конфигураций:

    В списке видим pc_x86_64_bios_defconfig. Мы создадим свою плату, скопировав её с конфигурации:

    Сразу же создадим каталог платы для хранения своих скриптов, rootfs-overlay и прочих нужных файлов:

    Переключаемся на этот defconfig:

    Таким образом, теперь конфиг сборки (хранится в .config в корне каталога buildroot’а) соответствует целевой машине x86-64 legacy(bios) загрузкой.

    Скопируем конфигурацию linux-kernel (пригодится в дальнейшем):

    Настройка параметров сборки через KConfig

    Откроется окно KConfig. Есть возможность конфигурировать с графическим интерфейсом (make nconfig, make xconfig, make gconfig):

    Входим в первый раздел Target Options. Здесь можно выбрать целевую архитектуру, под которую будет вестись сборка.

    Build options — здесь есть различные настройки сборки. Можно указать каталоги с исходными кодами, количество потоков сборки, зеркала для скачивания исходных кодов и прочие настройки. Оставим настройки по-умолчанию.

    Toolchain – здесь настраивается сам инструментарий сборки. О нем подробнее.

    Toolchain type – тип используемого тулчейна. Это может быть встроенный в buildroot или внешний тулчейн (можно указать каталог с уже собранным или url для скачивания). Для разных архитектур есть дополнительные опции. Например, для arm можно просто выбрать версию внешнего тулчейна Linaro.

    C library – выбор библиотеки С. От этого зависит работа всей системы. Обычно используется glibc, поддерживающая весь возможный функционал. Но она может оказаться слишком большой для встроенной системы, поэтому часто выбирают uClibc или musl. Мы выберем glibc (в дальнейшем это потребуется для использования systemd).

    Kernel Headers и Custom Kernel Headers series – должно совпадать с версией ядра, которое будет в собираемой системе. Для kernel headers можно так же указать путь к тарболу или git-репозиторий.

    GCC COMPILER VERSIONS – выбор версии компилятора, которая будет использована для сборки
    Enable C++ support – выберем для сборки с поддержкой библиотек c++ в системе. В дальнейшем нам это пригодится.

    Additional gcc options – можно задать дополнительные опции компилятора. Нам без надобности пока что.

    System configuration позволяет задать будущие параметры созданной системы:

    Большинство пунктов понятны из названия. Обратим внимание на следующие пункты:
    Path to the users tables — таблица с создаваемыми пользователями (https://buildroot.org/downloads/manual/manual.html#makeuser-syntax).

    Пример файла. Будет создан пользователь user с паролем admin, автоматически gid/uid, /bin/sh шеллом, группой по-умолчанию user, член группы root, комментарием Foo user

    Root filesystem overlay directories — каталог, накладываемый поверх собранной target-fs. Добавляет новые файлы и заменяет имеющиеся.

    Custom scripts to run before creating filesystem images — Скрипты, выполняемые непосредственно перед сворачиванием файловой системы в образы. Сам скрипт пока оставим пустым

    Перейдём в раздел Kernel

    Здесь задаются настройки ядра. Само ядро конфигурируется через make linux-menuconfig.
    Задать версию ядра можно по-разному: выбрать из предложенных, ввести версию вручную, указать репозиторий или готовый tarball.

    Kernel configuration — путь к конфигу ядра. Можно выбрать конфигурацию по-умолчанию для выбранной архитектуры или defocnfig из Linux. В исходниках Linux есть набор defconfig’ов для разных целевых систем. НАйти нужный можно, глянув напрямую в исходники здесь. Например, для платы beagle bone black можно выбрать конфиг.

    Раздел Target packages позволяет выбрать, какие пакеты будут установлены в собираемую систему. Пока оставим без изменений. Позже мы добавим свои пакеты в этот список.
    Filesystem images — список образов файловых систем, которые будут собраны. Добавим iso-образ

    Bootloaders — выбор собираемых загрузчиков. Выберем isolinix

    Конфигурирование Systemd

    Systemd становится одним из столбов linux, наравне с kernel и glibc. Поэтому вынес его настройку в отдельный пункт.

    Настраивается через make menuconfig, далее Target packages → System tools → systemd. Здесь можно указать, какие службы systemd будут установлены и запущены при старте системы.

    Сохранение конфигурации системы

    Сохраняем этот конфиг через KConfig.

    После чего сохраним наш defconfig:

    Конфигурирование ядра Linux

    Конфигурирование ядра linux вызывается следующей командой:

    Добавим поддержку видеокарты Virtualbox

    Добавим Virtualbox Guest integration support

    Сохраняем и выходим. ВАЖНО: конфигурация сохранится в output/build/linux-$version/config, но не в board/my_x86_board/linux.config

    Поэтому нужно вручную скопировать конфиг в место хранения:

    Этой командой я копирую ПОЛНЫЙ конфиг ядра, что нужно не всегда. Более правильный путь — сохранять defconfig ядра:

    После чего выполним полную пересборку всей системы.Т.к. buildroot не пересобирает уже собранное, то надо вручную указать пакеты для пересборки. Чтобы не терять время и нервы, небольшую систему проще пересобрать целиком):

    По завершении сборки запускаем VirtualBox(проверялось на версии 5.2 и 6.0) с загрузкой с cd-диска.Параметры системы:

    Источник

    Как выйти на путь разработки ОС

    Чтобы понять, в чем заключается роль разработчика ОС, представим, что происходит после нажатия на кнопку включения ПК.

    Сначала запускается BIOS и подготавливает жизненно важное оборудование, после чего загружает в память MBR загрузочного диска, содержащую код первой части загрузчика. Под непосредственно исполняемую часть отведено всего 446 байт, чего крайне недостаточно, поэтому мало загрузчиков действительно укладываются в эти границы. В связи с этим загрузчик обычно разделяется на две части, и единственное, что делает первая часть загрузчика — читает с диска и запускает вторую часть. Вторая часть уже может занимать хоть весь диск, и обычно переводит процессор в защищенный режим, загружает в память ядро и модули, после чего передает управление ядру.

    Ядро полностью подготавливает оборудование и запускает первые пользовательские процессы, обеспечивая им и их потомкам runtime-поддержку.

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

    Инструментарий

    Теоретически, разработку можно вести на любой ОС, но большинство инструментов рассчитаны на UNIX-подобные системы, и хотя бы собрать их на Windows уже будет страданием. Более того, поскольку WSL не поддерживает модули ядра, смонтировать образ диска не получится, и придется настраивать коммуникацию между WSL и Windows. На этом этапе уже становится проще поставить виртуальную машину с Linux. В статье будут предоставлены инструкции для Linux и macOS.

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

    На место виртуальной машины лучше всего подходят Bochs и QEMU, поскольку они быстро запускаются и предоставляют возможность отладки запущенного ядра.

    Загрузчик некоторые мазох особенно идейные разработчики пишут сами, но, если речь идет о разработке собственно операционной системы, писать загрузчик будет очень скучно, а также ненужно, поскольку есть готовые решения. Благодаря спецификации Multiboot можно написать ядро, которое будет почти из коробки загружаться с помощью, например, GRUB или LILO.

    Со сборкой же всё не так просто: понадобится кросс-компилятор под x86. Зачем кросс-компилятор, если собирать под ту же архитектуру? Дело в том, что стандартный компилятор генерирует код, опирающийся на ту же ОС, на которой он запущен, или т.н. hosted-код. Hosted-код использует системные вызовы, взаимодействует с другими процессами, но привязан к операционной системе. Freestanding-код существует сам по себе и для запуска требует только само оборудование. Ядро ОС относится к freestanding, а программы, им запускаемые — к hosted. Кросс-компилятору достаточно соответствующего флага, и будет сгенерирован freestanding-код.

    Подготовка

    Сборка инструментов

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

    $TARGET — система, под которую будет собирать полученный компилятор. Обычно она называется наподобие i686-linux-gnu , но здесь результат запускается без ОС, поэтому указывается просто формат исполняемого файла. Почему i686, а не i386? Просто архитектуре 80386 уже, кхм, много лет, и с тех пор многое изменилось; в частности, появились кэши, многоядерные и многопроцессорные системы, встроенные FPU, “большие” атомарные инструкции вроде CMPXCHG , так что, собирая под i386, можно сильно потерять в быстродействии и немного приобрести в поддержке старых компьютеров.

    $PREFIX — то, куда будут установлены инструменты. Обычно используются пути вроде /usr/i686-elf , /usr/local/i686-elf и подобные, но можно установить и в произвольную папку. Этот каталог также называется sysroot, поскольку он будет представлять собой корневой каталог для кросс-компилятора и утилит. Говоря точнее, это не полноправный путь, а именно префикс к пути; таким образом, для установки в корень $PREFIX будет представлять из себя пустую строку, а не / . На время сборки GCC потребуется добавить в PATH путь $PREFIX/bin .

    Если потом ОС понадобится собирать под другую архитектуру, достаточно будет установить другие переменные окружения, а команды скопировать.

    Binutils

    Загружаем и распаковываем последнюю версию с официального FTP. Осторожно: minor-версии давно перешагнули за 10, вследствие чего сортировка по алфавиту сломалась, для поиска актуальной версии можно использовать сортировку по дате последнего изменения. На момент написания статьи актуальной версией Binutils является 2.29.

    Binutils не поддерживает сборку в каталоге с исходным кодом, поэтому создаем каталог рядом с распакованным кодом и заходим в него. Далее обычная сборка из исходников:

    Подробнее о параметрах:

    —with-sysroot — использовать sysroot;
    —disable-nls — выключить поддержку родного языка. OSDev-сообщество не так велико, чтобы на какую-нибудь непонятную ошибку сборки обязательно нашёлся человек, говорящий на языке того, у кого она возникла;
    —disable-werror — компилятор при сборке Binutils выдает предупреждения, а с -Werror это приводит к остановке сборки.

    Так же загружаем, распаковываем и создаем каталог для сборки. Процесс сборки немного отличается. Понадобятся библиотеки GMP, MPFR и MPC. Их можно установить из стандартных репозиториев многих пакетных менеджеров, а можно запустить из каталога с исходным кодом скрипт contrib/download_prerequisites , который их скачает и использует при сборке. Конфигурацию выполняем так:

    —disable-nls — то же самое, что и для Binutils;
    —without-headers — не предполагать, что на целевой системе будет стандартная библиотека (этим, собственно, и отличается необходимый нам компилятор от стандартного);
    —enable-languages=c,c++ — собрать компиляторы только для выбранных языков. Опционально, но существенно ускоряет сборку.

    В условиях отсутствия целевой ОС обычный make && make install не подойдет, поскольку некоторые компоненты GCC ориентируются на готовую операционную систему, поэтому собираем и устанавливаем только необходимое:

    libgcc — библиотека, в которой содержатся внутренние функции компилятора. Компилятор вправе вызывать их для некоторых вычислений, например, для 64-битного деления на 32-битной платформе.

    На большинстве дистрибутивов Linux эту секцию можно пропустить, поскольку на них уже установлены подходящие утилиты для работы с GRUB. Для других же ОС его потребуется загрузить и собрать. Также понадобится маленькая утилита objconv:

    На время сборки GRUB потребуется добавить в PATH только что собранный objconv
    и кросс-инструменты (i686-elf-*).

    GDB (для macOS)

    Стандартная версия GDB не знает об ELF-файлах, поэтому при использовании GDB его потребуется пересобрать с их поддержкой. Загрузка, распаковка, сборка:

    Образ диска

    Процесс создания такового в разных ОС происходит по-своему, поэтому здесь я приведу отдельные инструкции.

    Создаем пустой файл:

    Создаем таблицу разделов:

    Создаём файловую систему:

    В дальнейшем можно будет монтировать посредством

    Устанавливаем загрузчик (здесь GRUB):

    Создаем пустой файл:

    Разделяем таблицу разделов и единственный раздел:

    Подключаем раздел как диск:

    Создаем ФС, здесь FAT32:

    “Склеиваем” MBR и ФС обратно:

    Подключаем и запоминаем точку монтирования (обычно “/Volumes/NO NAME”):

    Образ диска после этого спокойно подключается встроенными средствами системы. Можно на собственное усмотрение создать иерархию директорий и настроить загрузчик. Например, для GRUB можно создать такой grub.cfg в /boot/grub:

    Настройка сборочной системы

    Популярных сборочных систем в мире великое множество, поэтому инструкций к каждой здесь не будет, но общие моменты всё же опишу.

    Ассемблерные файлы собираем в объектные формата ELF (32 бита):

    C-файлы собираем при помощи кросс-компилятора с флагом -ffreestanding:

    Для компоновки используем всё тот же кросс-компилятор, но указываем чуть больше информации:

    -ffreestanding — генерировать freestanding-код;
    -nostdlib — не включать стандартную библиотеку, поскольку ее реализация является hosted-кодом и будет совершенно бесполезна;
    -lgcc — подключаем описанную выше libgcc. Ее подключение всегда идет после остальных объектных файлов, иначе компоновщик будет жаловаться на неразрешенные ссылки;
    -T — поскольку нужно где-то разместить заголовок Multiboot, обычная раскладка ELF-файла не подойдёт. Ее можно изменить при помощи скрипта компоновщика, который и задает этот флаг. Вот готовый его вариант:

    Минимальное ядро

    Получаем управление

    Получаем управление от загрузчика в небольшом ассемблерном файле:

    Proof of Work

    Чтобы хоть как-то увидеть, что код действительно выполняется, можно вывести что-то на экран. Полноценный драйвер терминала — тема большая, но, вкратце, по адресу 0xB8000 располагается буфер на 2000 записей, каждая из которых состоит из атрибутов и символа. Белому тексту на черном фоне соответствует байт атрибутов 0x0F. Попробуем что-либо вывести при помощи заранее подготовленной строки:

    Запуск

    Копируем ядро в образ диска по нужному пути, и после этого любая виртуальная машина должна его успешно загрузить.

    Отладка

    Для отладки в QEMU можно задать флаги -s -S . QEMU будет дожидаться отладчика и включит сетевую отладку. Также стоит заметить, что отладка не будет работать при использовании ускорителя, так что флаг —enable-kvm придется убрать, если он используется.

    Bochs понадобится собрать с —enable-gdb-stub , а в конфиг включить строку наподобие gdbstub: enabled=1, port=1234, text_base=0, data_base=0, bss_base=0 .
    В GDB можно подключиться и запустить машину таким образом (kernel.elf — файл ядра):

    Все остальное работает так же, как и всегда — точки останова, чтение памяти и пр. Также можно включить отладчик в само ядро, что позволит производить отладку на реальной машине. Можно написать его самостоятельно, но отладка ошибки в отладчике принесет много-много радости. GNU распространяет почти готовые отладчики, требующие от ядра только несколько функций. Например, для i386. Впрочем, пока это делать рано, поскольку еще нет необходимых функций, таких как установка обработчика прерывания или получения/отправки данных через последовательный порт.

    Заключение

    На текущий момент до минимальной рабочей операционной системы остается настроить следующее:

    Источник

    Читайте также:  Для перемещения нажмите аутентификация mac os
    Оцените статью