RTAI入門(2) 何が出来るか?その2

この記事からの続きです。

今回の記事は、必要な章だけつまみ読みでどうぞ!

RTAIで何が出来るか?の「リアルタイム化」編です。

  • タスクのリアルタイム化
  • タイマーによるトリガー
  • ハードウェア割り込み

ただし、「タスクのリアルタイム化」だけは全ての基礎になっています。ですから、ここだけはご一読をお勧めします。

タスクのリアルタイム化

リアルタイム化って、どうやるの?何が嬉しいの?

この章では、これらの疑問に答えて行きます。

どうやるの?

「何か特別な事をしないといけないんじゃない?そのために、システム作成上での制約がでてくるんじゃない?」

これは半分正解、半分不正解です。

リアルタイムタスクを作るのは、簡単です。

1.通常どおりにスレッドを作る
2.そのスレッドから、 RTAIのAPIをコールする

たったこれだけの作業で、たんなるスレッドがリアルタイムタスクへと生まれ変わります。コードから見ると、スレッド上からAPIを1つ叩いているだけで、それ以外かわった所は全くありません。

これは、ユーザープロセスでもカーネルモジュールでも同じです。ただしカーネルモジュール方式はいろいろ面倒なので、RTAI入門ではユーザープロセスをリアルタイム化する方法だけを紹介する事にします。

howto_rtai_02_00

図1 スレッドをそのままリアルタイムタスク化できる

ここまでは、とてもシンプルですね。スレッド上でRTAIのAPIを叩けば、そのスレッドが勝手にリアルタイムタスクに変身します。

ここからが、ちょっとややこしい話です。でも、すごくすごく重要です

リアルタイムタスクからは、非リアルタイムなライブラリを呼び出してはいけません。正確には、外部の非リアルタイムなプロセスに処理をゆだねるようなライブラリを呼び出してはいけません。ライブラリだけでなく、Linuxの特殊ファイルシステム (/proc など)へのアクセスも同様です。

なぜいけないのか?それは、外部のプロセスがリアルタイム性能をもっていないからです。たとえば外部マシンとの通信をしたいからといって、TCP/IPで通信しようとしたりすると、リアルタイム性が保障されなくなります。TCP/IPでデータが来るのを待っている間、RTAIの管理外にあるため、リアルタイム性能が確保できなくなるからです。

一方、ライブラリを呼び出しても構わない場合もあります。呼び出したいライブラリが「絶対に、自分のプロセスの内だけで処理が完結している」事が分かっていればOKです。mathライブラリで絶対値を計算したり、三角関数の計算をしたりする。こういうのは、だいたい処理時間がわかりますし、自分のプロセス内で処理が完結しますから、使っても構いません。

何が嬉しいの?

リアルタイムOSの嬉しさは、「挙動が予測できること」に尽きます。100回実行したら100回同じように動く。これがおいしさです。

では、タスクをリアルタイム化すると何が起きるのか、もう少し詳しく見て行きましょう。

ここで、3つのルールを覚えて下さい。RTAIによるリアルタイムタスクは、次のルールに従います。

  • タスクには、プライオリティ(優先順位)が付けられている
  • タスク中断の原因は2つだけ
    • 意図的に自タスクの実行を中断させた
    • よりプライオリティの高いタスクに処理を奪われた
  • タスク再開の原因は2つだけ
    • 「外部からシグナルが来た 」かつ「 よりプライオリティの高いタスクは動いていない」
    • よりプライオリティの高いタスクが停止し、CPUが空いた

(本当はもうちょっとありますが、詳しくは次回以降でご説明します)

話をわかりやすくするために、タスクを2つ用意します。1つは「仕事」するタスク。もう1つは「同僚と雑談」するタスクです。

シャベリタガリータなひとは、仕事のプライオリティ=中、同僚と雑談のプライオリティ=最高 ですよね。

通常は、こんなかんじでしょうか。何かあるとすぐに雑談がはじまります。しかしボスの目があるため、適当なところで雑談を切り上げています。

howto_rtai_02_01図2 もうちょっと真面目に働いてみないかい?

じゃあ、ボスが出張だったらどうなるでしょう?この場合、妨げるものがありませんから、ずーっと雑談タスクが走りつづけます。

howto_rtai_02_02図3 ねぇ、仕事は?

このとき、雑談タスクと、仕事タスクは次のようなコードで動いています。

void 雑談タスク()
{
    while( 1 )
    {
        自分の言いたい事を言う();
        相手の話を聞いてるフリをする();
        if( ボスが来た? )
        {
            タスク実行を中止する();  // RTAIのAPIを使用する
        }
    }
}

void 仕事タスク()
{
    while( 1 )
    {
        右の物を左に動かす();
        左の物を右に動かす();
    }
}

社長としては、ねぇシャベリタガリータちゃん、もっと仕事してよ!ね、お願いだから!と言いたい。でも残念!雑談タスクが処理を手放さないかぎり、仕事タスクは始まりません。

