シグナルがキャッチされたときのシステムコールの中断


29

read()とのwrite()呼び出しのマニュアルページを読むと、これらの呼び出しは、ブロックする必要があるかどうかに関係なく、シグナルによって中断されるようです。

特に、仮定

  • プロセスが何らかのシグナルのハンドラーを確立します。
  • デバイスが設定されてO_NONBLOCK いない状態で開かれている(つまり、ブロッキングモードで動作している)
  • その後、プロセスread()はデバイスから読み取るシステムコールを行い、その結果、カーネル空間でカーネル制御パスを実行します。
  • read()プロセスがカーネル空間で実行している間、ハンドラーが以前にインストールされたシグナルがそのプロセスに配信され、そのシグナルハンドラーが呼び出されます。

SUSv3の「System Interfaces volume(XSH)」のマニュアルページと適切なセクションを読むと、次のことがわかります。

私。a read()がデータを読み込む前に信号によって中断された場合(つまり、データが利用できなかったためブロックしなければならなかった場合)、errno[EINTR]に設定された-1を返します。

ii。a read()がデータを正常に読み取った後(つまり、要求の処理をすぐに開始できた)にシグナルによって中断された場合、読み取られたバイト数を返します。

質問A): どちらの場合(ブロック/ブロックなし)でも、信号の配信と処理は完全に透過的ではないと仮定するのは正しいread()ですか?

ケースi。ブロッキングread()は通常、プロセスをTASK_INTERRUPTIBLE状態にするため、シグナルが配信されるとカーネルがプロセスをTASK_RUNNING状態にするため、理解できるようです。

しかし、read()ブロックする必要がなく(ケースii。)、カーネル空間でリクエストを処理している場合、シグナルの到着とその処理は、ハードウェアの到着と適切な処理と同様に透過的であると考えていました。割り込みになります。特に、シグナルの配信時に、プロセスは一時的にユーザーモードになり、そのシグナルハンドラーを実行して、最終的に戻り、中断されたread()(カーネルスペースで)処理を終了しread()て、完了までの経過後、プロセスはread()(ユーザー空間での)呼び出しの直後のポイントに戻り、結果として利用可能なすべてのバイトが読み込まれます。

しかし、ii。はread()、データがすぐに利用可能であるため、が中断されていることを暗示しているように見えますが、(すべてではなく)一部のデータのみを返します。

これにより、2番目の(そして最後の)質問に導かれます。

質問B): A)での私の仮定が正しい場合、read()要求を満たすために利用可能なデータがあるためブロックする必要はないのに、なぜ中断されるのですか?言い換えると、なぜread()シグナルハンドラーを実行した後に再開されず、最終的にすべての利用可能なデータ(結局利用可能だった)が返されることになりますか?

回答:


29

要約:iの場合(何も読み取らずに中断された場合)もiiの場合(部分的な読み取り後に中断された場合)でも、シグナルの受信は透過的ではありません。そうでなければ、オペレーティングシステムのアーキテクチャとアプリケーションのアーキテクチャの両方に根本的な変更を加える必要がある場合に備えます。

OS実装ビュー

システムコールがシグナルによって中断された場合に何が起こるかを考えてください。シグナルハンドラーはユーザーモードコードを実行します。ただし、syscallハンドラーはカーネルコードであり、ユーザーモードコードを信頼しません。それでは、syscallハンドラの選択肢を見てみましょう。

  • システムコールを終了します。ユーザーコードに対して行われた処理量を報告します。必要に応じて、何らかの方法でシステムコールを再起動するのはアプリケーションコード次第です。それがunixの仕組みです。
  • システムコールの状態を保存し、ユーザーコードがコールを再開できるようにします。これにはいくつかの理由で問題があります。
    • ユーザーコードの実行中に、保存された状態が無効になる可能性があります。たとえば、ファイルから読み取る場合、ファイルは切り捨てられる場合があります。そのため、これらのケースを処理するには、カーネルコードに多くのロジックが必要です。
    • ユーザーコードがsyscallを再開し、ロックが永久に保持される保証がないため、保存された状態はロックを保持できません。
    • カーネルは、syscallを開始する通常のインターフェイスに加えて、進行中のsyscallを再開またはキャンセルするための新しいインターフェイスを公開する必要があります。これは、まれなケースでは非常に複雑です。
    • 保存された状態では、リソース(少なくともメモリ)を使用する必要があります。これらのリソースはカーネルが割り当てて保持する必要がありますが、プロセスの割り当てに対してカウントされます。これは乗り越えられませんが、複雑です。
      • シグナルハンドラーは、それ自体が中断されるシステムコールを行う場合があることに注意してください。したがって、可能なすべてのsyscallをカバーする静的なリソース割り当てを持つことはできません。
      • また、リソースを割り当てることができない場合はどうなりますか?それから、syscallはとにかく失敗しなければなりません。これは、アプリケーションがこのケースを処理するためのコードを必要とすることを意味するため、この設計ではアプリケーションコードが単純化されません。
  • 進行中のまま(ただし一時停止)、シグナルハンドラ用の新しいスレッドを作成します。これもまた問題です。
    • 初期のUNIX実装では、プロセスごとに単一のスレッドがありました。
    • シグナルハンドラは、syscallの靴を踏み越えてしまうリスクがあります。とにかくこれは問題ですが、現在のUNIXの設計には含まれています。
    • 新しいスレッドにリソースを割り当てる必要があります。上記を参照。

