read()
とwrite()
の呼び出しに関するmanページを読むと、これらの呼び出しは、ブロックする必要があるかどうかに関係なく、シグナルによって中断されているように見えます。
特に、
O_NONBLOCK
notが設定されている(つまり、ブロッキングモードで動作している)デバイスが開かれている(ターミナルなど)read()
システムコールを実行してデバイスから読み取り、その結果、カーネル空間でカーネル制御パスを実行します。read()
を実行している間に、ハンドラーが先にインストールされたシグナルがそのプロセスに配信され、そのシグナルハンドラーが呼び出されます。SUSv3 'System Interfaces volume(XSH)' のマニュアルページと適切なセクションを読むと、次のことがわかります。
私。 read()
がデータを読み取る前にシグナルによって中断された場合(つまり、使用可能なデータがなかったためにブロックする必要があった場合)、errno
を[EINTR]に設定して-1を返します。
ii。 read()
が一部のデータの読み取りに成功した後(つまり、要求の処理をすぐに開始できた場合)に信号によって中断された場合、読み取ったバイト数を返します。
質問A):どちらの場合(ブロック/ブロックなし)でも、信号の配信と処理は、 read()
?
ケースi。ブロッキングread()
は通常、プロセスをTASK_INTERRUPTIBLE
状態にして、シグナルが配信されるとカーネルがプロセスをTASK_RUNNING
状態にするため、理解できるようです。
ただし、read()
がブロックする必要がなく(ケースii。)、リクエストをカーネル空間で処理している場合、シグナルの到着とその処理は、到着と適切な処理のように透過的であると考えていました。 HW割り込みになります。特に、シグナルが配信されると、プロセスは一時的にuserモードに配置され、シグナルハンドラーを実行して返されると想定していました。最終的には、中断されたread()
(カーネルスペース内)の処理を終了し、read()
が完了するまでコースを実行した後、プロセスは、read()
(ユーザースペース内)の呼び出し直後のポイントに戻ります。結果として読み取られたバイト。
しかし、ii。データはすぐに利用できるため、read()
が中断されているように見えますが、返されるのは(すべてではなく)データの一部だけです。
これにより、2番目(および最後)の質問が表示されます。
質問B):A)での私の仮定が正しい場合、データがあるためにブロックする必要がないのに、read()
が中断されるのはなぜですか?すぐに要求を満たすことができますか?言い換えると、シグナルハンドラの実行後にread()
が再開されず、最終的に利用可能なすべてのデータ(結局利用可能であった)が返される結果になるのはなぜですか。
要約:信号の受信は透過的ではなく、ケースi(何も読み取らずに中断された場合)でも、ケースii(部分的な読み取り後に中断された場合)でも正しくありません。それ以外の場合は、オペレーティングシステムのアーキテクチャとアプリケーションのアーキテクチャの両方に基本的な変更を加える必要がある場合に備えます。
システムコールがシグナルによって中断された場合にどうなるかを考えます。シグナルハンドラーはユーザーモードコードを実行します。ただし、syscallハンドラーはカーネルコードであり、ユーザーモードコードを信頼しません。それでは、syscallハンドラーの選択肢を見てみましょう。
割り込みとの主な違いは、割り込みコードが信頼され、非常に制約されていることです。通常、リソースを割り当てたり、永久に実行したり、ロックを取得してそれらを解放したり、その他の厄介なことをしたりすることはできません。割り込みハンドラーはOSの実装者自身が作成したものなので、何も悪いことはしないことがわかっています。一方、アプリケーションコードは何でも実行できます。
システムコールの途中でアプリケーションが中断された場合、syscallは完了まで続行する必要がありますか?常にではない。たとえば、ターミナルから行を読み取るシェルのようなプログラムを考えて、ユーザーが_Ctrl+C
_を押してSIGINTをトリガーしたとします。読み取りは完了してはなりません。それが信号のすべてです。この例は、バイトがまだ読み取られていなくても、read
syscallが割り込み可能でなければならないことを示していることに注意してください。
したがって、アプリケーションがカーネルにシステムコールをキャンセルするように指示する方法が必要です。 UNIXの設計では、これは自動的に行われます。信号によってsyscallが返されます。その他の設計では、アプリケーションがシステムコールを自由に再開またはキャンセルする方法が必要です。
オペレーティングシステムの一般的な設計を考えると、read
システムコールは、プリミティブが意味をなすため、このようになっています。それが意味することは、おおざっぱに、「限界(バッファサイズ)まで可能な限り読み取り、他のことが発生した場合は停止する」ということです。バッファ全体を実際に読み取るには、可能な限り多くのバイトが読み取られるまで、ループでread
を実行する必要があります。これはより高レベルの関数 fread(3)
です。システムコールである read(2)
とは異なり、fread
は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
_のほか、いくつかを処理します。
質問Aに答えるには:
はい、シグナルの配信と処理はread()
に対して完全に透過的ではありません。
途中で実行されているread()
は、シグナルによって中断されている間、一部のリソースを占有している可能性があります。そして、シグナルのシグナルハンドラは、別のread()
(または他の async-signal safe syscalls )を呼び出すこともできます。したがって、シグナルによって中断されたread()
は、使用するリソースを解放するために最初に停止する必要があります。そうしないと、シグナルハンドラーから呼び出されたread()
が同じリソースにアクセスし、再入可能な問題が発生します。
read()
以外のシステムコールはシグナルハンドラーから呼び出すことができ、read()
と同じリソースセットを占有する可能性があるためです。上記の再入可能な問題を回避するために、最も簡単で安全な設計は、実行中に信号が発生するたびに中断されたread()
を停止することです。