Building embedded linux systems

Embedded Linux в двух словах. Первое

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

Платформой послужит плата BeagleBone Black с процессором производства Техасских Инструментов AM3358 и ядром Arm Cortex-A8, и, чтобы не плодить мигающие светодиодами мануалы, основной задачей устройства будет отправка смайлов в топовый чат, широко известного в узких кругах, сайта, в соответствии с командами от смайл-пульта. Впрочем, без мигания светодиодами тоже не обошлось.

Итак, на столе лежит чистая, т.е. без каких-либо предустановленных дистрибутивов, плата BeagleBone Black, блок питания, переходник USB-UART. Для общения с платой, переходник нужно подключить к 6-ти выводному разъему, где первый вывод обозначен точкой — это GND, выводы 4/5 — RX/TX соответственно. После установки скорости в какой-либо терминальной программе, например putty, на 115200, можно взаимодействовать с платой, о подключении подробнее и с картинками здесь.

Топовые чаты, пульты и светодиоды будут позже, а сейчас на плату подается питание и плата отвечает CCCCCCCCCCC

В переводе с бутлоадерского это означает, что первичному загрузчику, зашитому в ROM процессора, нечего загружать. Ситуацию проясняет Reference Manual, где на странице 5025 в разделе 26.1.5 описана процедура начальной загрузки. Процедура такая: первичный загрузчик проводит некоторую инициализацию: тактирование процессора, необходимой периферии, того же UART, и, в зависимости от логических уровней на выводах SYSBOOT, строит приоритетный список источников где можно взять следующий загрузчик, т.е. посмотреть сначала на MMC карте, SPI-EEPROM или сразу ждать данных по Ethernet.

Я использую способ загрузки с помощью SD карты, вот что говорит об этом раздел RM 26.1.8.5.5 на странице 5057: первичный загрузчик сначала проверяет несколько адресов 0x0/ 0x20000/ 0x40000/ 0x60000 на наличие так называемой TOC структуры, по которой он может определить загрузочный код, если так код не найти, то первичный загрузчик, предполагая на SD карте наличие файловой системы FAT, будет искать там файл с названием MLO, как это расшифровывается в RM не сказано, но многие склоняются что Master LOader. Возникает резонный вопрос, где же взять этот MLO?

Das U-Boot

Das U-Boot или просто U-Boot — Universal Boot Loader, один из самых, если не самый, распространенный загрузчик для встроенных систем, именно с его помощью можно создать требуемый вторичный загрузчик (MLO), который будет загружать третичный загрузчик (сам U-Boot), который будет загружать ядро Linux.

Перед скачиванием U-Boot, стоит сходить в репозиторий и найти тег последней версии, далее

U-Boot содержит больше тысячи конфигураций, в том числе нужную:

Это конфигурация платы AM335x evaluation module, этот модуль лежит в основе других плат, в том числе BeagleBone Black, что можно видеть, к примеру, по Device Tree, но о нем позже. Настраивается и собирается U-Boot с помощью Kconfig, то же, что используется и при сборке ядра Linux.

Установка нужного конфига:

Можно, к примеру, убрать, установленную по умолчанию, 2-х секундную задержку при загрузке платы с U-Boot

Boot options —> Autoboot options —> (0) delay in seconds before automatically booting

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

ARM Toolchain

Один из видов кросскомпиляции это сборка на одной архитектуре, как правило x86-64, именуемой HOST, исходного кода для другой, именуемой TARGET. Например, для TARGET архитектуры ARMv7-A, ядра ARM CortexA-8 процессора AM3358, платы BeagleBone Black. К слову, чтобы не запутаться в ARM’ах, даже есть свой справочник, так их много и разных.

