C言語でシグナルハンドラーを使用するにはどうすればよいですか?

How Use Signal Handlers C Language



この記事では、C言語を使用してLinuxでシグナルハンドラーを使用する方法を紹介します。ただし、最初にシグナルとは何か、プログラムで使用できるいくつかの一般的なシグナルを生成する方法について説明し、次にプログラムの実行中にプログラムがさまざまなシグナルを処理する方法について説明します。それでは、始めましょう。

信号

シグナルは、重要な状況が発生したことをプロセスまたはスレッドに通知するために生成されるイベントです。プロセスまたはスレッドがシグナルを受信すると、プロセスまたはスレッドはその実行を停止し、何らかのアクションを実行します。信号は、プロセス間通信に役立つ場合があります。







標準信号

信号はヘッダーファイルで定義されています signal.h マクロ定数として。信号名はSIGで始まり、信号の簡単な説明が続きます。したがって、すべての信号には一意の数値があります。プログラムでは、シグナル番号ではなく、常にシグナルの名前を使用する必要があります。その理由は、信号番号はシステムによって異なる場合がありますが、名前の意味は標準です。



マクロ NSIG 定義された信号の総数です。の値 NSIG は、定義された信号の総数より1つ大きいです(すべての信号番号は連続して割り当てられます)。



標準信号は次のとおりです。





信号名 説明
SIGHUP プロセスを切断します。 SIGHUP信号は、おそらくリモート接続が失われたか、電話を切ったために、ユーザーの端末の切断を報告するために使用されます。
シギント プロセスを中断します。ユーザーがINTR文字(通常はCtrl + C)を入力すると、SIGINTシグナルが送信されます。
SIGQUIT プロセスを終了します。ユーザーがQUIT文字(通常はCtrl + )を入力すると、SIGQUIT信号が送信されます。
密閉 違法な指示。ガベージ命令または特権命令を実行しようとすると、SIGILL信号が生成されます。また、SIGILLは、スタックがオーバーフローした場合、またはシステムでシグナルハンドラの実行に問題が発生した場合に生成される可能性があります。
SIGTRAP トレーストラップ。ブレークポイント命令およびその他のトラップ命令は、SIGTRAPシグナルを生成します。デバッガーはこの信号を使用します。
SIGABRT アボート。 SIGABRTシグナルは、abort()関数が呼び出されたときに生成されます。このシグナルは、プログラム自体によって検出され、abort()関数呼び出しによって報告されるエラーを示します。
SIGFPE 浮動小数点例外。致命的な算術エラーが発生すると、SIGFPE信号が生成されます。
SIGUSR1およびSIGUSR2 信号SIGUSR1およびSIGUSR2は、必要に応じて使用できます。単純なプロセス間通信のシグナルを受信するプログラムに、それらのシグナルハンドラーを作成すると便利です。

シグナルのデフォルトアクション

各シグナルには、次のいずれかのデフォルトのアクションがあります。

学期: プロセスは終了します。
芯: プロセスは終了し、コアダンプファイルを生成します。
Ign: プロセスは信号を無視します。
やめる: プロセスは停止します。
アカウント: プロセスは停止されないまま続行されます。



デフォルトのアクションは、ハンドラー関数を使用して変更できます。一部のシグナルのデフォルトアクションは変更できません。 SIGKILLSIGABRT シグナルのデフォルトのアクションを変更または無視することはできません。

信号処理

プロセスがシグナルを受信した場合、プロセスはその種類のシグナルに対するアクションを選択できます。プロセスは、シグナルを無視したり、ハンドラー関数を指定したり、その種類のシグナルのデフォルトのアクションを受け入れたりすることができます。

  • シグナルに指定されたアクションが無視された場合、シグナルはすぐに破棄されます。
  • プログラムは、次のような関数を使用してハンドラ関数を登録できます。 信号 また sigaction 。これはハンドラーと呼ばれ、シグナルをキャッチします。
  • シグナルが処理も無視もされていない場合、デフォルトのアクションが実行されます。

