はじめに
3年前だったか、何か作ってみようとスマートカー(Rpiで動く)をElixir/Nervesで作ってみた。
そのオモチャの車、一通り動くようにはなったが、どうも思ったように作れない。
一つはI2C通信モジュールをいろんな機能が使うようにするが、排他制御がないなどエラーが発生する。GenServerでメッセージにより排他をとるのはは可能だが、GenServerのカスケードとなり、そのオンパレードになってしまう。
と、人の好みだろうが、私にとってはなんか作りにくいし、GenServer自体好みじゃない。
で、えぃ、このNervesを自分の好みに変えてやれ、と無茶苦茶な事を考え始める。どうせ、退職の身、仕事はないので時間は十分だし、ただただ、好きな事やればいいや。
ここでは、その時以降にやってきた事を支離滅滅に書き下ろします。やった順番はバラバラで。
LinuxベースをRTOS(Real-TimeOS)に変更しよう
世の中にRTOS上で動作するNervesがあるけど、年金生活の身、買う余裕なんてない。で、タダのFreeRTOSにしよう。
マニュアルを読み流したところ、やはり、どのRTOSもそんな変わらないものだと感じる。
次にCPU(core)のマニュアルを見る。RTOSを移植するportingが必要となるので。ちなみに自慢になるが、仕事上、ハード周りの仕事が多かったので、このporting作業をよくやっていた。そのため、CPUのマニュアルを見る事が多いし、ソフトマニュアル(コンパイラ、ニーモニックなど)も見る。実作業はしない、ただ、どんなものかの把握のみ。これはすべてついて、同じ。
- RTOSはシングルcoreでしか対応できない
マルチCPU対応(coreって呼びなれない)のRTOSはCPU間の信号やり取りが必要、ハードに依存しているのでFreeRTOSなど普通のRTOSはシングルCPUでしか、動作しない。ラズパイを含め、一般的に使われるCPUはマルチコアのため、RTOSを使用できないと思われる。
AtomVMではFreeRTOSを使っているがESP32などシングルのため、使えているのだろう。
あーぁ、ここで既にRTOS使用不可、だめだ。
LinuxドライバーをRTOS用に変えなくちゃ
RTOS使用不可だが、あれやこれやとバラバラでやっていたので、時系列に書いてはいない。
RTOSに変更すると今あるlinuxに依存する箇所をRTOS用にする必要がある。
おそらく、
・linuxのIOドライバー
・プロセス制御などLinuxの機能箇所
・その他、linuxAppを直接使っている箇所
などがあると思う。
その流れで、Nervesのごく簡単なGPIOモジュールを見始めた。どんな処理をされているかを。
そして、実際のGPIO制御する方法、ハードを調査する。ハードレジスタに入力なのか出力なのかの設定と実出入力レジスタと、あまりやり方は変わらないと感じる。
ハードを直接みるツールの組込み
IOレジスタを直接見たり、書いたりしたいと思ったので、デバックツールをNervesに組み込む事を考える。関わった開発にはすべて入れていて、直接アドレスをリード/ライトするツール。特殊のものでなく、linuxで言えば、hexdumpのようなbinutils
みたいなもの。
Nerves上ではコンソール(標準入出力)を握っているので、その切り替えをどうするのか、わからなかったのでシリアルなど別ルートを検討。と、いろいろとやるが、ここまで。
今見たのところ、Nervesにはmix firmware.gen.gdb
ようなデバッグツールが準備されているので探せばあるかも。
NervesのIOドライバーを作るため、Zigを勉強
IOドライバーのためもあるが、元々組込みC言語に代わる言語を探していた中、Rustをやってみようと勉強。Tutorialを一通り実施、コード間違い探しなどで習得。だが、Rustはオブジェクト指向のため、これは作りにくいとZigに移行し、同じようにTutorialなどを実施、そしてZigler
NIFをコードレベルで習得した。
Zigler(NIF)って、Elixirのコード内部に埋め込む形のため、使いにくい
RustlerはRustのプロジェクト管理が独立していて、単独でRustを作る事ができる。Rustだけでbuildし、ライブラリーを作り、最後にElixirとリンクするようになっている。
一方、Ziglerは*.ex
のElixirコード上に埋め込まれる形で、単独でbuild、デバッグができない。そのため、ziglerをRustlerようにしたく、やっていたが、NIFを使うつもりがない、Zig言語よりC言語の方が慣れている、また、自由度があるので、やめた。
Erlang/ElixirからRTOSを呼べない
ErlangにはERTS(Erlang Run-Time System)があり、その中でVM(Beam処理)とプロセス管理処理などがある。
そして、Erlang/Elixirの処理(App)はすべてこのERTSの上で動作する。そして、ERTSは元のlinuxから起動される。
今、linuxをRTOSに変更しても、ERTSが管理しているため、AppからRTOSを呼べない、使用できない。
じゃ、AtomVMはどうなっているのか
AtomVMでは、ERTSと違うそれ専用のERTSなるものを持っている。だが、現状Erlang/Elixirのappの動作が同じになるように作れらているので、ErlangのERTSとAtomVMのERTSとは基本、動作は同じ。ただ、AtomVMでは、Erlang/Elixirで作られたBEAMを独自な構造に変換され処理されているため、同じと言えども、おそらく、機能削減されているものと推定される。
これらの事は実コードで確認したわけでないので、推定の域を出ないが、動作が同じならば、せっかくRTOS上なのに AppからはRTOSを呼べない。つまり、AtomVMがRTOSを使っていると言えど、表面上の話でRTOSだが、意味がないと思う。
この「Erlang/Elixirがそのまま動く」という事はGleam(RTOSで動作する)についても言え、使えないのではないか、と思われる。
なぜ、RTOSが必要なんだろうか
RTOSを使用すると言うが、一体何が問題で必要となるのだろうか。リアルタイム性が必要だからか、他にも理由があるのだろうか。
RTOSには主に以下の機能がある。
- タスク管理(Scheduler)
- メッセージ
- イベント
- 排他制御(セマフォ、ミューテックス)
- メモリ管理
- 割り込み
タスク管理について、linuxなどとは違い、あるタスクが動作中は他のタスクが動作しないようになっている。仮りにあるタスクが無限ルーブに陥れば、他が動かないため、そこでシステムは停止する。
また、より優先度の高いタスクに即座にタスク遷移される。例えば、Aタスクの優先度 > Bタスクの優先度の場合、Bタスクからメッセージなどを高い優先度Aに送信すると、即座、Aタスクに切り替わるしくみとなっている。
これらの事により処理時間が予測可能となりリアルタイム性が保証される事となり、リアルタイムOSと呼ばれる所以となる。
その他の機能については、このタスク管理下でのタスク間IF処理となる。(メモリ管理、割り込みは別)
ところが、このリアルタイム性が本当に必要なのだろうか、疑問視している。
まず、この処理時間を考慮して組込みを作る人が30年ほど前からいない、そんな人に出会ったことがないなどからリアルタイム性が必要だと思う人がいないのだ。
おそらく、昔比べて、CPUクロックが何十倍にも大きくなり、そんな処理時間を考慮せずとも、作れてしまうからではないだろうか。
ある人のプログラムコードを見た時、何かの処理を100回ルーブしているものを見た。「おい、100回も回ったら処理時間がどれくらいかかるんだ、あかん、作り直せ」と言った事がある。ベタの検索で検索データにより9000回ループする処理を平気で書いている。一体全体、どこに組込み技術者がいるのだろうか、悲しい。
という経験から、開発システムの要求仕様によるだろうが、今となってはリアルタイム性は不要かもしれないと思うようになった。
4MHzのクロックで処理できていた処理を今や2GHzで処理でやるので、もはや、時間は重要ではないのではないだろうか。
リアルタイム性が必要でないなら、一体、RTOSには他に特徴がないのだろうか。と考えると、処理時間が予測できる事から、動きが正確に把握できるという事、つまり、書いたコードがそのまま確実に動く点があると思う。
そして、大きな特徴として、タスク間同期がある。タスクが相互にどう動くか、動かせるかを確実となり、複数のタスクで機能分担が可能で、構造的に明確で、より確実な動作ができるようになる。つまり、Linuxなので一つのプロセスがなんでもかんでもするより、細かく分離にし、効率良く動かす事が可能となる。「作りにくい」と思うのは、この機能をタスク毎に分割できる点が必要なんだと思う。
メッセージとprocessとを分離
RTOSは無理があるので、前章のタスク同期にあたるものを組み込んではどうか、と考え始めた。
メッセージとタスクとがRTOSでは分離独立している。そのため、タスク間のとりあいが自由で確実となる。一方、Erlang/ERTSは一つのプロセスにひとつのメッセージ口(くち)となっている。この部分を変更できないのだろうか、となった。
この処理の詳細説明はしない。一見複雑そうに見えるが、各機能の動きが限定され、正確に動作する事になると思う。
この分離作業は、雲のような話で問題が多くある。
- メッセージメモリの分割
- PCBの変更、軽量から重くなる限度
- スケジューラの変更
- PID固定化するか
- 現状のものと共生可能か
現状、やっている事
兎にも角にも、Erlang/ERTSをコードレベルまで理解しようとしている。
プロセス管理のコードを見たが、ひとつのソースに数万行と馬鹿でかいものがあり、一体Erlangのkernelチームに理解しているものがいるのだろうか、不思議になるほど、複雑怪奇。大変なので、cflow
で構造だけでもわかるかと使ったが、ファィル数が多く、さっぱり。ドキュメントがあるが、概念的で詳細はわからない。と言っても、大まかな動きはしらないといけないので、ちょっこ、ちょっこ見る。
ひとつわかったのは、erl_init.c内のerl_start()
からすべてが始まるとの事だけが現状。まぁ、他にもertsのファイル(directory)構造、機能ブロックなど、だいだいわかってきたが。
どこから手をつけたらよいのやら、とお手上げ状態で、erlangのbuildエラーが発生したおりにconfigure
とmake
を調査している。
最後に
組込みを長くやってきたのためか、すぐ、処理がどうなっているのかを見たがる。仕事では、開発システムでのソースコードはRTOSを含めほとんどすべて見ていた。それは、ある不具合が発生した時に原因を追求する必要があるからで、不具合内容によりハード、制御実機器、ユーザの操作方法など、関連するあらゆるものの検討、調査が必要となるのだ。そのせいで、なんでも首を突っ込むくせとなり、自分でもわけのわからない事をしているんだと思う。
ただ、一点、Nervesを開発してきた組込みシステムに利用できるようにしたいという事。
Elixir/Nervesは関数型で、処理の答えが明確な言語で正確性が求めらる組込みには最適だと思うし、私にとって、その最大の魅力は軽量なタスク管理にあり、機能毎に分離できるという事だと思う。