Inter-process-communication-signals

提供:Dev Guides
移動先:案内検索

プロセス間通信-シグナル

*signal* は、イベントの発生を示すプロセスへの通知です。 信号は「ソフトウェア割り込み」とも呼ばれ、その発生を予測することは予測できないため、「非同期イベント」とも呼ばれます。

シグナルは番号または名前で指定できます。通常、シグナル名はSIGで始まります。 使用可能な信号は、コマンドkill –l(信号名をリストするためのl)で確認できます。これは次のとおりです-

シグナル

シグナルが発生するたび(プログラムまたはシステムで生成されたシグナル)、デフォルトのアクションが実行されます。 デフォルトのアクションを実行したくないが、信号の受信時に独自のアクションを実行したい場合はどうなりますか? これはすべての信号で可能ですか? はい、信号を処理することはできますが、すべての信号を処理することはできません。 信号を無視したい場合、これは可能ですか? はい、信号を無視することは可能です。 シグナルを無視することは、デフォルトのアクションの実行もシグナルの処理も行わないことを意味します。 ほとんどすべての信号を無視または処理できます。 無視または処理/捕捉できないシグナルは、SIGSTOPおよびSIGKILLです。

要約すると、信号に対して実行されるアクションは次のとおりです-

  • デフォルトのアクション
  • 信号を処理する
  • 信号を無視する

説明したように、シグナルはデフォルトアクションの実行を変更して処理できます。 シグナル処理は、システムコール、signal()およびsigaction()の2つの方法のいずれかで実行できます。

#include <signal.h>

typedef void (*sighandler_t) (int);
sighandler_t signal(int signum, sighandler_t handler);

システムコールsignal()は、signumで述べられているように、シグナルの生成時に登録済みハンドラーを呼び出します。 ハンドラーは、SIG_IGN(シグナルの無視)、SIG_DFL(シグナルをデフォルトのメカニズムに戻す)、またはユーザー定義のシグナルハンドラーまたは関数アドレスのいずれかです。

このシステムコールは、成功時に整数引数を取り、戻り値を持たない関数のアドレスを返します。 この呼び出しは、エラーの場合にSIG_ERRを返します。

signal()を使用すると、ユーザーが登録した各シグナルハンドラーを呼び出すことができますが、ブロックする必要のあるシグナルのマスキング、シグナルの動作の変更、その他の機能などの微調整はできません。 これは、sigaction()システムコールを使用して可能です。

#include <signal.h>

int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact)

このシステムコールは、シグナルアクションを検査または変更するために使用されます。 行為がnullでない場合、信号signumの新しい行為が行為からインストールされます。 oldactがnullでない場合、以前のアクションはoldactに保存されます。

sigaction構造には次のフィールドが含まれています-

  • フィールド1 *-sa_handlerまたはsa_sigactionで言及されたハンドラー。
void (*sa_handler)(int);
void (*sa_sigaction)(int, siginfo_t *, void *);

sa_handlerのハンドラーは、signumに基づいて実行されるアクションを指定し、SIG_DFLはデフォルトのアクションを示し、SIG_IGNはシグナルまたはシグナル処理関数へのポインターを無視します。

sa_sigactionのハンドラーは、最初の引数としてシグナル番号、2番目の引数としてsiginfo_t構造体へのポインター、3番目の引数としてユーザーコンテキストへのポインター(詳細についてはgetcontext()またはsetcontext()を確認)を指定します。

構造体siginfo_tには、配信されるシグナル番号、シグナル値、プロセスID、送信プロセスの実際のユーザーIDなどのシグナル情報が含まれます。

  • フィールド2 *-ブロックする信号のセット。
int sa_mask;

この変数は、シグナルハンドラーの実行中にブロックされるべきシグナルのマスクを指定します。

  • フィールド3 *-特別なフラグ。
int sa_flags;

このフィールドは、信号の動作を変更するフラグのセットを指定します。

  • フィールド4 *-ハンドラーを復元します。
void (*sa_restorer) (void);

このシステムコールは、成功すると0を返し、失敗すると-1を返します。

いくつかのサンプルプログラムを考えてみましょう。

まず、例外を生成するサンプルプログラムから始めましょう。 このプログラムでは、システムが例外を生成するゼロ除算を実行しようとしています。

