オープンソースのRTOS実装・NuttX RTOSでNimのasync/awaitのデモを動かした話

## はじめに

今回NimとNuttXという選択で、なぜNim?なぜNuttX?そもそもどんな特徴があるの？という話を書く。

### プログラミング言語 Nim

Nimはefficient,expressive,elegantを謳っている、静的型つきでコンパイル型のプログラミング言語である。

* C++やRustにインスパイアされたメモリ管理機能
* C言語,C++,JavaScriptなど複数のターゲットへコンパイルが可能
* ASTの書き換えが可能な強力なマクロシステム
* エレガントな構文

といった点を特徴としてあげている。
より詳細な特徴に関しては[公式サイト](https://nim-lang.org/)をみてほしい。

Nimの特徴のひとつとして、言語設計としてhard-real time systems向けにデザインされている点がある。たとえばTLSFアルゴリズムのメモリアロケータ・ARC/ORCメモリモードによる参照カウントベースのメモリ管理・Goto-based exceptionsによる決定論的な例外処理などが実装されている。

- [Nim for embedded system](https://nim-lang.org/docs/nimc.html#nim-for-embedded-systems)
- [A cost model for Nim](https://nim-lang.org/blog/2022/11/11/a-cost-model-for-nim.html)
- [Goto-base exceptions](https://nim-lang.org/araq/gotobased_exceptions.html)

さらにNimは現在develでFreeRTOS/Zehpyr/NuttXといった複数のRTOSをサポートしており、一部の標準ライブラリの機能を利用することができる。またNuttXに関しては[日本人の開発者の方がポーティング作業](https://github.com/nim-lang/Nim/pull/21372)を行っている。

今回はNuttX上でNimのサンプルプログラム(async/awaitの利用例)を動かすことに挑戦する。

### NuttX

[NuttX](https://nuttx.apache.org/docs/latest/index.html)はオープンソースのRTOS実装である。

国内での利用事例として、ソニーのSpresenseに搭載ということで[話題](https://monoist.itmedia.co.jp/mn/articles/2105/10/news022.html)にもなったことがある。NuttXはPOSIX準拠・高機能といったあたりが主な特徴で、詳しくはやはり[公式ドキュメント](https://nuttx.apache.org/docs/latest/introduction/about.html)をみることをおすすめする。

個人的にはPOSIXやLinuxのインターフェースの実装で他のオープンソースのRTOSよりportingが充実している点に魅力を感じる。たとえば `posix_spawn` や `epoll` なんかがわりとそのまま使えたりする。

## やっていくぞ

### Nimの入手

Nimの入手方法はいろいろ手段があるが、nightlyを使う必要があることを考えるとchoosenimを使うのが一番手っ取り早いだろう。

- choosenim: https://github.com/dom96/choosenim

choosenimをダウンロードしたらdevel channel(実質的にnightly相当)を持ってくる。

```console
$ choosenim devel
```

### NuttXのダウンロード

NuttXのインストールの手順は https://nuttx.apache.org/docs/latest/quickstart/install.html をみればだいたいできるはず。
サンプルプログラムやアプリケーションは別レポジトリで管理されているため、そちらのレポジトリも用意することを忘れずに。

```console
$ 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を動かす

#### プログラムの内容

実際に動かしていく前に、少しだけプログラムをみていく。

```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のスクリプト機能で実行してビルド時の設定を決定するようになっている。

その設定内容として一部を抜粋したものが以下である:

```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ではメモリリークがおきる](https://nim-lang.org/docs/mm.html)ため
  - またこれによりgoto-based exceptionも自動的に使われる
- クロスコンパイル時に利用するコンパイラをswitchで指定
- NuttXはデフォルトでmmapがないので、`useMalloc`/`nimAllocPagesViaMalloc`を指定
- NuttXはデフォルトでシグナル処理をしないので`noSignalHandler`を指定
- `noMain`でNimのメイン関数を利用しないように指定(`NimMain`の呼び出しなどはC言語のエントリポイントから呼んでいる)
- 最終的なリンク作業などはNuttX側に任せるので、`compileOnly`/`noLinking`の設定を有効に

などの設定が行われている。

またその他`nimPageSize`や`nimMemAlignTiny`の設定などいろいろやっているが、マニュアルと照らし合わせながら実際に目で確認することをおすすめする。

- [nimPage256 / nimPage512 / nimPage1k](https://nim-lang.org/docs/nimc.html#nimpage256-slash-nimpage512-slash-nimpage1k)
- [nimMemAlignTiny](https://nim-lang.org/docs/nimc.html#nimmemaligntiny)

今回は指定されていないが、配列の境界チェックなどの機能を使いたい場合は`panics:on`フラグをつけることでsysFatal関数が例外を投げようとせずにpanicするようになるので、より大きなプログラムを書く際にデバッグ時に利用するとよいだろう。

#### NuttX: 設定初期化

次にnuttxの下に移動して設定の初期化を行っていく。

今回は simulator を使うことにするので、以下のようにスクリプトを実行してまず環境の初期化を行う。

```console
$ cd nuttx
$ tools/configure.sh -l sim:nsh # Linuxなので-lにしているが、macOSなら-mにする必要がある
```

次にTUIベースの設定画面でシステム設定の初期化を行う。

```console
$ 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を切っているが、
このあたりはお好みで。

ここまでで準備が終わったら、あとはビルドして実行するだけとなる。

```console
$ make
```

実行結果は以下の通りになるはず。

ループカウントよりも先にタスクのIDがインクリメントしていることを確認してほしい。

```console
$ ./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を使う際の有力な選択肢の一つとなりえるポテンシャルを秘めているのではないかと期待している。