システムインタフェース

第 9 章 実時間プログラミングと管理

この章では、SunOS 5 で実行する実時間アプリケーションの書き方と移植方法について説明します。この章は、実時間アプリケーションを書いた経験があるプログラマと、実時間処理と Solaris システムに詳しい管理者向けに書かれています。

実時間アプリケーションの基本的な規則

実時間応答は、一定の条件を満たした場合に保証されます。この節ではその条件を明らかにし、問題を生じたりシステムを無効にしたりしてしまう重大な設計上のエラーをいくつか説明します。

ここでは、システムの応答時間を劣化させる可能性のある問題を取り上げます。その中にはワークステーションをハングさせてしまうものもあります。それほど重大ではないエラーとしては、優先順位の反転やシステムの過負荷 (処理が多すぎる) などがあります。

Solaris の実時間プロセスには、次のような特色があります。

この章では、実時間操作を単一スレッドのプロセスとして説明しますが、説明の内容はマルチスレッドプロセスにも当てはまります (マルチスレッドプロセスの詳細は、『マルチスレッドのプログラミング』を参照してください)。スレッドの実時間スケジューリングを保証するには、結合スレッドとして扱われなければならず、スレッドの LWP が実時間スケジューリングクラスで実行されなければなりません。メモリのロッキングと初期ダイナミック結合は、プロセス内のすべてのスレッドについて有効です。

プロセスが最も高い優先順位を持つ場合は、次のようになります。

実時間プロセスは、システム上の他のイベントのために、プロセッサの制御を失ったり、プロセッサの制御を得られなかったりすることがあります。そのようなイベントの例としては、外部イベント (割り込みなど)、資源不足、外部イベント待ち (同期入出力)、より高い優先順位のプロセスによる横取りなどがあります。

実時間スケジューリングは通常、open(2)close(2) など、システムの初期化と終了を行うサービスには適用されません。

応答時間の劣化

この節で説明する問題は程度は異なりますが、どれもシステムの応答時間を劣化させます。劣化が大きいと、アプリケーションがクリティカルデッドラインを逃してしまうことがあります。

実時間処理は、システム上で実時間アプリケーションを実行している他の有効なアプリケーションの操作に対しても大きな影響を及ぼします。実時間プロセスの優先順位は高いので、タイムシェアリングプロセスはかなりの時間、実行を妨げられます。表示に対してキーボードで応答するなどの対話型操作は著しく遅くなります。

システムの応答時間

SunOS 5 でのシステムの応答が、入出力イベントのタイミングを制限することはありません。これは、実行がタイムクリティカルなプログラムセグメントには、同期入出力呼び出しを入れてはいけないということです。時間制限が非常に長いプログラムセグメントでも、同期入出力を行わないでください。たとえば、大量の記憶領域の入出力の際に読み取りや書き込み操作を行うと、その間システムはハングしてしまいます。

よくあるアプリケーションの誤りは、入出力を実行してエラーメッセージのテキストをディスクから取得することです。この処理は、独立した実時間ではないプロセスまたはスレッドから実行しなければなりません。

割り込みサービス

割り込みの優先順位は、プロセスの優先順位に左右されません。プロセスの優先順位を設定しても、プロセスの動作によって生じるハードウェア割り込みの優先順位は設定されません。つまり、実時間プロセスによって制御されるデバイスについての割り込み処理は、必ずしもタイムシェアリングプロセスによって制御される他のデバイスについての割り込み処理よりも前に実行されるとは限りません。

共用ライブラリ

タイムシェアリングプロセスでは、ダイナミックにリンクされる共用ライブラリを使用すると、メモリ量をかなり節約できます。このようなタイプのリンクは、ファイルマッピングの形で実装されます。ダイナミックにリンクされたライブラリルーチンは、暗黙の読み取りを行います。

実時間プログラムでは、プログラムを起動する際に、環境変数 LD_BIND_NOW を非 NULL に設定すると、共用ライブラリを使用してもダイナミック結合が行われないようにできます。このようにすると、プログラムの実行前にダイナミック結合が実行されます。詳細は、『リンカーとライブラリ』を参照してください。

優先順位の反転

実時間プロセスが必要とする資源を、タイムシェアリングプロセスが取得すると、実時間プロセスをブロッキングできます。優先順位の反転とは、優先順位の高いプロセスが優先順位の低いプロセスによってブロッキングされる際に生じる状態です。「ブロッキング」とは、あるプロセスが、1 つまたは複数のプロセスが資源の制御を手放すのを待っている状態を指します。このブロッキングが長引くと、たとえ低レベル資源についてでも、デッドラインを逃してしまうことがあります。

図 9-1 の場合を例にとると、優先順位の低いプロセスが共用資源を保持しているために、その共用資源を使用したい優先順位の高いプロセスがブロッキングされています。この優先順位の低いプロセスは、中間の優先順位を持つプロセスによって横取りされます。この状態は長く続く場合があり、実際には優先順位の高いプロセスが資源を待たなければならない時間は、優先順位の低いプロセスによって危険領域が実行されている継続時間だけではなく、中間のプロセスがブロッキングされるまでの時間によって決まるので、どれだけ長く続くかわかりません。中間のプロセスは、いくつ関与していてもかまいません。

図 9-1 制限されない優先順位の反転

Graphic

スティッキロッキング

ページは、ロッキングカウントが 65535 (0xFFFF) に達すると、メモリ内に永久にロッキングされます。0xFFFF の値は実装時に定義されており、将来のリリースで変更される可能性があります。このようにしてロッキングされたページのロッキングは解除できません。

ランナウェイ実時間プロセス

ランナウェイ実時間プロセスは、システムを停止させたり、システムが停止したように見えるほどシステムの応答を遅くしたりすることがあります。


注 -

SPARCTM システム上にランナウェイプロセスがある場合は、Stop-A キーを押します。この手順を何回も繰り返さなければならない場合があります。この手順がうまく機能しない場合は、電源を切ってからしばらく待ち、もう一度電源を入れてください。


優先順位の高い実時間プロセスが CPU の制御を手放さない場合、無限ループを強制的に終了させなければ、システムの制御は得られません。このようなランナウェイプロセスは、Control-C キーを入力しても応答しません。ランナウェイプロセスよりも高い優先順に設定されているシェルを使用しようとしても失敗します。

入出力の特性

非同期入出力

非同期入出力操作がカーネルへの待ち行列に入った順序で行われるという保証はありません。また、非同期操作が実行された順序で呼び出し側に戻されるという保証もありません。

aioread(3) 呼び出しの高速シーケンスのために単一のバッファが指定されている場合、最初の呼び出しが行われてから最後の結果が呼び出し側にシグナルとして送信されるまでの間のバッファの状態については、保証されていません。

1 つの aio_result_t 構造体は、一度に 1 つの非同期読み取りまたは書き込みだけに使用してください。

実時間ファイル

SunOS 5 には、ファイルを確実に物理的に連続して割り当てる機能は用意されていません。

通常のファイルについては、read(2)write(2) の操作は常にバッファリングされます。アプリケーションは mmap(2)msync(3C) を使用して、二次記憶領域とプロセスメモリ間の入出力転送を直接実行できます。

スケジューリング

実時間スケジューリング制約は、データ取得やプロセス制御ハードウェアの管理のために必要です。実時間環境では、プロセスが制限された時間内で外部イベントに反応する必要があります。この制約は、処理する資源をタイムシェアリングプロセスのセットに「公平に」分配するように設計されているカーネルの能力を超えることがあります。

この節では、SunOS 5 の実時間スケジューラ、その優先順位待ち行列、およびスケジューリングを制御するシステムコールとユーティリティの使用方法について説明します。この節で説明している関数の詳細は、『 man Pages(3): Library Routines』を参照してください。

ディスパッチ中の潜在的な時間

実時間アプリケーションをスケジューリングする際に最も重要な要素は、実時間スケジューリングクラスを用意することです。標準のタイムシェアリングのスケジューリングクラスは、どのプロセスも平等に扱って優先順位の概念に制限があるので、実時間アプリケーションには適しません。実時間アプリケーションでは、プロセスの優先順位が絶対的なものとして受け取られ、アプリケーションの明示的な操作によってしか変更されないスケジューリングクラスが必要です。