/*signal_fpe.c*/
#include<stdio.h>

int main() {
   int result;
   int v1, v2;
   v1 = 121;
   v2 = 0;
   result = v1/v2;
   printf("Result of Divide by Zero is %d\n", result);
   return 0;
}

コンパイルおよび実行手順

Floating point exception (core dumped)

したがって、算術演算を実行しようとすると、システムはコアダンプを伴う浮動小数点例外を生成します。これは、信号のデフォルトアクションです。

次に、signal()システムコールを使用してこの特定の信号を処理するようにコードを変更しましょう。

/*signal_fpe_handler.c*/
#include<stdio.h>
#include<signal.h>
#include<stdlib.h>

void handler_dividebyzero(int signum);

int main() {
   int result;
   int v1, v2;
   void (*sigHandlerReturn)(int);
   sigHandlerReturn = signal(SIGFPE, handler_dividebyzero);
   if (sigHandlerReturn == SIG_ERR) {
      perror("Signal Error: ");
      return 1;
   }
   v1 = 121;
   v2 = 0;
   result = v1/v2;
   printf("Result of Divide by Zero is %d\n", result);
   return 0;
}

void handler_dividebyzero(int signum) {
   if (signum == SIGFPE) {
      printf("Received SIGFPE, Divide by Zero Exception\n");
      exit (0);
   }
   else
      printf("Received %d Signal\n", signum);
      return;
}

コンパイルおよび実行手順

Received SIGFPE, Divide by Zero Exception

説明したように、信号はシステムによって生成され(ゼロ除算などの特定の操作を実行すると)、ユーザーはプログラムで信号を生成することもできます。 プログラムで信号を生成する場合は、ライブラリ関数raise()を使用します。

次に、信号の処理と無視を示す別のプログラムを取り上げます。

raise()を使用してシグナルを発生させたと仮定すると、その後はどうなりますか? シグナルを上げた後、現在のプロセスの実行が停止します。 次に、停止したプロセスはどうなりますか? 2つのシナリオがあります。まず、必要なときに実行を継続します。 次に、プロセスを(killコマンドで)終了します。

停止したプロセスの実行を継続するには、SIGCONTをその特定のプロセスに送信します。 fg(フォアグラウンド)またはbg(バックグラウンド)コマンドを発行して、実行を継続することもできます。 ここでは、コマンドは最後のプロセスの実行のみを再開します。 複数のプロセスが停止した場合、最後のプロセスのみが再開されます。 以前に停止したプロセスを再開する場合は、ジョブ番号とともにジョブを(fg/bgを使用して)再開します。

次のプログラムは、raise()関数を使用してシグナルSIGSTOPを発生させるために使用されます。 シグナルSIGSTOPは、CTRL + Z(Control + Z)キーを押すことでも生成できます。 このシグナルを発行した後、プログラムは実行を停止します。 シグナル(SIGCONT)を送信して、実行を継続します。

次の例では、コマンドfgで停止したプロセスを再開しています。

/*signal_raising.c*/
#include<stdio.h>
#include<signal.h>
#include<stdlib.h>

int main() {
   printf("Testing SIGSTOP\n");
   raise(SIGSTOP);
   return 0;
}

コンパイルおよび実行手順

Testing SIGSTOP
[1]+ Stopped ./a.out
./a.out

ここで、別の端末からSIGCONTを発行して、停止したプロセスの実行を継続するように前のプログラムを強化します。

/*signal_stop_continue.c*/
#include<stdio.h>
#include<signal.h>
#include <sys/types.h>
#include <unistd.h>

void handler_sigtstp(int signum);

int main() {
   pid_t pid;
   printf("Testing SIGSTOP\n");
   pid = getpid();
   printf("Open Another Terminal and issue following command\n");
   printf("kill -SIGCONT %d or kill -CONT %d or kill -18 %d\n", pid, pid, pid);
   raise(SIGSTOP);
   printf("Received signal SIGCONT\n");
   return 0;
}

コンパイルおよび実行手順

Testing SIGSTOP
Open Another Terminal and issue following command
kill -SIGCONT 30379 or kill -CONT 30379 or kill -18 30379
[1]+ Stopped ./a.out

