シグナルとは?信号はソフトウェア割り込みです。
堅牢なプログラムはシグナルを処理する必要があります。これは、シグナルが非同期イベントをアプリケーションに配信する方法であるためです。
ユーザーが ctrl+c を押す、別のプロセスを強制終了するためにシグナルを送信するプロセスなどはすべて、プロセスがシグナル処理を行う必要がある場合です。
Linux シグナル
Linux では、すべてのシグナルに SIG という文字で始まる名前があります。例:
- ユーザーが ctrl+c を押したときに生成される SIGINT シグナル。これは、端末からプログラムを終了する方法です。
- アラーム機能で設定されたタイマーがオフになると、SIGALRM が生成されます。
- プロセスが中止関数を呼び出すと、SIGABRT シグナルが生成されます。
- など
シグナルが発生すると、プロセスはそれをどう処理するかをカーネルに伝える必要があります。シグナルを破棄できる 3 つのオプションがあります:
<オール>すでに述べたように、SIGKILL と SIGSTOP の 2 つのシグナルは無視できません。これは、これら 2 つのシグナルが、root ユーザーまたはカーネルがあらゆる状況でプロセスを強制終了または停止する方法を提供するためです。これらのシグナルのデフォルトのアクションは、プロセスを終了することです。これらのシグナルはキャッチすることも無視することもできません。
プログラムの開始時に何が起こるか?
それはすべて、exec を呼び出すプロセスに依存します。プロセスが開始されると、すべてのシグナルのステータスは無視またはデフォルトのいずれかになります。 exec を呼び出すプロセスがシグナルを無視していない限り、発生する可能性が高いのは後者のオプションです。
任意のシグナルに対するアクションをデフォルト アクションに変更するのは、exec 関数のプロパティです。簡単に言えば、親がシグナルの発生時に呼び出されるシグナルキャッチ関数を持っている場合、その親が新しい子プロセスを実行する場合、この関数は新しいプロセスでは意味がないため、同じシグナルの処理がデフォルトに設定されます新しいプロセスで。
また、通常はバックグラウンドでプロセスを実行しているため、ユーザーが ctrl+c キーを押してバックグラウンド プロセスを終了させたくないため、プロセスを作成する目的が無効になるため、シェルは終了シグナルの処理を無視されるように設定します。バックグラウンドで実行します。
シグナル キャッチ関数を再入可能にする理由
すでに説明したように、シグナル処理のオプションの 1 つはシグナルをキャッチすることです。プロセス コードでは、これは、シグナルが発生したときにカーネルが呼び出す関数をカーネルに登録することによって行われます。覚えておくべきことの 1 つは、プロセスが登録する関数は再入可能でなければならないということです。
その理由を説明する前に、まずリエントラント関数とは何かを理解しましょう。再入可能関数とは、何らかの理由 (割り込みやシグナルなど) によって途中で実行を停止でき、その後、以前の呼び出しが実行を完了する前に安全に再入できる関数です。
問題に戻ると、関数 func() がシグナル発生時のコールバック用に登録されているとします。ここで、シグナルが発生したときに、この func() がすでに実行されていたとします。この関数はこのシグナルに対してコールバックされるため、このシグナルでの現在の実行はスケジューラによって停止され、この関数は (シグナルにより) 再度呼び出されます。
この問題は、この関数の実行が途中で停止されたときに、func() が一部のグローバル値またはデータ構造で動作し、一貫性のない状態のままである場合に発生する可能性があり、同じ関数への 2 回目の呼び出し (シグナルによる) によって、望ましくない結果が生じる可能性があります。
そのため、シグナル キャッチ関数は再入可能にする必要があると言えます。
プロセスにシグナルを送信する方法に関する実用的な例については、記事 send-signal-to-process および Linux fuser コマンドを参照してください。
スレッドとシグナル
前のセクションの 1 つで、シグナル処理には独自の複雑さが伴うことを既に確認しました (再入可能関数の使用など)。複雑さを増すために、通常、シグナル処理が非常に複雑になるマルチスレッド アプリケーションがあります。
すべてのスレッドには独自のプライベート シグナル マスク (配信可能なシグナルを定義するマスク) がありますが、シグナルの処理方法はアプリケーション内のすべてのスレッドで共有されます。これは、スレッドによって設定された特定のシグナルの処理が、他のスレッドによって簡単に覆される可能性があることを意味します。この場合、すべてのスレッドの配置メカニズムが変更されます。
たとえば、スレッド A は特定のシグナルを無視することを選択できますが、同じプロセス内のスレッド B は、コールバック関数をカーネルに登録することによって同じシグナルをキャッチすることを選択できます。この場合、スレッド A によるリクエストは、スレッド B のリクエストによって却下されます。
シグナルは、どのプロセスでも単一のスレッドにのみ配信されます。ハードウェア例外またはタイマーの期限切れ (イベントを引き起こしたスレッドに配信される) を除いて、すべてのシグナルは任意にプロセスに渡されます。
この欠点に対抗するために、使用できる pthread_sigmask() などのいくつかの posix API があります。
このシリーズの次の記事 (パート 2) では、プロセスでシグナルをキャッチする方法について説明し、コード スニペットを使用してシグナル処理の実際的な側面について説明します。