-2- OSは何をするか
2-1 タスク

 タスクの概念とは何か.タスクあるいはジョブ,プロセス,スレッドなど,各RTOSごとに多少異なる概念を表している.意味がはっきりしている用語は,プロセスとスレッドである.どちらもカーネルの構成要素であるスケジューラの制御対象である.

 プロセスとスレッドの違いは,メモリ空間の扱いの違いである.プロセスは独立した排他的なメモリ空間をもっているが,スレッドはもっていない.また,プロセスの内部に複数のスレッドを走らせることもできる.いわゆるマルチプロセス・マルチスレッド環境である.

 スケジューラの制御対象が,プロセスかスレッドかによってプロセス型RTOS,スレッド型RTOSと呼ばれる.同時に複数の制御対象を走らせることができる場合にマルチプロセスあるいはマルチスレッドと呼ばれる.そうでない場合は,シングルスレッドと呼ばれる.シングルプロセスというのはあまり聞かない.

 タイミング解析を行う場合は,つまりデッドラインなどの時間制約を守れるかどうかを調べる場合には,制御対象をタスクと呼ぶことが多い.タイミング解析はRTOSを決める前から行うことができるので,タスクがスレッドとして実装されてもプロセスとして実装されてもかまわないためタスクと呼ぶのではないかと思う.また,同様に実装を意識しない開発の上流工程でもタスクと呼ぶことが多い.分析工程ではタスクがプロセスになるかスレッドになるか決まっていない,つまりまだRTOSを決めていないこともありうるためである.また,開発の上流工程でプロセスというと,開発プロセスと勘違いされる場合もある.開発プロセスは開発行為全体の中で,いつ,誰が,何をするかという開発手順のことである.

 タスクの中には,周期的に実行されるものがある.たとえば,同一のタスクを10msごとに起動するような場合である.タイミング解析では,1回目に起動されたタスクと2回目に起動されたタスクを区別する必要が生じる.そこで,起動されたタスクをジョブと呼ぶ.

 まとめると,RTOSの制御で並行に動く実装単位をメモリの扱いに応じてプロセスまたはスレッドと呼ぶ.実装方法を意識しない場合はタスクと呼ぶ.また,起動されたタスクをジョブと呼ぶ.ただし,これはあくまでも一般論である.ここではタスクと呼ぶことにする.

 結局,タスクとはRTOSが管理する並列化単位というように抽象的に定義できるが,知っている人には当たり前でも,知らない人はこれではイメージがわかないかもしれない.そこで,具体的な例をあげると,ITRONの場合,関数を作って,その関数のアドレスをパラメータとしてcre_tskシステムコールを実行すると,その関数がタスクになる.

●タスクの実体

 マルチタスクシステムにおけるタスクの実体は,じつはたんなる関数である.関数型は,使用するRTOSごとに指定されている.たとえば,ITRON 4.0仕様書では以下のように定義している.

 void task(VP_INT exinf);

 タスク関数の中身には大きく分けて二つあり,一つは1回だけ実行して終了するタイプ,もう一つはループ構造をもっているタイプである.

/* 1回だけ実行するタスク */

  void task(VP_INT exinf);

  {

   ...

  }

  /*繰り返し実行するタスクの例 */

  void task(VP_INT exinf);

  {

   ...

   while(1)

   {

   ...

   }

  }

 1回だけ実行するタイプは,タスクの起動や終了などのオーバヘッドがあるのであまり使われない.普通は,ループタイプが使われる.ただし,ループタイプは時間的なオーバヘッドはないが,スタックなどのシステムリソースを確保したままになる.また,タスク数が多いとシステム全体で時間的オーバヘッドが増加する場合もある.

 プログラムの分類法の一つに,変換型(transformational)と応答型(reactive)がある1).1回だけ実行するタイプは変換型に対応し,スタート時に用意されたデータを使って黙々と計算処理をして終了する.ループタイプは応答型に対応し,内部に待ち状態をもっていて,外界との相互作用を繰り返す.複雑な応答型タスクは,ステートマシンとしてモデル化される場合が多い.

 たんなる関数とタスクとは,コーディング上は基本的な区別がない.しかし,たんなる関数はアプリケーションから呼ばれて結果を返すだけだが,タスクの場合は,RTOSから呼び出されてRTOSに帰る.イメージ的には図5のようになる.図5では,実行経路を線で描いてある.

〔図5〕タスク関数とスレッド

 「thread=スレッド」を辞書で引くと第一番目に「糸」と説明してある.ただの関数をタスクにするには,タスクの先頭アドレス,つまりその関数名を引き数としてタスク生成システムコールを呼び出す.すると,RTOS内にこの「糸」を管理するためのデータ構造が生成され,スタック領域が割り当てられる.

 スレッドあるいはタスクとは何かといわれれば,さしあたりこのデータ構造とスタックのほうがタスク関数よりも本質的かもしれない.というのは,図5の右側に示したように,一つのタスク関数に対して「青い糸」と「赤い糸」がまとわり付くように,複数のスレッドが存在できるからである.マルチスレッド環境というのは,ソースコード上に絡まりやすいいろいろな糸が存在する環境である.まず,この糸が見えるようにならないと,ソースコード上に展開される動的な世界を理解することはできない.

 組み込み制御ソフトウェアの設計では,論理的な設計ばかりでなくタイミング設計も重要になる.代表的なソフトウェアアーキテクチャである4+1Viewモデル2)に従えば,それぞれの設計部分は論理ビューとプロセスビューに対応している(図6).