Сама сборка осуществляется набором инструментов — компилятор, компоновщик, runtime библиотеки, заголовочные файлы ядра; так называемый Toolchain. Toolchain можно собрать самостоятельно либо с помощью crosstool-NG, а можно взять готовый от компании Linaro, или самой ARM. Здесь я буду использовать Toolchain от ARM “GNU Toolchain for the A-profile Architecture Version 10.2-2020.11, x86_64 Linux hosted cross compilers, AArch32 target with hard float (arm-linux-none-gnueabihf)», если не вдаваться в излишние подробности, то это все означает, что набор инструментов будет работать на десктопной машине с Linux и собирать программы для 32-х битной ARM платформы с аппаратной поддержкой операций с плавающей запятой.

Теперь для успешной сборки U-Boot, нужно указать в переменных ARCH и CROSS_COMPILE требуемые архитектуру и путь к кросскомпилятору соответственно, например так

Либо использовать export ARCH/CROSS_COMPILE , чтобы каждый раз не набирать все это. Я, для наглядности, буду каждый раз набирать все это.

После сборки U-Boot, в папке появятся необходимые файлы, а именно

MLO — вторичный загрузчик (напомню, первичный зашит в самом процессоре)

u-boot.img — третичный загрузчик, собственно U-Boot

Для успешной загрузки с SD карты, нужно ее некоторым образом разметить. Карта должна содержать минимум два раздела, первый, отмеченный как BOOT, с файловой системой FAT, второй раздел с ext4. Разметить карту можно, к примеру, программой fdisk.

Читайте также:  Как установить yasm windows

Теперь нужно просто скопировать результаты сборки U-Boot в FAT раздел, вставить карту в BeagleBone Black и в терминале наблюдать уже более осознанный ответ платы

В ответе платы есть такие строки

Failed to load ‘boot.scr’

Failed to load ‘uEnv.txt’

U-Boot, во время загрузки, смотрит наличие дополнительных команд, сначала в файле boot.scr, при его наличии, затем, если boot.scr не нашлось, в uEnv.txt. Эти файлы, помимо очередности при поиске, отличаются тем, что в файле uEnv.txt, дополнительные команды представлены в текстовом виде, т.е. он проще для восприятия и редактирования. U-Boot не создает файлы с дополнительными командами, делать это нужно самостоятельно.

Здесь происходят некоторые манипуляции в результате которых U-Boot загружает из SD карты в RAM по адресу [loadaddr] — образ ядра [zImage], и по адресу [fdtaddr] — дерево устройств [Flattened Device Tree]. Формируются аргументы, передаваемые ядру Linux, это параметры консоли, к которой подключен переходник USB-UART [console=ttyS0,115200n8], место размещения корневой файловой системы [bootpartition=mmcblk0p2], параметры разрешения на чтение/запись корневой файловой системы [rw], ее тип [ext4] и ожидание появления корневой файловой системы [rootwait]. Чтобы раскрутить всю цепочку действий U-Boot, можно, после того как U-Boot прекратит попытки найти что бы загрузить и выдаст приглашение на работу в виде =>, ввести команду printenv , она покажет значения всех переменных, которыми располагает U-Boot.

В завершении своей работы U-Boot, командой bootz , вместе с вышеуказанными аргументами и адресом дерева устройств, передает управление ядру Linux.

Ядро Linux

Прежде чем приступать к любым действиям с ядром, стоит заглянуть сюда и убедится в наличии необходимых пакетов. Следующим шагом нужно определиться с тем, какую версию ядра использовать. Здесь я использую версию 5.4.92 и вот по каким соображениям. Одной из основных причин того, что не стоит брать просто последнюю версию ядра, доступную на данный момент, наряду с наличием драйверов, является невозможность быстро протестировать это ядро на всем разнообразии платформ поддерживаемых Linux, а значит можно потратить кучу сил и времени на исправление неполадок, если что-то пойдет не так, и не факт что это вообще получится сделать. BeagleBone Black имеет официальный репозиторий, где можно найти версию ядра, протестированную на данной платформе, и long term версия 5.4.92 была последней на тот момент.

Нужный конфиг, расположенный в /arch/arm/configs, называется omap2plus_defconfig, OMAP — это название линейки процессоров, продолжением которых является AM3358, впринципе, подойдет и более общий multi_v7_defconfig.

