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

A signalイベントの発生を示すプロセスへの通知です。信号はまた呼ばれますsoftware interrupt そして、その発生を知ることは予測できないので、それはまた、 asynchronous event

信号は番号または名前で指定できます。通常、信号名は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でない場合、シグナルシグナムの新しいアクションが行為からインストールされます。oldactがnullでない場合、前のアクションはoldactに保存されます。

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

Field 1 −sa_handlerまたはsa_sigactionのいずれかで言及されているハンドラー。

void (*sa_handler)(int);
void (*sa_sigaction)(int, siginfo_t *, void *);

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

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

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

Field 2 −ブロックする信号のセット。

int sa_mask;

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

Field 3 −特別なフラグ。

int sa_flags;

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

Field 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つの信号ハンドラーがあることを確認しました。複数のシグナルを処理する単一のハンドラーを持つことはできますか?答えは「はい」です。これをプログラムで考えてみましょう。

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

Step 1 −シグナルSIGINT(CTRL + C)またはSIGQUIT(CTRL + \)をキャッチまたは処理するためのハンドラー(handleSignals)を登録します

Step 2 −ユーザーがシグナルSIGQUITを生成した場合(killコマンドまたはCTRL + \を使用したキーボード制御のいずれかを介して)、ハンドラーは単にメッセージをreturnとして出力します。

Step 3 −ユーザーが最初にシグナルSIGINTを生成した場合(killコマンドまたはCTRL + Cを使用したキーボード制御のいずれかを使用)、次回からデフォルトのアクション(SIG_DFLを使用)を実行するようにシグナルを変更します。

Step 4 −ユーザーがシグナルSIGINTを2回生成すると、プログラムの終了であるデフォルトのアクションが実行されます。

/* 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

2番目の方法

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