ディスパッチ中の潜在的な時間とは、プロセスの操作開始の要求にシステムが応答するのにかかる時間を指します。アプリケーションの優先順位を尊重するように特別に作成されたスケジューラを使用すると、ディスパッチ中の潜在的な時間を制限した実時間アプリケーションを開発できます。

図 9-2 に、アプリケーションが外部イベントの要求に応答するのにかかる時間を示します。

図 9-2 アプリケーションの応答時間

Graphic

全体的なアプリケーションの応答時間には、割り込み応答時間、ディスパッチ中の潜在的な時間、およびアプリケーションが応答を決定するのにかかる時間が含まれます。

アプリケーションの割り込み応答時間には、システムの割り込み中の潜在的な時間とデバイスドライバの割り込み処理時間が含まれます。割り込み中の潜在的な時間は、システムが割り込みを無効にして実行しなければならない最長の間隔によって決まります。これは SunOS 5 では、プロセッサの割り込みレベルの上昇を通常は要求しない同期プリミティブを使用して最小化されています。

割り込み処理中は、ドライバの割り込みルーチンが優先順位の高いプロセスを呼び起こして終了すると戻ります。システムでは、割り込まれたプロセスよりも高い優先順位を持つプロセスが現在ディスパッチ可能であることが検知され、そのプロセスをディスパッチするように指定されます。優先順位の低いプロセスから高いプロセスへコンテキストスイッチングする時間は、ディスパッチ中の潜在的な時間に含まれます。

図 9-3 に、システムが外部イベントに応答するのにかかる時間として定義された、システムの内部ディスパッチ中の潜在的な時間とアプリケーションの応答時間を示します。内部イベントのディスパッチ中の潜在的な時間は、あるプロセスがより高い優先順位のプロセスを呼び起こし、システムでそのプロセスがディスパッチされるのにかかる時間を表します。

アプリケーションの応答時間は、ドライバがより高い優先順位のプロセスを呼び起こし、優先順位の低いプロセスに資源を解放させ、より高い優先順位のタスクを再スケジュールして応答を計算し、タスクをディスパッチするのにかかる合計時間です。


注 -

ディスパッチ中の潜在的な時間のインタバル間に割り込みが入って処理されることがあります。この処理でアプリケーションの応答時間は増えますが、ディスパッチ中の潜在的な時間の測定には影響を与えないので、ディスパッチ中の潜在的な時間の保証によって制限されることはありません。


図 9-3 内部ディスパッチ中の潜在的な時間

Graphic

実時間 SunOS 5 で用意されている新しいスケジューリング手法によって、システムのディスパッチ中の潜在的な時間は指定された範囲内になります。下の表に示すように、ディスパッチ中の潜在的な時間はプロセス数を制限すると向上します。

表 9-1 SunOS 5 での実時間システムのディスパッチ中の潜在的な時間
 ワークステーション 制限されたプロセス数 任意のプロセス数
SPARCstationTM 2 有効なプロセスが 8 個未満の場合は、システム内で 0.5 ミリ秒未満 1.0 ミリ秒
 SPARCstation 5 1.0 ミリ秒 0.3 ミリ秒
UltraTM 1-1677 0.15 ミリ秒未満 0.15 ミリ秒

ディスパッチ中の潜在的な時間の検査と、製造業務やデータ収集業務などのクリティカルな環境での経験によって、Sun ワークステーションは実時間アプリケーション開発のための有効なプラットフォームであることが証明されています。(ただし上記の結果は、最新製品によるものではありませんのでご了承ください)。

スケジューリングクラス

SunOS 5 のカーネルは、プロセスを優先順位によってディスパッチします。スケジューラ (またはディスパッチャ) は、スケジューリングクラスの概念をサポートしています。クラスは、実時間 (RT)、システム (sys)、またはタイムシェアリング (TS) として定義されます。各クラスには、プロセスをディスパッチするための固有のスケジューリング方式があります。

カーネルは、最も優先順位が高いプロセスを最初にディスパッチします。デフォルトでは、実時間プロセスが sysTS のプロセスよりも優先されますが、管理者は TSRT のプロセスの優先順位が重なり合うように設定することもできます。

図 9-4 に SunOS 5 から見たクラスの概念を示します。

図 9-4 スケジューリングクラスのディスパッチ優先順位

Graphic

最も優先順位が高いのはハードウェア割り込みで、これはソフトウェアでは制御できません。割り込みを処理するルーチンは、割り込みが生じるとただちに直接ディスパッチされ、その際には現在のプロセスの優先順位は考慮されません。

実時間プロセスは、ソフトウェアでは最も高い優先順位をデフォルトで持ちます。RT クラスのプロセスは、優先順位とタイムカンタム値を持ちます。RT プロセスは、厳密にこれらのパラメタに基づいてスケジュールされます。RT プロセスが実行可能である限り、sys や TS のプロセスは実行できません。固定優先順位スケジューリングでは、クリティカルプロセスを完了まで事前に指定した順序で実行できます。この優先順位は、アプリケーションで変更されない限り変わりません。

RT クラスのプロセスは、有限無限を問わず親プロセスのタイムカンタムを継承します。有限タイムカンタムを持つプロセスは、タイムカンタムの有効時間が切れるか、プロセスが終了するか、ブロッキングされるか (入出力イベントを待つ)、またはより高い優先順位を持つ実行可能な実時間プロセスに横取りされるまで実行されます。無限タイムカンタムを持つプロセスは、プロセスが終了するか、ブロッキングされるか、または横取りされるまで実行されます。

sys クラスは、ページング、STREAMS、スワッパなどの特殊なシステムプロセスをスケジュールするために存在します。あるプロセスのクラスを sys クラスに変更できません。プロセスの sys クラスは、プロセスの開始時にカーネルによって確立された固定優先順位を持っています。

優先順位が最も低いのは、タイムシェアリング (TS) クラスです。TS クラスのプロセスは、各タイムスライスを数百ミリ秒としてダイナミックにスケジュールされます。TS スケジューラは、頻繁にコンテキストスイッチングを行なって、各プロセスに実行する機会を平等に与えます。これは、各プロセスのタイムスライスの値とプロセスの履歴 (プロセスが最後に休眠したのはいつか) に基づき、CPU の利用率を考慮して行われます。デフォルトのタイムシェアリング方式では、優先順位の低いプロセスに長いタイムスライスを与えます。

子プロセスは fork(2) を通じて、親プロセスのスケジューリングクラスと属性を継承します。プロセスのスケジューリングクラスと属性は、exec(2) を実行しても変わりません。

各スケジューリングクラスは、異なったアルゴリズムによってディスパッチされます。クラスに依存するルーチンは、カーネルによって呼び出され、CPU のプロセススケジューリングが決定されます。カーネルはクラスに依存し、待ち行列内から最も優先順位の高いプロセスを取り出します。各クラスは、自分のクラスのプロセスの優先順位値を計算しなければなりません。この値は、そのプロセスのディスパッチ優先順位変数に入れられます。

図 9-5 に示すように各クラスのアルゴリズムは、それぞれ独自の方法によってグローバル実行待ち行列に入れる最も優先順位の高いプロセスを指定します。

図 9-5 カーネルのディスパッチ待ち行列

Graphic

各クラスには、そのクラスのプロセスに適用される優先順位レベルのセットがあります。クラス固有のマッピングによって、この優先順位がグローバル優先順位のセットに割り当てられます。グローバルスケジューリング優先順位のセットへの対応は 0 で始まったり連続したりしている必要はありません。

デフォルトでは、タイムシェアリング (TS) プロセスのグローバル優先順位の値は -20 から +20 までの範囲で、カーネルの 0 から 40 までに割り当てられており、一時的な割り当ては 99 まであります。実時間 (RT) プロセスのデフォルトの優先順位は 0 から 59 までの範囲で、カーネルの 100 から 150 までに割り当てられます。カーネルのクラスに依存しないコードは、待ち行列内のグローバル優先順位の最も高いプロセスを実行します。

ディスパッチ待ち行列

ディスパッチ待ち行列は、同じグローバル優先順位を持つプロセスが線状にリンクしたリストです。各プロセスは、それぞれに接続されているクラス固有の情報によって起動されます。プロセスは、グローバル優先順位に基づいたカーネルのディスパッチテーブルからディスパッチされます。

