RTLinux HOWTO Dinil Divakaran 1.1, 2002-08-29 日本語訳: yomoyomo v1.1j 2003 年 01 月 18 日 RTLinux の導入と Linux でリアルタイムなプログラムを書くことについて ______________________________________________________________________ 目次 1. はじめに 1.1 目的 1.2 この HOWTO を読むべき人 1.3 謝辞 1.4 フィードバック 1.5 配布の方針 2. RTLINUX のインストール 3. なぜ RTLinux なのか 4. RTLinux のプログラムを書く 4.1 モジュールの書き方入門 4.2 RTLinux におけるスレッドの作成 4.3 例プログラム 5. コンパイルと実行 6. プロセス間通信 6.1 リアルタイム FIFO 6.2 FIFO を利用するアプリケーション 7. 次に行うこと 8. 日本語訳について ______________________________________________________________________ 1. はじめに 1.1. 目的 この文書は、初心者のユーザを助け、できるだけ簡単に RTLinux を稼働でき るようにすることを目的とします。 1.2. この HOWTO を読むべき人 この文書は、リアルタイムカーネルの動作を知りたいと思っている人達すべて を対象にしています。既にモジュールプログラミングに精通している人達は、 この文書を難しいものとは感じないでしょう。そうでない人達にしても、心配 する必要はありません。モジュールプログラミングの基本概念しか必要ありま せんし、それについては必要なところで実際に議論しますので。 1.3. 謝辞 まず何より、私を励まし助けてくれた、私の相談相手である Pramode C. E に 感謝したいと思います。また、Victor Yodaiken にも心からの感謝を述べさせ てもらいます。この文書は、Victor Yodaiken が寄稿した種々の論文から取り 入れた情報なしには成立しなかったでしょう。また、"A Linux-based Real- Time Operating System" という論文を書いた Michael Barabanov にも感謝し ます。 1.4. フィードバック この文書に関する疑問や意見はどんなものでも常に歓迎します。遠慮なく私に メールしてください。この文書中 に何か間違いを見つけたら、次版で修正できるように私に知らせてください。 ご協力感謝します。 1.5. 配布の方針 Copyright (C)2002 Dinil Divakaran. この文書はフリーです。Free Software Foundation により発行されている GNU General Public License バージョン2、もしくは(任意で) それ以降の バージョンの条件に従ってこの文書を再配布したり修正できます。 この文書は役に立つことを期待して配布されるものですが、いかなる保証も行 いません。商品性や特定用途への適合性についての暗黙の保証すらありませ ん。詳しくは、GNU General Public License をご覧ください。 2. RTLINUX のインストール RTLinux カーネルのコンパイルの第一段階は、予めパッチのあたったカーネル 2.2.18 (x86 only) か 2.4. 0-test1 (x86, PowerPC, Alpha) をダウンロードして /usr/src/ で展開することで す。また www.rtlinux.org から RTLinux カーネ ル(バージョン3.0)を新たにコピーして /usr/src/rtlinux/ に置いてくださ い。(この文書では、プロンプトを表すのに $ を使います) 1. それでは Linux カーネルを設定してください: $ cd /usr/src/linux $ make config か $ make menuconfig もしくは $ make xconfig 2. カーネルイメージをビルドするには、以下のように入力してください: $ make dep $ make bzImage $ make modules $ make modules_install $ cp arch/i386/boot/bzImage /boot/rtzImage 3. 次に LILO の設定を行います。/etc/lilo.conf ファイルに以下の4行を追 加してください。 image=/boot/rtzImage label=rtl read-only root=/dev/hda1 警告: 上記の /dev/hda1 を、あなたの環境におけるルートファイルシステム に置き換えてください。しかるべきファイルシステムを見つける最も簡単な方 法は、 /etc/lilo.conf ファイルの既存のエントリにおける "root=" を見る ことです。 4. それではコンピュータを再起動し、LILO プロンプトで 'rtl' と入力して RTLinux カーネルをロードしてください。それから /usr/src/rtlinux/ に 'cd' し、RTLinux を設定します。 $ make config か $ make menuconfig もしくは $ make xconfig 5. ようやく RTLinux のコンパイルです $ make $ make devices $ make install 最後の手順により、以下のディレクトリが作成されます: /usr/rtlinux-xx (xx はバージョンを意味する) ここは RTLinux のデフォルトのインストール先となるディレクトリで、ユー ザプログラムを作成したりコンパイルするのに必要なもの (include ファイ ル、ユーティリティ、ドキュメントなど) が入ります。また、最後の手順によ り /usr/rtlinux-xx を指す以下のシンボリックリンクが作成されます。 /usr/rtlinux 将来にわたる互換性を確保するために、あなた自身が作成したすべての RTLinux 用プログラムにおいて、デフォルトのパスに /usr/rtlinux を適用す るようにしてください。 注記: 何か Linux カーネルのオプションを変更する場合は、以下の手順を行 うことをお忘れなく: $ cd /usr/src/rtlinux $ make clean $ make $ make install 3. なぜ RTLinux なのか 標準の Linux カーネルの動作を調べれば、RTLinux を設計した理由が分かり ます。 Linux カーネルはハードウェアをユーザレベルのタスクから切り離し ます。カーネルはスケジューリングアルゴリズムを用い、平均的に優れた性 能、スループットを提供するよう各タスクに優先度を割り当てます。従って、 どのユーザレベルのタスクであっても、そのタスクが CPU により割り当てら れているタイムスライスを超過したら、 Linux カーネルはタスクを停止でき ます。このスケジューリングアルゴリズムに加え、デバイスドライバ、割り込 み不可能なシステムコール、割り込み無効化や仮想メモリ操作などが予測不可 能性(unpredictability) の原因になります。つまり、以上の要素がタスクの リアルタイム性能の障害になっています。 Linux がリアルタイムな性能を保証しないことは、例えば 'mpg123' などのプ レイヤーで音楽を聴いているなら、既にご承知のことかもしれません。予め決 められたタイムスライスでプロセスを実行した後で、標準の Linux カーネル はタスクを中断して CPU に別のタスク(例えば、X サーバや Netscape の立ち 上げを行うタスク)を割り当てることが可能です。結果として、音楽の連続性 が失われてしまいます。このように、 CPU 時間をすべてのプロセスに公平に 分配しようと試みることにより、カーネルは他のイベントが起こらないように できるのです。 リアルタイムカーネルならば、そのカーネルの元で動くプロセスが要求するタ イミングを保証することが可能なはずです。RTLinux カーネルは、上で議論し た予測不可能性の原因となるものを除去することによりリアルタイム性能を実 現しています。RTLinux カーネルは、標準の Linux カーネルとハードウェア の間に位置すると考えることができます。 Linux カーネルは、リアルタイム 層を事実上のハードウェアとみなします。つまりユーザは、一つ一つのタスク の導入とその優先度の設定の両方が行えます。ユーザはスケジューリングアル ゴリズム、優先度、実行頻度などを決めることでプロセスの正確な実行タイミ ングを実現できます。 RTLinux カーネルは、標準の Linux カーネルに最低の 優先度を割り当てます。こうすることでユーザタスクがリアルタイムに実行さ れることになります。 実際のリアルタイム性能は、すべてのハードウェア割り込みを遮断することに より達成されます。RTLinux に関連する割り込みに対してのみ、適切な割り込 みサービスルーチンが動作します。それ以外の割り込みは全て一旦保留さ れ、RTLinux カーネルがアイドル状態になり標準の Linux カーネルが稼働し たときにソフトウェア割り込みとしてカーネルにわたります。 RTLinux の実 行部そのものはノンプリエンプティブです。 リアルタイムタスクは優先され(つまりそれらはハードウェアに直接アクセス する)、仮想メモリは利用しません。リアルタイムタスクは、メモリに動的に ロード可能な、特殊な Linux モジュールとして書かれます。リアルタイムタ スクの初期化コードは、リアルタイムタスク構造体を初期化し、 RTLinux カーネルにその期限、周期、そして解放時間制限を通知します。 Linux カーネルには手を加えないので、RTLinux は Linux カーネルと共存し ます。比較的単純な修正を一通り行えば、将来の Linux 開発の妨げになるこ となく、既存の Linux カーネルをハードリアルタイム環境にうまく変えるこ とができます。 4. RTLinux のプログラムを書く 4.1. モジュールの書き方入門 さてモジュールとは何でしょうか? Linux のモジュールは、通常 gcc に -c フラグ引数を付けて生成されたオブジェクトファイルに過ぎません。モジュー ル自体は、main() 関数のない普通の C 言語ファイルをコンパイルすることに より生成されます。main() 関数がない代わりに、init_module 関数と cleanup_module 関数のペアが含まれます: o init_module() は、そのモジュールをカーネルに組みこむ際にコールされ ます。成功時には 0 を返し、失敗したときには負の値を返します。 o cleanup_module() は、そのモジュールを削除する直前にコールされます。 通常 init_module() では、カーネルに何か処理を行うハンドラを登録する か、カーネルの関数の一つを独自のコード(通常は何か行い、それから元の関 数をコールするコード)に置き換えるかします。 cleanup_module() 関数 は、init_module() が行ったことすべてを元に戻すことになっているので、モ ジュールを安全に外すことが可能です。 例えば、module.c という名前の C 言語ファイル(main() 関数の代わりに init_module() と cleanup_module() がある)を書いたとしたら、以下のよう に入力すれば、そのコードがモジュールに変換されます: $ gcc -c {SOME-FLAGS} my_module.c このコマンドにより module.o という名前のモジュールファイルが生成されま す。 module.o は 'insmod' コマンドを使用してカーネルに組みこめます: $ insmod module.o 同様にモジュールを削除するには 'rmmod' を使えばよいです: $ rmmod module 4.2. RTLinux におけるスレッドの作成 通常リアルタイムアプリケーションは、複数の実行「スレッド」で構成されま す。スレッドは、共通のアドレス空間を共有する軽量のプロセスで す。RTLinux では、すべてのスレッドが Linux カーネルのアドレス空間を共 有します。スレッドを使うことの利点は、コンテキストスイッチに比べてス レッド間の切り替えが極めて軽いことにあります。以下の例で紹介する各種関 数を利用すれば、スレッドの実行を完全に制御できます。 4.3. 例プログラム スレッドの動作を理解するのに最も良いのは、リアルタイムプログラムをト レースすることです。例えば、以下に示すプログラムは、1秒に一度実行し、 その度に 'Hello World' と出力します。 プログラムコード (file - hello.c) : #include #include #include pthread_t thread; void * thread_code(void) { pthread_make_periodic_np(pthread_self(), gethrtime(), 1000000000); while (1) { pthread_wait_np (); rtl_printf("Hello World\n"); } return 0; } int init_module(void) { return pthread_create(&thread, NULL, thread_code, NULL); } void cleanup_module(void) { pthread_delete_np(thread); } それでは init_module() から始めましょう。init_module() が pthread_create() をコールしています。pthread_create() は、コールするス レッドと同時に動作する、新規のスレッドを作成する関数です。この関数 は、Linux カーネルスレッドからしか (つまり、init_module() を用いてし か)コールしてはいけません。 int pthread_create(pthread_t * thread, pthread_attr_t * attr, void * (*thread_code)(void *), void * arg); 作成された新規スレッドの型は pthread_t で、これは pthread.h ヘッダファ イルで定義されています。このスレッドは、関数 thread_code() を実行し、 それに arg を引数として渡します。引数 attr は、新規スレッドに適用され るスレッド属性を指定します。もし attr が NULL なら、デフォルトの属性が 適用されます。 ここでは thread_code() は引数なしでコールされます。thread_code は、初 期化、ランタイム、そして終了という三つの構成要素から成ります。 初期化フェーズでは、pthread_make_periodic_np() をコールします。 int pthread_make_periodic_np(pthread_t thread, hrtime_t start_time, hrtime_t period); pthread_make_periodic_np は、実行の準備ができたスレッド thread を指定 します。スレッドは start_time で指定された時間に実行を開始し、ナノ秒単 位で指定された period の間動作します。 gethrtime は、システムがブートしてからの時間をナノ秒単位で返します。 hrtime_t gethrtime(void); この時間はリセットされることも修正されることもありません。gethrtime は 常に、単調に増加する値を返します。hrtime_t は64ビットの符号付き整数型 です。 pthread_make_periodic_np() 関数をコールすると、スレッドは自分を 1Hz の 頻度で定期的に実行するよう、スケジューラに命令します。これでスレッドの 初期化部は終わります。 while() ループは pthread_wait_np() 関数のコールで始まります。この関数 は、次の周期が開始するまで、現在動作しているリアルタイムスレッドの実行 を停止します。このスレッドは前に pthread_make_periodic_np により実行指 定がされています。スレッドが再びコールされると、 pthread_wait_np() が あらためてコールされるまで while ループ内の残りの処理を実行します。 ループを抜ける手段を入れていないので、このスレッドは 1Hz 間隔で永久に 実行され続けることになります。プログラムを停止する唯一の手段は、 rmmod コマンドによりそれをカーネルから削除することです。 rmmod は cleanup_module() をコールしますが、これが スレッドを中止し、そのリソー スを解放する pthread_delete_np() をコールするのです。 5. コンパイルと実行 hello.c プログラムを実行するには(もちろんですが、rtlinux のブート後に です)、以下の手順を行わなければなりません: 1. GCC コンパイラを使ってソースコードをコンパイルし、モジュールを生成 します。とはいえ、Makefile を作成して作業を簡略化するほうが良いで す。そうすればソースコードをコンパイルするのに 'make' と入力するだ けで済みます。Makefile は、'Makefile' という名前のファイルに以下の 内容を入力することで作成できます。 include rtl.mk all: hello.o clean: rm -f *.o hello.o: hello.c $(CC) ${INCLUDE} ${CFLAGS} -c hello.c 2. rtl.mk ファイルを見つけ、hello.c や Makefile があるのと同じディレク トリに置いてください。rtl.mk ファイルは、コードをコンパイルするのに 必要なすべてのフラグを含む include ファイルです。rtl.mk ファイルは RTLinux のソースツリーからコピーして hello.c ファイルと同じディレク トリに格納できます。 3. コードをコンパイルするには、'make' コマンドを使用してください。 $ make 4. この結果できるオブジェクトバイナリは、カーネルの RTLinux により実行 される部分に組みこまなくてはなりません。'rtlinux' コマンドを使用し てください (それには 'root' になる必要があります)。 $ rtlinux start hello これで hello.o プログラムがメッセージを毎秒出力するのを確認できるはず です。マシンの設定によっては、コンソールで直接見ることができるはずです が、そうでなければ以下のコマンドを入力すれば見ることができます: $ dmesg プログラムを停止するには、これをカーネルから削除する必要があります。そ れを行うには、以下のコマンドを入力してください: $ rtlinux stop hello モジュールの組みこみ、削除を行う別のやり方として、それぞれ insmod と rmmod を利用するというのもあります。 ここまでは例となるプログラムが簡単過ぎました。これまで見てきたものと違 い、実際には一つのプログラム中に複数のスレッドが存在するかもしれませ ん。優先度をスレッド作成時に設定したり、それを後で修正することが可能で す。また、しかるべきスケジューリングアルゴリズムを選択することも可能で す。実は、独自のスケジューリングアルゴリズムを書くことも可能なんです! 我々の例では、thread_code() 関数の先頭に以下の3行を挿入することで、ス レッドの優先度を1に設定し、FIFO スケジューリングを選択できます。 struct sched_param p; p . sched_priority = 1; pthread_setschedparam (pthread_self(), SCHED_FIFO, &p); 6. プロセス間通信 これまで見てきた例プログラムは、リアルタイムプロセスと呼ばれるもので す。アプリケーションプログラムのすべての部分をリアルタイム用に書く必要 はありません。プログラムの中でも正確な時間制限を要求される部分だけがリ アルタイムプロセスとして書かれるべきであることが分かります。それ以外は ユーザ空間で実行するように書いてかまいません。ユーザ空間のプロセスは、 多くの場合リアルタイムスレッドよりも作成、実行、そしてデバッグが容易で す。しかしその一方で、ユーザ空間にある Linux プロセスとリアルタイムス レッドの間で通信を行う手段を用意すべきです。 プロセス間通信を行う手段はいくつかあります。通信を行う最も有力で一般的 な手段であるリアルタイム FIFO について議論します。 6.1. リアルタイム FIFO リアルタイム FIFO は、一方向のキューです(First In First Out:先入れ先出 し)。つまり一方からプロセスが FIFO にデータを書くと、FIFO のもう一方か ら情報が別のプロセスに読みこまれます。通常、そうしたプロセスの片側がリ アルタイムスレッドで、もう一方がユーザ空間のプロセスになります。 リアルタイム FIFO は、実際にはメジャーナンバー150のキャラクターデバイ ス (/dev/rtf*)です。リアルタイムスレッドは、各 FIFO を参照するのに整数 を用います(例えば /dev/rtf2 なら2)。 FIFO の数には制限があります。FIFO を扱う関数には、rtf_create(), rtf_destroy(), rtf_get(), rtf_put() など があります。 一方、Linux のユーザプロセスはリアルタイム FIFO を普通のキャラクターデ バイスとみなします。そのため、open(), close(), read() そして write() といった関数がこれらのデバイスに対して利用できます。 6.2. FIFO を利用するアプリケーション まず PC のスピーカーから(二つの音だけからなる)音楽を演奏する簡単な C プログラム(ファイル名は pcaudio.c)を考えてみましょう。当面は、音符を鳴 らすにはキャラクターデバイス /dev/rtf3 に書きこむだけでよいことにしま しょう(後で、この FIFO (/dev/rtf3)から読みこみ、 PC のスピーカーに送信 するリアルタイム時間プロセスを考えます)。 #include #include #include #include #define DELAY 30000 void make_tone1(int fd) { static char buf = 0; write (fd, &buf, 1); } void make_tone2(int fd) { static char buf = 0xff; write (fd, &buf, 1); } main() { int i, fd = open ("/dev/rtf3", O_WRONLY); while (1) { for (i=0;i #include #include #include #define FIFO_NO 3 #define DELAY 30000 pthread_t thread; void * sound_thread(int fd) { int i; static char buf = 0; while (1) { for(i=0; i または、yomoyomo 宛に連絡してください。 本日本語訳について、誤訳・誤記の指摘をして下さったつぎの方々に感謝しま す(50音順)。 o 中野武雄さん o 山口つかささん o 山下義之さん