Ruby and Embedded Systems

It would seem, what does “hipster scripts for the web” have to do with the harsh realities of embedded development and embedded systems, with all their low-level work and limited resources? Alas, reality once again turned out to be much more interesting than prejudice, and this is how this article was born.

The picture was posted on ENT to attract attention.

A picture to attract attention was posted to ENT.

What is it and why

I'll start as usual with a quote:

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.

In a word, this is such special implementation of the Ruby language, with an emphasis on embedding and embedded systems – yes, yes, that very “bloody embedded“, where pure C reigns, reference arithmetic, malloc() and other nightmares and horrors for the modern developer.

And suddenly you appear in this kingdom of Hades all in white and write something like this, high-level:

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

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

And.. it just works:

Running the assembled binary. Pay attention to the text in Russian.

Running the assembled binary. Pay attention to the text in Russian.

And even like this:

Server response in browser. Pay attention to the text in Russian.

Server response in browser. Pay attention to the text in Russian.

Cool?

How many pounds of salt do you need to eat in order to work with Unicode from C so easily, especially in an embedded environment?

By the way, all this “hipster joy” is also collected in a very small binary:

2 Megabytes for everything about everything

2 Megabytes for everything about everything

Inside there will be “everything at once”:

MRuby, all used libraries and the application itself.

Although the ENT specialists noticed that “640kb is enough for everyone” this will somehow be a bit much, but let me remind you that we live in a world of pennies 128GB flash drives and the battle for every byte of free space is no longer as relevant as 10 years ago.

I fully admit that such an application displays a web interface on your home router, displays a menu on a TV, or plays advertisements on a bus – in short, it is used in most places where embedded systems are used.

Supported Platforms

Unfortunately, it was not possible to find all platforms supported by MRuby in one list, so I will limit myself only to specific examples found.

The most interesting:

development for Sega Dreamcast and Nintendo WiiFor POS terminalsembedding in iOS applications.

Well, the actual built-in systems:

  1. ESP32 the project template is here here.

Appearance of the ESP32 board.

Board appearance ESP32.
  1. RP2040 (Raspberry Pi), an example project is located here.

  2. PIC32here's an example here.

Test project

Since “raspberry” was once again not at hand, it was decided to implement a test project on a banal x86 – the entire development chain was assembled, including frameworks, “Hello world” was implemented as a web application running on a built-in web server.

All manipulations were performed on an unsupported no one and nowhere FreeBSD, so most likely the problems described below with assembly on a more common Linux will not occur.

For the test project I used this: This “miracle”:

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

In short, this is a kind of attempt to implement “mini-Rails running on mini-Ruby.” Quite successful, it should be noted.

And now the most important thing:

Frameworks for mruby represent superstructurewhich in the process collects itself mruby and adds itself to the compiled binaries.

It sounds complicated and looks scary, but for an embedded environment it is commonplace.

So we will need to get the binaries mruby And mrbc with a framework packed inside yeah and all the libraries, and then use this build to build your application.

To build the Yeah! requires external “big” Ruby and rakeboth 2.x and 3.x versions will work.

We clone a project with the Yeah! framework:

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

Since by default only the compiler is built mirbcwithout an interactive console (mirb) and an interpreter (mruby), which is not enough for normal development of the final application, add it to the file build_config.rb:

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

And start the build:

rake compile

This command will automatically download dependent repositories, including the desired branch of the mruby. At this stage, the author encountered two errors.

The first one is about the header file 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!

In file mman.h there is this line:

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

Variable _MSC_VER is not set when building on FreeBSD, so the default option works – for Windows and MinGW. As a correction, I simply commented out this block without bothering with further research.

The second error is also quite trivial and occurs due to a difference in the implementation of the function mmap:

/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!

In the same file mman.h replace:

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

to:

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

And we restart the assembly.

If the assembly was successful, then in the folder build/host/bin there will be ready-made binaries:

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

Now, with their help, we launch the assembly of our test application:

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

This will generate the file test.c with this content:

#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,
};

This is nothing more than ready-made mruby bytecode, written as a static byte array.

Parameter -Btest_symbol as you might guess, is responsible for the name of this array.

And this is what the launch application itself looks like (file 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;
}

All that happens here is simply issuing bytecode to the interpreter mruby when the wrapper starts. Pay attention to the line:

#include <test.c>

This is the inclusion of a file with bytecode mruby.

And finally, building the final test application:

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

If everything goes well, the final binary will appear in the current folder test_programwhich I launched at the very beginning.

Epilogue

This article began with a post on ENT about a “Ruby binary” with a ridiculous size of less than a megabyte and cross-compilation for embedded systems. I was impressed and started digging, and this article was born.

I think the material presented is enough for those readers who deal with embedded systems to try MRuby for their tasks, fortunately the author sees this thing as extremely promising.

As well as the government of Japan, which this project finances.

Simply because it removes a whole class of problems associated with developing application systems in pure C – memory management, Unicode, strings, and so on.

I'm looking forward to feedback on actual use.

PS

This is a slightly censored version of the article, original which is available on our blog.

0x08 Software

We are a small team of IT industry veterans, we create and develop a wide variety of software; our software automates business processes on three continents, in a wide variety of industries and conditions.

Let's revive long dead, fixing something that never worked and create impossible — then we talk about it in our articles.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *