シグナルをハンドルする(1) signal/sigaction

# 導入

シグナルシリーズ第1弾です。目次は [こちら](https://qiita.com/AAsada/items/13b00ad76ca757d762d6)

シグナルをハンドルするといえば、signalだよね、ということで。

# 動作確認環境

Host OS: Windows10 Pro 20H2
Guest OS: WSL2 Ubuntu-20.04
Compiler: g++ (Ubuntu 9.3.0-17ubuntu1~20.04) 9.3.0

# 注意

後から書いても私なら読まないので先に注意事項を記載します。
以下に代表的な注意事項を示しますが、他にもあります。(JPCERTのページを参照)

シグナルハンドラからは非同期安全な関数のみをコールしてください。
JPCERT CC: [SIG30-C](https://www.jpcert.or.jp/sc-rules/c-sig30-c.html)

共有オブジェクトにはアクセスしない(volatileなsig_atomic_t型変数は可)
JPCERT CC: [SIG31-C](https://www.jpcert.or.jp/sc-rules/c-sig31-c.html)

また、signal() は非推奨です。
移植性がないのでsigactionを使用すること。詳細はman pageを見てください。

# signal

みんな大好き非推奨のsignalです。

man page: [signal(2)](https://linuxjm.osdn.jp/html/LDP_man-pages/man2/signal.2.html)

signalの第2引数に signal_handlerではなく SIG_DFL を指定すればデフォルト動作、 SIG_IGN を指定すれば無視します。

<details><summary>動作確認コード</summary><div>

```c++:signal.cpp
#include <stdio.h>
#include <stdlib.h>
#include <signal.h> // signal
#include <unistd.h> // usleep

#define LOG(STR, ...) printf("%s " STR "\n", __func__, ## __VA_ARGS__)

// ハンドラ
volatile sig_atomic_t g_signal = 0;
void signal_handler(int signo) { g_signal ++; }

int main() {
    LOG("start");

    // ハンドラを登録する
    if( signal(SIGINT, signal_handler) == SIG_ERR ) {
        LOG("err signal");
        return 1;
    }

    // シグナル待ち
    volatile sig_atomic_t old = g_signal;
    int cnt = 0;
    char mark[5] = {'|', '/', '-', '\\'};
    while(true) {
        // ハンドルチェック
        if( g_signal > old ) {
            LOG("signal %d", g_signal);
            old = g_signal;
            if( g_signal >= 3 ) {
                break; //3回目をハンドルしたら終了
            }
        }

        // アプリが止まっていないことの確認用
        printf("%c\r", mark[(cnt + 1) % 4]); fflush(stdout); cnt++;
        usleep(200 * 1000);
    }
    LOG("end");
    return 0;
}
```

</div></details>

# sigaction

こっちが推奨です。
man page: [sigaction(2)](https://linuxjm.osdn.jp/html/LDP_man-pages/man2/sigaction.2.html)

## (1) signal互換

まずは、signalと互換の動作です。
sa.sa_flags に SA_SIGINFO を指定しなければ sa.sa_handler を使います。

<details><summary>動作確認コード</summary><div>

```c++:signalaction_1.cpp
#include <stdio.h>
#include <stdlib.h>
#include <signal.h> // sigaction
#include <string.h> // memset
#include <unistd.h> // usleep

#define LOG(STR, ...) printf("%s " STR "\n", __func__, ## __VA_ARGS__)

// ハンドラ
volatile sig_atomic_t g_signal = 0;
void signal_handler(int signo) { g_signal ++; }

int main() {
    LOG("start");

    // ハンドラを登録する
    struct sigaction sa;
    memset(&sa, 0, sizeof(sa));
    sa.sa_handler = signal_handler;
    if( sigaction(2, &sa, nullptr) < 0 ) {
        LOG("err signal");
        return 1;
    }

    // シグナル待ち
    volatile sig_atomic_t old = g_signal;
    int cnt = 0;
    char mark[5] = {'|', '/', '-', '\\'};
    while(true) {
        // ハンドルチェック
        if( g_signal > old ) {
            LOG("signal %d", g_signal);
            old = g_signal;
            if( g_signal >= 3 ) {
                break; //3回目をハンドルしたら終了
            }
        }

        // アプリが止まっていないことの確認用
        printf("%c\r", mark[(cnt + 1) % 4]); fflush(stdout); cnt++;
        usleep(200 * 1000);
    }
    LOG("end");
    return 0;
}
```

</div></details>

## (2) 拡張

sa.sa_flags に SA_SIGINFO をすると sa.sa_sigaction を使います。

<details><summary>動作確認コード</summary><div>

```c++:signalaction_2.cpp
#include <stdio.h>
#include <stdlib.h>
#include <signal.h> // sigaction
#include <string.h> // memset
#include <unistd.h> // usleep

#define LOG(STR, ...) printf("%s " STR "\n", __func__, ## __VA_ARGS__)

// ハンドラ
volatile sig_atomic_t g_signal = 0;
volatile sig_atomic_t g_signo = 0;
volatile sig_atomic_t g_errno = 0;
volatile sig_atomic_t g_code = 0;

void sigaction_handler(int signo, siginfo_t *info, void *ctx) {
    g_signal ++;

    // 第2引数 info は以下の3要素はすべてのシグナルで定義される。
    // その他のフィールドはシグナルに応じて定義される。
    g_signo = info->si_signo;
    g_errno = info->si_errno;   // linuxでは一般的に使用されない
    g_code = info->si_code;
}

int main() {
    LOG("start");

    // ハンドラを登録する
    struct sigaction sa;
    memset(&sa, 0, sizeof(sa));
    sa.sa_sigaction = sigaction_handler;
    sa.sa_flags = SA_SIGINFO;
    if( sigaction(2, &sa, nullptr) < 0 ) {
        LOG("err signal");
        return 1;
    }

    // シグナル待ち
    volatile sig_atomic_t old = g_signal;
    int cnt = 0;
    char mark[5] = {'|', '/', '-', '\\'};
    while(true) {
        // ハンドルチェック
        if( g_signal > old ) {
            LOG("signal %d %d %d %d", g_signal, g_signo, g_errno, g_code);
            old = g_signal;
            if( g_signal >= 3 ) {
                break; //3回目をハンドルしたら終了
            }
        }

        // アプリが止まっていないことの確認用
        printf("%c\r", mark[(cnt + 1) % 4]); fflush(stdout); cnt++;
        usleep(200 * 1000);
    }
    LOG("end");
    return 0;
}
```

</div></details>