Встраиваемое устройство живёт по другим правилам, чем сервер в облаке. Ему нужны компактный образ, строго ограниченный набор программ и ядро, собранное под конкретную модель платы. Собрать всё это вручную, цепочкой вызовов gcc и mkdir, реально, но один пропущенный флаг превращает день работы в перебор ошибок линковщика. Buildroot закрывает задачу целиком: из исходников он строит кросс-компилятор, загрузчик, ядро и корневую файловую систему одним запуском make. Ниже разобрана реальная процедура сборки, от выбора defconfig до файла образа в output/images, и отдельно разговор о настройке ядра, потому что именно она порождает больше всего вопросов у тех, кто делает свой первый прошивочный проект.

Один make menuconfig вместо ручной сборки toolchain

Buildroot это набор Makefile-ов и патчей, который автоматизирует кросс-компиляцию целой системы. Он решает четыре задачи сразу: собирает toolchain с gcc и стандартной библиотекой, компилирует загрузчик вроде U-Boot или Barebox, собирает ядро Linux и формирует корневую файловую систему на базе BusyBox. Конфигурация напоминает меню ядра: make menuconfig для консоли, make nconfig для альтернативного текстового интерфейса, make xconfig и make gconfig для графических окон. Разработчики проекта честно предупреждают, что базовая система на современном компьютере собирается за 15-30 минут, и это близко к правде, если у хоста есть хотя бы 8 ГБ памяти и свободный диск от 20 ГБ. Каталог пакетов перевалил за несколько тысяч позиций, от привычных coreutils и curl до Qt и веб-серверов, поэтому законченное устройство вроде домашнего контроллера или промышленного датчика целиком описывается одним файлом .config. При этом Buildroot не дистрибутив. На собранной плате нет пакетного менеджера, обновление выполняется пересборкой всего образа, и такой уклад нужен там, где прошивки одинаковы на тысячах одинаковых устройств, от счётчика тепла до панели управления станком. Ядро и загрузчик не обязательны: если плата получает ядро от производителя, соответствующие пункты можно выключить и собирать только корень.

Первый запуск от defconfig до готового файла образа

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

  1. распаковать архив выбранной версии в рабочий каталог или клонировать git-репозиторий проекта;
  2. заглянуть в каталог configs и найти ближайший defconfig своей платы, например raspberrypi4_64_defconfig или qemu_aarch64_virt_defconfig, после чего применить его командой make с именем выбранного файла;
  3. открыть make menuconfig и пройтись по разделам "Target options", "Toolchain", "System configuration" и "Filesystem images", поправив имя хоста, пароль root и набор типов образа;
  4. запустить make -j$(nproc) и отдать машине полчаса на работу;
  5. проверить результат в output/images, где появятся ядро zImage или Image, корневой rootfs.ext4 и файлы загрузчика;
  6. записать образ и ядро на носитель платы и убедиться, что последовательная консоль отвечает.
make raspberrypi4_64_defconfig
make menuconfig
make -j$(nproc)
ls output/images

Каталог output становится рабочим домом проекта: в host живёт собранный toolchain, в build лежат распакованные исходники пакетов, в target собрано содержимое будущего корня, а images хранит готовые артефакты. Запись rootfs.ext4 на карту или eMMC делается dd с параметром bs=4M и последующим sync, файлы загрузчика для плат с U-Boot лежат рядом. Если сборка падает, вывод make почти всегда называет конкретный пакет, а повторный запуск продолжит работу с места сбоя, не пересобирая всё заново.

Кастомизация ядра через фрагменты конфигурации

Готовый defconfig платы редко совпадает с потребностями проекта, поэтому ядро почти всегда приходится дорабатывать. Быстрый путь это make linux-menuconfig: открывается привычное меню ядра, изменения сохраняются в дереве сборки. Беда в том, что при чистой пересборке с нуля такая настройка исчезнет, и потому Buildroot предлагает фрагменты конфигурации. В .config задаётся переменная BR2_LINUX_KERNEL_CONFIG_FRAGMENT_FILES с путём к текстовому файлу, и при сборке нужные опции накладываются поверх базового конфига платы. Фрагмент весит несколько строк:

CONFIG_CFG80211=y
CONFIG_MAC80211=y
CONFIG_TMPFS_POSIX_ACL=y

Проверив результат, настройки стоит закрепить командой make linux-update-defconfig, тогда ядро запоминает их в собственном defconfig внутри дерева Buildroot. Выгода здесь дисциплинарная: в git попадает короткий фрагмент с десятком строк, а не полный конфиг на четыре тысячи пунктов, который невозможно ревьюить. Для диагностики полезны опции CONFIG_IKCONFIG и CONFIG_IKCONFIG_PROC: конфигурация ядра встраивается в сам образ и доступна на плате через /proc/config.gz, так что сверка настроек устройства и хоста сводится к одному zcat. Если плата требует внешнего модуля ядра, его подкладывают отдельным пакетом и подключают к сборке ядра штатными средствами, не переписывая Makefile в самом дереве Linux.

Свои файлы в корне через overlay и скрипты сборки

Практический проект требует положить в образ свои скрипты, конфиги, ключи и юниты служб. Для этого есть overlay-каталог: путь к нему задаётся в BR2_ROOTFS_OVERLAY, а всё его содержимое копируется поверх корневой системы после сборки пакетов. Внутри overlay повторяется структура корня: etc, usr/bin, lib/firmware, и файл ложится туда, куда ему положено быть на работающей плате. Более тонкие правки выполняет post-build скрипт, путь к которому указывается в BR2_ROOTFS_POST_BUILD_SCRIPT. Он запускается в момент, когда target уже собран, но образ ещё не упакован, и может сменить пароль, сгенерировать ключи, убрать лишние файлы или подставить серийный номер из переменных окружения сборочной машины. Пользователей и группы удобнее описывать таблицей из BR2_ROOTFS_USERS_TABLES, где одна строка задаёт имя, пароль, shell и домашний каталог. Финальная упаковка тоже настраивается: BR2_ROOTFS_POST_IMAGE_SCRIPT запускает свои скрипты после формирования образа, например для сборки wic-файла или проверки подписи прошивки.