プロセスのディスパッチ

プロセスがディスパッチされると、プロセスのコンテキストがメモリ管理情報、レジスタ、スタックとともにメモリ内に割り当てられて実行が始まります。メモリ管理情報は、現在実行中のプロセスのために仮想記憶変換を実行する際、必要となるデータを含むハードウェアレジスタの形をとります。

横取り

より高い優先順位を持つプロセスがディスパッチ可能になると、カーネルは計算に割り込んでコンテキストスイッチングを強制し、現在実行中のプロセスを横取りします。より高い優先順位のプロセスがディスパッチ可能になったことをカーネルが見つけると、プロセスはいつでも横取りされます。

たとえば、プロセス A が周辺デバイスから読み取りを行なっているとします。プロセス A はカーネルによって休眠状態に置かれます。次に、カーネルはより優先順位の低いプロセス B が実行可能になったのに気づき、プロセス B がディスパッチされ実行が始まります。ここで周辺デバイスが割り込み、デバイスのドライバが入ります。デバイスドライバはプロセス A を実行可能にして戻ります。ここで、カーネルは割り込まれたプロセス B に戻るのではなく、B の処理を横取りして、呼び起こされたプロセス A の実行を再開します。

もう 1 つの重要な例としては、複数のプロセスがカーネル資源を奪い合う場合があります。優先順位の高い実時間プロセスが待っている資源を優先順位の低いプロセスが解放すると、カーネルはただちに優先順位の低いプロセスを横取りして、優先順位の高いプロセスの実行を再開します。

カーネル優先順位の反転

優先順位の反転は、優先順位の高いプロセスが 1 つまたは複数の優先順位の低いプロセスによって長時間ブロッキングされた場合に生じます。SunOS 5 のカーネルで相互排他ロッキングなどの同期プリミティブを使用すると、優先順位の反転につながることがあります。

「ブロッキング」とは、あるプロセスが 1 つまたは複数のプロセスが資源を手放すのを待たなければならない状態のことです。このブロッキングが継続すると、使用レベルが低いものでもデッドラインを逃してしまうことがあります。

相互排他ロッキングの優先順位反転の問題については、SunOS 5 のカーネルで基本的な優先順位継承方式を実装することによって対応しています。この方式では、優先順位の低いプロセスが優先順位の高いプロセスの実行をブロッキングすると、優先順位の低いプロセスが優先順位の高いプロセスの優先順位を継承することになります。このため、プロセスがブロッキングされている時間の上限が設定されます。この方式はカーネルの特性で、プログラマがシステムコールや関数の実行によって講じる解決策ではありません。ただしこの場合でも、ユーザレベルのプロセスは優先順位の反転を生じることがあります。

ユーザ優先順位の反転

他のプロセスと同期がとられているプロセスが、待っているプロセスの優先順位を自動的に継承する機構はありません。アプリケーションは、優先順位の上限をエミュレートして自分の優先順位反転を制限できます。

このモデルでは、アプリケーションは、それぞれの同期オブジェクトに優先順位を関連付けますが、通常はそのオブジェクトをブロッキングできる、あらゆるプロセス中で最も高い優先順位になります。

次に、各プロセスは次のようなシーケンスを使用して共用資源を操作します。

スケジューリングを制御するシステムコール

priocntl(2)

有効なクラスのスケジューリング制御は、priocntl(2) で処理します。クラスの属性は、fork(2)exec(2) を実行した場合にも、優先順位制御に必要なスケジューリングパラメタやアクセス権とともに継承されます。この特色は、RT と TS クラスのどちらにも当てはまります。

priocntl(2) 関数は、システムコールが適用される実時間プロセス、プロセスのセット、またはクラスを指定するインタフェースを提供します。priocntlset(2) のシステムコールも、システムコールを適用するプロセスのセット全体を指定する、さらに一般的なインタフェースを提供します。

priocntl のコマンド引数は、PC_GETCIDPC_GETCLINFOPC_GETPARMSPC_SETPARMS のいずれかにします。呼び出しプロセスの実識別子または実効識別子は、対象となるプロセスのものと一致するか、スーパーユーザ特権を持っていなければなりません。

PC_GETCID

このコマンドは、認識可能なクラス名 (実時間なら RT、タイムシェアリングなら TS) を含む構造体の名前フィールドを受け入れます。クラス ID とクラス属性データの配列が戻されます。 

PC_GETCLINFO

このコマンドは、認識可能なクラス識別子を含む構造体の ID フィールドを受け入れます。クラス名とクラス属性データの配列が戻されます。 

PC_GETPARMS

このコマンドは、指定したプロセスの 1 つのスケジューリングクラス識別子またはクラス固有のスケジューリングパラメタ、あるいはその両方を戻します。idtypeid によって大きなセットが指定された場合でも、PC_GETPARMS は 1 つのプロセスのパラメタだけを戻します。どのプロセスを選択するかはクラスによって決まります。

PC_SETPARMS

このコマンドは、指定したプロセス (複数でも可) のスケジューリングクラス識別子またはクラス固有のスケジューリングパラメタ、あるいはその両方を設定します。 

sched_get_priority_max(3R)

指定された方針の最大値を戻します。

sched_get_priority_min(3R)

指定された方針の最小値を戻します (詳細は、sched_get_priority_max(3R) のマニュアルページを参照してください)。

sched_rr_get_interval(3R)

指定された timespec 構造体を現在の実行時間限界に更新します (詳細は、sched_get_priority_max(3R) のマニュアルページを参照してください)。

sched_setparam(3R)sched_getparam(3R)

指定されたプロセスのスケジューリングパラメタを設定または取得します。

sched_yield(3R)

プロセスリストの先頭に戻るまで、呼び出しプロセスをブロッキングします。

スケジューリングを制御するユーティリティ

プロセスのスケジューリングを制御する管理用ユーティリティとして、dispadmin(1M)priocntl(1) があります。どちらのユーティリティも、互換性のあるオプションとロード可能なモジュールを伴う priocntl(2) のシステムコールをサポートします。これらのユーティリティは、実行中に実時間プロセスのスケジューリングを制御するシステム管理機能を提供します。

priocntl(1)

priocntl(1) コマンドは、プロセスのスケジューリングパラメタの設定と取り出しを行います。

dispadmin(1M)

dispadmin(1M) ユーティリティに -l コマンド行オプションを付けると、実行中に現プロセスのすべてのスケジューリングクラスが表示されます。実時間クラスを表す引数として RT を -c オプションの後ろに指定すると、プロセスのスケジューリングを変更することもできます。

表 9-2 に示しているオプションも使用できます。

表 9-2 dispadmin(1M) ユーティリティのクラスオプション

オプション 

意味 

-l

現在設定されているスケージュリングクラスを表示する。 

-c

パラメタを表示または変更するクラスを指定する。 

-g

指定したクラスのディスパッチパラメタを取得する。 

-r

-g オプションを使用した場合、タイムカンタムの解像度を指定する。

-s

値が保存されているファイルを指定する。 

ディスパッチパラメタが保存されているクラス固有のファイルを実行中にロードすることもできます。このファイルを使用して、起動時に確立されたデフォルトの値を新しい優先順位のセットで置き換えることができます。このクラス固有のファイルでは、-g オプションで使用される書式の引数を挿入しなければなりません。RT クラスのパラメタは rt_dptbl(4) にあり、この節の終わりに例を示します。

システムに RT クラスのファイルを追加するには、次のモジュールが存在しなければなりません。

  1. 次のコマンドでクラス固有のモジュールをロードします。

    この場合、module_name はクラス固有のモジュールにします。


       # modload /kernel/sched/module_name
    
  2. dispadmin(1M) コマンドを起動します。


       # dispadmin -c RT -s file_name
    

    上書きされるテーブルと同じ数のエントリを持つテーブルが、ファイルに記述されていなければなりません。

スケジューリングの設定

両方のスケジューリングクラスにはパラメタテーブル rt_dptbl(4)ts_dptbl(4) が関連づけられています。これらのテーブルは、起動時にロード可能なモジュールを使用するか、実行中に dispadmin(1M) を使用して設定できます。

ディスパッチャパラメタテーブル

