September 22, 2023

MRuby и embedded-разработка 

Еще один интересный язык с неожиданной областью применения. Казалось бы какое отношение скрипты и интерпретатор могут иметь к embedded-разработке со всей их байто#блей и ограниченными ресурсами? Читайте и узнаете.

Картинка для привлечения внимания, была выложена на ЛОР.

Что это и зачем

Начну как обычно с цитаты:

mruby is the lightweight implementation of the Ruby language complying with part of the ISO standard. mruby can be linked and embedded within your application.

Вообщем это такая упрощенная реализация языка Ruby, с упором на встраивание.

Да да, тот самый кровавый embedded, где pure C, ссылочная арифметика, malloc(), free() и Сотона с рогами.

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

extend Yeah::DSL                                     
set port: 3000                                        
    
get '/hi/{name}' do |name|    
  "Привет #{name}"    
end
    
ENV['SHELF_ENV'] = 'production'

puts "Запуск.."   
  __main__ [0]

И.. оно просто работает:

Запуск бинарника. Обратите внимание на текст на русском.

И даже вот так:

Ответ сервера в браузере. Обратите внимание на текст на русском.

Правда ведь круто? Сколько там пудов соли нужно сожрать чтобы так просто работать с юникодом из Си?

Да кстати, вся эта радость еще и собирается в очень небольшой бинарник:

2 Мегабайта на все про все. Для сравнения я вчера купил флешку на 128 Гигабайт за примерно $10

Внутри вообщем-то все сразу, вся среда:

MRuby, все используемые библиотеки и сама программа.

Что-то такое вполне возможно показывает веб-интерфейс в вашем домашнем роутере прямо сейчас.

Вот в таком:

Тестовый проект

Я как обычно пошел несколько дальше «Hello world», поэтому собрал всю цепочку разработки, включая фреймворки. Не стал только доводить тестовый проект до чего-то полноценного ввиду нехватки времени.

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

Для тестового проекта я использовал вот это:

Yeah! is a DSL for quickly creating shelf applications in mruby with minimal effort

Если кратко то это такая попытка сделать мини-Rails, работающий на мини-Ruby. Вполне себе успешная, надо сказать.

А теперь самое важное:

Фреймворки для mruby представляют собой надстройку, которая в процессе собирает сам mruby с добавлением себя в собираемые бинарники mruby.

Звучит сложно и выглядит страшно, но для embedded среды это привычное дело.

Суть в том что нужно будет получить бинарники mruby и mrbc с упакованным внутрь фреймворком yeah и всеми библиотеками.

И только затем использовать этот билд mruby для сборки уже своего приложения.

Для сборки фреймворка нужен системный Ruby и rake, работает как старая 2я так и 3я версии.

Клонируем проект с фреймворком Yeah!:

git clone https://github.com/katzer/mruby-yeah.git

По-умолчанию будет собран один только компилятор mirbc, без интерактивной консоли (mirb) и интерпретатора (mruby).

Этого не хватит для нормальной разработки, поэтому добавляем в build_config.rb:

  conf.gem :core => 'mruby-bin-mruby'
  conf.gem :core => 'mruby-bin-mirb'
  conf.gem :core => 'mruby-bin-mrbc'

Запускаем сборку:

rake compile

Эта команда автоматически начнет выкачивать зависимые репозитории, в том числе сам mruby.

В этом месте у меня появлялись две ошибки.

Первая про mingw.h:

In file included from /opt/work/tmp/mruby-yeah/mruby/build/repos/host/mruby-r3/r3/src/memory.c:34:
/opt/work/tmp/mruby-yeah/mruby/build/repos/host/mruby-r3/r3/src/mman.h:15:10: fatal error: _mingw.h: No such file or directory
   15 | #include <_mingw.h>
      |          ^~~~~~~~~~
compilation terminated.
rake aborted!

В файле mman.h есть вот такое:

/* All the headers include this file. */
#ifndef _MSC_VER
#include <_mingw.h>
#endif

Переменная _MSC_VER не задается при сборке на FreeBSD, поэтому срабатывает вариант по-умолчанию: Windows и MinGW.

Вообщем я просто закомментировал этот блок, не заморачивась дальше.

Вторая ошибка не менее тупая:

/opt/work/tmp/mruby-yeah/mruby/build/repos/host/mruby-r3/r3/src/mman.h:52:9: error: conflicting types for 'mmap'; have 'void *(void *, size_t,  int,  int,  int,  OffsetType)' {aka 'void *(void *, long unsigned int,  int,  int,  int,  unsigned int)'}
   52 | void*   mmap(void *addr, size_t len, int prot, int flags, int fildes, OffsetType off);
      |         ^~~~
In file included from /opt/work/tmp/mruby-yeah/mruby/build/repos/host/mruby-r3/r3/src/memory.c:27:
/usr/include/stdio.h:444:10: note: previous declaration of 'mmap' with type 'void *(void *, size_t,  int,  int,  int,  __off_t)' {aka 'void *(void *, long unsigned int,  int,  int,  int,  long int)'}
  444 | void    *mmap(void *, size_t, int, int, int, __off_t);
      |          ^~~~
rake aborted!

В этом же файле mman.h заменяем:

