habrahabr

Embedded Linux для начинающих (Часть первая)

  • понедельник, 7 июля 2025 г. в 00:00:11
https://habr.com/ru/articles/924624/

Однажды по работе мне прилетела задача по сборке и запуску Linux на одноплатном ПК. Тогда я, будучи разработчиком ПО для микроконтроллеров, встал в небольшой ступор — задачка явно не решалась установкой IDE и нажатием в ней кнопки «Собрать проект». Гугл помог узнать о том, что существует некий Buildroot. В материалах по теме всё выглядело довольно просто: скачай, настрой, дерни пару команд, загрузи результат на одноплатник — и можно запускать! Получается, процесс не многим сложнее установки дистрибутива Linux или Windows на обычный ПК? Конечно же, нет. Ведь если у тебя в руках кастомный одноплатник неизвестного китайского бренда, а не BeagleBone или Raspberry Pi, то зарыться в Buildroot придётся с головой...

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

Так что, пройдя долгий и тернистый путь накопления знаний, их систематизации и применения на практике, я решил создать этот цикл статей. В нём я постараюсь максимально понятно и подробно рассказать о том, что такое Embedded Linux в целом и Buildroot в частности.

Я буду описывать усреднённый сценарий работы System on Chip (SoC) с Embedded Linux (EL), чтобы не погружаться в детали, характерные для отдельных чипов. А поскольку статьи рассчитаны на новичков, чей уровень владения Linux мне заранее неизвестен, некоторые моменты буду объяснять, возможно, слишком подробно.

Введение

Что такое System on Chip?

Если описывать максимально просто, SoC — это микросхема, содержащая в себе процессор, интерфейсы и аппаратные блоки. У тех, кто знаком с микроконтроллерами, закономерно возникнет вопрос: «А в чём тогда разница? Разве микроконтроллер — не то же самое?»

И действительно, любой микроконтроллер тоже содержит в себе всё перечисленное. Но главное отличие не столько в железе, сколько в подходе к программированию и уровне абстракции, с которым работает разработчик. Чтобы понять, где заканчивается микроконтроллер и начинается SoC, давайте сравним три основных подхода к созданию встроенных решений — от простейших к более сложным:

  1. Микроконтроллер без ОС

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

    Программирование таких микроконтроллеров — это почти всегда «низкий уровень»: прямой доступ к регистрам, минимальные обёртки, если таковые вообще есть. Иногда единственное, что у вас есть в распоряжении — это заголовочный .h файл с описанием регистров и datasheet. Каждая конкретная модель микроконтроллера имеет свой набор периферии и регистров, поэтому код, как правило, сильно платформозависим.

    Исключением здесь можно назвать, например, HAL от STM, который позволяет относительно безболезненно переносить код с чипа на чип в рамках линейки STM32.

  2. Микроконтроллер с RTOS

    Следующий шаг — это использование ОС реального времени (RTOS), таких как FreeRTOS или SYS-BIOS. Здесь появляется многозадачность (не путать с многопоточностью), драйверы и абстракции над железом.

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

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

  3. System on Chip

    На этом уровне начинается настоящая «взрослая» жизнь: многоядерные процессоры, мегабайты оперативной памяти, гигабайты хранилища, аппаратные модули обработки видео, различные интерфейсы, такие как HDMI, Bluetooth и USB — и всё это в одном чипе.

    Со стороны, SoC может выглядеть как просто «мощный микроконтроллер», но на практике это уже полноценный компьютер, на который можно поставить Embedded Linux.

    С этого момента разработчик работает с регистрами не напрямую, а через ядро Linux, драйверы, DeviceTree-файлы и огромный стек инструментов — от компилятора до менеджера пакетов.

    Что особенно важно — код становится максимально переносимым. Всё, что не связано напрямую с конкретным чипом (например, управление GPIO) может быть одинаковым на разных SoC. Аппаратные различия абстрагируются на уровне ядра Linux и DeviceTree.

    Непереносимой частью обычно остаётся SDK от производителя — набор специфичных библиотек для работы с уникальными аппаратными блоками конкретного SoC.

    Стоит уточнить, что речь идёт про те SoC, которые рассчитаны на работу с полноценной ОС. Существуют и более простые, например ESP32, которые ближе по духу к микроконтроллерам и нередко работают без Linux.

Таким образом SoC — это не просто «продвинутый микроконтроллер», а архитектурно совсем другой класс устройств. Это решение для задач, где нужны ресурсы, многопоточность, полноценная ОС и комплексная обработка данных.

Главное отличие — в уровне программного обеспечения, которое идёт «в комплекте» с железом.

Если микроконтроллер — это устройство, где программист управляет железом напрямую, то SoC — это уже платформа, где программист работает через полноценную ОС и драйверную модель.

Что такое Embedded Linux?

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

Обычно EL включает в себя:

  • Загрузчик

  • Ядро Linux

  • Корневую файловую систему

  • Утилиты, библиотеки, службы

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

