| index | section 1 | section 2 | section 3 | section 4 | section 5 | section 6 |
| bibrography | appendix A | appendix B | appendix C |
まずはじめに、RTLinuxによるリアルタイム処理プログラムについて説明する。
Real-Time Linux は、Linux OS と共存可能な リアルタイムOSである。 しかし、厳密な意味ではリアルタイムOSではなく、 RTLinuxが提供するのは スケジューラとプロセス間通信のみである。 RTLinuxは、仮想マシンをLinuxに提供し、 Linuxを優先順位の低い1つのリアルタイムプロセスとして実行することで、 リアルタイム処理と従来のLinux OSの処理の共存を実現している。 このリアルタイム処理というのは ある幅で処理開始タイミングと処理終了までの時間制限を保証した処理 のことである。 RTLinuxはリアルタイムOSであり、リアルタイムOSは、 リアルタイム処理を行うマルチタスクOSである。( CPUの処理能力を越える処理は、とうぜん制約された時間内では処理できない。)
ここで、MS-DOS上で割り込み処理プログラムを走らせたのと、どう違うと 言うのだろうかという疑問もわいてくる。 たしかにタイマーカウンタのハードウエアを用意してDOSの割り込みを使えば、 DOSでもリアルタイム処理は可能である。 しかし、MS-DOSは一度に1つのプログラムしか走らせることができない(つまり、マルチタスクではなくシングルタスクである)上、 開発環境が UNIXの資産を受け継いでいるLinux に比べると遥かに貧弱である。 RTLinuxならば、リアルタイム処理を実行しながら、複数のプログラムを 実行できる上、開発環境も整っているので開発も楽にできるという強みがある。 また、タイマーカウンタ等のハードウェアを用意しなくても、 (CPUにそこまでの処理能力があるかどうかは別として) OS自体が 10-9 s ( 1ns )程度の時間制約まで守れる設計になっている。 MS-DOSやMS-Windowsといった OS の設計では 1ms 以下のサンプリングは 保証されない*1。 したがって、安価で開発環境の整っているRTLinuxの方が、 リアルタイム制御を行うのに(開発行程も含めて)向いているといえるだろう。
*1 Intel社の 80386命令アーキテクチャ AI-32 の仮想86モードが使用できないため。 MS-DOSベースのOSはAI-32のリアルモードしか使用できないが、 RTLinuxはAI-32の能力をフル活用することができる。
RTLinuxカーネルは、非商用で開発されているいわゆる Free Soft である。 カーネルはインターネットで公開されており、www.rtlinux.org から だれでも自由に入手できる。 RTLinux のカーネルをダウンロードするときは、 現在使用中のLinuxカーネルのバージョンに合わせてダウンロードする。 例えば、カーネルが 2.2.x ならば、rtl-v2.x を選ぶ。 インストールする際には Linuxカーネルに RTLinuxのパッチを当てることになるので、 パッチ済カーネルをダウンロードしてきた方が簡単でよい (ただし、ダウンロードには時間がかかる)。 ダウンロードしてきたファイル(例えば rtlinux-2.0-prepatched.tgz ) は、展開する場所 /usr/src に展開する。 それからインストールが完了するまでの手順は、以下のとおりである。
rtl-v2.x には RTLinux で使用する関数の man page がついている。 再起動したら、実際にRTLinuxがインストールされたことを確認するために、 rtl/examples 以下にあるサンプルプログラムを実行してみよう。
RTLinxuでは、リアルタイム処理のプログラムをモジュールの形で作成するので、 まずはじめにモジュールについて説明する。 モジュールはカーネルを拡張する小さな部品である。 Linux module のソースコードは、 mainのかわりに、一組の init_module と cleanup_module という関数をもっている。 まず、モジュールプログラムの第一歩として、 何もしない hello_module.o を作ってみよう。
|
コンパイル、実行、確認には、
|
と入力すればよい。 X上のターミナルから実行したのでは何も表示されない。 printk の出力は、コンソールから実行して確認するか、 dmesg により吐き出される文字列から確認できる。
insmod,lsmod,rmmod といったコマンドは、スーパーユーザ(root) しか実行できない。 insmod により モジュールがカーネルに組み込まれると、 最初に 関数 init_module が呼び出される。 デバイスドライバ等を作成する場合は、ここでデバイスの登録や I/Oポートの設定、メモリの確保などを行う。 init_module は、普通は戻値として 0 を返し、 モジュールの初期化処理に失敗した場合は 負の値を返すことになっている。 逆に、rmmod によりモジュールがカーネルから削除されると、 関数 cleanup_module が呼び出される。
RTLinux では、このモジュールの中でリアルタイム・スレッド*2の生成、スケジューリング、実行、停止、削除等を行う。 したがって、RTLinuxにおける リアルタイム処理 のプログラムは、 大まかに言えば以下のような作りになる。
このリアルタイム処理プログラムの大まかな構造をもとに、 次はサンプルプログラムについて説明する。
*2 ここで タスク(task)やプロセス(process)、スレッド(thread)という概念が出てくるが、すべて実行中のプログラムという意味で理解してほしい。 厳密に言えば、プロセスは並列処理システムをCPU側からでなく、 プログラム側から考えた概念であり、 同じものをIntelのCPUアーキテクチャであるIA-32の用語ではタスクと呼ぶ。 これがカーネル内ではスレッドという処理単位になるのだが、 CPUが1つしかない場合は、視点が違うだけで同じものを指しているだけである。
プロセスとスレッドは、(カーネルの構成要素である)スケジューラの制御対象です。 プロセスとスレッドの違いはメモリの実装方法にあります。 プロセスは独立した排他的なメモリ空間を持っていますが、スレッドのメモリ空間は独立しておらず、OSによって保護されません。 プロセスの中には1つまたは複数のスレッドが走っていますが、スレッドの中でプロセスが走ることはありません。
プロセスやスレッドをスケジューラが時間的制限を守れるかどうかから見た場合、これらをタスクといいます。タスクがプロセスとして実装されるかスレッドとして実装されるかは、OSがプロセス型のRTOSか、スレッド型のRTOSかに拠ります。
ジョブは一連の仕事をしているプロセスの集合ですが、RTOSによってはジョブでもタスクと呼ぶものがあるそうです。同じタスクが何度も起動される場合、起動されるたびに異なるジョブとして扱われます。
こんどは実際に簡単なリアルタイム処理プログラムについて説明する。 プログラムの例として、付録Aに示すプログラムを使用する。 このサンプルプログラムはリアルタイム・スレッドを1つだけ生成し、 ユーザプログラムからこのスレッドを起動/停止し、 スレッドでリアルタイム処理されたデータをユーザプログラムに返すという、 リアルタイム処理とFIFOを用いたプログラム間通信の雛形となる プログラムである。さらに、このプログラムはスレッド内での 浮動小数点演算の実行も実現している。 このプログラムの大まかな流れを示すと、以下のようになる。
この流れに沿ってソースコードを追ってみることにする。 このプログラムのエントリーポイントとなるのは、 timer_module.c の 関数 init_module である。 コマンド insmod により timer_module.o がカーネルに組み込まれると、
|
の部分で /dev/rtf1, /dev/rtf2, /dev/rtf3 にRT-FIFOの生成を行う。 続いて、
|
で、リアルタイム・スレッドの生成とスケジューリングを行う。 この処理により、優先順位が 4 のスレッドが生成され、 このスレッドは 関数 thread_code を実行するものとなる。
|
その後、先ほど生成したスレッドで浮動小数点演算を行うことを許可し、 FIFOハンドラーに 関数 my_handler を登録して、 関数 init_module が終了する。 つぎに、ユーザプログラムが起動すると usr_app.c の 関数 main に が始まり、
|
の部分で/dev/rtf1 と /dev/rtf3 のRT-FIFOが開かれ通信可能な状態になる。 /dev/rtf1 はスレッド制御メッセージ用に書き込み専用、 /dev/rtf3 はデータ用に読み込み専用としてオープンする。 ユーザプログラムからスレッドに制御メッセージを送ると、 設定した周期でスレッドが走り始める。
|
ここでは周期を 0.5[s] に指定した。 ここでまた処理を timer_module の方に戻って追い掛ける。 (ただし、ここでユーザアプリケーションが一時停止するわけではない) timer_module側ではFIFOからメッセージを受け取ると、 関数 my_handler が呼び出される。
|
関数 my_handler は FIFO から STMsg の大きさだけメッセージを読み出し、 これをスレッドに引き渡す。この rtf_put の第1引数がメッセージの 引き渡し先である。引き渡し先を複数用意すれば、複数のスレッドを 制御することが可能である。 関数 my_handler からスレッドがメッセージを受け取ると、 処理がスレッドの本体である関数 thread_code に移る。
|
thread_code の無限ループは、普段は関数 pthead_wait_np により 次の実行周期を待機する状態になっている。 関数 my_handler から pthread_wakeup_np によりスレッドが起動されると、 スレッドは my_handler から渡されたメッセージを読みに行く。 このメッセージに基づき、 関数 pthread_make_periodic_np で スレッドの実行周期を決定して周期処理を開始する。 ここで、周期を指定している第3引数の値 real2in(Msg.period) は、 マクロ定義による関数で、指定された秒数をコンピュータの内部時間に 置き換える関数である。その実体は、
|
のように定義している。 スレッドが動き始めると、データの値を計算してデータ構造体の中身を DATA_FIFOに送り、関数 pthread_wait_np によりCPUの使用権を放棄して 次の周期まで待つ。そして次の周期が来るとまた COMMAND_FIFO に メッセージを読みに行き、メッセージがなければデータの値を更新して データ構造体の中身をDATA_FIFOに送り・・・という処理を続ける。 スレッドが動き続けている間、DATA_FIFOにはデータ構造体の中身が 送られ続け、ユーザプログラム側では
|
という部分でDATA_FIFOからデータの読み出しを行うことができる。 もしここで、スレッドの周期が速いためにユーザプログラムが DATA_FIFOの中身を受け取りきらないまま、 スレッドからどんどん書込があれば、 FIFOを生成したときに指定したバッファの大きさまでは保存され、 バッファの大きさを越えると古いメッセージから破棄されることになる。 逆に、スレッドの周期が遅く、ユーザプログラムがDATA_FIFOの 読み出しをまっていてもなかなかデータが送られてこない場合は、
|
で指定したタイムアウト期間だけ待ち、それでもデータが来なければ、 その回は読み出しを行わない。 このようにして、サンプルプログラムは、ユーザプログラムのメインループが 回っている間はスレッドからのデータを読み込んでは表示するという作業を 繰り返す。 メイン関数のループが終了すると 今度はスレッドに停止命令を出す。
|
するとスレッドは DATA_FIFO から関数 my_mandler を介して メッセージを受け取り、
|
このようにしてスレッドを停止する。 スレッドはこの状態で停止していても、また起動するメッセージを 受け取れば、いつでも周期処理に入ることができる。 ユーザアプリケーションが終了すれば、もう timer_module は不要なので コマンド rmmod によりカーネルから削除する。 そうすると、関数 cleanup_module が呼び出され、
|
使用した RT-FIFO とスレッドを削除して、サンプルプログラムの 全行程が終了する。