void*   mmap(void *addr, size_t len, int prot, int flags, int fildes, OffsetType off);

на

void*   mmap(void *addr, size_t len, int prot, int flags, int fildes, __off_t); 

И едем дальше.

Если сборка прошла успешно то в папке build/host/bin будут готовые бинарники:

ls ./mruby/build/host/bin/
mirb	mrbc	mruby

Теперь с их помощью запускаем сборку уже нашего тестового приложения:

/opt/work/mruby-yeah/mruby/build/host/bin/mrbc -Btest_symbol ~/test.rb 

Это сгенерирует файл test.c с вот таким контентом:

#include <stdint.h>
#ifdef __cplusplus
extern
#endif
const uint8_t test_symbol[] = {
0x52,0x49,0x54,0x45,0x30,0x33,0x30,0x30,0x00,0x00,0x01,0x3e,0x4d,0x41,0x54,0x5a,
0x30,0x30,0x30,0x30,0x49,0x52,0x45,0x50,0x00,0x00,0x01,0x0c,0x30,0x33,0x30,0x30,
0x00,0x00,0x00,0xcd,0x00,0x01,0x00,0x05,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x3d,
0x1d,0x02,0x01,0x1f,0x02,0x00,0x2d,0x01,0x02,0x01,0x10,0x02,0x03,0x0e,0x03,0x0b,
0xb8,0x2d,0x01,0x04,0x10,0x51,0x02,0x00,0x57,0x03,0x00,0x2e,0x01,0x05,0x01,0x1d,
0x01,0x06,0x51,0x02,0x01,0x51,0x03,0x02,0x24,0x01,0x51,0x02,0x03,0x2d,0x01,0x07,
0x01,0x06,0x02,0x47,0x02,0x01,0x2d,0x01,0x08,0x01,0x38,0x01,0x69,0x00,0x04,0x00,
0x00,0x0a,0x2f,0x68,0x69,0x2f,0x7b,0x6e,0x61,0x6d,0x65,0x7d,0x00,0x00,0x00,0x09,
0x53,0x48,0x45,0x4c,0x46,0x5f,0x45,0x4e,0x56,0x00,0x00,0x00,0x0a,0x70,0x72,0x6f,
0x64,0x75,0x63,0x74,0x69,0x6f,0x6e,0x00,0x00,0x00,0x0e,0xd0,0x97,0xd0,0xb0,0xd0,
0xbf,0xd1,0x83,0xd1,0x81,0xd0,0xba,0x2e,0x2e,0x00,0x00,0x09,0x00,0x03,0x44,0x53,
0x4c,0x00,0x00,0x04,0x59,0x65,0x61,0x68,0x00,0x00,0x06,0x65,0x78,0x74,0x65,0x6e,
0x64,0x00,0x00,0x04,0x70,0x6f,0x72,0x74,0x00,0x00,0x03,0x73,0x65,0x74,0x00,0x00,
0x03,0x67,0x65,0x74,0x00,0x00,0x03,0x45,0x4e,0x56,0x00,0x00,0x04,0x70,0x75,0x74,
0x73,0x00,0x00,0x08,0x5f,0x5f,0x6d,0x61,0x69,0x6e,0x5f,0x5f,0x00,0x00,0x00,0x00,
0x33,0x00,0x03,0x00,0x05,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0e,0x34,0x04,0x00,
0x00,0x51,0x03,0x00,0x01,0x04,0x01,0x52,0x03,0x38,0x03,0x00,0x01,0x00,0x00,0x0d,
0xd0,0x9f,0xd1,0x80,0xd0,0xb8,0xd0,0xb2,0xd0,0xb5,0xd1,0x82,0x20,0x00,0x00,0x00,
0x4c,0x56,0x41,0x52,0x00,0x00,0x00,0x16,0x00,0x00,0x00,0x01,0x00,0x04,0x6e,0x61,
0x6d,0x65,0x00,0x00,0xff,0xff,0x45,0x4e,0x44,0x00,0x00,0x00,0x00,0x08,
};

Напоминает шеллкод правда? Вообщем это ни что иное как готовый байткод mruby, запиханный в статичный массив байт.

Параметр -Btest_symbol как нетрудно догадаться отвечает за название этого массива.

Дальше сама запускалка (test_stub.c):

#include <mruby.h>
#include <mruby/irep.h>
#include <test.c>

int
main(void)
{
  mrb_state *mrb = mrb_open();
  if (!mrb) { /* handle error */ }
  mrb_load_irep(mrb, test_symbol);
  mrb_close(mrb);
  return 0;
}

Все что тут делается это скармливание байткода интерпретатору mruby при запуске.

Обратите внимание на строчку:

#include <test.c>

это как раз включение файла с байткодом mruby.

Ну и наконец сама сборка:

gcc -std=c99 -static -Os -s -I/opt/work/mruby-yeah/mruby/include -I. test_stub.c -o test_program /opt/work/mruby-yeah/mruby/build/host/lib/libmruby.a -lm -lpthread

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

Эпилог

Эта статья началась с поста на ЛОРе про «бинарь на руби» с смешным размером меньше мегабайта, с кросс-компиляцией под встраиваемые системы.

Я впечатился и начал копать.

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

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

С нетерпением жду отзывов о реальном использовании.