実時間のための中心となるテーブルで、RT スケジューリングの設定項目を指定します。rt_dptbl(4) 構造体は、パラメタ配列の structrt_dpent_t 構造体から成り、これは n 個の優先順位レベルそれぞれに 1 つずつあります。ある優先順位レベル i の設定項目は、配列内の i 番目のパラメタ構造体 rt_dptbl[i] によって指定されます。

パラメタ構造体は次のメンバーから成ります (/usr/include/sys/rt.h ヘッダファイルにも記述されています)。

rt_globpri

この優先順位レベルに関係づけられているグローバルスケジューリング優先順位。rt_globpri の値は dispadmin(1M) では変更できません。

rt_quantum

このレベルのプロセスに割り当てられるタイムカンタムの長さを目盛で表したもの (「タイムスタンプ機能」を参照)。タイムカンタム値は、特定のレベルのプロセスのデフォルト値、つまり開始値です。実時間プロセスのタイムカンタムは、priocntl(1) コマンドまたは priocntl(2) システムコールによって変更できます。

config_rt_dptbl の再設定

実時間管理者は、いつでも config_rt_dptbl を再設定して、スケジューラの実時間部分の振る舞いを変更できます。1 つの方法は、rt_dptbl(4) のマニュアルページの「REPLACING THE RT_DPTBL LOADABLE MODULE」というセッションで説明しています。

もう 1 つの方法は、dispadmin(1M) コマンドを使用して、実行中のシステムで実時間パラメタテーブルを調査または変更する方法です。dispadmin(1M) を実時間クラスで起動すると、カーネルの中心テーブルにある現在の config_rt_dptb1 内から現在の rt_quantum 値を取り出すことができます。現在の中心テーブルを上書きする際、dispadmin(1M) への入力に使用された設定ファイルは、rt_dptbl(4) のマニュアルページで説明されている書式に合致していなければなりません。

config_rt_dptbl[] 内にある優先順位を設定されたプロセス rtdpent_t と、関連づけられているタイムカンタム値を次に示します。

rtdpent_t   rt_dptbl[] = {       
      /* 優先順位レベルのタイムカンタム */
                 100,    100,
                 101,    100,
                 102,    100,
                 103,    100,
                 104,    100,
                 105,    100,
                 106,    100,
                 107,    100,
                 108,    100,
                 109,    100,
                 110,    80,
                 111,    80,
                 112,    80,
                 113,    80,
                 114,    80,
                 115,    80,
                 116,    80,
                 117,    80,
                 118,    80,
                 119,    80,
                 120,    60,
                 121,    60,
                 122,    60,
                 123,    60,
                 124,    60,
                 125,    60,
                 126,    60,
                 127,    60,
                 128,    60,
                 129,    60,
                 130,    40,
                 131,    40,
                 132,    40,
                 133,    40,
               
                 134,    40,
                 135,    40,
                 136,    40,
                 137,    40,
                 138,    40
                 139,    40,
                 140,    20,
                 141,    20,
                 142,    20,
                 143,    20,
                 144,    20,
                 145,    20,
                 146,    20,
                 147,    20,
                 148,    20,
                 149,    20,
                 150,    10,
                 151,    10,
                 152,    10,
                 153,    10,
                 154,    10,
                 155,    10,
                 156,    10,
                 157,    10,
                 158,    10,
                 159,    10,
 }

メモリロッキング

メモリのロッキングは、実時間アプリケーションにとって最重要事項の 1 つです。実時間環境では潜在的な時間を減らし、ページングとスワッピングを防ぐために、プロセスは連続してメモリ内に常駐していることを保証される必要があります。

この節では、SunOS 5 で実時間アプリケーションに利用できるメモリロッキング機構について説明します。メモリ管理関数や呼び出しの使用方法の詳細は、『man Pages(3): Library Routines』を参照してください。

概要

SunOS 5 では、プロセスのメモリ常駐性は、プロセスの現在の状態、使用できる全物理記憶、有効なプロセス数、およびプロセッサのメモリに対する要求によって決まります。これは、タイムシェアリング環境には適合しますが、実時間プロセスについては受け入れられないことがよくあります。実時間環境では、プロセスは、メモリアクセスとディスパッチ中の潜在的な時間を減らすために、プロセスの全部または一部がメモリ内に常駐していることが保証される必要があります。

SunOS 5 の実時間では、メモリロッキングはスーパーユーザ特権付きで実行されているプロセスが、自分の仮想アドレス空間の指定された部分を物理記憶にロッキングできるようにするライブラリルーチンのセットによって提供されています。このようにしてロッキングされたページは、ロッキングを解除されるか、プロセスが終了するまでページングの対象になりません。

一度にロッキングできるページ数には、システム全体で共通の制限があります。これは調整可能なパラメタで、デフォルト値は起動時に計算されます。値は、ページフレーム数マイナス一定のパーセント (現在は 10 パーセントに設定) が基本になります。

ページのロッキング

mlock(3C)は、メモリの 1 セグメントをシステムの物理記憶にロッキングするように要求します。指定したセグメントを構成するページは、ページフォルトが発生して物理記憶に入り、各ロッキングカウント値は 1 増やされます。ロッキングのカウントが 0 より大きいページは、ページング操作から除外されます。

特定のページを異なったマッピングで複数のプロセスを使って何度もロッキングできます。2 つの異なったプロセスが同じページをロッキングすると、両方のプロセスがロッキングを解除するまで、そのページはロッキングされたままです。ただし、マッピング内でページロッキングはネストしません。同じアドレスに対して同じプロセスがロッキング関数を何度も呼び出した場合でも、ロッキングは一度のロッキング解除要求で削除されます。

ロッキングが実行されているマッピングが削除されると、そのメモリセグメントのロッキングは解除されます。ファイルを閉じるまたは切り捨てることによってページが削除された場合も、ロッキングは解除されます。

fork(2) 呼び出しが行われた後、ロッキングは子プロセスには継承されません。したがって、メモリをロッキングしているプロセスが子プロセスをフォークすると、子プロセスはページをロッキングするために、自分でメモリロッキング操作を行わなければなりません。そうしないと、プロセスをフォークした場合に通常必要となるページのコピー即時書き込みを行わなければならなくなります。

ページのロッキング解除

メモリによるページのロッキングを解除するには、プロセスは munlock(3C) 呼び出しによって、ロッキングされている仮想記憶のセグメントを解放するように要求します。こうすると、指定された物理ページのロッキングカウントが減らされます。ページのロッキングカウントが 0 まで減ると、そのページは普通にスワップされます。

全ページのロッキング

スーパーユーザプロセスは、mlockall(3C) 呼び出しによって、自身のアドレス空間内の全マッピングをロッキングするように要求できます。MCL_CURRENT フラグが設定されている場合は、既存のメモリマッピングがすべてロッキングされます。MCL_FUTURE フラグが設定されている場合は、既存のページに追加されるか、既存のマッピングを置き換えるマッピングはすべてメモリ内にロッキングされます。

スティッキロッキング

ページのロッキングカウントが 65535 (0xFFFF) に達すると、そのページは永久にロッキングされます。0xFFFF の値は実装の際に定義されているため、将来のリリースでは変更される可能性があります。このようにしてロッキングされたページは、ロッキングを解除できません。復元するにはシステムを再起動してください。

高性能入出力

この節では、実時間プロセスでの入出力について説明します。SunOS 5 では、高速で非同期の入出力操作を実行するための 2 種類のインタフェースをライブラリで提供しています。Solaris 非同期入出力インタフェースは、Solaris 1.x でも提供されています。POSIX 非同期入出力インタフェースは新しい標準です。堅牢性向上のため、SunOS は情報の消失やデータの不一致を防止するためのファイルおよびメモリ内同期操作とモードも提供しています。詳細は、『man Pages(3): Library Routines』を参照してください。

標準の UNIX 入出力は、アプリケーションのプログラマと同期します。read(2) または write(2) を呼び出すアプリケーションは、通常はシステムコールが終了するまで待ちます。

実時間アプリケーションは、入出力に非同期で結合された特性を必要とします。非同期入出力呼び出しを発行したプロセスは、入出力操作の完了を待たずに先に進むことができます。呼び出し側は、入出力操作が終了すると通知されます。その間、プロセスは他の動作を行います。

