monami-ya.mrb での Static Thread Binding サポート

注: 2014-06-17 00:30:00JST バグフィックスがあったので,コードの引用を修正し,体裁も整えました.

昨日で,monami-ya.mrb への sandbox サポートについて,峠を超えました.

当日記でも,実装背景について取り上げました.

その際,sandbox が必要な理由の説明で,スレッドの使い方について説明しました. つまり,monami-ya.mrb は,下位層にスレッドライブラリ(RTOS含む)の存在を暗黙的に期待しています.

RTOS 含めると,動作が重くなるんじゃないの?

みたいなことを仰る方も,世間には稀にいます. それは概ね誤解です. その辺りの話は,別の機会に取り扱いましょう.

注: 本稿は,mruby のビルドシステムと,uITRON4.0 仕様でのアプリケーション記述についての知識があることを前提として書かれています.

目指せ,ゆるふわ組み込みライフ

いやほんと,組み込み向け軽量 Ruby って言葉を聞いた時に,大いに期待したのですが. 組み込みって,分野広いですから,仕方ないと言えば仕方ないのですが.

愚痴っていてもしかたがないので,fork して拡張するのです.

解決したいこと

せっかく軽量 Ruby で “ゆるふわ” したくても,RTOS の細かい差異に振り回されるようではゲンナリです.

OSEK の OIL とか,uITRON/TOPPERS のコンフィギュレーションファイルとか,書きたくないわけです.

そこで,static thread binding の登場となります.

記述と内部処理

static thread binding は,sandbox 同様, monami-ya.mrb の master ブランチにも develop ブランチにさえも入っていません. なので,仕様は微調整される可能性があります. でも,もう手元では概ね動いているので,ガッツリ変わるということは,ないでしょう.たぶん.

build_config.rb への記述

“static” と謳っているくらいですから,記述箇所は build_config.rb になります.

1
2
3
4
5
6
7
8
9
10
11
  s = conf.sandbox('blink') do |conf|
    conf.gem :github => 'ShinyaEsu/mruby-direct'
    conf.gem :bitbucket => 'monami_ya_mrb/mruby-toppers-itron'
  end

  conf.thread do |conf|
    conf.sandbox = s
    conf.activate = true
    conf.type = :task
    conf.script_path = "script/bf533cb/led_blink.rb"
  end

前半は,sandbox です.後半の conf.thread でスレッドを定義します. あまり説明の必要は無いでしょう. アクセサは,こんな感じです.

1
2
3
4
5
6
7
class ThreadBind
  attr_accessor :activate
  attr_accessor :script_path
  attr_accessor :sandbox
  attr_accessor :priority
  attr_accessor :stack_size
  attr_accessor :type

activate は,システム起動時に,スレッドを実行開始の状態にするかを決めます. Win32 や POSIX では無視かエラーかかもしれません. RTOS ではシステム起動時のタスクの実行状態を決められることが多いので,重要なパラメタです.

stack_size は,RTOS では必要でしょうけれども,Win32 や POSIX なら無視されるのでしょう. type は,uITRON/TOPPERS の場合には,:task, :cycric, :alarm, :interrupt とかになるでしょう.

script_path は,そのスレッドが実行する Ruby (mruby)スクリプトを指定します.

1
2
3
4
5
Direct.write16(0xffc00730, 0x0001)
while true
  ITRON.dly_tsk 1000
  Direct.write16(0xffc0070c, 0x0001)
end

とか. 普通のスレッド(タスク)なら,while ループの中で何かをさせる感じになります. 上記の場合は,BF533CB ボード上の LED を 1000ms 周期で Lチカしています.

ワンショットのスレッド(uITRONでの,alarm / cyclic / 割り込みハンドラ)では ループをさせないで終わらせることになるでしょう.

余談:割り込みハンドラをRubyで書けるか

ええ,割り込みハンドラも書けます. monami-ya.mrb ならね.

性能的に使い物になるかどうかなんて気にしてはいけません. ムーアの法則が解消する可能性が高いからです.

割り込みハンドラを RiteVM (mruby の VM) で書く場合に,物理時間制約以外のリスクがあるとすると,malloc 時のロックとメモリ枯渇です. monami-ya.mrb では,TLSF アロケータの採用による,mrb_state 間のロックフリー化をしてあります. また,VMのスタック上限をmrb_state毎に指定可能にしてあるため,どうしようもない枯渇は起こし辛いようにしてあります.

現在の本家 mruby では,これらの対応が抜けています. 本家のままでは,割り込みハンドラを Ruby で書くのはリスクが高すぎて実用不能でしょう. まさか割り込みハンドラまで書くとは思っていないフシがあるので,たぶん今後も.

内部処理

build_config.rb の記述から,下位のスレッドライブラリへのバインディングに必要なコードを静的に生成します.

現時点では,TOPPERS/JSP 用のコードを生成します.決め打ちです. 将来的には, toolchain のように,動作環境の OS を build_config.rb に記述することで,生成されるコードを,変更可能になるべきでしょう.

具体的には,このようなファイルができます.

1
2
3
#include "mrb_thread_bind.h"
INCLUDE("\"mrb_thread_bind.h\"");
ATT_INI({TA_HLNG, 0, mrb_thread_bind_initialize});

uITRON4.0 のコンフィギュレーションファイル

