SWEet

A Software Engineer Is Eating Technologies

Contikiにおけるファイル分割してコンパイルの仕方とPROCESSによる並列実行

卒業研究で使ってるWSN用のOS、というより超巨大ライブラリのContikiというものについてメモしておきます。

Why

そもそも日本語のドキュメントは勿論、英語でのドキュメントもサンプルを動かしただけとかばっかで深いところに突っ込むにはソースコード読むしかないという現状だったのでもし他にこの分野に手を付ける人がいたらその助けになればいいなという思いと、自分で忘れないようにするために書いておきます。

Contikiとは

WSN用のOSです。マルチスレッドを標準でサポートしています。何を当たり前のことをと思うかもしれませんが無線センサネットワークとかの界隈ではリソースの都合上結構すごいことだったりする。

このOSで動くプログラムはC言語で書けます。これまた「今時C〜?古くない〜?」と言われそうですが、結構有名なWSN用のOS、TinyOSとかはnesCとかいうドキュメントは英語の本一冊のみという魔の言語で書かなきゃいけなかったりするので恵まれている方です。

あとCoojaというGUIシミュレータが付属しているので基本的には書いたプログラムをこのシミュレータでデバッグしながら開発することができます。 とりあえずContikiで動かすことができるもっとも簡単なプログラムを示します。

#include "contiki.h"
#include "stdio.h"

/*---------------------------------------------------------------------------*/
PROCESS(hello_world_process, "Hello world process");
AUTOSTART_PROCESSE(&hello_world_process);
/*---------------------------------------------------------------------------*/
PROCESS_THREAD(hello_world_process, ev, data)
{
  PROCESS_BEGIN();
   
    printf("Hello, Cooja\n");

  PROCESS_END();
}
/*---------------------------------------------------------------------------*/

お決まりのHello world!ですがこれだけでも結構なことをしています。 一行ずつ説明していきます。

  1. PROCESS(プロセス名, プロセス呼び出し時に出力する文字列)

    これは hello_world_process という名前のプロセス構造体を定義しています。第二引数は呼び出し時に標準出力に出力する文字列です。

  2. AUTOSTART_PROCESS(プロセス構造体のアドレス)

    センサー起動時にこのプロセスを自動的に起動します。これがないとセンサを起動しても何も起きません。 プロセスをコード上で任意に起動することもできますがそれは後述します。

  3. PROCESS_THREAD(プロセス構造体, イベント, データ) { ... }

    プロセスの実際の処理を記述する関数です。このPROCESS_THREADは他のPROCESS_THREADとは並列に処理されます。 イベントはセンサ本体のボタンなどが押されたときのシグナルを判定する時に使用します。 データはコード上でプロセスを起動した時に任意のデータポインタを渡すことができます。

  4. PROCESS_BEGIN() ... PROCESS_END()

    この間に書かれた処理が実行されます。 PROCESS_END()が実行されても完全にプログラムの実行が終わったわけではなく, バックグラウンドではセンサ毎に定義されているOSプログラムが存在して, 低電力モードなどへの移行を行っています。あくまでここに定義されたプロセスをベースとなるOS上で実行しているイメージです。

コンパイルの仕様

センサで動かすプログラムをContikiがコンパイルする際には全てMakefileでセンサーごとに独自のバイナリにコンパイルしていきます。 例えば、上の hello_world.cコンパイルする際にはMakefileはこのようになります。

CONTIKI_PROJECT = hello-world
all: $(CONTIKI_PROJECT)

CONTIKI = ../..
include $(CONTIKI)/Makefile.include

makeする際には make TARGET=... でTARGETに動かしたいセンサを指定します。

あとはhello-world.cにガリガリ書いていけばいいのですがある程度コードの規模が大きくなると1ファイルで管理するのは非常に面倒でした。 そこで他のヘッダファイルに定義した関数を別のcファイルで実装し、それを呼び出す場合は

MakefilePROJECT_SOURCEFILES += hoge.c fuga.c と定義します。 この時のディレクトリ構成はこんな感じです。

.
├── Makefile
├── README.md
├── fuga.c
├── fuga.h
├── hello-world.c
├── hoge.c
├── hoge.h
├── symbols.c
└── symbols.h

この時, fuga.c hoge.c を別階層のディレクトに配置すると, コンパイルしたファイルを参照できなくなりエラーが起こります。

別プロセスの呼び出し

では試しに hoge.hhoge_processを定義し, hoge.c に実装し, hello-world.c でボタンが押された時に hoge_process を呼び出す処理を書いてみます

hoge.h

#ifndef _HOGE_H_
#define _HOGE_H_

void start_hoge_process();
#endif

hoge.c

#include "contiki.h"
#include "stdio.h"

PROCESS(hoge_process, "hoge process");
PROCESS_THREAD(hoge_process, ev, data) {
    PROCESS_BEGIN();
    printf("hoge %d\n", data);
    PROCESS_END();
}

void start_hoge_process(void) {
    process_start(&hoge_process, (void *)100);
}

hello-world.c

#include "contiki.h"

#include "dev/button-sensor.h"
#include "dev/leds.h"


#include <stdio.h> /* For printf() */

/*---------------------------------------------------------------------------*/
PROCESS(hello_world_process, "Hello world process");
AUTOSTART_PROCESSES(&hello_world_process);
/*---------------------------------------------------------------------------*/
PROCESS_THREAD(hello_world_process, ev, data)
{
  PROCESS_BEGIN();
  SENSORS_ACTIVATE(button_sensor);
  printf("Hello, Cooja\n");

  while(1) {
    PROCESS_WAIT_EVENT_UNTIL(ev == sensors_event &&
            data == &button_sensor);
    start_hoge_process();
  }
  PROCESS_END();
}

これによって hello-word.cコンパイルしてcoojaシミュレータで起動(センサーの種別は問いません)し、 センサのボタンをクリックするとログに hoge 100 と出力されるはずです。

ContikiのMakefileには独自のフラグが多くあるのでまた自分が何か見つけたらメモしておこうと思います。 私のContikiで開発中のリポジトリはこちらです。興味があったらContikiでプログラミングしてみてください。 そして私に知見をください。

github.com

contiki本体のリポジトリはこちらになります

github.com

wikiも一応あるにはありますが大体動かしつつソース読むのが手っ取り早いです。

それにしてもWSNの具体的なユースケースや規模がググっても全然出てこないので評価の仕様策定に苦戦中です。 辛い