Возникает логичный вопрос: а зачем что-то собирать вручную, если можно взять готовый дистрибутив вроде Debian или Ubuntu?

Вот ключевые отличия:

  1. Поддержка аппаратной платформы

    Не под каждый SoC существует готовый образ Linux. Даже если вы найдёте нечто совместимое, это вовсе не означает, что оно запустится «из коробки» или будет работать корректно с вашим железом.

  2. Аппаратные возможности могут быть не раскрыты

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

  3. Избыточность и ресурсоёмкость

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

Таким образом, Embedded Linux — это тот же самый Linux, но не в виде универсального дистрибутива, а в виде конструктора, собранного под конкретную задачу. Это система, в которой под контролем оказывается всё: от ядра до последнего скрипта автозапуска. Она создаётся под конкретное устройство, работает эффективно, стабильно и выполняет ровно ту работу, для которой задумывалась — ничего лишнего.

Тем не менее, это не значит, что все компоненты EL аналогичны компонентам, используемым в классических ПК. Например, в обычной системе загрузка идёт через BIOS и GRUB, а в SoC— через набор специализированных загрузчиков. Так что, чтобы разобраться, как устроена такая система и что в ней собирать, давайте посмотрим, из каких компонентов она состоит и как происходит процесс загрузки.

Как устроен Embedded Linux изнутри?

Процесс загрузки от включения до входа в систему

Пожалуй, продолжу традицию сравнений — так разница становится особенно наглядной.

Процесс загрузки ПК

Начнём с привычного многим процесса загрузки ПК:

  1. После подачи питания запускается базовая прошивка — BIOS (или UEFI на современных платформах). Она проверяет наличие и работоспособность основных компонентов: процессора, оперативной памяти и т.д. Далее происходит опрос устройств, подключённых к материнской плате. Если на одном из них обнаружен загрузочный сектор, это устройство попадает в очередь загрузки.

  2. BIOS (или UEFI) копирует загрузочный сектор (например, MBR) с первого подходящего устройства в оперативную память и передаёт управление коду, содержащемуся в этом секторе. Этот код называется загрузчиком первого этапа.

  3. Этот загрузчик, зная, где находится его продолжение (обычно в /boot), загружает основной загрузчик — чаще всего это GRUB. GRUB отображает меню (если задано) и передаёт управление ядру Linux (Kernel), указанному в конфигурации.

  4. Ядро монтирует корневую файловую систему (RootFS), указанную в параметрах загрузки, и запускает процесс init.

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

Процесс загрузки SoC

Теперь взглянем на загрузку встраиваемой системы на базе SoC:

  1. После подачи питания стартует Primary Program Loader (PPL) — загрузчик, находящийся в ROM. Он инициализирует базовую периферию, например, Static RAM (SRAM), и начинает поиск следующего этапа — Secondary Program Loader (SPL). Если он найден, его код выгружается в SRAM, и PPL передаёт ему управление.

    У загрузчика первого уровня нет конкретного термина. Он и Boot ROM и ROM code и internal bootloader и PPL. Но далее я буду использовать термин PPL

  2. SPL инициализирует более сложную периферию, в частности, RAM. Далее он ищет следующий этап — Third Program Loader (TPL) или сразу Kernel. Найденный код загружается в RAM, управление передаётся ему.

  3. TPL (если он есть) проводит дополнительную инициализацию оборудования и ищет ядро. Обнаружив его, загружает в RAM и передаёт управление.

  4. Kernel перенастраивает периферию согласно Device Tree, монтирует RootFS и запускает процесс Init. Дальше, наличие отличий от обычного ПК зависит от того, что именно содержится в RootFS и Init.

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

Сходства и ключевые отличия

На первый взгляд, процессы загрузки похожи. Да и выглядят одинаково запутанно. Отчасти, так и есть. Но!

В ПК множество задач решают микроконтроллеры: например, память и шины инициализируются «сами» — BIOS лишь координирует их работу. Каждый узел системы автономен и содержит собственную прошивку. Загрузка ОС в этом случае — скорее организация взаимодействия между независимыми компонентами, чем ручное включение каждого.

В SoC всё иначе. Это монолитный чип без отдельных контроллеров или лишнего пространства — всё должно быть максимально компактным, надёжным и понятным. Задача пробудить систему полностью ложится на её плечи. Поэтому и нужен каскад загрузчиков, каждый из которых решает свою задачу.

  • PPL — минимальный, максимально надёжный и неизменяемый код в ROM. Он выполняет лишь базовую инициализацию и запускает следующий этап. Его компактность делает его легко проверяемым и надёжным. Повредить его — значит убить всю систему: чип станет кирпичом.

  • SPL — получает чуть больше возможностей: в частности, может инициализировать RAM. Но SRAM слишком мала, чтобы вместить сложную логику, и потому SPL обычно нужен только как мост к следующему шагу.

  • TPL — уже работает в полноценной RAM и может позволить себе роскошь сценариев: например, откуда загружать ОС, как отобразить процесс загрузки, как реагировать на подключённые устройства и т.д.