仕事タスクが活動できるのは、より高いプライオリティをもつタスクである「雑談」が停止した時のみです。上のコードでは、ボスが来たら、スレッド停止するようになっています。このように明示的に停止してくれて初めて、仕事タスクが動けるようになります。

図2だと、適当なタイミングで「雑談タスク」が開始していますよね。これは絵には書いていませんが、なにかシグナルが来たものと思って下さい。たとえば、「同僚と目が会った」シグナルを受信すると、「雑談タスク」が再開してしまいます。そうなると「仕事タスク」は無理やり処理を奪われ、雑談タスクだけが走ります。

このように、どのタスクがどう動くのか?が完全にルール化されているため、挙動を読むのが簡単です。こういう挙動を「決定論的である(deterministicである)」と言います。これが、タスクをリアルタイム化する事のうれしさです。

これの裏返しもまた真です。リアルタイムOSでは、すべてのタスクの挙動を計算にいれた上で、システムを作らないといけません。一方、非リアルタイムOSの場合は、自分のタスクの事だけ考えていれば、あとはOSが適当に調整してくれます。リアルタイム性が要らないのであれば、調整をOSに丸投げ出来る方がだんぜん楽です。

(参考)同プライオリティのタスクは?

じゃあ、同じプライオリティをもつタスク同志はどうなるんでしょうか?「雑談タスク」 と 「お菓子を食べるタスク」がともに最高レベルだったら?

この場合、RTAIの設定によって2つのモードが選択できます。

1つ目は、早い者勝ちモード。いっぺん雑談を始めたら、ボスがきて雑談が止まるまで、決してお菓子を食べ始める事はありません。逆に、いっぺんお菓子を食べ始めたら、おなかいっぱいになって「お菓子を食べるタスク」を停止するまで、雑談を始める事はありません。

2つ目は、持ち回りモード(=ラウンドロビン)。この場合、同じレベルのタスクは適当な割合で処理能力を分けあいます。雑談しつつ、お菓子も食べつつ、雑談しつつ、お菓子も食べつつ・・・と、交互に処理を繰り返せます。

2つ目のラウンドロビン方式は便利なようではありますが、挙動を完全には読めなくなります。その上、パフォーマンス的にもちょっぴり不利です。ですから、注意が必要です。

タイマーによるトリガー

RTAIを使用すると、1ms毎にセンサーの値を読み取って、アクチュエータを動かす、といった事が出来ます。

howto_rtai_02_03

タイマーイベントがある度に、タスクが実行される

とある関数を、1ms毎に実行したいとします。その場合には、

・その関数をリアルタイムタスク化する(そのためには、RTAIのAPIを1つ呼ぶだけです)
・その関数を1ms毎にアクティブ化するように、RTAIのAPIを呼ぶ
・その関数では、1ms毎にやりたい処理をしたら、スリープするようにする

という処理をするだけでOKです。すると、リアルタイムタスクが1ms毎にアクティブ化されます。そうすることで、きっかり1ms毎にタスクの処理を行う事が出来ます。

なお、このために何か特別なタイマーボードを用意する必要はありません。

howto_rtai_02_04

PCには、タイマーチップが内蔵されている

一般に売られているPCには、8254と呼ばれるタイマーチップが内蔵されています。このチップは、1193810Hzでカウントアップしています。(約1μ秒)。ですから、特別にハードウェアを用意しなくても、かなりの精度のタイマー設定が可能です。1msだけではなく、500μ、100μといったオーダーでも可能です。

ただし、きっかり1msではなく、すごく微妙にタイミングがズレていたりします。ですから、このタイミングでデータをサンプルし、それをFFTに掛ける、というような使い方なしない方が良いでしょう。FFTみたく、きっかりタイミングが欲しい場合は、ちゃんとしたタイマーボードを設置し、そこから基準信号をADボードに与えるべきです。

ハードウェア割り込み

もしもI/Oボードを使って、厳しいタイミングで動かさないといけない、そんな案件を取ってきたとしましょう。

タイミングが厳しい場合には、割り込みを使用するのが常套手段です。I/Oボードで、たとえばデジタル信号が変化しただとか、あるいは通信が発生しただとか、そういったときに「すぐ」処理をするためには、割り込みという機能を使います。

たとえば、デジタル信号がONになったときに割り込みが発生するようにしたとします。そして、実際にデジタル信号がONになると、CPUは今実行しているタスクを中断します。そして、すぐに割り込みハンドラとよばれる関数に処理が移ります。こうすることで、デジタル信号がONになってから、リアルタイムシステムがなにか反応を返すまでの時間を、とても短くする事ができます。

なおRTAIでは、ユーザーアプリの中で割り込みを扱う事が出来ます。

・割り込み番号(IRQ)に対する、割り込みハンドラを指定する(ユーザーアプリ内の関数)
・割り込みを有効にする

といったことをRTAIのAPIで行えます。ですから、まるでマイコンのプログラミングをしている時のように、とても簡単に割り込みハンドラを用意する事ができます。

次回

次回は、「IPC」編その1として、メッセージについて書きます。