非同期入出力は、任意の SunOS ファイルで使用できます。ファイルは同期して開かれますが、特別なフラグ設定は必要ありません。非同期入出力転送には、呼び出し、要求、操作の 3 つの要素があります。アプリケーションは非同期入出力関数を呼び出し、入出力要求が待ち行列に置かれ、呼び出しはただちに復帰します。ある時点で、システムは要求を待ち行列から取り出し、入出力操作を開始します。

非同期入出力要求と標準入出力要求は、任意のファイル記述子で混在させることができます。システムは、読み取り要求と書き込み要求の特定の順序を維持しません。システムは、保留状態にあるすべての読み取り要求と書き込み要求の順序を任意に並べ替えます。特定の順序を必要とするアプリケーションは、前の操作の完了を確認してから従属する要求を発行しなければなりません。

POSIX 非同期入出力

POSIX 非同期入出力は、aiocb 構造体を使用して行います。aiocb 制御ブロッキングは、各非同期入出力要求を識別し、すべての制御情報を持っています。制御ブロッキングは、一度に 1 つの要求だけに使用でき、その要求が完了すると再使用できます。

一般的な POSIX 非同期入出力操作は、aio_read(3R) または aio_write(3R) 呼び出しによって開始します。ポーリングまたはシグナルを使用して、操作の完了を判断できます。シグナルを操作の完了に使用する場合は、各操作に一意にタグを付けることができます。タグは生成されたシグナルの si_value 構成要素に戻されます (詳細は、siginfo(5) のマニュアルページを参照してください)。

aio_read(3R)

aio_read(3R) は、読み取り操作の開始のために非同期入出力制御ブロッキングを使用して呼び出します。

aio_write(3R)

aio_write(3R) は、書き込み操作の開始のために非同期入出力制御ブロッキングを指定して呼び出します。

aio_return(3R)aio_error(3R)

aio_return(3R)aio_error(3R)は、操作が完了しているとわかった後、それぞれ戻り値とエラー値を取得するために呼び出します。

aio_cancel(3R)

aio_cancel(3R) は、保留状態の操作を取り消すために非同期入出力制御ブロッキングを指定して呼び出します。

aio_fsync(3R)

aio_fsync(3R) は、指定したファイルで保留状態のすべての入出力操作に対する非同期の fsync(3C) または fdatasync(3R) 要求を待ち行列に並べます。

aio_suspend(3R)

aio_suspend(3R) は、1 つ以上の先行する非同期入出力要求が同期して行われるかのように呼び出し側を一時停止します。

Solaris 非同期入出力

通知 (SIGIO)

非同期入出力呼び出しが正常に復帰しても、入出力操作は待ち行列に並べられただけであり、実行を待っています。実際の操作は、戻り値と潜在的なエラー識別子も持っています。これらの値は、同期呼び出しの結果として呼び出し側に戻されます。入出力が終了すると、戻り値とエラー値は、要求時点でユーザが aio_result_t へのポインタとして指定した位置に格納されます。aio_result_t 構造体は、<sys/asynch.h> に次のように定義されています。

typedef struct aio_result_t {
 	ssize_t	aio_return; /* 読み取りまたは書き込みの戻り値 */
 	int 		aio_errno;  /* 入出力によって生成されたエラー番号 */
 } aio_result_t;

aio_result_t が変更されると、入出力要求を行なったプロセスに SIGIO シグナルが配信されます。

2 つ以上の非同期入出力操作を保留状態にしている場合、どの要求によって SIGIO シグナルが生じたか、またはどちらの要求によって SIGIO シグナルが生じたのかは調べることはできません。SIGIO を受け取ったプロセスは、SIGIO を生じた原因となる条件をすべてチェックしなければなりません。

aioread(3)

aioread(3)read(2) の非同期版です。aioread は通常の読み取り引数に加えて、ファイル位置と、システムが操作結果を格納する aio_result_t 構造体のアドレスを指定する引数を取ります。ファイル位置には、操作前にファイル内で行うシークを指定します。aioread 呼び出しが成功したか失敗したかに関係なく、ファイルポインタが更新されます。

aiowrite(3)

aiowrite(3) 関数は write(2) の非同期版です。aiowrite は通常の書き込み引数以外にファイル位置と、操作結果が格納される aio_result_t 構造体のアドレスを指定する引数を取ります。

ファイル位置には、操作の前にファイル内で行うシークを指定します。aiowrite 呼び出しが成功すると、ファイルポインタはシークと書き込みが成功した場合の位置に変更されます。ファイルポインタは書き込みを行なった後、以降の書き込みができなくなった場合も変更されます。

aiocancel(3)

aiocancel(3) は、その aio_result_t 構造体を引数として指定した非同期要求の取り消しを試みます。aiocancel 呼び出しは、要求がまだ待ち行列にある場合だけに成功します。操作がすでに進行していると aiocancel は失敗します。

aiowait(3)

aiowait(3) を呼び出すと、少なくとも 1 つの未処理の非同期入出力操作が完了するまで呼び出しプロセスはブロッキングされます。タイムアウトパラメタは、入出力の完了を待つ最大インタバルを指します。0 のタイムアウト値は、待つ必要がないことを指定します。aiowait(3) は、完了した操作の aio_result_t 構造体へのポインタを戻します。

poll(2)

SIGIO 割り込みに依存するのではなく、デバイスをポーリングしたい場合は、poll(2) システムコールを使用します。SIGIO 割り込みの原因を調べるためにポーリングすることもできます。

close(2)

ファイルは、close(2) 呼び出しによって閉じられます。close(2) を呼び出すと、取り消すことができる未処理の非同期入出力要求はすべて取り消されます。close(2) 関数は、取り消せない操作の完了を待ちます (詳細は、aiocancel(3)を参照してください)。close(2) 呼び出しが戻ると、そのファイル記述子について保留状態にある非同期入出力要求はなくなります。ファイルが閉じられると、取り消されるのは指定したファイル記述子に対する待ち行列内にある非同期入出力要求だけです。他のファイル記述子について、保留状態にある入出力要求は取り消されません。

同期入出力

アプリケーションは、情報が安定した記憶領域に書き込まれたことや、ファイル変更が特定の順序で行われることを保証する必要がある場合があります。同期入出力は、このような場合のために用意されています。

同期のモード

SunOS 5 では、物理記憶領域媒体でエラーなしに読み取れることがシステムで保証されている場合、書き込まれたデータがすべて、そのファイルをあとで開いた際に (システムや電源の障害後であっても)、書き込み操作のために通常ファイルへ正しく転送されます。物理記憶領域媒体上のデータのイメージを要求側のプロセスが利用できる場合、データは読み取り操作のために正しく転送されます。入出力操作は、関連づけられているデータが正しく転送されたか、操作が失敗と診断された場合に完了します。

入出力操作は、次の場合に同期入出力データの保全を完了します。

読み取りについては、操作は完了または失敗と診断されます。読み取りが完了するのは、データのイメージが要求側のプロセスに正しく転送された場合だけです。同期読み取り操作が要求された時点で、読み取るデータに影響を与える書き込み要求が保留状態にある場合は、データを読み取る前に書き込み要求が正しく転送されます。

書き込みについては、操作は完了または失敗と診断されます。書き込みが完了するのは、書き込み要求で指定されたデータが正しく転送され、そのデータを取り出すために必要なファイルシステム情報がすべて正しく転送された場合だけです。

データの取り出しに必要のないファイル属性 (アクセス時間、変更時間、状態変更時間) は、呼び出し側プロセスに戻る前に正しく転送されているわけではありません。

同期入出力ファイルの保全の完了は、呼び出し側プロセスに戻る前に入出力操作に関連するすべてのファイル属性 (アクセス時間、修正時間、状態変更時間を含む) が正しく転送されなければならない点を除けば、同期入出力データの保全の完了と同じです。

ファイルの同期

fsync(3C) 関数と fdatasync(3R) 関数は、ファイルを二次記憶領域と明示的に同期をとります。形式は次のようになります。

fsync(3C) は、入出力ファイルの保全完了レベルで関数の同期をとることを保証し、fdatasync(3R) は、入出力データの保全完了レベルで関数の同期をとることを保証します。