Received signal SIGCONT
[1]+ Done ./a.out

別のターミナルで

kill -SIGCONT 30379

これまで、システムによって生成された信号を処理するプログラムを見てきました。 次に、プログラムで生成されたシグナルを見てみましょう(raise()関数またはkillコマンドを使用)。 このプログラムは、シグナルSIGTSTP(端末停止)を生成します。デフォルトのアクションは、実行を停止することです。 ただし、現在はデフォルトのアクションではなくシグナルを処理しているため、定義されたハンドラーに到達します。 この場合、メッセージを出力して終了するだけです。

/*signal_raising_handling.c*/
#include<stdio.h>
#include<signal.h>
#include<stdlib.h>

void handler_sigtstp(int signum);

int main() {
   void (*sigHandlerReturn)(int);
   sigHandlerReturn = signal(SIGTSTP, handler_sigtstp);
   if (sigHandlerReturn == SIG_ERR) {
      perror("Signal Error: ");
      return 1;
   }
   printf("Testing SIGTSTP\n");
   raise(SIGTSTP);
   return 0;
}

void handler_sigtstp(int signum) {
   if (signum == SIGTSTP) {
      printf("Received SIGTSTP\n");
      exit(0);
   }
   else
      printf("Received %d Signal\n", signum);
      return;
}

コンパイルおよび実行手順

Testing SIGTSTP
Received SIGTSTP

デフォルトのアクションを実行したり、シグナルを処理したりするインスタンスを見てきました。 今、信号を無視する時です。 ここで、このサンプルプログラムでは、SIG_IGNを介して無視するシグナルSIGTSTPを登録し、シグナルSIGTSTP(ターミナルストップ)を上げています。 無視されるシグナルSIGTSTPが生成されているとき。

/*signal_raising_ignoring.c*/
#include<stdio.h>
#include<signal.h>
#include<stdlib.h>

void handler_sigtstp(int signum);

int main() {
   void (*sigHandlerReturn)(int);
   sigHandlerReturn = signal(SIGTSTP, SIG_IGN);
   if (sigHandlerReturn == SIG_ERR) {
      perror("Signal Error: ");
      return 1;
   }
   printf("Testing SIGTSTP\n");
   raise(SIGTSTP);
   printf("Signal SIGTSTP is ignored\n");
   return 0;
}

コンパイルおよび実行手順

Testing SIGTSTP
Signal SIGTSTP is ignored

これまでのところ、1つのシグナルを処理するシグナルハンドラーが1つあることを確認しました。 単一のハンドラーで複数の信号を処理できますか? 答えはイエスです。 これをプログラムで考えてみましょう。

次のプログラムは次のことを行います-

  • ステップ1 *-シグナルSIGINT(CTRL + C)またはSIGQUIT(CTRL + \)をキャッチまたは処理するハンドラー(handleSignals)を登録します。
  • ステップ2 *-ユーザーがシグナルSIGQUITを生成する場合(killコマンドまたはCTRL + \を使用したキーボード制御のいずれかを介して)、ハンドラーは単に戻り値としてメッセージを出力します。
  • ステップ3 *-ユーザーがシグナルSIGINT(killコマンドまたはCTRL + Cを使用したキーボード制御)を初めて生成する場合、次回からデフォルトアクション(SIG_DFLを使用)を実行するようにシグナルを変更します。
  • ステップ4 *-ユーザーが2回目にシグナルSIGINTを生成すると、デフォルトのアクション(プログラムの終了)が実行されます。
/*Filename: sigHandler.c*/
#include<stdio.h>
#include<unistd.h>
#include<signal.h>

void handleSignals(int signum);

int main(void) {
   void (*sigHandlerInterrupt)(int);
   void (*sigHandlerQuit)(int);
   void (*sigHandlerReturn)(int);
   sigHandlerInterrupt = sigHandlerQuit = handleSignals;
   sigHandlerReturn = signal(SIGINT, sigHandlerInterrupt);
   if (sigHandlerReturn == SIG_ERR) {
      perror("signal error: ");
      return 1;
   }
   sigHandlerReturn = signal(SIGQUIT, sigHandlerQuit);

   if (sigHandlerReturn == SIG_ERR) {
      perror("signal error: ");
      return 1;
   }
   while (1) {
      printf("\nTo terminate this program, perform the following: \n");
      printf("1. Open another terminal\n");
      printf("2. Issue command: kill %d or issue CTRL+C 2 times (second time it terminates)\n", getpid());
      sleep(10);
   }
   return 0;
}