Выбор пакетов и лимиты памяти платы

В меню "Target packages" каждая галочка превращается в строку вида BR2_PACKAGE_DROPBEAR=y в .config, поэтому конфигурацию удобно ревьюить как код. Опыт подсказывает несколько практических правил. Первый образ стоит собрать минимальным и замерить размер: прошивка с BusyBox укладывается в несколько мегабайт, а версия с Qt и графической библиотекой вырастает до десятков мегабайт, и на плате со 128 МБ флеш-памяти разница принципиальна. Утилиты BusyBox редактируются отдельным меню make busybox-menuconfig, где отключаются апплеты, которые на плате никогда не понадобятся. Для удалённого доступа обычно выбирают dropbear вместо openssh, он легче и закрывает потребности одной платы. Инициализацию можно оставить на BusyBox init, но когда сервисов много и между ними есть зависимости, проще сразу включить systemd через BR2_INIT_SYSTEMD, приняв добавку к образу. Тип корневой файловой системы подбирается под носитель: squashfs для прошивки только на чтение с отдельным разделом данных, ext4 для обычной eMMC или карты памяти. Когда нужной программы нет в каталоге, пакет добавляется в своём дереве: файл Config.in с описанием пункта меню и makefile с версией, адресом архива и командой установки, а включается всё через механизм BR2_EXTERNAL, который держит свои пакеты рядом с проектом и не трогает дерево Buildroot. Для устройств, уходящих в продажу, пригодится make legal-info: команда собирает список лицензий всех включённых пакетов и их исходники, что упрощает проверку требований свободных лицензий.

Типичные ошибки первой сборки и порядок их отладки

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

Вторая ошибка это дефицит памяти сборочной машины. Сборка qtwebkit или тяжёлых пакетов с параллелизмом на полный набор ядер легко выбивает лимит памяти, и процесс крашится с ошибкой, которая на компиляцию похожа мало. Те же правки решаются снижением параллелизма, файла подкачки или прогоном на машине с большей памятью. Третья частая ловушка лежит глубже: сборка идёт со старым конфигурационным файлом и современным тулчейном, и несовместимость пакетов всплывает через десять минут бесплодной работы. Лечится пересборкой конфигурации из дерева с соответствующей версией.

Четвёртая ошибка это игнорирование данных об обновлении: пакет в дереве уже исправлен, а образ собирается с архивной версией из локального кэша. Каталог загрузки накапливает архивы за десятки сборок, и иногда полезно потребовать свежего tar.gz. Пятая ловушка связана с производительностью: полная сборка с нуля занимает треть часа, но кэш компилятора и постоянный каталог загрузки вынимают из подобного урона львиную долю времени, поэтому настройки BR2_CCACHE и BR2_DL_DIR должны стоять в привычном конфиге с первого дня.

Чаще всего первая сборка спотыкается не о ядро, а об окружение хоста. Для make menuconfig нужны development-пакеты ncurses, для сборки host-инструментов полный комплект компилятора и make, о чём стоит позаботиться до старта. Полный вывод сборки полезно сразу направлять в лог: make 2>&1 | tee build.log, потому что реальная причина сбоя обычно прячется за сотнями строк выше сообщения об ошибке. Вторая ловушка это ручная чистка: удалять содержимое output/target руками бессмысленно, target пересоздаётся из пакетов, а для честного пересборного прогона используют make clean или отдельный каталог сборки с переменной O=, чтобы не трогать рабочий образ. Третья ловушка это попытка править файлы прямо в output/target: изменения пропадут при следующей сборке, и правильное место им в overlay или в post-скрипте. Когда непонятно, откуда в образ взялась лишняя программа, помогает make graph-depends: он рисует граф зависимостей и показывает, какой пакет притянул соседа.

Поддержка образа, воспроизводимость и сроки жизни версий

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

Отдельный аспект это обновления на устройстве без загрузки: Buildroot делает среду для них, и готовые дистрибьюторы обновлений, вроде RAUC или Mender, включаются как пакеты. Они поднимают два корневых раздела и перекат между ними, и при проблемном обновлении устройство грузится со старого образа. В сценариях с полевым устройством без локального доступа такая конструкция не является антуражем, она является страховкой производителя от потери контакта с устройством.

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

Новые версии выходят каждые три месяца, февральские выпуски получают статус долгосрочной поддержки, и выпуск 2025.02 LTS проект обещает поддерживать до марта 2028 года. Для серийного устройства разумно прикрепиться к одной ветке и двигаться по графику LTS, а не прыгать за каждым квартальным релизом, потому что каждая смена версии toolchain означает новый прогон тестов всей платы. Повторные сборки ускоряет ccache, включаемый опцией BR2_CCACHE, и общий каталог загрузок DL_DIR, который стоит вынести за пределы дерева, чтобы его переиспользовала вся команда. Команда make source заранее скачивает все архивы и репозитории, что выручает при сборке в изолированном контуре без интернета. Дороже удобства здесь только воспроизводимость: один и тот же .config, зафиксированная версия Buildroot и скрипты в git дают прошивку, которую можно пересобрать год спустя, и для встраиваемого изделия это не роскошь, а условие поддержки продукта после окончания его продаж.