0
0

More than 1 year has passed since last update.

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

Last updated at Posted at 2021-10-23

導入

シグナルシリーズ第1弾です。目次は こちら

シグナルをハンドルするといえば、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

共有オブジェクトにはアクセスしない(volatileなsig_atomic_t型変数は可)
JPCERT CC: SIG31-C

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

signal

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

man page: signal(2)

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

動作確認コード
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;
}

sigaction

こっちが推奨です。
man page: sigaction(2)

(1) signal互換

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

動作確認コード
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;
}

(2) 拡張

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

動作確認コード
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;
}

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0