POSIXシグナル

シグナルはプロセスに対するイベント発生の通知。ソフトウェア割り込みとも呼ばれる。シグナルは普通、非同期に発生する。

シグナルは、

  • あるプロセスから別のプロセス(自分自身)へ あるいは、
  • カーネルからプロセスへ
    送ることができる。

    シグナルには、処理配備(アクション)が伴う。3種類の処理配備がある。

    1.シグナルハンドラにより、シグナルを捕捉する。
    SIGKILLとSIGSTOPは捕捉できない。

    シグナルハンドラの関数プロトタイプ
    void handler(int signo);


    2.シグナルを無視する
    シグナルの処理配備をSIG_IGNに設定することにより、そのシグナルを無視するように設定できる。SIGKILLとSIGSTOPは無視できない。

    3.デフォルトの動作に設定する
    シグナル処理配備をSIG_DFLに設定することにより、そのシグナルに対するデフォルトの動作を行うように設定できる。SIGCHLDとSIGURGは、デフォルト動作がシグナル無視となっている。


    シグナルの処理配備を設定するためには、sigaction関数を呼び出す。

    typedef void Sigfunc(int);
    
    Sigfunc*
    signal(int signo, Sigfunc *func)
    {
      struct sigaction act, oact;
      act.sa_handler = func;
      sigemptyset(&act.sa_mask);
      act.sa_flags = 0;
      if (signo == SIGALRM) {
        act.sa_flags |= SA_RESTART;
      }
    
      if (sigaction(signo, &act, &oact) < 0)
        return SIG_ERR;
      return oact.sa_handler;
    }

  • ハンドラの設定
    singaction構造体のsa_handlerメンバにfunc引数を設定する。

  • ハンドラ用シグナルマスクの設定
    シグナルハンドラが呼び出されている間ブロックされるシグナルの集合を指定できる。ブロックされたシグナルは、プロセスに配送されなくなる。Posixでは、捕捉されたシグナルは、対応するハンドラが呼び出されている間ブロックされることを保証している。


  • オプショナルフラグ SA_RESTART
    sigaction構造体のSA_RESTARTが設定されていると、シグナルによって割り込まれたシステムコールを、カーネルが自動的に再実行する。


    Posix適合システムにおけるシグナル処理について

  • 一旦インストールされたシグナルハンドラは、インストールされたままになる。
  • シグナルハンドラの実行中は、同じシグナルの配送はブロックされる。さらに、sigactionを用いたシグナルハンドラのインストール時にsa_maskで指定されたシグナルの集合に含まれるシグナルもブロックされる。

  • シグナルがブロックされている間に複数回発生したシグナルは、ブロックが解除されても普通は1度しかよばれない。つまり、普通のUnixシグナルはキューイングされない。(Posixの実時間標準である1003.1bでは、シグナルのキューイングを行う信頼性のあるシグナルを定義している)

  • sigprocmask関数を用いると、あるシグナルの集合に対するブロックとブロックの解除を選択的に操作することができる。
    この機能を用いるとコードの危険領域(実行中にシグナルの捕捉が起きてはならない部分を保護することが可能になる)



    SIGCHLDシグナルの処理
    ゾンビ状態の目的は、子プロセスのリソースの利用状況(プロセスID、終了状態、CPU時間、メモリ使用量など)を親プロセスがあとで取得できるようにすること。
    プロセスが終了した際、そのプロセスにゾンビ状態になっている子プロセスがあれば、これらの子プロセスの親プロセスIDはすべて1(init)に付け替えられ、initは子プロセスを引き継いで終了処理を行う(子プロセスに対してwaitを実行し、ゾンビプロセスを消滅させる)。psを実行したときに、ゾンビプロセスは、defunctと表示される。


  • ゾンビの処理
    SIGCHLDシグナルを捕捉sるうシグナルハンドラを設定し、このシグナルハンドラ中からwaitを呼び出す。

    printfのような標準入出力関数をシグナルハンドラから呼び出すことは、良くない。

    //waitを呼び出すシグナルハンドラ
    void sig_child(int signo)
    {
      pid_t pid;
      int stat;
      
      pid = wait(&stat);
      printf("Child %d terminated\n", pid);/*これは良くないが。表示して分かりやすくため*/
      return;
    }


    割り込まれたシステムコールの処理
    プロセスが遅いシステムコール(永久のブロックする可能性のあるシステムコール)でブロックして、かつプロセスによるシグナルの捕捉が起き、かつシグナルハンドラから制御が戻った際に、そのシステムコールがEINTRエラーを返す可能性がある。よって、遅いシステムコールがEINTRを返す場合に備えておかなければならない。

     
    //acceptへの割り込みへの対処の例 
    for (;;) {
      clilen = sizeof(cliaddr);
      if (connfd = accept(listenfd, (struct sockaddr*)&cliaddr, &clilen) < 0) {
        if (errno == EINTR) 
          continue;
        else
          exit(1);
      }
    }


    システムコールが割り込まれたことを検出し、
    単にシステムコールを再実行している。



    参考文献

  • UNIXネットワークプログラミング スティーブンズ