Сам конфиг пока остается без изменений, поэтому можно просто его установить и запустить компиляцию ядра(zImage), модулей(modules) и дерева устройств(dtbs)

Проходит некоторое время.

Результат сборки, в виде zImage, находится в /arch/arm/boot, там же в папке /dts находится скомпилированное дерево устройств am335x-boneblack.dtb, оба отправляются на SD карту к файлам загрузчика. На этом FAT раздел SD карты можно считать скомплектованным. Итого, там присутствуют:

MLO — вторичный загрузчик

u-boot.img — третичный загрузчик

uEnv.txt — дополнительные команды загрузчика

zImage — образ ядра Linux

am335x-boneblack.dtb — скомпилированное дерево устройств платы

Еще при сборке ядра заказывались модули ядра, но они уже относятся к корневой файловой системе.

Корневая файловая система. BusyBox

Ядро получает корневую файловую систему путем монтирования блочного устройства, заданного в, переданном при запуске ядра, аргументе root=, и далее, первым делом, исполняет оттуда программу под названием init.

Если запустить BeagleBone Black, имея только вышеуказанные файлы для FAT раздела, то ядро будет паниковать по причине отсутствия init и, в целом, по причине пустой rootfs, т.е. корневой файловой системы.

Можно шаг за шагом создать все минимально необходимые компоненты корневой файловой системы, такие как оболочка, различные демоны запускаемые init, сам init, конфигурационные файлы, узлы устройств, псевдофайловые системы /proc и /sys и просто системные приложения. Для желающих совершать подобные подвиги, существует проект Linux From Scratch, здесь же я воспользуюсь швейцарским ножом встроенных систем с Linux, утилитой BusyBox.

Скачивание последней, на тот момент, версии:

Настройка конфигурации по умолчанию:

Чтобы не думать сейчас о разделяемых библиотеках, стоит установить статическую сборку BusyBox:

Settings —> Build static binary (no shared libs)

Установка в папку по умолчанию _install:

Теперь в папке _install можно видеть будущую корневую файловую систему, в которую нужно добавить некоторые вещи.

Папки, помимо созданных BusyBox:

Стартовый скрипт. Дело в том, что, запускаемая в первую очередь, программа init, делает много полезного, например, выводит в консоль приглашение, но до выдачи приглашения, init проверяет наличие стартового скрипта /etc/init.d/rcS, и, при наличии, запускает его.

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

Стоит сказать, что работа init, на самом деле, начинается с чтения конфигурационного файла /etc/inittab, но BusyBox’овская init включает таблицу inittab по умолчанию, если таковой не окажется в корневой файловой системе.

Теперь пора вспомнить про модули ядра. Их также нужно разместить в корневой файловой системе в /lib/modules/5.4.92/, но сейчас они разбросаны по всей папке в которой собиралось ядро. Чтобы собрать модули в кучу, нужно в папке с ядром выполнить

Где в INSTALL_MOD_PATH указать путь к папке с корневой файловой системой, кросскомпилятор указывать не нужно, т.к. здесь модули ядра просто копируются по месту назначения. В результате папка /lib корневой файловой системы пополнится разделом /lib/mudules/5.4.92/ содержащим модули ядра, полученные при компиляции ядра.

Читайте также:  Windows 10 en francais

Осталось скопировать все содержимое папки _install во второй раздел SD карты, тот который с ext4, и поменять владельца всего содержимого на root.

После запуска BeagleBone Black с корневой файловой системой, через 1.910315 секунды после старта ядра, система предложит активировать консоль и начать работу.

Но начать работу в такой системе, скорее всего не получится, т.к. в ней нет ничего кроме системных утилит BusyBox и моей небольшой программы, нарисовавшей приветствие, зато, эта система поможет получить общее представление о том, какая магия происходит внутри подобных устройств. Именно общее, т.к. в реальных устройствах, из-за необходимости минимизации времени загрузки, ограниченности ресурсов, заточенности под конкретную задачу, различий между ARM процессорами, построение системы может сильно отличаться. Например, на малинке, вообще сначала стартует графический процессор, который затем запускает все остальное.

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

