Вы никогда не задумывались о том, чтобы запустить на домашнем роутере какой-нибудь пакет Ubuntu? Для этого можно было бы использовать контейнеры LXC. Всё это легко настроить, но такая установка будет не очень эффективной: зачем устанавливать целый дистрибутив Linux на маршрутизатор ради маленьких программ, используемых в домашней автоматизации?

В качестве примера в статье рассматривается маршрутизатор Turris Omnia.

Какие ещё есть варианты?

  • Писать программы на Python. Turris OS реализована на версии Python 2.7, и многие инструменты администрирования также написаны на Python. Я никогда не испытывал особого интереса по отношению к Python, к тому же это высокоуровневый интерпретируемый язык со сборщиком мусора. В общем, не самый эффективный вариант. ?‍♂️
  • Использовать низкоуровневый язык типа C или C++ и выполнять компиляцию программ в код целевой платформы для Turris. Оказывается, кросс-компиляцию довольно легко настроить, и мы можем получить оптимальную производительность при самом эффективном использовании памяти. Всё это так, если бы только не было регулярной утечки памяти или аварийного завершения работы процесса при обращении к памяти, которая уже была освобождена ? (не говоря уже о сложности установки сторонних зависимостей из-за отсутствия диспетчера пакетов для C/C++).
  • Использовать Rust, чтобы взять максимум из этих двух вариантов: продуктивность и надежность высокоуровневых языков, таких как Python, вместе с производительностью низкоуровневых языков типа C. ?

Остановимся на варианте с Rust. Чтобы получить программу Rust, запускаемую на Turris, нужно:

  1. Найти платформу, используемую оборудованием маршрутизатора. Она будет целью кросс-компиляции.
  2. Установить инструментальные средства кросс-компиляции и убедиться, что мы можем выполнить компиляцию простой программы на C в код целевой платформы и запустить её на маршрутизаторе.
  3. Установить Rust, настроить кросс-компиляцию и убедиться, что мы можем выполнить компиляцию простой программы на Rust в код целевой платформы и запустить её на маршрутизаторе.

Примечание: следующие инструкции предназначены для Ubuntu 18.04 LTS. Для запуска Ubuntu на компьютере с Windows я использую подсистему Windows для Linux.

Находим целевую платформу ?️‍♂️

Цели кросс-компиляции обычно представлены в таком формате:

{architecture}-{vendor}-{system}-{abi}

Мы уже знаем, что процессор Turris Omnia имеет архитектуру ARMv7. В случае с системами Linux поставщик обычно неизвестен (unknown), потому что имя поставщика дистрибутива не так важно. Последняя часть (ABI) — это двоичный интерфейс приложений, используемый операционной системой Turris OS. В Linux это связано с реализацией libc, которую можно узнать с помощью ldd --version.

$ ssh [email protected] "ldd --version"
musl libc (armhf)
Version 1.1.19
Dynamic Program Loader
Usage: ldd [options] [--] pathname

Цель кросс-компиляции для Turris Omnia представлена в таком виде: armv7-unknown-linux-musleabihf. К счастью для нас, в Rust есть поддержка 2-ого уровня платформ:

Второй уровень платформ предусматривает«гарантированную сборку». Автоматические тесты не выполняются, поэтому создание рабочей сборки не гарантируется, но платформы работают обычно довольно хорошо, и предлагаемые улучшения всегда приветствуются!

Настраиваем кросс-компилятор на C ⚙

Прежде чем начинать, давайте убедимся, что у нас установлен необходимый инструментарий.

$ sudo apt-get install build-essential

Наша задача стала бы намного проще, если бы мы нацеливались на gnueabihf вместо musleabihf, потому что в Ubuntu есть пакеты с набором инструментальных средств кросс-компиляции для целей gnueabihf. MUSL пока что для меня загадка: я почти ничего не знаю об этой реализации стандартной библиотеки C.

К счастью, есть проект под названием musl-cross-make, который даст нам «простую makefile-сборку для кросс-компилятора musl», и он отлично работает! Сначала загружаем исходные коды с GitHub:

$ wget https://github.com/richfelker/musl-cross-make/archive/master.tar.gz
$ tar xzf master.tar.gz
$ cd musl-cross-make-master

Давайте настроим параметры конфигурации, прежде чем приступать к сборке. Скопируем файл шаблона конфигурации config.mak.dist в config.mak и установим следующие параметры (вы можете отменить преобразование соответствующих строк шаблона в комментарий):