アプリケーションは、操作が完了する前に各入出力操作の同期をとるように指定できます。open(2) または fcntl(2) によってファイル記述に O_DSYNC フラグを設定すると、操作が完了したと見なされる前にすべての入出力書き込み (write(2)aiowrite(3)) が入出力データ完了に達します。ファイル記述に O_SYNC フラグを設定すると、操作が完了したと見なされる前に、すべての入出力書き込みが入出力ファイル完了に達します。ファイル記述に O_RSYNC フラグを設定すると、すべての入出力読み取り (read(2)aio_read(3R)) が、O_DSYNC または O_SYNC を記述子に設定した書き込み要求と同じ完了レベルに達します。

プロセス間通信

この節では、SunOS 5 のプロセス間通信 (IPC) 機能を、実時間処理との関連で説明します。シグナル、パイプ、FIFO (名前付きパイプ)、メッセージ待ち行列、共用メモリ、ファイルマッピング、およびセマフォについて説明します。プロセス間通信に役立つライブラリ、関数、およびルーチンについては、第 8 章「プロセス間通信」と『man Pages(3): Library Routines』を参照してください。

概要

実時間処理は、しばしば高速な高いバンド幅のプロセス間通信を必要とします。どの機構を使用すればよいかは機能的な要求によって決まり、相対的な性能はアプリケーションの特性に依存します。

UNIX での従来のプロセス間通信の方法はパイプですが、パイプはフレーム上の問題を生じます。複数の人がメッセージを書いた結果が混合したり、あるメッセージを複数の人が読むと分断されてしまったりすることがあります。

IPC のメッセージは、ファイルの読み取りや書き込みと似たものです。これは 2 つ以上のプロセスが 1 つの媒体によって通信しなければならない場合、パイプより使いやすいです。

IPC の共用セマフォ機能では、プロセスの同期をとることができます。共用メモリは最も高速なプロセス間通信の形式です。共用メモリの主な長所は、メッセージデータのコピーが不要な点です。共用メモリアクセスの同期をとるには、通常はセマフォの機構を使用します。

シグナル

シグナルを使用してプロセス間で少量の情報を送信できます。次のように、送り側は sigqueue(3R) 関数を使用して、少量の情報とともにシグナルをターゲットプロセスに送信します。

ターゲットプロセスは、以降に発生した保留状態のシグナルも待ち行列に入れるため、指定されたシグナルの SA_SIGINFO ビットを設定していなければなりません (詳細は、sigaction(2) のマニュアルページを参照してください)。

ターゲットプロセスは、シグナルを同期または非同期に受信できます。シグナルをブロッキングしたまま (sigprocmask(2) のマニュアルページを参照)、sigwaitinfo(3R) または sigtimedwait(3R) を呼び出すと、シグナルは、siginfo_t 引数の si_value メンバーに格納されている、sigqueue(3R) の呼び出し側によって送信された値と同期をとって受信されます。シグナルのブロッキングを解除しておくと、シグナルは sigaction(2) によって指定されたシグナルハンドラに配信され、値はハンドラへの siginfo_t 引数の si_value に設定されます。

関連づけられた値を持つシグナルで、送信しても配信されないものの数は、1 プロセスあたり固定です。{SIGQUEUE_MAX} 個のシグナルの記憶領域は、sigqueue(3R) を最初に呼び出した時点で割り当てられます。その後 sigqueue(3R) を呼び出すと、ターゲットプロセスの待ち行列にシグナルが正常に入るか、または制限時間内で異常終了します。

パイプ

パイプは、プロセス間の一方方向の通信を提供します。プロセスがパイプで通信するには、共通の祖先を持っていなければなりません。パイプを通して渡されるデータは、通常の UNIX バイトストリームとして扱われます。パイプについては、「パイプ」を参照してください。

名前付きパイプ

SunOS 5 では名前付きパイプ (FIFO) が用意されています。FIFO はディレクトリ内の名前付きエンティティなので、パイプに比べて柔軟性があります。FIFO が作成されると、適切なアクセス権を持っていればどのプロセスでも FIFO を開くことができます。プロセスは親を共用している必要はなく、親がパイプを初期化して子孫に渡す必要もありません。詳細は、「名前付きパイプ」を参照してください。

メッセージ待ち行列

メッセージ待ち行列は、プロセス間で通信するもう 1 つの手段を提供します。任意の数のプロセスが 1 つのメッセージ待ち行列だけで送受信できます。メッセージは、バイトストリームとしてではなく、任意の大きさのブロッキングとして渡されます。メッセージ待ち行列は、System V 版と POSIX 版の両方で提供されています。詳細は、「System V メッセージ」「POSIX メッセージ」を参照してください。

セマフォ

セマフォは、共用資源に対してアクセスの同期をとる機構です。セマフォも、System V と POSIX の両方で提供されています。System V セマフォは非常に柔軟性がありますが、重量がかなりあります。POSIX セマフォは、極めて軽量です。詳細は、「System V セマフォ」「POSIX セマフォ」を参照してください。

セマフォを使用すると、この章で前述した技法によって明示的に回避しない限り、優先順位の反転が生じる場合があるので注意してください。

共用メモリ

プロセスが通信するための最も高速な方法は、直接メモリの共用セグメントを使用した場合です。共通メモリ領域が共用しているプロセスのアドレス空間に追加されます。アプリケーションは、データを格納することでデータを送信し、データを取り出すことで通信データを受信します。SunOS 5 では、共用メモリのための機構として、メモリにマッピングされたファイル、System V IPC 共用メモリ、POSIX 共用メモリの 3 つの方法を提供しています。

共用メモリを使用するときの最も大きな問題点は、3 つ以上のプロセスが同時に共用メモリに読み取りや書き込みを行おうとすると結果が正しくなくなる場合があることです。詳細は、「共用メモリの同期」を参照してください。

メモリにマッピングされたファイル

mmap(2) インタフェースは、共用メモリセグメントを呼び出し側のアドレス空間に接続します。呼び出し側は、アドレスと長さによって共用セグメントを指定します。呼び出し側は、アクセス保護フラグとマッピングされたページを管理する方法も指定しなければなりません。mmap(2) を使用して、ファイルまたはファイルのセグメントをプロセスのメモリにマッピングすることもできます。この技法は、あるアプリケーションでは非常に便利ですが、マッピングされたファイルセグメントへの格納が暗黙の入出力になる場合があるということを忘れがちです。それ以外の場合では、結合されているプロセスの応答時間が予測できないものになることもあります。msync(3C) は、指定したメモリセグメントのその時のまたは最終的なコピーをパーマネント記憶領域に作成します。詳細は、「メモリ管理インタフェース」を参照してください。

ファイルなしメモリマッピング

0 の特別ファイルである /dev/zero(4S) は、名前がなく 0 で初期化されたメモリオブジェクトを作成するのに使用できます。メモリオブジェクトの長さはマッピングを含むページの最小の番号になります。オブジェクトは、共通の先祖プロセスをもつ子孫だけが共用できます。

System V IPC 共用メモリ

shmget(2) 呼び出しを使用して、共用メモリセグメントの作成と既存の共用メモリセグメントを取得できます。shmget(2) 関数は、ファイル識別子に似た識別子を戻します。shmat(2) を呼び出すと、mmap(2) とほとんど同じように共用メモリセグメントがプロセスメモリの仮想セグメントになります。詳細は、「System V 共用メモリ」を参照してください。

POSIX 共用メモリ

POSIX 共用メモリは System V 共用メモリの変形で、若干の違いはありますが同様の機能を提供します。詳細は、「POSIX 共用メモリ」を参照してください。

共用メモリの同期

共用メモリでは、メモリの一部が 1 つ以上のプロセスのアドレス空間にマッピングされます。アクセスを協調させる方法は自動的には提供されないため、2 つのプロセスが同時に同じ場所の共用メモリに書き込もうとすることがあります。このため共用メモリは、通常はプロセスの同期をとるセマフォやその他の機構と一緒に使用します。System V セマフォと POSIX セマフォは、両方ともこの目的のために使用できます。マルチスレッドライブラリに提供されている相互排他ロッキング、リーダロッキングとライタロッキング、セマフォ、および条件変数もこの目的のために使用できます。

IPC および同期の機構の選択