Источник

Сборка Embedded Linux от Yocto для QEMU x86 и первое приложение к нему

Эта статья — быстрый старт с картинками для тех, кому нужно собрать Embedded Linux с помощью Yocto.
Если вы собрались собирать Embedded Linux для специфичных аппаратных средств, например, для SoC на FPGA, то, наверное, как и я столкнётесь с проектом Yocto.

Yocto — проект, объединяющий

  • инструментарий разработчика;
  • систему сборки;
  • набор программных интерфейсов;
  • коллекцию мета-пакетов, расширяющих возможности платформы;
  • плагины для Eclipse и Anjuta.

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

Сборка образа Linux

Основа для сборки — статья
«Yocto Project Quick Start».
Собирать буду в папке

Небольшое отступление:
Запустив терминал, все команды выполняйте в нём, потому что в ходе работы создаются и используются переменные среды. Если закрыли терминал и открыли новый, то заходите в

и придётся повторять некоторые команды. Например,

создаю репозиторий git

теперь текущая директория

теперь, согласно терминам Yocto, получилось
Source Directory — «poky»
local working area (local branch) — dizzy
Первым делом выполняется

Этой командой настраивается окружение оболочки, создается начальный изменяемый набор файлов конфигурации и осуществляется взаимодействие с окружением выполнения системы BitBake путем использования файла сценария, позволяющего Poky установить, выполняются ли минимальные системные требования. Результат выполнения команды сообщит или о проблемах, например, недостающих пакетах, или о возможности продолжить редактированием файла conf/local.conf.

Здесь следует указать число нитей, которое будет запускать средство сборки bitbake. Чтобы максимально использовать вычислительные возможности, число нитей должно соответствовать числу ядер процессора. Я указал на 1 меньше, чтобы компьютером во время сборки удобнее было пользоваться.
Так же я добавил дополнительные возможности, полезные для разработки.
Так как собираем для стандартной QEMU x86, то остальное можно не изменять.

Далее можно выбрать “рецепт” — вариант сборки со своим набором библиотек, утилит, создаваемых файлов, образов и т.п.
Список рецептов с их краткими описаниями можно посмотреть здесь:
рецепты openembedded-core
пролистайте до рецептов, начинающихся с core-…
Не советую собирать core-image-minimal для ознакомления. Там нет ssh и других удобных для отладки инструментов.
Я выбрал core-image-sato. Это сборка с GUI SATO

Будьте готовы, что ждать завершения сборки придётся, возможно, от 3 часов до 3 и более дней. В счастью, процесс можно приостановить и возобновить.
Чтобы приостановить, нажмите + один (!) раз и подождите, когда завершатся все начатые операции. Иначе, возможно, появятся ошибочки и придётся переделывать. Чтобы возобновить, повторите

Если закрыли терминал, то в новом терминале перед сборкой придётся повторить установку переменных среды:

По завершении сборки можно проверить, что получилось, запустив эмулятор QEMU

Появится окно терминала xterm, там надо будет ввести пароль root хоста для создания интерфейса tap. Там же можно увидеть IP host-машины на tap интерфейсе и IP эмулируемой машины. У меня 192.168.7.1 и 192.168.7.2 соответственно.
Затем, если собран SATO должно появится окно графического интерфейса:

Если запущенный эмулятор мешает, пока его можно закрыть. Полистайте стрелочками в верхней части GUI до появления соответствующей иконки выключения или введите poweroff через терминал SATO или по ssh. Про ssh будет позже.
Не закрывайте QEMU крестиком на главном окошке эмулятора. Закрывайте штатными средствами эмулируемой машины. Иначе у вас в системе от QEMU останутся интерфейсы ethX, tapX, virbrX, которые придётся будет ifconfig… ifdown, или как это делается на вашей ОС. Или каждый раз при запуске QEMU будут новые IP хоста и QEMU, что неудобно.

Средства разработки