void handleSignals(int signum) {
   switch(signum) {
      case SIGINT:
      printf("\nYou pressed CTRL+C \n");
      printf("Now reverting SIGINT signal to default action\n");
      signal(SIGINT, SIG_DFL);
      break;
      case SIGQUIT:
      printf("\nYou pressed CTRL+\\ \n");
      break;
      default:
      printf("\nReceived signal number %d\n", signum);
      break;
   }
   return;
}

コンパイルおよび実行手順

To terminate this program, perform the following:
1. Open another terminal
2. Issue command: kill 74 or issue CTRL+C 2 times (second time it terminates)
^C
You pressed CTRL+C
Now reverting SIGINT signal to default action
          
To terminate this program, perform the following:
1. Open another terminal
2. Issue command: kill 74 or issue CTRL+C 2 times (second time it terminates)
^\You pressed CTRL+\
To terminate this program, perform the following:
1. Open another terminal
2. Issue command: kill 120
Terminated

別のターミナル

kill 71

第二の方法

To terminate this program, perform the following:
1. Open another terminal
2. Issue command: kill 71 or issue CTRL+C 2 times (second time it terminates)
^C
You pressed CTRL+C
Now reverting SIGINT signal to default action

To terminate this program, perform the following:
1. Open another terminal
2. Issue command: kill 71 or issue CTRL+C 2 times (second time it terminates)
^C

シグナルを処理するには、signal()またはsigaction()の2つのシステムコールがあることを知っています。 これまでsignal()システムコールで見てきましたが、今はsigaction()システムコールの時間です。 次のようにsigaction()を使用して実行するように上記のプログラムを変更しましょう-

/*Filename: sigHandlerSigAction.c*/
#include<stdio.h>
#include<unistd.h>
#include<signal.h>

void handleSignals(int signum);

int main(void) {
   void (*sigHandlerReturn)(int);
   struct sigaction mysigaction;
   mysigaction.sa_handler = handleSignals;
   sigemptyset(&mysigaction.sa_mask);
   mysigaction.sa_flags = 0;
   sigaction(SIGINT, &mysigaction, NULL);

   if (mysigaction.sa_handler == SIG_ERR) {
      perror("signal error: ");
      return 1;
   }
   mysigaction.sa_handler = handleSignals;
   sigemptyset(&mysigaction.sa_mask);
   mysigaction.sa_flags = 0;
   sigaction(SIGQUIT, &mysigaction, NULL);

   if (mysigaction.sa_handler == SIG_ERR) {
      perror("signal error: ");
      return 1;
   }
   while (-1) {
      printf("\nTo terminate this program, perform either of the following: \n");
      printf("1. Open another terminal and issue command: kill %d\n", getpid());
      printf("2. Issue CTRL+C 2 times (second time it terminates)\n");
      sleep(10);
   }
   return 0;
}

void handleSignals(int signum) {
   switch(signum) {
      case SIGINT:
      printf("\nYou have entered CTRL+C \n");
      printf("Now reverting SIGINT signal to perform default action\n");
      signal(SIGINT, SIG_DFL);
      break;
      case SIGQUIT:
      printf("\nYou have entered CTRL+\\ \n");
      break;
      default:
      printf("\nReceived signal number %d\n", signum);
      break;
   }
   return;
}

コンパイルと実行のプロセスを見てみましょう。 実行プロセスで、CTRL + Cを2回発行し、残りのチェック/方法(上記)を確認して、このプログラムでも試してみます。

コンパイルおよび実行手順

To terminate this program, perform either of the following:
1. Open another terminal and issue command: kill 3199
2. Issue CTRL+C 2 times (second time it terminates)
^C
You have entered CTRL+C
Now reverting SIGINT signal to perform default action
To terminate this program, perform either of the following:
1. Open another terminal and issue command: kill 3199
2. Issue CTRL+C 2 times (second time it terminates)
^C