アプリケーションには特定の機能上の要求があって、それによってどの IPC 機構を使用するかが決まります。いくつかの機構が使用できる場合は、アプリケーションの作成者が、そのうちどれが最もアプリケーションに適しているかを決定します。アプリケーションの特性によって、IPC および同期の機構を選択してください。アプリケーションで使用される様々な長さのメッセージの組み合わせについて各機構のスループットを測定し、どの機構の応答が最も良いかを調べてください。

非同期ネットワーキング

この節では、実時間アプリケーションでトランスポートレベルインタフェース (TLI) を使用して非同期ネットワーク通信を行う方法を説明します。SunOS では、STREAMS の非同期機能と TLI ライブラリルーチンの非ブロッキングモードの組み合わせを使用した TLI イベントの非同期ネットワーク処理をサポートしています。

トランスポートレベルインタフェースの詳細は、『Transport Interfaces Programming Guide』と『man Pages(3): Library Routines』を参照してください。

ネットワーキングのモード

トランスポートレベルインタフェースでは、「接続モード」と「接続なしモード」という 2 つのモードサービスが用意されています。

接続モードサービス

「接続モード」は回線中心で、確立された接続上を信頼できるシーケンスでデータを伝送します。データ伝送フェーズでのアドレスの解決と伝送のオーバヘッドを避けるための識別手続きも用意されています。このサービスは、比較的長い時間持続するデータストリーム中心の対話を必要とするアプリケーションに適しています。

接続なしモードサービス

「接続なしモード」はメッセージ中心で、複数のユニット間の論理的な関係を要求されない独立した単位でのデータ伝送をサポートします。宛先を含むデータのユニットを配信するために必要なすべての情報は、データと合わせて単一のサービス要求として送信側からトランスポートプロバイダに渡されます。接続なしモードサービスは、短い時間の要求と応答の対話を行い、データの配信の保証やシーケンスを必要としないアプリケーションに適しています。接続なしモードによる伝送は、概して信頼性が低いと言えます。

ネットワーキングプログラミングモデル

ファイルやデバイスの入出力と同様に、ネットワーク転送はプロセスサービス要求によって同期または非同期に実行できます。

同期ネットワーキング

同期ネットワーキングは、同期したファイルやデバイスの入出力と同様に進行します。write(2) 関数と同様に、送信要求はメッセージをバッファリングして戻りますが、バッファ空間がすぐ使用できない場合は、呼び出しプロセスを一時停止させることもあります。read(2) 関数と同様に、受信要求は要求を満たすデータが到着するまで、呼び出しプロセスの実行を一時停止させます。SunOS 5 では、転送サービスについて限界を保証していないので、同期ネットワーキングは他のデバイスに対して実時間特性が必要なプロセスには適していません。

非同期ネットワーキング

非同期ネットワーキングは、非ブロッキングサービス要求によって用意されます。また、アプリケーションは接続が確立またはデータが送受信されたときに非同期通知を要求することもできます。

非同期接続なしモードサービス

非同期接続なしモードのネットワーキングは、非ブロッキングサービスのエンドポイントを設定して、データ転送についての非同期通知をポーリングまたは受信することによって行われます。非同期通知を使用する場合は、実際のデータ受信はシグナルハンドラ内で行われます。

エンドポイントを非同期にする

t_open(3N) によってエンドポイントを確立して、t_bind(3N) によって識別情報を確立したら、エンドポイントを非同期サービス用に設定できます。これは、fcntl(2) 関数によってエンドポイントに O_NONBLOCK フラグを設定して行います。これ以降は、 t_sndudata(3N) を呼び出すと、バッファ空間がすぐに利用できない場合は -1 が戻され、t_errnoTFLOW に設定されます。同様に t_rcvudata(3N) を呼び出すと、データがない場合は -1 が戻され、t_errnoTNODATA に設定されます。

非同期ネットワーク転送

アプリケーションは、poll(2) 関数を使用してエンドポイントでデータの受信を定期的に調べるか、データの受信を待つことができますが、データが到着したことを知らせる非同期通知を受信する必要がある場合もあります。この場合は、ioctl(2) 関数に I_SETSIG コマンドを付けて実行し、エンドポイントにデータが到着したら SIGPOLL シグナルをプロセスに送信するように要求します。アプリケーションは、複数のメッセージによって 1 つのシグナルが生成される場合についても対処しなければなりません。

次の例で、protocol はアプリケーションが選択したトランスポートプロトコル名です。


#include <sys/types.h>
#include <tiuser.h>
#include <signal.h>
#include <stropts.h>

int				fd;
struct t_bind				*bind;
void				sigpoll(int);

	fd = t_open(protocol, O_RDWR, (struct t_info *) NULL);

	bind = (struct t_bind *) t_alloc(fd, T_BIND, T_ADDR);
	... /* バインドアドレスを設定する */
	t_bind(fd, bind, bin

	/* エンドポイントを非ブロッキングにする */
	fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK);

	/* SIGPOLL のためのシグナルハンドラを確立する */
	signal(SIGPOLL, sigpoll);

	/* 受信データがあるときに SIGPOLL シグナルを要求する */
	ioctl(fd, I_SETSIG, S_INPUT | S_HIPRI);

	...