TARGET=arm-linux-musleabihf
OUTPUT=/usr/local
MUSL_VER = 1.1.19

(Лучше использовать ту же версию MUSL, которая указана в ldd --version на вашей Turris. У меня на момент написания статьи была версия 1.1.19).

Пора делать сборку!

$ make
$ make install

Весь инструментарий у нас должен быть установлен в /usr/local и доступен на PATH. Давайте проверим и запустим gcc:

$ arm-linux-musleabihf-gcc --version
arm-linux-musleabihf-gcc (GCC) 9.2.0
Copyright (C) 2019 Free Software Foundation, Inc.
(...)

Выполняем кросс-компиляцию программы на C ?

Пока что у нас есть только предположительная цель компиляции, соответствующая платформе Turris. Пришло время проверить наши предположения на практике.

Напишем простую программу “Hello world” на C и сохраним следующий код в файл с именем hello.c:

#include <stdio.h>
int main() {
   printf("Hello, World!\n");
   return 0;
}

Выполняем кросс-компиляцию этой программы для Turris:

arm-linux-musleabihf-gcc hello.c -o hello

Загружаем программу на маршрутизатор и выполняем её там. Я сохраняю файл в /srv (который поддерживается накопителем mSATA SSD), чтобы избежать ненужных записей во внутреннюю флэш-память. Если всё пройдёт хорошо, вы должны получить знакомое приветствие:

$ scp hello [email protected]:/srv
hello                                 100% 7292     1.6MB/s   00:00
$ ssh [email protected] /srv/hello
Hello, World!

Устанавливаем Rust и настраиваем кросс-компиляцию ?

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

$ curl https://sh.rustup.rs -sSf | sh

Необходимо также установить стандартные крейты (базовые модули Rust), скомпилированные для нашей целевой платформы.

$ rustup target add armv7-unknown-linux-musleabihf

На последнем этапе мы сообщаем компилятору Rust, какой компоновщик следует использовать при компиляции для целевой платформы. Добавляем в ~/.cargo/config следующее:

[target.armv7-unknown-linux-musleabihf]
linker = "arm-linux-musleabihf-gcc"

Выполняем компиляцию программы на Rust в код целевой платформы ?

Создаём программу “Hello world” на Rust и компилируем её:

$ cargo new --bin hello
$ cd hello
$ cargo build --target=armv7-unknown-linux-musleabihf
   Compiling hello v0.1.0 (/home/bajtos/src/hello)
    Finished dev [unoptimized + debuginfo] target(s) in 2.53s

Загружаем программу на маршрутизатор и выполняем её там. Если всё идёт хорошо, снова получаем наше приветствие.

$ scp target/armv7-unknown-linux-musleabihf/debug/hello [email protected]:/srv
hello                                  100% 2903KB  10.8MB/s   00:00 $ ssh [email protected] /srv/hello
Hello, World!

Поздравляю, теперь вы можете взять любую программу на Rust и запустить её на маршрутизаторе Turris Omnia. Например, если вы используете Mastodon и Twitter, то можете синхронизировать между этими двумя сетями всё то, что вы в них выкладываете, с помощью mastodon-twitter-sync.

Полезные ссылки и благодарности ?‍♂️

Большая часть информации в этой статье основана на замечательном руководстве по кросс-компиляции программ на Rust.

Список платформ, поддерживаемых Rust, можно найти в официальной документации проекта здесь.

И наконец, мне потребовалась бы целая вечность, чтобы понять, как выполняется кросс-компиляция для MUSL ABI, если бы не было отличного проекта musl-cross-make project Рича Фелкера.

P.S.

Вы заметили, что наша программа на C весит 7 Кб, в то время как версия Rust имеет 2903 Кб? Есть несколько способов уменьшить размер исполняемого файла программ на Rust. Активировав оптимизацию во время компоновки, мне удалось быстро уменьшить размер полученной сборки до 1408 Кб. О других, более продвинутых способах можно узнать в «Минимизации размера двоичного кода Rust»: https://github.com/johnthagen/min-sized-rust.

Читайте также:


Перевод статьи Miroslav Bajtoš: Cross-compile Rust programs to run on Turris Omnia

Предыдущая статьяКак достичь уровня senior-разработчика
Следующая статьяСтанут ли прогрессивные веб-приложения заменой нативным?