read()
そして、呼び出しのマニュアルページを読んでみると、write()
これらの呼び出しは、ブロックされているかどうかにかかわらずシグナルによって中断されるように見えます。
特に、仮定しましょう。
- プロセスは信号のハンドラを生成します。
- 次のコマンドを使用してデバイス(端末など)を開きます。
O_NONBLOCK
いいえ設定(例:ブロックモードで実行) - その後、プロセスは
read()
システムコールを介してデバイスからデータを読み取り、カーネル空間でカーネル制御パスを実行します。 - プロセスが
read()
カーネル空間で実行されると、以前にインストールされたハンドラからの信号がプロセスに渡され、対応する信号ハンドラが呼び出されます。
マニュアルページとそのセクションを読んでください。SUSv3「システムインタフェースボリューム(XSH)」、見つけた人:
私。データを読み取る前にaがread()
信号によって中断された場合(つまり、利用可能なデータがないためブロックする必要がある場合)、-1が返されerrno
[EINTR]に設定されます。
2.read()
一部のデータが正常に読み出された後、シグナルによって a が中断された場合 (つまり、要求サービスをすぐに開始できる)、読み取ったバイト数を返します。
質問1):
どちらの場合も(遮断/遮断しない)シグナル伝達と処理が完全に透明ではないと仮定するのが正しいですかread()
?
最初のケースです。ブロックはread()
通常、プロセスが適切なTASK_INTERRUPTIBLE
状態に設定され、シグナルが転送されるとカーネルがプロセスをその状態に設定するため、理解できるようですTASK_RUNNING
。
ただし、read()
ブロックを必要とせず(ケース2)、要求がカーネル空間で処理される場合、ハードウェア割り込みの到着と適切な処理が透過的であるかのように、信号の到着と処理が透過的であると仮定します。 。具体的には、信号を送信した後にプロセスが一時的に実行されると仮定します。ユーザーモードシグナルハンドラを実行し、最終的にそのハンドラから戻り、割り込み処理を完了しますread()
(カーネル空間で)。その後、プロセスはread()
完了するまでプロセスを実行します。その後、プロセスは呼び出し後のポイントread()
(ユーザー空間で)に戻り、結果は次のようになります。使用可能なすべてのバイトを取得します。
しかし、ii。データはすぐに使用できるため、中断されたことを意味するように見えますが、read()
データのすべてではなく一部のみを返します。
これは2番目の(そして最後の)質問につながります。
質問B):
A)の仮定が正しい場合は、read()
すぐに要求を満たすデータがありますが、ブロックする必要はありませんが、なぜ中断されますか?つまり、read()
シグナルハンドラの実行後に回復が行われず、最終的に利用可能なすべてのデータ(最終的に利用可能)が返されるのはなぜですか?
答え1
要約:そうです。 i(何も読み取らず中断)の場合やii(部分読み込み後中断)の場合、信号受信は透明ではありません。それ以外の場合は、オペレーティングシステムのアーキテクチャとアプリケーションのアーキテクチャを根本的に変更する必要があります。
オペレーティングシステムの実装の表示
システムコールがシグナルによって中断された場合、何が起こるのか考えてみましょう。信号ハンドラはユーザモードコードを実行する。ただし、システムコールハンドラはカーネルコードであり、ユーザーモードコードを信頼しません。それでは、システムコールハンドラのオプションを見てみましょう。
- システムコールの終了。ユーザーコードで行われた操作の数を報告します。必要に応じてシステムコールを再開するのはアプリケーションコードによって異なります。これがUnixが動作する方法です。
- システムコールの状態を保存し、ユーザーコードが呼び出しを再開できるようにします。これは、次のようなさまざまな理由で問題になります。
- ユーザーコードの実行中に保存された状態が無効になることがあります。たとえば、ファイルから読み取ると、ファイルが切り捨てられる可能性があります。したがって、カーネルコードには、このような状況を処理するために多くのロジックが必要です。
- ユーザコードがシステムコールを再開し、ロックが永久に維持されるという保証がないため、保存された状態ではロックを保持することはできません。
- カーネルは、システムコールを開始するための一般的なインターフェイスに加えて、進行中のシステムコールを再開またはキャンセルするための新しいインターフェイスを公開する必要があります。まれに、これは深刻な合併症です。
- 保存状態にはリソース(少なくともメモリ)を使用する必要があります。これらのリソースはカーネルで割り当てて保持する必要がありますが、プロセス割り当てに含まれます。これは克服できないわけではありませんが、複雑な問題です。
- シグナルハンドラはそれ自体が中断される可能性があるシステムコールを実行する可能性があるため、すべての可能なシステムコールを網羅する静的リソース割り当てを持つことはできません。
- リソースを割り当てることができない場合はどうなりますか?これにより、システムコールはとにかく失敗します。つまり、アプリケーションにはこの状況を処理するためのコードが必要であるため、この設計はアプリケーションコードを単純化しません。
- 続行しますが(一時停止)、シグナルハンドラの新しいスレッドを作成します。今回も問題があります。
- 初期のUNIX実装には、プロセスごとに1つのスレッドしかありませんでした。
- シグナルハンドラはシステムコールを無視する危険があります。とにかくこれは問題ですが、現在UNIX設計にはすでに含まれています。
- 新しいスレッドにリソースを割り当てる必要があります。上記を参照してください。
割り込みとの主な違いは、割り込みコードが信頼でき、非常に制限されていることです。通常、リソースを割り当てたり、永久に実行したり、ロックを解除せずにロックを取得したり、その他の不快なタスクを実行したりすることはできません。割り込みハンドラは、オペレーティングシステムの実装者が直接作成するので、悪いことをしないことを知っています。 。一方、アプリケーションコードは何でもできます。
アプリケーションデザインの表示
システムコール中にアプリケーションがハングした場合、システムコールは継続して完了する必要がありますか?いつもそうではありません。たとえば、端末から1行を読み、ユーザーがそれを押してSIGINTをトリガーするシェル型プログラムを考えてみますCtrl+C
。読み取りが完了してはいけません。これがまさに信号の目的です。この例は、read
バイトが読み取られていなくてもシステムコールが中断可能であることを示しています。
したがって、アプリケーションにはカーネルにシステムコールをキャンセルするように指示する方法が必要です。 Unix設計では、これが自動的に発生します。シグナルによりシステムコールが返されます。他の設計では、アプリケーションが自由にシステムコールを再開またはキャンセルする方法が必要です。
システムread
コールは、オペレーティングシステムの全体的な設計を考慮する際に意味のある基本要素であるため、その名前になります。大まかに言えば、「限度(バッファサイズ)に達するまでできるだけ読み、他のことが発生したら停止する」という意味です。実際にバッファ全体を読み取るにread
は、できるだけ多くのバイトを読み取るまで繰り返す必要があります。これはより高いレベルの機能です。fread(3)
。同じではないread(2)
fread
これはNETのライブラリ関数であるシステムコールですread
。ファイルを読み込んだり失敗したりするアプリケーションに適しています。接続を明確に制限する必要があるコマンドラインソルバーやネットワークプログラム、または同時接続と接続を持つアプリケーションには適していません。スレッドを使用しないネットワークプログラム。
Robert Loveの「Linuxシステムプログラミング」は、ループ読み出しの例を提供します。
ssize_t ret;
while (len != 0 && (ret = read (fd, buf, len)) != 0) {
if (ret == -1) {
if (errno == EINTR)
continue;
perror ("read");
break;
}
len -= ret;
buf += ret;
}
case i
処理やcase ii
その他のいくつかのタスクを処理します。
答え2
質問Aに答えてください:
はい、信号伝達と処理はread()
。
途中で実行すると、read()
一部のリソースが占有される可能性があり、シグナルによって中断される可能性があります。その信号の信号ハンドラは、他の信号read()
(または他の信号ハンドラ)を呼び出すことができます。非同期信号安全システムコール)やはり同じだ。したがって、read()
シグナルによって中断されたシグナルは、使用しているリソースを解放するために最初に停止する必要があります。そうしないと、シグナルread()
ハンドラから呼び出されたシグナルが同じリソースにアクセスして再入の問題が発生します。
これは、シグナルハンドラから呼び出すことができるシステムコールに加えて、read()
同じリソースセットを占有できるためですread()
。上記の再進入の問題を避けるために、最も簡単で安全な設計は、read()
動作中に信号が発生するたびに中断を停止することです。