Linux : シグナルを使ったサンプルプログラム

LinuxのC/C++プログラムで、シグナル(割り込み)を使ったサンプルコードの備忘録

シグナルを送信する主要コマンド

シグナルはkillコマンドで送信できる。killという名前からプロセスを終了させるイメージが強いが、実際には「特定のプロセスにシグナルを送信する」コマンドである。シグナルも幾つか種類があり、オプションで使い分けることができる。

killコマンドで指定するプロセスIDはpsコマンドで列挙し特定のプロセスIDを見つける。以下の場合、testモジュールのプロセスがID84224で動作していることが分かる。

# ユーザプロセスIDの確認
> ps u
USER    PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
hoge   69990  0.0  0.1  22788  5704 pts/4    Ss   05:34   0:00 bash
hoge   84224  0.0  0.0  13268  1652 pts/4    S+   07:08   0:00 ./test
hoge   84228  0.0  0.0  37096  3268 pts/4    R+   07:07   0:00 ps u

強制終了させるシグナルを送信する

killコマンドの既定の動作になる。

# プロセスの終了
> kill [id]
> kill -TERM [id]

親プロセスが終了したときに送信される

例えば、ターミナルからプロセスを起動し、ターミナルが終了したタイミングでコールされる。バックグラウンドで、ターミナルが終了しても動作し続けるようにするnohupコマンドもこのあたりの仕組みと関係がある。一般的にサーバーアプリケーションで良く利用される。

# 割り込みシグナル
> kill -HUP [id]
> kill -1 [id]

割り込みシグナルを送信する

Ctrl+Cもこの処理に相当する。

# 割り込みシグナル
> kill -INT [id]
> kill -2 [id]
> Ctrl + C

強制Killシグナル

引数なしの既定のシグナルもここになる。

# 割り込みシグナル
> kill -KILL [id]
> kill -9 [id]
> kill [id]

ほかにも幾つかあるが、省略

シグナルを使ったサンプルプログラム

シグナルを受信したときにコールする関数はsignal関数を使って登録する。関数の型は、void hoge(int a)というインタフェースであれば登録することができる。

以下は、100msごとに無限ループしてカウンタを表示し、HUPとINTのシグナルを受信したら、プログラムを終了するサンプルになる。

#include <iostream>
#include <signal.h>
#include <stdlib.h>
#include <unistd.h>

volatile sig_atomic_t sigflg = 0;

static void sighup(int sig)
{
    std::cout<<"[sighup] = "<<sig<<std::endl;
    exit(sig);
}
static void sigint(int sig)
{
    std::cout<<"[sigint] = "<<sig<<std::endl;
    exit(sig);
}

int main(int argc, char *argv[])
{
    int x = 0;
    if( SIG_ERR == signal(SIGHUP,sighup) ){
        std::cout<<"error"<<std::endl;
        return 1;
    }
    if( SIG_ERR == signal(SIGINT,sigint) ){
        std::cout<<"error2"<<std::endl;
        return 1;
    }
    while(true){
        usleep(100000);
        std::cout<<x<<std::endl;
        x ++;
    }
    return 0;
}

ビルドした後、ターミナルを2つ用意して片方でプログラムを実行する。

> gcc -o sample sample.cpp -lstdc++
> ./sample
0
1
2
3
4
5
:

もう片方のターミナルで先ほど起動したプロセスを確認して、シグナルを送信してみる。

> ps u
USER        PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
hoge   84402  0.0  0.0  13268  1628 pts/4    S+   07:34   0:00 ./sample

> kill -INT 84402

ループしていたプログラムは、シグナルを受信してsigint関数をコール。sigint内でexit()してプロセスが終了している。

863
864
865
866
867
868
[sigint] = 2
>

ちなみに-HUPを引数にした場合は、[sighup] = 1で終了する。

シグナル関数でexitしないとどうなる?

INTやHUPシグナルを受けるコールバック関数を定義し、exit()しないようにすると、Ctrl+Cでも停止しないプログラムになる。

起動したターミナルを閉じたときは、そのターミナルの振る舞いに依存する。Ubuntuのターミナルの場合は、実行中のプロセスを停止するが、git bashでリモートログインしたときはプロセスが削除されずに残った。このプロセスを削除するときは、強制停止オプションをつけて停止する。

> kill -KILL [id]

singalは推奨されてないので、sigactionを通常は使う

どうもsignalは推奨されてないようなので、sigactionの使い方も知っておく必要がありそう。

Man page of SIGACTION

早速サンプルを作成してみる。

// gcc -o sigact sigact.cpp -lstdc++

#include <iostream>
#include <stdio.h>
#include <string.h>
#include <signal.h>
#include <stdlib.h>
#include <unistd.h>

volatile sig_atomic_t sigflg = 0;

void handler(int sig, siginfo_t *info, void *ctx);

int main(int argc, char *argv[])
{
    struct sigaction act;
    act.sa_sigaction = handler;
    act.sa_flags = SA_SIGINFO;

    if( sigaction(SIGINT, &act, NULL) < 0 ){
        std::cout<<"sigaction error."<<std::endl;
        exit(1);
    }

    int x = 0;
    //sigflg!=0でループを抜ける
    while(!sigflg){
        usleep(100000);
        std::cout<<x<<std::endl;
        x ++;
    }

    return 0;
}

void handler(int sig, siginfo_t *info, void *ctx){
    std::cout<<"sig = "<<sig<<std::endl;
    std::cout<<"sigaction.si_signo = "<<info->si_signo<<std::endl;
    std::cout<<"sigaction.si_code = "<<info->si_code<<std::endl;
    std::cout<<"sigaction.si_pid = "<<info->si_pid<<std::endl;
    std::cout<<"sigaction.si_uid = "<<info->si_uid<<std::endl;
    std::cout<<"sigaction.si_status = "<<info->si_status<<std::endl;
    std::cout<<"sigaction.si_fd = "<<info->si_fd<<std::endl;
    sigflg = 1;
}

実行して、Ctrl+Cで停止してみる。

> ./sigact
:
12
13
14
15
16
17
18
19
20
21
^Csig = 2
sigaction.si_signo = 2
sigaction.si_code = 128
sigaction.si_pid = 0
sigaction.si_uid = 0
sigaction.si_status = 174493056
sigaction.si_fd = 174493056
22
>

今後はこちらを利用したほうが良い。