シグナルに関する重要な事実:
- Linuxのシグナルは、プロセス間通信(およびスレッド間)のいくつかの手段の役割を果たします。
- 各プロセスにはシグナルのマスクがあります(受信を無視するシグナル)
- プロセスのような各スレッドには、独自のシグナルマスクがあります
- シグナルを受信すると(ブロックされていない場合)、プロセス/スレッドが中断され、制御がシグナルハンドラー関数に転送され、この関数がプロセス/スレッドの完了に至らない場合、制御はプロセス/スレッドが中断されたポイントに転送されます
- シグナルハンドラー関数を設定できますが、プロセスに対してのみです。 このハンドラは、このプロセスから生成されたスレッドごとに呼び出されます。
私は信号の理論については掘り下げません。なぜ、どこで、どこで。 まず、私は彼らと働くメカニズムに興味があります。 したがって、使用されるシグナルはSIGUSR1とSIGUSR2になります。これらは、ユーザーの完全な廃棄に与えられる唯一の2つのシグナルです。 また、信号のスレッド間相互作用にもっと注意を払おうとします。
行きましょう。
シグナルハンドラー機能
この関数は、プロセス(またはスレッド)が非ブロッキング信号を受信したときに呼び出されます。 デフォルトのハンドラーはプロセス(スレッド)を終了します。 しかし、関心のあるシグナルのハンドラーを定義できます。 シグナルハンドラを記述するときは非常に注意が必要です。これは単なるコールバック関数ではなく、現在の実行スレッドが準備作業なしで中断されるため、グローバルオブジェクトが矛盾した状態になる可能性があります。 著者自身がルールを知らないため、一連のルールを提供することを約束せず、 Kobologのアドバイスに従うこと (彼が私がそれを参照することを気にしないことを願っています)と、少なくともこのFAQの資料を勉強することをお勧めします。
void hdl(int sig) { exit = true; }
新しい信号プロセッサをインストールするための2つの機能があります。
sighandler_t signal(int signum, sighandler_t handler);
シグナル番号、関数ハンドラー(またはSIG_IGN(シグナルを無視)またはSIG_DFL(デフォルトハンドラー))へのポインターを受け取り、古いハンドラーを返します。 SIGKILLおよびSIGSTOPシグナルは「インターセプト」または無視できません。 次の理由により、この関数を使用しないことを強くお勧めします。
- 関数は、現在のハンドラーの実行中に他のシグナルの受信をブロックしません。中断され、新しいハンドラーが開始されます
- (ハンドラーを設定した)シグナルを最初に受信した後、そのハンドラーはSIG_DFLにリセットされます
機能にはこれらの欠点がない
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
シグナル番号も受け入れます(SIGKILLとSIGSTOPを除く)。 2番目の引数は信号の新しい説明で、3番目の値は古い値を返します。 struct sigaction構造には、次の興味深いフィールドがあります。
- sa_handler-シグナル関数のsighandler_tに類似
- sa_mask-ハンドラーの実行中にブロックされるシグナルのマスク。 +デフォルトでは、受信信号自体がブロックされます
- sa_flags-シグナルを処理するときに追加のアクションを設定できる
この関数の使用方法は非常に簡単です。
struct sigaction act; memset(&act, 0, sizeof(act)); act.sa_handler = hdl; sigset_t set; sigemptyset(&set); sigaddset(&set, SIGUSR1); sigaddset(&set, SIGUSR2); act.sa_mask = set; sigaction(SIGUSR1, &act, 0); sigaction(SIGUSR2, &act, 0);
ここでは、シグナルSIGUSR1およびSUGUSR2のハンドラーをインストールし、ハンドラーの実行中は同じシグナルをブロックする必要があることも示しました。
シグナルハンドラーにはあまり便利ではない瞬間が1つあり、プロセス全体と生成されたすべてのスレッドに一度にインストールされます。 各スレッドが独自のシグナルハンドラを設定する機能はありません。
ただし、シグナルがプロセスにアドレス指定されると、ハンドラーはメインスレッド(プロセスを表す)専用に呼び出されることを理解しておく必要があります。 シグナルがスレッドに宛てられている場合、ハンドラーはこのスレッドのコンテキストから呼び出されます。 例1を参照してください 。
シグナルブロッキング
プロセスの一部のシグナルをブロックするには、それらをこのプロセスのシグナルマスクに追加する必要があります。 これを行うには、関数を使用します
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
既存のシグナルマスクに新しいシグナル(SIG_BLOCK)を追加し、このマスクからシグナルの一部(SIG_UNBLOCK)を削除し、シグナルマスク(SIG_SETMASK)を完全に設定できます。
スレッド内でシグナルマスクを使用するには、関数を使用します
int pthread_sigmask(int how, const sigset_t *set, sigset_t *oset);
これにより、すべてを行うこともできますが、スレッドごとに個別に行います。
これらの関数を使用してSIGKILLまたはSIGSTOPシグナルをブロックすることはできません。 これを実行しようとしても無視されます。
シグウェイト
この関数を使用すると、目的のシグナル(またはシグナルマスクの1つ)が受信されるまで、プロセス(またはスレッド)を一時停止できます。 この関数の特徴は、シグナルを受信したときにシグナルハンドラー関数が呼び出されないことです。 例2を参照してください 。
信号送信
プロセスにシグナルを送信するには、2つの機能を使用できます。
int kill(pid_t pid, int sig); int raise(int sig);
最初から、すべてが明確です。 2番目はそれ自体にシグナルを送信するために必要であり、基本的にkill(getpid()、signal)と同等です。 getpid()関数は、現在のプロセスのPIDを返します。
単一のスレッドにシグナルを送信するには、関数を使用します
int pthread_kill(pthread_t thread, int sig);
信号使用例
上記で説明したすべてのことは、「信号を使用する理由」という質問には答えません。 今、私は信号の使用の実際の例を挙げたいと思います。
一部のデータを一部のデバイスで読み書きすることを想像してみてください。しかし、これはブロッキングにつながる可能性があります。 さて、たとえば、ソケットを操作する場合の読み取り。 またはパイプエントリかもしれません。 これを別のスレッドに入れて、メインの作業を妨げないようにすることができます。 しかし、アプリケーションを完了する必要がある場合はどうすればよいでしょうか? ブロッキングIO操作を正しく中断する方法 タイムアウトを設定できますが、これは良い解決策ではありません。 これには、より便利な手段があります:pselectおよびppoll関数。 それらの違いは、使いやすさだけで、動作は同じです。 まず第一に、これらの機能はIO(選択/ポーリング)との多重化作業に必要です。 関数の先頭にある接頭辞「p」は、この関数が信号によって正しく中断される可能性があることを示しています。
したがって、要件を定式化します。
(UDPを簡単にするために)ソケットを開き、ストリームで読み取り操作を実行するアプリケーションを開発する必要があります。 このアプリケーションは、ユーザーの要求に応じて遅延なく正しく完了する必要があります。
スレッド関数は次のようになります
void* blocking_read(void* arg) { if(stop) { // , ? std::cout << "Thread was aborted\n"; pthread_exit((void *)0); } // SIGINT sigset_t set, orig; sigemptyset(&set); sigaddset(&set, SIGINT); sigemptyset(&orig); pthread_sigmask(SIG_BLOCK, &set, &orig); if(stop) { // // std::cout << "Thread was aborted\n"; pthread_sigmask(SIG_SETMASK, &orig, 0); pthread_exit((void *)0); } // SIGINT std::cout << "Start thread to blocking read\n"; // ... // , , ppoll ppoll((struct pollfd*) &clients, 1, NULL, &orig); if(stop) { // std::cout << "Thread was aborted\n"; close(sockfd); pthread_sigmask(SIG_SETMASK, &orig, 0); // SIGINT } // , . // " " close(sockfd); pthread_exit((void *)0); }
stopは、ハンドラーによってtrueに設定されるグローバルブールフラグであり、スレッドに終了するように指示します。
作業のロジックは次のとおりです。
- スレッドが開始されている間、彼らはまだそれを完了したくないことを確認します
- 最終信号をブロックする
- 彼らが私たちをブロックしている間、彼らは私たちを完成させたくないことを確認します
- ppollを呼び出し、最後のパラメーターとして、シグナルが期待されるシグナルマスクを渡します
- ppollを終了した後、それらが完了のシグナルによるものではないことを確認します
主な機能は次のとおりです
int main() { ... struct sigaction act; memset(&act, 0, sizeof(act)); act.sa_handler = hdl; sigemptyset(&act.sa_mask); sigaddset(&act.sa_mask, SIGINT); sigaction(SIGINT, &act, 0); ... pthread_kill(th1, SIGINT); ... }
SIGINTのハンドラーをインストールし、子スレッドを完了する必要がある場合、このシグナルを送信します。
完全なリストについては、 例3を参照してください 。
私の意見では、この方法の欠点は、複数のスレッドの場合、それらを一度に完了することしかできないことです。 各スレッドに独自のシグナルハンドラを設定する方法はありません。 したがって、信号を介した本格的なスレッド間相互作用を実現する方法はありません。 Linuxが提供しない方法。
PS。 PasteBinサービスにソースコードを配置しました(リンクは提供しません。そうしないと、広告で考慮されます)。
PPS たくさんのエラーをおaびします。 言語、私の弱点。 それらを修正するのを手伝ったすべての人に感謝します。
この記事は、シグナルの操作に関する完全な(そして深い)説明のふりをするものではなく、主に、現時点まで「シグナル」の概念に出会っていない人を対象としています。 信号の動作をより深く理解するために、著者は、より有能なソースに連絡し、コメントの建設的な批判に精通することをお勧めします。