Используется Eclipse IDE for C/C++ Developers, версия Luna. В этой сборке имеются плагины CDT, GNU ARM и некоторые другие полезные для кросс-компиляции и разработки встроенного ПО плагины.
Можно скачать отсюда:
Eclipse Luna SR2.

Будем собирать приложения Hello, world!

Eclipse, Cross GCC

Сначала простой путь, соберём обычными средствами Eclipse: CDT + GNU ARM
Если этих плагинов в вашем Eclipse ещё нет, то добавьте плагин CDT из стандартного репозитория Eclipse и отсюда GNU ARM Plugin for Eclipse GNU ARM C/C++ Cross Compiler и что ещё захочется.

Создаём новый проект C++:
Меню Eclipse File->New->Project…->C/C++->C++ Project
Нужно выбрать
Project type: Executable->Hello World C++ Project
Toolchains: Cross GCC

заполните поле Author
Теперь самое важное, что касается системы сборки:
Cross compiller prefix: i586-poky-linux-
Cross compiler path: /home/. /My_Designs/Poky/poky/build/tmp/sysroots/x86_64-linux/usr/bin/i586-poky-linux
Компилируем проект
Project->Build Project
Должен появиться исполняемый файл
Так как компилировали для QEMU x86 Linux, то должен запуститься и на вашей host машине, если, конечно, вы работаете на x86 Linux или совместимой с ней. У меня запустилось на IA64 Ubuntu.
Теперь, если успели закрыть QEMU, то запустите опять

Читайте также:  Linux thermald что это

Появится окно терминала xterm, надо будет ввести пароль root для создания tap интерфейса.

Как видно, у меня IP host-машины 192.168.7.1, IP qemu 192.168.7.2
Затем появится окно QEMU с графическим интерфейсом
Можно подсоединиться к QEMU через ssh и sftp. Первый полезен для запуска программы, второй — для отправки этой программы на эмулируемую машину.
Файл я отправлял с помощью файлового менеджера Krusader. В Windows можно использовать FileZilla или другими.
В Krusader я выбрал Tools->New Net Connection…, в диалоговом окне нужно выбрать sftp://, ввести IP и Username: root

В домашнюю папку на QEMU я скопировал исполняемый файл hello_cross_gcc_02 из папки Debug проекта
Далее можно открыть терминал в графическом интерфейсе QEMU и запустить программу на выполнение оттуда. Мне более удобным показалось соединиться через ssh. В окне терминала host-машины запускаем ssh

и запускаем недавно скопированный исполняемый файл:

Должно получиться
Hello World!

Сборка в ADT

Подготовка Eclipse

Делалось на основе этого: Yocto — Настройка Eclipse
В Eclipse нужны плагины из стандартной комплектации:
Linux Tools

  • LTTng — Linux Tracing Toolkit

Mobile and Device Development

  • C/C++ Remote Launch
  • Remote System Explorer End-user Runtime
  • Remote System Explorer User Actions
  • Target Management Terminal
  • TCF Remote System Explorer add-in
  • TCF Target Explorer

Programming Languages

  • Autotools Support for CDT
  • C/C++ Development Tools

И нужно добавить плагин от Yocto. Скачать можно отсюда: Yocto Plugin for Eclipse

Подготовка сборки

Для организации взаимодействия с IDE и средствами разработки, предусмотрены скрипты:

для запуска QEMU плагином Yocto нужен rpcbind

нужно отредактировать файл /etc/default/rpcbind

нужно распаковать root filesystem в ту папку в sysroots, где лежит файловая система эмулируемой машины. В нашем случае qemux86:

Настройка Yocto ADT в Eclipse

В Eclipse меню Windows->Preferences, Yocto Project ADT
Cross Compiler Options
Build system derived toolchain
Toolchain Root Location:
/home/. /Poky/poky/build/
Sysroot Location:
/home/. /Poky/poky/build/tmp/sysroots/qemux86
Target Architecture: i586-poky-linux
Target Options:
QEMU
Kernel: /home/. /Poky/poky/build/tmp/deploy/images/qemux86/bzImage-qemux86.bin

Проект Hello, World! в Eclipse c Yocto ADT

