はじめに
今回NimとNuttXという選択で、なぜNim?なぜNuttX?そもそもどんな特徴があるの?という話を書く。
プログラミング言語 Nim
Nimはefficient,expressive,elegantを謳っている、静的型つきでコンパイル型のプログラミング言語である。
- C++やRustにインスパイアされたメモリ管理機能
- C言語,C++,JavaScriptなど複数のターゲットへコンパイルが可能
- ASTの書き換えが可能な強力なマクロシステム
- エレガントな構文
といった点を特徴としてあげている。
より詳細な特徴に関しては公式サイトをみてほしい。
Nimの特徴のひとつとして、言語設計としてhard-real time systems向けにデザインされている点がある。たとえばTLSFアルゴリズムのメモリアロケータ・ARC/ORCメモリモードによる参照カウントベースのメモリ管理・Goto-based exceptionsによる決定論的な例外処理などが実装されている。
さらにNimは現在develでFreeRTOS/Zehpyr/NuttXといった複数のRTOSをサポートしており、一部の標準ライブラリの機能を利用することができる。またNuttXに関しては日本人の開発者の方がポーティング作業を行っている。
今回はNuttX上でNimのサンプルプログラム(async/awaitの利用例)を動かすことに挑戦する。
NuttX
NuttXはオープンソースのRTOS実装である。
国内での利用事例として、ソニーのSpresenseに搭載ということで話題にもなったことがある。NuttXはPOSIX準拠・高機能といったあたりが主な特徴で、詳しくはやはり公式ドキュメントをみることをおすすめする。
個人的にはPOSIXやLinuxのインターフェースの実装で他のオープンソースのRTOSよりportingが充実している点に魅力を感じる。たとえば posix_spawn
や epoll
なんかがわりとそのまま使えたりする。
やっていくぞ
Nimの入手
Nimの入手方法はいろいろ手段があるが、nightlyを使う必要があることを考えるとchoosenimを使うのが一番手っ取り早いだろう。
- choosenim: https://github.com/dom96/choosenim
choosenimをダウンロードしたらdevel channel(実質的にnightly相当)を持ってくる。
$ choosenim devel
NuttXのダウンロード
NuttXのインストールの手順は https://nuttx.apache.org/docs/latest/quickstart/install.html をみればだいたいできるはず。
サンプルプログラムやアプリケーションは別レポジトリで管理されているため、そちらのレポジトリも用意することを忘れずに。
$ mkdir nuttxspace
$ cd nuttxspace
$ git clone https://github.com/apache/nuttx.git nuttx
$ git clone https://github.com/apache/nuttx-apps apps
依存ライブラリなどは各種環境によって違ってくるので、上記公式ドキュメントのgetting startedを読んで用意する。
hello nimを動かす
プログラムの内容
実際に動かしていく前に、少しだけプログラムをみていく。
import std/asyncdispatch
import std/strformat
proc task(id: int): Future[void] {.async.} =
for loop in 0..2:
echo &"Hello from task {id}! loops: {loop}"
if loop < 2:
await sleepAsync(1000)
proc launch() {.async.} =
for id in 1..2:
asyncCheck task(id)
await sleepAsync(200)
await task(3)
proc hello_nim() {.exportc, cdecl.} =
waitFor launch()
GC_runOrc()
このプログラムの特徴として:
- async/awaitによる非同期処理で、taskがforループ内でsleepしている間に別のtaskが起動する
- exportc, cdecl プラグマにより外部のCコードから参照できるように
- GC_runOrc で明示的にcycle collectorを起動
などがある。
またビルド時にconfig.nimsをNimのスクリプト機能で実行してビルド時の設定を決定するようになっている。
その設定内容として一部を抜粋したものが以下である:
switch "os", "nuttx"
switch "mm", "orc"
switch "arm.nuttx.gcc.exe", "arm-none-eabi-gcc"
switch "arm64.nuttx.gcc.exe", "aarch64-none-elf-gcc"
switch "riscv32.nuttx.gcc.exe", "riscv64-unknown-elf-gcc"
switch "amd64.nuttx.gcc.exe", "x86_64-linux-gnu-gcc"
switch "nimcache", ".nimcache"
switch "d", "useStdLib"
switch "d", "useMalloc"
switch "d", "nimAllocPagesViaMalloc"
switch "d", "noSignalHandler"
switch "threads", "off"
switch "noMain", "on"
switch "compileOnly", "on"
switch "noLinking", "on"
-
os:nuttx
を指定 -
mm:orc
を指定- async/awaitなプログラムではarcではメモリリークがおきるため
- またこれによりgoto-based exceptionも自動的に使われる
- クロスコンパイル時に利用するコンパイラをswitchで指定
- NuttXはデフォルトでmmapがないので、
useMalloc
/nimAllocPagesViaMalloc
を指定 - NuttXはデフォルトでシグナル処理をしないので
noSignalHandler
を指定 -
noMain
でNimのメイン関数を利用しないように指定(NimMain
の呼び出しなどはC言語のエントリポイントから呼んでいる) - 最終的なリンク作業などはNuttX側に任せるので、
compileOnly
/noLinking
の設定を有効に
などの設定が行われている。
またその他nimPageSize
やnimMemAlignTiny
の設定などいろいろやっているが、マニュアルと照らし合わせながら実際に目で確認することをおすすめする。
今回は指定されていないが、配列の境界チェックなどの機能を使いたい場合はpanics:on
フラグをつけることでsysFatal関数が例外を投げようとせずにpanicするようになるので、より大きなプログラムを書く際にデバッグ時に利用するとよいだろう。
NuttX: 設定初期化
次にnuttxの下に移動して設定の初期化を行っていく。
今回は simulator を使うことにするので、以下のようにスクリプトを実行してまず環境の初期化を行う。
$ cd nuttx
$ tools/configure.sh -l sim:nsh # Linuxなので-lにしているが、macOSなら-mにする必要がある
次にTUIベースの設定画面でシステム設定の初期化を行う。
$ make menuconfig
nuttx-appsのhello_nimプログラムを動かすためには一部ネットワークまわりの設定を有効にする必要がある。具体的には以下の設定が有効になっている必要がある。(詳しくは https://github.com/apache/nuttx-apps/pull/1597#issue-1599857833 を参照)
CONFIG_SCHED_CHILD_STATUS=y
CONFIG_NET=y
CONFIG_NET_SOCKOPTS=y
CONFIG_SCHED_CHILD_STATUSは初期状態で有効になっているため、menuconfigで以下の設定を有効にすればよい。
設定を有効にするには、フォーカスが当たっている状態でスペースキーを押して [*]
のような表示になっていることを確認できればよい。
Networking Support --->
[*] Networking Supprt
Socket Supprt --->
[*] Socket options
TCP/IP Networking --->
[*] TCP/IP Networking
また今回はhello_nimのサンプルプログラムを利用することが目的であるため、このプログラムもビルド対象に含める必要がある。
こちらのプログラムは以下の設定を有効にすることでビルド対象に含めることができる。
Application Configuration --->
Examples --->
[*] "Hello, World!" example (Nim)
NuttXはデフォルトの設定では毎度コンソールログインを要求されるので自分はNsh Libraryの設定でConsole Loginを切っているが、
このあたりはお好みで。
ここまでで準備が終わったら、あとはビルドして実行するだけとなる。
$ make
実行結果は以下の通りになるはず。
ループカウントよりも先にタスクのIDがインクリメントしていることを確認してほしい。
$ ./nuttx
NuttShell (NSH) NuttX-12.0.0
nsh> hello_nim
Hello from task 1! loops: 0
Hello from task 2! loops: 0
Hello from task 3! loops: 0
Hello from task 1! loops: 1
Hello from task 2! loops: 1
Hello from task 3! loops: 1
Hello from task 1! loops: 2
Hello from task 2! loops: 2
Hello from task 3! loops: 2
nsh> poweroff
まとめ
オープンソースのRTOSであるNuttX上でNimのサンプルプログラムを動かすことができた。
このサンプルプログラムはasync/awaitといった言語機能や標準ライブラリの機能が使われており、RTOSでそういった機能がすんなり動くというのはわかっていてもやはり驚きがある。
まだポーティング作業も完璧というわけではないが、Nim+NuttXは組み込み向けでRTOSを使う際の有力な選択肢の一つとなりえるポテンシャルを秘めているのではないかと期待している。