void sigpoll(int sig)
{
	int					flags;
	struct t_unitdata					ud;

	for (;;) {
		... /* ud を初期化する */
		if (t_rcvudata(fd, &ud, &flags) < 0) {
			if (t_errno == TNODATA)
				break; /* これ以上メッセージはない */
			... /* その他のエラー状態を処理する */
	}
	... /* ud 内のメッセージを処理する */
}

非同期接続モードサービス

接続モードサービスの場合、アプリケーションは、データ転送だけではなく接続も非同期に確立されるように設定できます。操作のシーケンスは、プロセスが他のプロセスに接続しようとしているのか、接続試行を待っているのかによって異なります。

非同期に接続を確立する

プロセスは、接続を試みてから非同期に完了できます。プロセスはまず接続エンドポイントを作成し、fcntl(2) を使用してエンドポイントを非ブロッキング操作用に設定します。接続なしデータ転送の場合と同様に、エンドポイントは接続の完了と以降のデータ転送について非同期通知を行うように設定することもできます。次に、接続プロセスは t_connect(3N) 関数を使用して転送の設定を開始します。最後に、t_rcvconnect(3N) 関数を使用して接続が確立されたことを確認します。

接続の非同期使用

接続を非同期に待つには、プロセスはまずサービスアドレスに結合した非ブロッキングエンドポイントを確立します。poll(2) の結果または非同期通知によって接続要求が到着したことがわかったら、プロセスは t_listen(3N) 関数を使用して接続要求を取得できます。接続を受け入れるには、プロセスは t_accept(3N) 関数を使用します。応答するエンドポイントは、非同期データ転送用に別個に設定しなければなりません。

次の例は、接続を非同期に要求する方法を示しています。

#include <tiuser.h>
int 				fd;
struct t_call 	*call;

	fd = .../* 非ブロッキングエンドポイントを確立する */

	call = (struct t_call *) t_alloc(fd, T_CALL, T_ADDR);
	.../* call 構造体を初期化する */
	t_connect(fd, call, call);

	/* 接続要求は現在非同期に進行している */

	.../* 接続が受け付けられたという通知を受け取る */
	t_rcvconnect(fd, &call);

次の例は、接続を非同期に監視する方法を示しています。

#include <tiuser.h>
int 				fd, res_fd;
struct t_call 	call;

	fd = ... /* 非ブロッキングエンドポイントを確立する */

	.../* 接続要求が到着したという通知を受け取る */
	call = (struct t_call *) t_alloc(fd, T_CALL, T_ALL);
	t_listen(fd, &call);

	.../* 接続を受け入れるかどうかを決定する */
	res_fd = ... /* 応答用に非ブロッキングエンドポイントを確立する */
	t_accept(fd, res_fd, call);

非同期でファイルを開く

アプリケーションは、遠隔ホストからマウントされたファイルシステム内や、初期化が遅れているデバイス上の通常ファイルをダイナミックに開く必要がある場合もあります。ただし、そのようなファイルを開く状態が進行している間は、アプリケーションは他のイベントへ実時間応答できなくなります。この問題は SunOS 5 では、2 番目のプロセスが実際にファイルを開き、そのファイル記述子を実時間プロセスに渡すことによって解決できます。

ファイル記述子の転送

SunOS 5 の STREAMS インタフェースは、開いているファイルの記述子をプロセスからプロセスへ渡す機構を提供しています。開いているファイルの記述子を持つプロセスは、ioctl(2) 関数にコマンド引数 I_SENDFD を付けて使用します。2 番目のプロセスは、ioctl() 関数にコマンド引数 I_RECVFD を付けて呼び出し、ファイル記述子を取得します。

例 9-1 では、親プロセスがまずテストファイルについての情報を表示し、その後パイプを作成します。次に、親プロセスは子プロセスを作成し、子プロセスはテストファイルを開いて、そのファイル記述子をパイプを通じて親に戻します。親プロセスは、新しいファイル記述子についての状態情報を表示します。


例 9-1 ファイル記述子の転送

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stropts.h>
#include <stdio.h>

#define TESTFILE "/dev/null"
main(int argc, char *argv[])
{
	int fd;
	int pipefd[2];
	struct stat statbuf;

	stat(TESTFILE, &statbuf);
	statout(TESTFILE, &statbuf);
	pipe(pipefd);
	if (fork() == 0) {
		close(pipefd[0]);
		sendfd(pipefd[1]);
	} else {
		close(pipefd[1])
		recvfd(pipefd[0]);
	}
}

sendfd(int p)
{
	int tfd;

	tfd = open(TESTFILE, O_RDWR);
	ioctl(p, I_SENDFD, tfd);
}

recvfd(int p)
{
	struct strrecvfd rfdbuf;
	struct stat statbuf;
	char			fdbuf[32];

	ioctl(p, I_RECVFD, &rfdbuf);
	fstat(rfdbuf.fd, &statbuf);
	sprintf(fdbuf, "recvfd=%d", rfdbuf.fd);
	statout(fdbuf, &statbuf);	
}

statout(char *f, struct stat *s)
{
	printf("stat: from=%s mode=0%o, ino=%ld, dev=%lx, rdev=%lx¥n",
		f, s->st_mode, s->st_ino, s->st_dev, s->st_rdev);
	fflush(stdout);
}

タイマ

この節では、SunOS 5 で実時間アプリケーションのために使用できるタイミング機能について説明します。実時間アプリケーションでこの機能を活用したい場合は、この節に示されているルーチンについて、『man Pages(3): Library Routines』を参照してください。

SunOS 5 のタイミング機能は、「タイムスタンプ」と「インタバルタイマ」という 2 つの機能に分けられます。タイムスタンプ機能は経過時間を測定して、アプリケーションが、ある状態の持続時間やイベント間の時間を測定できるようにします。インタバルタイマ機能は、アプリケーションを指定した時間に呼び起こして、アプリケーションが時間の経過に基づいて動作をスケジュールできるようにします。アプリケーションは、タイムスタンプ機能をポーリングして自分をスケジュールすることもできますが、そのようなアプリケーションは、プロセッサを独占して他のシステム関数に悪影響を与えます。

タイムスタンプ機能

タイムスタンプは 2 つの関数によって提供されます。gettimeofday(3C) 関数は、グリニッジ標準時間 1970 年 1 月 1 日午前 0 時からの秒数とマイクロ秒数によって時間を表し、現在の時間を timeval 構造体に与えます。clock_gettime(3R) 関数は、CLOCK_REALTIME のクロック ID とともに使用して、gettimeofday(3C) が戻すタイムインタバルと同じ時間を秒とナノ秒で表して、現在の時間を timespec 構造体に与えます。

SunOS 5 はハードウェア定期タイマを使用します。ある種のワークステーションでは、これが唯一の時間情報で、タイムスタンプの精度はその定期タイマの解像度までに制限されます。その他のプラットフォームでは、1 マイクロ秒の解像度を持つタイマレジスタによって、タイムスタンプ精度は 1 マイクロ秒となっています。

インタバルタイマ機能

実時間アプリケーションは、インタバルタイマを使用して活動をスケジュールすることがよくあります。インタバルタイマには、「単発」型と「周期」型の 2 種類があります。

単発タイマは、現在時間または絶対時間に相対的な有効時間に設定されるタイマです。タイマは、有効時間が終了すると解除されます。このようなタイマは、データを記憶領域に転送した後のバッファの消去や操作のタイムアウトの管理に便利です。

周期タイマには、初期有効時間 (絶対時間または相対時間) と繰り返しインタバルが設定されています。インタバルタイマの有効時間が経過するたびに、タイマは繰り返し再ロードされ、自動的に再度有効になります。このタイマはデータロギングやサーボ制御に便利です。インタバルタイマ機能を呼び出す際は、システムのハードウェア定期タイマの解像度より小さな時間値は、ハードウェア定期タイマインタバル (10 ミリ秒) の時間値より大きい最小の倍数に丸められます。

SunOS 5 には、setitimer(2) インタフェースと getitimer(2) インタフェースの 2 組のタイマインタフェースがあります。これらのインタフェースは、タイムインタバルを指定する timeval 構造体を使用して、BSD タイマと呼ばれる固定設定タイマを動作させます。POSIX タイマである timer_create(3R) は、POSIX クロック CLOCK_REALTIME を動作させます。POSIX タイマの動作は、timespec 構造体によって表されます。

getitimer(2) 関数と setitimer(2) 関数は、それぞれ指定された BSD インタバルタイマの値の取り出しと設定を行います。プロセスは ITIMER_REAL で指定する実時間タイマを含め、3 つの BSD インタバルタイマを利用できます。BSD タイマを使用して有効になっている場合は、システムによってタイマにふさわしいシグナルがタイマを設定したプロセスに送信されます。

timer_create(3R)は、{TIMER_MAX} 個までの POSIX タイマを生成できます。呼び出し側はタイマの有効時間が経過したときに、どのシグナルと関連値をプロセスに送るかを指定できます。timer_settime(3R)timer_gettime(3R) は、指定された POSIX インタバルタイマの値を、それぞれ設定および検索します。必要なシグナルが保留状態にある間に POSIX タイマの有効時間が経過すると配信がカウントされ、timer_getoverrun(3R) はそのような有効時間切れのカウントを検索します。timer_delete(3R) は、POSIX タイマの割り当てを解除します。

例 9-2 に、setitimer(2) を使用して定期割り込みを発生させる方法とタイマ割り込みの到着の制御方法を示します。


例 9-2 タイマ割り込みの制御

#include	<unistd.h>
#include	<signal.h>
#include	<sys/time.h>

#define TIMERCNT 8

void timerhandler();
int	 timercnt;
struct	 timeval alarmtimes[TIMERCNT];

main()
{
	struct itimerval times;
	sigset_t	sigset;
	int		i, ret;
	struct sigaction act;
	siginfo_t	si;

	/* SIGALRM をブロッキングする */
	sigemptyset(&sigset);
	sigaddset(&sigset, SIGALRM);
	sigprocmask(SIG_BLOCK, &sigset, NULL);

	/* SIGALRM のためのハンドラを設定する */
	act.sa_handler = timerhandler;
	sigemptyset(&act.sa_mask);
	act.sa_flags = SA_SIGINFO;
	sigaction(SIGALRM, &act, NULL);
	/*
	 * 3 秒後に開始し、そのあとは 3 分の 1 秒おきに開始するように 
	 * インタバルタイマを設定する
	 */
	times.it_value.tv_sec = 3;
	times.it_value.tv_usec = 0;
	times.it_interval.tv_sec = 0;
	times.it_interval.tv_usec = 333333;
	ret = setitimer(ITIMER_REAL, &times, NULL);
	printf("main:setitimer ret = %d¥n", ret);

	/* 現在はアラーム待ち */
	sigemptyset(&sigset);
	timerhandler(0, si, NULL);
	while (timercnt < TIMERCNT) {
		ret = sigsuspend(&sigset);
	}
	printtimes();
}

void timerhandler(sig, siginfo, context)
	int		sig;
	siginfo_t	siginfo;
	void		*context;
{
	printf("timerhandler:start¥n");
	gettimeofday(&alarmtimes[timercnt], NULL);
	timercnt++;
	printf("timerhandler:timercnt = %d¥n", timercnt);
}

printtimes()
{
	int	i;

	for (i = 0; i < TIMERCNT; i++) {
		printf("%ld.%0l6d¥n", alarmtimes[i].tv_sec,
				alarmtimes[i].tv_usec);
	}
}