1
2
3
4
5
#include "itron.h"
#ifdef _MACRO_ONLY
extern void mrb_thread_bind_initialize(VP_INT exinf);
extern void mrb_thread_bind_entry(VP_INT exinf);
#endif

TOPPERS/JSP側が用いるヘッダファイル

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <stdint.h>
const uint8_t mrb_thread_script0[] = {
0x52,0x49,0x54,0x65,0x30,0x30,0x30,0x32,0xe9,0xce,0x00,0x00,0x00,0xe2,0x4d,0x41,
0x54,0x5a,0x30,0x30,0x30,0x30,0x49,0x52,0x45,0x50,0x00,0x00,0x00,0xc4,0x30,0x30,
0x30,0x30,0x00,0x00,0x00,0xb8,0x00,0x01,0x00,0x05,0x00,0x00,0x00,0x00,0x00,0x10,
0x01,0x00,0x00,0x11,0x02,0x00,0x00,0x02,0x03,0x00,0x01,0x02,0x01,0x00,0x82,0x20,
0x00,0x80,0x07,0x17,0x01,0x00,0x02,0x11,0x02,0x00,0x02,0x02,0x01,0x01,0x81,0x20,
0x01,0x00,0x00,0x11,0x02,0x00,0x03,0x02,0x03,0x00,0x01,0x02,0x01,0x00,0x82,0x20,
0x01,0x00,0x00,0x07,0x01,0x7f,0xf7,0x18,0x01,0x00,0x00,0x05,0x00,0x00,0x00,0x4a,
0x00,0x00,0x00,0x04,0x02,0x00,0x16,0x34,0x2e,0x32,0x39,0x30,0x37,0x37,0x34,0x38,
0x33,0x32,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x65,0x2b,0x30,0x39,0x01,0x00,0x01,
0x31,0x01,0x00,0x04,0x31,0x30,0x30,0x30,0x02,0x00,0x16,0x34,0x2e,0x32,0x39,0x30,
0x37,0x37,0x34,0x37,0x39,0x36,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x65,0x2b,0x30,
0x39,0x00,0x00,0x00,0x04,0x00,0x06,0x44,0x69,0x72,0x65,0x63,0x74,0x00,0x00,0x07,
0x77,0x72,0x69,0x74,0x65,0x31,0x36,0x00,0x00,0x05,0x49,0x54,0x52,0x4f,0x4e,0x00,
0x00,0x07,0x64,0x6c,0x79,0x5f,0x74,0x73,0x6b,0x00,0x45,0x4e,0x44,0x00,0x00,0x00,
0x00,0x08,
};

conf.script_path で指定したスクリプトファイルのコンパイル結果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include "itron.h"
#include "mruby.h"
#include "mruby/irep.h"
#include "/Users/monaka/git/monami-ya.mrb/monami-ya.mrb/build/jsp-bfin/threads/thread_script_0.cinc"

static const uint8_t *thread_scripts[] = {
  mrb_thread_script0,
  NULL
};

static mrb_state *mrb[1];

void
mrb_thread_bind_initialize()
{
  mrb[0] = mrb_open_sandbox(1);
}

void mrb_thread_bind_entry(VP_INT exinf)
{
  mrb_load_irep(mrb[(size_t)exinf], thread_scripts[(size_t)exinf]);
}

ITRONタスクのエントリポイントと,カーネル初期化ルーチンのエントリポイント.

これらのうち,Cソースコードは,コンパイルされ libmruby.a に纏められます.

最終的に,TOPPERS/JSP の libitron.a とコンパイルするところは,monami-ya.mrb の外です. イマイチ,パンチに欠けますが. あんまりタイトにバインドさせると,OS毎のビルドシステムの差異に苦しめられることになるので,今のところはここまでです.

セルフビルドが可能な環境では,全てを隠ぺいすることは容易でしょう.

設計方針

設計方針は,繰り返しになりますが「ゆるふわ Ruby でガチ組み込み」です.

Arduino 方面ではありません.

Arduinoのスケッチみたいなのにも存在価値は認めますが,狙っているのは,そこではありません. mruby と Arduino的なライブラリの組み合わせについては,kyab 氏が約1年ほど先行しています. そこを今更になって再発明する必要性は無いでしょう.

Ruby で全ての記述をすべきとは思いません.

ここで,monami-ya.mrb は全てのタスクを Ruby で書くことを強要しては いない ということは,注目して頂きたい点です.

大目に見ても,RiteVM の処理時間は,C で書く場合より 2桁のオーダで遅いでしょう. なので,物理時間制約の大きなところでは,Cでタスク(スレッド)を書きたくなるはずです. そもそも,いちいち Ruby に移植しないと使えないというのであれば,使う気が削がれます.

TOPPERS にせよ uT-Kernel にせよ,既に資産があるわけです. それら資産を活用しながらも,新しいことができる. そういう設計方針で,この機能は作られます.

この機能だけで RTOS とのバインディングをする気はありません.(但:要検討)

それと,この機能はスレッドバインディングに特化しています. スレッドがある以上,スレッド間の通信機能が必要になります. この設計では,通信機能は mrbgems として提供されるべき,と割り切っています.

静的OSの場合,mrbgems だけで通信機能のためのカーネルオブジェクト生成を行うのは 辛いはずですが…それは今後の課題です.

本家へのマージは…?

MITライセンスで出します. 誰かが本家にプルリクを投げることは止め用が無いですし,止めませんが. 常識的に考えて,マージは無いでしょう. ターゲットが違いすぎます.