を使用して信号を処理できます 信号 また sigaction 関数。ここでは、最も単純な方法を確認します 信号() 関数は信号の処理に使用されます。

int信号(()。 ((intサイン 空所 ((*関数)。((int)。)。

NS 信号() を呼び出します 関数 プロセスがシグナルを受信した場合の機能 サイン 。 NS 信号() 関数へのポインタを返します 関数 成功した場合はエラーをerrnoに返し、それ以外の場合は-1を返します。

NS 関数 ポインタには次の3つの値を指定できます。

  1. SIG_DFL :システムデフォルト機能へのポインタです SIG_DFL() 、で宣言 NS ヘッダーファイル。シグナルのデフォルトアクションを実行するために使用されます。
  2. SIG_IGN :システム無視機能へのポインタです SIG_IGN() 、で宣言 NS ヘッダーファイル。
  3. ユーザー定義のハンドラー関数ポインター :ユーザー定義のハンドラー関数タイプは void(*)(int) 、は、戻り型がvoidであり、型intの1つの引数であることを意味します。

基本的なシグナルハンドラの例

#含む
#含む
#含む
空所sig_handler((intサイン)。{{

//ハンドラー関数の戻り値はvoidである必要があります
printf (('NS内部ハンドラー関数NS')。;
}

int主要(()。{{
信号((シギントsig_handler)。; //シグナルハンドラを登録します
にとって((int=1;;++)。{{ //無限ループ
printf (('%d:メイン関数の内部NS')。;
寝る((1)。; // 1秒間遅延
}
戻る 0;
}

Example1.cの出力のスクリーンショットでは、メイン関数で無限ループが実行されていることがわかります。ユーザーがCtrl + Cを入力すると、メイン関数の実行が停止し、シグナルのハンドラー関数が呼び出されます。ハンドラー関数の完了後、メイン関数の実行が再開されました。ユーザーがCtrl + と入力すると、プロセスは終了します。

信号を無視する例

#含む
#含む
#含む
int主要(()。{{
信号((シギントSIG_IGN)。; //シグナルを無視するためのシグナルハンドラを登録します

にとって((int=1;;++)。{{ //無限ループ
printf (('%d:メイン関数の内部NS')。;
寝る((1)。; // 1秒間遅延
}
戻る 0;
}

ここでハンドラー関数はレジスターです SIG_IGN() シグナルアクションを無視するための関数。したがって、ユーザーがCtrl + Cと入力すると、 シギント シグナルは生成されていますが、アクションは無視されます。

シグナルハンドラの再登録の例

#含む
#含む
#含む

空所sig_handler((intサイン)。{{
printf (('NS内部ハンドラー関数NS')。;
信号((シギントSIG_DFL)。; //デフォルトアクションのシグナルハンドラを再登録します
}

int主要(()。{{
信号((シギントsig_handler)。; //シグナルハンドラを登録します
にとって((int=1;;++)。{{ //無限ループ
printf (('%d:メイン関数の内部NS')。;
寝る((1)。; // 1秒間遅延
}
戻る 0;
}

Example3.cの出力のスクリーンショットでは、ユーザーが初めてCtrl + Cを入力したときに、ハンドラー関数が呼び出されたことがわかります。ハンドラー関数では、シグナルハンドラーは SIG_DFL シグナルのデフォルトアクション用。ユーザーが2回目にCtrl + Cを入力すると、プロセスは終了します。これは、のデフォルトのアクションです。 シギント 信号。

信号の送信:

プロセスは、それ自体または別のプロセスにシグナルを明示的に送信することもできます。 raise()およびkill()関数は、シグナルの送信に使用できます。両方の関数はsignal.hヘッダーファイルで宣言されています。

int 高める ((intサイン)。

シグナルの送信に使用されるraise()関数 サイン 呼び出しプロセス(それ自体)に。成功した場合はゼロを返し、失敗した場合はゼロ以外の値を返します。

int殺す((pid_t pid intサイン)。

シグナルの送信に使用されるkill関数 サイン によって指定されたプロセスまたはプロセスグループに pid

SIGUSR1シグナルハンドラーの例

#含む
#含む

空所sig_handler((intサイン)。{{
printf (('内部ハンドラー関数NS')。;
}

int主要(()。{{
信号((SIGUSR1sig_handler)。; //シグナルハンドラを登録します
printf (('主な機能の内部NS')。;
高める ((SIGUSR1)。;
printf (('主な機能の内部NS')。;
戻る 0;
}

ここで、プロセスは、raise()関数を使用してSIGUSR1シグナルを自身に送信します。

キルサンプルプログラムでレイズ

#含む
#含む
#含む
空所sig_handler((intサイン)。{{
printf (('内部ハンドラー関数NS')。;
}

int主要(()。{{
pid_t pid;
信号((SIGUSR1sig_handler)。; //シグナルハンドラを登録します
printf (('主な機能の内部NS')。;
pid=getpid(()。; //それ自体のプロセスID
殺す((pidSIGUSR1)。; // SIGUSR1をそれ自体に送信します
printf (('主な機能の内部NS')。;
戻る 0;
}

ここで、プロセスは送信します SIGUSR1 を使用してそれ自体に信号を送る 殺す() 関数。 getpid() それ自体のプロセスIDを取得するために使用されます。

次の例では、親プロセスと子プロセスがどのように通信(プロセス間通信)するかを確認します。 殺す() および信号機能。

信号による親子のコミュニケーション

#含む
#含む
#含む
#含む
空所sig_handler_parent((intサイン)。{{
printf (('親:子から応答信号を受信しましたNS')。;
}

空所sig_handler_child((intサイン)。{{
printf (('子:親からシグナルを受信しましたNS')。;
寝る((1)。;
殺す((getppid(()。SIGUSR1)。;
}

int主要(()。{{
pid_t pid;
もしも((((pid=フォーク(()。)。<0)。{{
printf (('フォークが失敗しましたNS')。;
出口 ((1)。;
}
/ *子プロセス* /
そうしないと もしも((pid==0)。{{
信号((SIGUSR1sig_handler_child)。; //シグナルハンドラを登録します
printf (('子:信号を待っていますNS')。;
一時停止(()。;
}
/ *親プロセス* /
そうしないと{{
信号((SIGUSR1sig_handler_parent)。; //シグナルハンドラを登録します
寝る((1)。;
printf (('親:子に信号を送信するNS')。;
殺す((pidSIGUSR1)。;
printf (('親:応答を待っていますNS')。;
一時停止(()。;
}
戻る 0;
}

ここ、 フォーク() 関数は子プロセスを作成し、子プロセスにゼロを返し、親プロセスに子プロセスIDを返します。そのため、親と子のプロセスを決定するためにpidがチェックされています。親プロセスでは、子プロセスがシグナルハンドラ関数を登録し、親からのシグナルを待つことができるように、1秒間スリープされます。 1秒後、親プロセスは送信します SIGUSR1 子プロセスへのシグナルを送信し、子からの応答シグナルを待ちます。子プロセスでは、最初に親からのシグナルを待機し、シグナルを受信するとハンドラー関数が呼び出されます。ハンドラー関数から、子プロセスは別のプロセスを送信します SIGUSR1 親への合図。ここ getppid() 関数は、親プロセスIDを取得するために使用されます。

結論

Linuxのシグナルは大きなトピックです。この記事では、非常に基本的な信号を処理する方法を説明しました。また、信号がどのように生成されるか、プロセスがそれ自体や他のプロセスに信号を送信する方法、プロセス間通信に信号を使用する方法についても理解しました。