〔図6〕“4+1 View”アーキテクチャモデル

 論理設計部分は,要求機能を分割していくことでクラスや関数などの形で実現することができるため,ハードウェアやRTOSとは独立に設計を行うことが可能であり,また,再利用性も高い.論理設計手法については,構造化分析・設計法3),オブジェクト指向分析設計法4),5)など多数の手法が提案されている.

 一方,タイミング設計部分の性能要求と実装制約の両面から影響を受けるため,使用するプラットホームを意識せずに設計することは難しく,再利用性も低い.タイミング設計については,タスク分割合成法6)などが知られている.

 タイミング設計部分を実現するには,RTOSが提供するスレッドやプロセスを使用することになる.つまり,「赤い糸」,「青い糸」の設計はタイミング設計部分に属する.論理設計部分との結合は,論理設計要素であるクラスあるいは関数の中で特定のものを,スレッドやプロセスのメイン関数とすることで行われる.

 タイミング設計要素と論理設計要素の結合度合いが強いと,使用するRTOSの影響を,タイミング設計部分ばかりでなく,論理設計部分も間接的あるいは直接的に受けることになり,論理設計要素の部品性まで損なわれることがある.

 さらに,開発フェーズの後半で期待していたパフォーマンスが出ない場合のチューニング時には,使用するスレッド数やプライオリティの変更を行うことが多いが,それにともない論理設計要素も作り直しになる確率が高く,開発効率を著しく悪化させる要因となる.したがって,タイミング設計と論理設計の独立性を保つことが重要になる.これは,RTOSの選び方ではなく使い方の問題になるかもしれない.

● タスクの待ち状態

 タスクが待つ状態とは何か.複数のタスクが並行に動くといっても,CPUは一つであり,実際にはRTOSのスケジューラが決めた順番で一つのタスクが動くことになる.余談だが,このような動きを並行動作(concurrent)と呼ぶ.一方,CPUが複数あって,本当に同時に動く場合は並列動作(parallel)と呼ぶ.ただし,この並行/並列の定義もあくまでも一般論であり,例外もある.

 また,やはり開発の初期の段階で実装が決まっていない場合,つまり,マルチCPUにするかシングルCPUにするか決まっていない場合には,スレッドとプロセスのときのように厳密に決めてしまうと話にならないので,とりあえず並列を使うことが多いようである.

 並行動作を擬似並列動作と呼ぶこともある.ここで問題にしているのは,並行動作である.タスクが待つというのは,あるタスクが動きを止めて,別のタスクが動き出すことである.たとえば,タスクがI/O処理を行う場合,I/Oデバイスの応答時間はCPUに比べて非常に遅い.つまり,時間的なギャップが存在する.

 デバイスからの応答待ちをしなければならないときに,そのタスクがbusy waitしてCPU時間を無駄に使わないように,RTOSがそのタスクを待ち状態にして別の動作可能なタスクを動かすのである.タスクがbusy waitしてしまうと,そのタスクより優先度の高いタスク以外は動作できなくなる.

 このようにタスクを切り替えることをスイッチングとかディスパッチングと呼ぶ.また,この処理をするのはRTOSのカーネルのディスパッチャである.ディスパッチャは,ほぼ100%アセンブラで書かれるマルチエントリ・マルチエグジット・リエントラントの非常に難解なモジュールである.

 タスクが切り替わる要因は複数ある.たとえば,システムコールによる場合,割り込みが入った場合,例外が発生した場合などである.要因によってスタックに積まれるレジスタの種類や順番が異なるCPUが多いので,別々のエントリからディスパッチャに入ってスタックフレームの違いを揃える必要がある.

 ディスパッチャから出るときも,同じタスクに戻る場合と別のタスクに移る場合があるので,複数の出口をもつことになる.また,タスク切り替え中は,割り込みをマスクするのが一般的だが,あまり長い間(通常は数μs〜数十μs)割り込みをマスクするとシステム全体の応答性を悪くするので,適当なところで一時的に割り込み禁止を解除する.そうすると,そこでペンディングされていた割り込みが入る.それがまた,ディスパッチの要因になるといった具合である.

 シンプルで効率の良いエレガントなディスパッチャがRTOS開発者の目標である.ディスパッチャの良し悪しは,RTOSの性能を左右する.昔のRTOSは,ほとんどの部分をアセンブラで書いていたので,オーバヘッドも少なく非常に効率が良かった.割り込みマスク時間も非常に短かった.が,今ではほとんどの部分をCで書き,レジスタを操作しなければならない部分のみをアセンブラで書いている.高級言語ではマルチエントリルーチンは書けない.また,誰が書いても同じようなルーチンになる.

 高級言語を使用することで,移植性やリアルタイム同期などの機能は向上したが,老舗メーカーのRTOSはどれもたいして違わなくなっている.一方,新しいアーキテクチャのRTOSもいくつかあるが,日本ではまだ実績がない.この業界では,実績がないとなかなか検討してもらえない.また,新しいアーキテクチャは,理解してもらうのに時間がかかる.

1 main()の前,printfの向こう側

2 OSは何をするか
 2-1 タスク
 2-2 タスクコントロールブロック(TCB)
 2-3 可変長メモリプール


12月号特集トップページへ戻る


Copyright 2001 藤倉俊幸