Создаём новый проект
New Project->C++ Project
Project name: hello_qemu_cmake_04
Project type: Yocto Project ADT CMake Project->Hello World C++ CMake Project

Next, Next, Finish
Build project
В папке проекта -> Debug должен получиться hello_qemu_cmake_04
Теперь можно запустить приложение так же как это было с проектом, созданным через Cross GCC за исключением того, что QEMU запускается теперь в Eclipse через меню
Run->External Tools->qemu_i586-poky-linux

Запуск и отладка из IDE Eclipse

Теперь настроим Eclipse так, чтобы можно было запускать и отлаживать приложение прямо из IDE.
Инструкция взята отсюда:
Загрузка и отладка приложения средствами ADT в Eclipse
Для удобства перед настройкой удалённая машина должна быть запущена (Run->External Tools->qemu_i586-poky-linux, как в предыдущем разделе).
Для настройки отладки выбираем в меню Eclipse
Run -> Debug Configurations…
В списке слева нужно отыскать “C/C++ Remote Application”, там должен быть пункт с названием нашего проекта и виртуальной машины. У меня это “hello_qemu_cmake_04_gdb_i586-poky-linux”. Надо его выбрать.
В руководстве от Yocto предлагается указать Remote Absolute File Path for C/C++Application. Но сейчас не самое лучшее время для этого. Удобнее будет сначала настроить связь с удалённой машиной.
А пока в правой области выбираем вкладку Debugger, здесь надо в выпадающем списке Debugger выбрать remote gdb/mi. У меня это единственный элемент списка, но его всё равно нужно выбрать. При этом заполняться некоторые другие поля формы.
Debug Configurations — Debugger» data-src=»https://habrastorage.org/files/578/799/303/5787993038a54954bb16b360004daac3.png»/>
Возвращаемся на вкладку Main и создаём соединение.
В разделе Connection выбираем New…. В окне New Connection выбираем TCF. У меня их два. Я не знаю, зачем второй, но выбор первого точно приводит к положительному результату.

Debug Configurations — Main->New Connection» data-src=»https://habrastorage.org/files/28f/05c/dea/28f05cdeade343ae876ee4beaede83be.png»/>

В поле Host name вписываем IP машины в QEMU. Поле Connection name можно оставить заполненным IP. , … Чтобы меньше было возни позже, здесь же можно указать login при соединении. В разделе Connection нажмите Edit… и заполните Default User ID. Логин root.

Debug Configurations — Main->Edit — Properties» data-src=»https://habrastorage.org/files/334/24c/4e6/33424c4e612d48fc95439282cf009346.png»/>
На вкладке Main выбираем только что созданное соединение.
Теперь указываем путь к файлу и имя файла, с которым Eclipse будет загружать отлаживаемое приложение на удалённую машину.
Если QEMU запущен, то можно нажать Browse… и, если с соединением всё в порядке, можно будет посмотреть файловую систему удалённой машины и выбрать место, куда загружать приложение. У вас не обязательно будет такая же картинка, как на скриншоте снизу. Но вот что важно: нужно указать файл. Если вы собираетесь указать папку, в которой нет файла с тем именем, с которым хотите загружать исполняемый файл, то в этим окном вы не ограничитесь. Нажмите и допишите имя файла в окне Debug Configurations.

Debug Configurations — Main->Select remote application file» data-src=»https://habrastorage.org/files/ffc/9c1/3cd/ffc9c13cd54a4f2aaa4a64c05469848e.png»/>

Итог должен получиться примерно такой:

Debug Configurations — Main» data-src=»https://habrastorage.org/files/5bb/ee6/a91/5bbee6a91d3849d7b2e250735bcd4bec.png»/>

Теперь можно нажать .

У меня в окне консоли Eclipse “Remote Shell” было так:

Затем приглашение переключиться в перспективу Debug. Есть смысл согласиться.
Отладчик остановился на строке

Жмём Step Over (F6)
В терминале

Теперь можно писать программу. Пока для QEMU x86.

Источник

Оцените статью