割り込みとの主な違いは、割り込みコードが信頼され、高度に制約されていることです。通常、リソースを割り当てたり、永久に実行したり、ロックを取得してそれらを解放したり、その他の厄介なことを行うことは許可されていません。割り込みハンドラはOSの実装者自身によって作成されているため、彼はそれが悪いことをしないことを知っています。一方、アプリケーションコードは何でもできます。

アプリケーション設計ビュー

システムコールの途中でアプリケーションが中断された場合、syscallは完了まで継続する必要がありますか?常にではない。たとえば、端末から行を読み取り、ユーザーがを押してCtrl+CSIGINTをトリガーするシェルのようなプログラムを考えます。読み取りは完了してはなりません。それが信号のすべてです。この例は、readバイトがまだ読み取られていない場合でもsyscallが割り込み可能でなければならないことを示していることに注意してください。

そのため、アプリケーションがカーネルにシステムコールをキャンセルするよう指示する方法が必要です。UNIXの設計では、それは自動的に行われます:シグナルはsyscallを返します。他の設計では、アプリケーションが自由にsyscallを再開またはキャンセルする方法が必要です。

readシステムコールは、オペレーティングシステムの一般的な設計を考えると、理にかなっている原始的だからそれがある方法です。それが意味することは、大まかに言って、「可能な限り、制限(バッファーサイズ)まで読み取りますが、何か他のことが発生した場合は停止します」です。実際に完全なバッファーを読み取るには、readできるだけ多くのバイトが読み取られるまでループで実行する必要があります。これは高レベル関数ですfread(3)read(2)どちらがシステムコールであるかとは異なりfread、ライブラリ関数は、上のユーザースペースに実装されていますread。これは、ファイルを読み取るか、試行中に終了するアプリケーションに適しています。コマンドラインインタープリターや、接続をきれいに調整する必要のあるネットワークプログラムや、同時接続がありスレッドを使用しないネットワークプログラムには適していません。

ループでの読み取りの例は、Robert LoveのLinux System Programmingで提供されています。

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 icase iiさらにいくつかをします。


UNIXデザイン哲学に関する記事で提唱された同様の見解を裏付ける非常に簡潔で明確な答えをくれたGillesに感謝します。syscallの中断動作は、技術的な制約や障害ではなく、UNIXの設計哲学に関係していることを非常に確信しているようです
-darbehdar

@darbehdar 3つすべて:Unixの設計哲学(ここでは主にプロセスはカーネルより信頼性が低く、任意のコードを実行できること、プロセスとスレッドは暗黙的に作成されないこと)、技術的制約(リソース割り当て)、およびアプリケーション設計(信号がsyscallをキャンセルする必要がある場合です。
ジル 'SO-悪であるのをやめる'

2

質問Aに回答するには

はい、信号の配信と処理はに対して完全に透過的ではありませんread()

read()それがシグナルによって割り込まれている間実行している途中では、いくつかのリソースを占有することができます。また、シグナルのシグナルハンドラは、別のread()(または他の非同期シグナルに対して安全なsyscalls)を呼び出すこともできます。そのread()ため、使用するリソースを解放するために、シグナルによって中断されたものを最初に停止する必要があります。そうしないread()と、シグナルハンドラーから呼び出されたリソースが同じリソースにアクセスしてリエントラントの問題が発生します。

以外のシステムコールread()はシグナルハンドラから呼び出すことができ、また同じようにリソースのセットを占有する可能性read()があるためです。上記の再入可能な問題を回避するための最も簡単で安全な設計はread()、実行中に信号が発生するたびに割り込みを停止することです。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.