Такая архитектура не усложняет — она защищает. Если сбой произошёл на уровне SPL или TPL, загрузку можно перехватить, загрузиться с резервного носителя и восстановить систему. Главное — предусмотреть такую возможность. А вот сбой в PPL означает полный отказ устройства. Поэтому он неизменяем и максимально прост.

Состав Embedded Linux

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

Bootloader

Для загрузки операционной системы на SoC чаще всего используется U-Boot — мощный и универсальный загрузчик с открытым исходным кодом. Именно с ним мы и будем работать. Вот его преимущества:

  • Поддерживает множество архитектур и SoC-платформ

  • Позволяет загружать ядро и RootFS с самых разных источников: USB, SD-карты, SATA-диска, по сети, из NAND/NOR-памяти

  • Может выполнять сценарии: например, пробовать сначала загрузку с USB, затем с eMMC и только потом — по сети

  • Поддерживает интерактивный режим (через консоль) и авто-загрузку по таймеру

  • Имеет гибкую конфигурацию через переменные среды

Скачать U-Boot можно с официального репозитория.

Если SPL не помещается в SRAM, его можно «разбить» на части — подробнее об этом было в разделе про загрузку SoC

Kernel

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

Скачать ядро можно с официального репозитория или с форков, адаптированных под конкретный SoC. Например, для Rockchip-процессоров есть репозиторий linux-rockchip от проекта Armbian, содержащий патчи, Device Tree-файлы и конфигурации под различные платы.

RootFS

RootFS — это не просто набор каталогов. Это полноценное окружение, в котором есть:

  • Утилиты вроде init, sh, ifconfig

  • Конфигурационные файлы

  • Зависимости для запуска программ

В отличие от ядра и загрузчика, RootFS не собирается как бинарник — он создаётся как структура каталогов. Вы можете создать RootFS вручную (например, скопировав нужные бинарники и библиотеки из хост-системы), но это требует аккуратности и глубокого понимания устройства системы.

Инструменты и средства сборки Embedded Linux

Итак, «представителей» рассмотрели. Возможно, кто-то даже полез смотреть исходники. Но не советую спешить, ведь нам осталось разобраться со средствами и инструментами сборки.

Toolchain

Toolchain — набор компиляторов, заголовков и утилит, ориентированных на целевую архитектуру, а именно:

  • Кросс-компиляторы gcc, g++, ld, as — всё необходимое для сборки кода

  • Заголовочные файлы ядра Linux (Linux kernel headers)

  • Библиотеки и заголовки libc, libstdc++, libm и другие системные библиотеки

  • Отладочные утилиты, анализаторы, системные хелперы

Главное требование к тулчейну — соответствие архитектурам Host (Где происходит компиляция) и Target (Где будет запускаться код) систем.

Скачать можно уже готовый, а можно собирать самому (Например, при помощи CrossTool-NG). Второй вариант заслуживает отдельного цикла статей, поэтому представим, что мы можем использовать только готовые решения.

Их можно скачать с сайта-производителя вашего устройства или сторонних сайтов производителей тулчейнов (Например, Linaro).

Ну, еще можно собрать самому...

Стоп, разве я не сказал, что мы так делать не будем? Скажем так, я слукавил. Собирать и настраивать тулчейн мы будем, но не сами, а попросим об этом инструмент сборки.

Инструменты сборки

Чтобы не собирать всё вручную, обычно используют специальные сборочные системы. Наиболее популярны две: Buildroot и Yocto Project.

Yocto Project

Yocto — это мощная и гибкая среда сборки Embedded Linux. Позволяет:

  • Собирать ядро, загрузчик, RootFS и тулчейн

  • Включать в систему поддержку пакетных менеджеров (opkg, rpm, dpkg)

  • Точно управлять версионностью, патчами и конфигурацией

Однако у Yocto довольно высокий порог вхождения. Он использует собственную систему рецептов (bitbake), множество слоёв и понятий. Взамен вы получаете максимальную гибкость и контроль.

Buildroot

Buildroot проще: это набор Makefile, Kconfig и bash-скриптов, собирают минимальную, но готовую к работе систему Linux. Как и Yocto, позволяет собрать ядро, загрузчик, RootFS и тулчейн. Из плюсов:

  • Низкий порог вхождения

  • Простой интерфейс (menuconfig)

  • Защищенная от изменений в Runtime система — RootFS по умолчанию read-only, встроить пакетный менеджер без танцев с бубном возможно, но крайне сложно и не рекомендуется.

Именно Buildroot мы будем использовать в этом руководстве. Его можно скачать с официального репозитория.

На этом вводную часть можно закончить и перейти от теории к практике.

Итог

Итак, мы прошлись по основным терминам, узнали, что такое SoC, EL, как он загружается, чем отличается от обычного дистрибутива, из каких компонентов состоит и чем все это собирать.

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

На этом первая статья из цикла подошла к концу. Спасибо за уделенное время! Еще увидимся!