みつきんのメモ

組み込みエンジニアです。Interface誌で「Yocto Projectではじめる 組み込みLinux開発入門」連載中

NuttX ビルトインアプリケーションを作成する

はじめに

NuttXにビルトインアプリケーションを追加する方法を調査した。

ここが日本語でよくまとまっていた。

プログラムを作成する場所

プログラムをどこに追加するかと言うのが一つ考えどころだが、apps/README.txtによると、

Use of the name ''apps/external'' is suggested because that name
is included in the .gitignore file and will save you some nuisance
when working with GIT.

ということなので、apps/external以下にプログラムを追加する。

$ mkdir -p external/helloworld
$ cd external/helloworld

apps/external以下はリポジトリの管理からは外れているので自分で好き勝手に管理することができる。

$ git init

ただし、externalより下のディレクトリはビルド対象に含まれていないので、シンボリックリンクを貼る。

$ ln -s external/helloworld helloworld

プログラムの作成

次のファイル作成する。

  • Kconfig
  • Make.defs
  • Makefile
  • helloworld_main.c

これらは、apps/examples/helloを参考に、というかコピーしてきて自分のプログラムに合わせて変更する感じで良い。

Kconfig

config EXTERNAL_HELLO
    tristate "\"Hello, World!\" example"
    default n
    ---help---
        Enable the \"Hello, World!\" example

if EXTERNAL_HELLO

config EXTERNAL_HELLO_PROGNAME
    string "Program name"
    default "helloworld"
    depends on BUILD_LOADABLE
    ---help---
        This is the name of the program that will be use when the NSH ELF
        program is installed.

config EXTERNAL_HELLO_PRIORITY
    int "Hello task priority"
    default 100

config EXTERNAL_HELLO_STACKSIZE
    int "Hello stack size"
    default 2048

endif

この例では、次のコンフィグレーションがmake menuconfigで設定出来るようになっている。

  • プログラム名
  • タスクの優先度
  • スタックサイズ

今回は無いが、他のライブラリを使用する場合などは依存関係も定義できる。

プログラム名については後で詳しく説明する。

Make.defs

make menuconfigでプログラムが有効化された場合に、CONFIGURED_APPSにプログラムのディレクトリをappsからの相対パスで追加する。

ifneq ($(CONFIG_EXTERNAL_HELLO),)
CONFIGURED_APPS += helloworld
endif

CONFIGURE_APPSに追加されたディレクトリがビルド対象となる。

Makefile

-include $(TOPDIR)/Make.defs

# Hello, World! built-in application info

CONFIG_EXTERNAL_HELLO_PRIORITY ?= SCHED_PRIORITY_DEFAULT
CONFIG_EXTERNAL_HELLO_STACKSIZE ?= 2048

APPNAME = helloworld

PRIORITY  = $(CONFIG_EXTERNAL_HELLO_PRIORITY)
STACKSIZE = $(CONFIG_EXTERNAL_HELLO_STACKSIZE)

# Hello, World! Example

ASRCS =
CSRCS =
MAINSRC = helloworld_main.c

CONFIG_EXTERNAL_HELLO_PROGNAME ?= hello$(EXEEXT)
PROGNAME = $(CONFIG_EXTERNAL_HELLO_PROGNAME)

MODULE = CONFIG_EXTERNAL_HELLO

include $(APPDIR)/Application.mk

ここでは主に次の変数を設定している。

変数 概要
APPNAME アプリケーション名
PRIORITY タスクの優先度
STACKSIZE スタックサイズ
ASRCS アセンブラソース
CSRCS Cソース
MAINSRC メインのソース
PROGNAME プログラム名
MODULE ローダブルバイナリ用

APPNAME、PRIORITY、STACKSIZEの項目はKconfigで設定されてない場合のデフォルト値を考慮した記述になっている。

MODULEはCONFIG_EXTERNAL_HELLOにmを設定した場合に正しくローダブルバイナリを出力(ローダブルビルド)できるようにするために必要な変数。

ローダブルバイナリ

NuttXはelfをロードする機能を持っているので、アプリケーション単体をelfファイルとして作成し、 NuttX上のファイルシステムにおいてある動的にロード、実行することができる。

フラットビルドの場合はローダブルバイナリだったとしても実行時のアドレス空間は単一となる。

ローダブルビルドを有効にするにはCONFIG_BUILD_LOADABLEをyにする必要がある。その上でアプリのコンフィグをmに設定する。

APPNAMEとPROGNAME

APPNAMEとPROGNAMEで何が違うのか。

詳細はAPPNAME vs. PROGNAMEを参照。

ただ、リンク元の情報は古くなっているので今のBUILD_LOADABLEBUILD_KERNELの説明がごちゃまぜになっている。 以前はカーネルビルド(BUILD_KERNEL)でしかローダブルバイナリのロードができなかったようなので、その名残だと思われる。

フラットビルドかつローダブルビルドしない、つまり単一のバイナリに結合されるケースについて特に用語見つからなかったので、 ここでは「単一ビルド」と呼ぶことにする。

単一ビルドの場合、アプリケーションはlibapps.aというライブラリとして生成されnuttxにリンクされる。 多くの場合プログラムのエントリポイントはmain()だが、単一ビルドの場合は、同じシンボルを使用できないのでそれぞれにエントリポイントの名前つける必要がある。

nshでは$(APPNAME)_mainをそれぞれのアプリケーションのエントリポイントとして探すというルールがあるため、単一ビルドの場合はAPPNAMEが必要となる。

PROGNAMEはローダブルビルドした場合に生成される実行ファイルの名前を設定する。

そのため、これらはそれぞれ別の役割を持っていると言える。

APPNAMEとエントリポイントとMAINSRC

先述の通りAPPNAMEは単一ビルド時にアプリケーションのエントリポイントを探すために使用される。 そのためAPPNAMEとエントリポイントとなる関数の名前は次のルールに則っている必要がある。

$(APPNAME)_main

また、MAINSRCに設定するファイル名もエントリポイントと一致している必要がある。

APPNAMEが「helloworld」だった場合の設定例を次に示す。

項目 概要
APPNAME helloworld
エントリポイント helloworld_main
MAINSRC helloworld_main.c

単一ビルド時の

helloworld_main.c

/****************************************************************************
 * Included Files
 ****************************************************************************/

#include <nuttx/config.h>
#include <stdio.h>

/****************************************************************************
 * Public Functions
 ****************************************************************************/

/****************************************************************************
 * hello_main
 ****************************************************************************/

#if defined(BUILD_MODULE)
int main(int argc, FAR char *argv[])
#else
int helloworld_main(int argc, char *argv[])
#endif
{
  printf("!!Hello, World!!\n");
  return 0;
}

BUILD_MODULEが定義されている時は、エントリポイントをmain、それ意外ではhelloworld_mainとしている。

BUILD_MODULEはプログラムをローダブルビルドした場合に自動的に定義される。

ビルド

nuttxディレクトリで次のコマンドを実行する。

$ make clean
$ make menuconfig

メニューで追加したプログラムを有効化してビルドする。

単一ビルドの場合は、次のようにBuiltin Apps:にhelloworldが追加される。

nsh> help
help usage:  help [-v] [<cmd>]

  [         cp        exec      kill      mv        set       uname
  ?         cmp       exit      ls        mw        sh        umount
  basename  dirname   false     mb        ps        sleep     unset
  break     dd        free      mkdir     pwd       test      usleep
  cat       df        help      mh        rm        time      xd
  cd        echo      hexdump   mount     rmdir     true

Builtin Apps:
  hello       helloworld

参考