Tociyuki::Diary RSSフィード

tociyuki による Perl・Ruby・C++・C で書き散らしたコードを中心に、日常雑記も混在 : B  F  twitter  GitHub  CPAN  本館  公開鍵
 | 

2017年10月13日

[]POSIX シグナルを捉える self-pipe トリック

Gtk+ がイベント・ループに使っている GMainContext は「POSIX シグナルの GSource イベント化の実装読み」のように、 スレッドで POSIX シグナル通知フラグを転記する凝ったやりかたを採用しています。 イベント・ハンドラを動かすスレッドでシグナル通知フラグをチェックしないのは、 poll (2) および select (2) システム・コールに存在する競合問題を避けるためなのでしょう。 POSIX 2001 以降なら pselect (2) システム・コールを使うことで競合問題が解決するので、 イベント・ループを動かすスレッドでフラグを直接チェックすれば良いのですけど、 可搬性から poll (2) システム・コールを使うこととし、 転記スレッド方式を取り入れたのでしょう。

では、 マルチ・スレッドより前の世代で競合問題をどのように解決していたのでしょう。 select (2) の man ページの BUG セクションが勧めるのは「self-pipe トリック」です。 これは簡単な仕組みで、 同じプロセス内でシグナル・ハンドラとイベント・ループをパイプでつなげるわけです。 パイプの書き込み側へシグナル・ハンドラが 1 バイト書き込みます。 そして、 パイプの読み取り側をイベント・ループで監視して読み取ります。 これで、 スレッドを使わずに、 お手軽にシグナル到着を poll することができます。

⇒ `man 2 select`

On systems that lack pselect(), reliable (and more portable) signal trapping can be achieved using the self-pipe trick. In this technique, a signal handler writes a byte to a pipe whose other end is monitored by select() in the main program.

ところで、 シグナル・ハンドラで利用可能なのは非同期安全なシステム・コールに限定されます。 Linux の write (2) システム・コールは非同期安全なので、 このトリックを使うことができます。 GMainContext が self-pipe トリックを使っていないのは、 write (2) システム・コールが非同期安全でないオペレーティング・システムへ移植することを視野にいれてあるからなのかもしれません。

self-pipe トリックを試してみます。 下の試行コードでは、 パイプのファイルディスクリプタは安直にグローバル変数にしています。

/*
 *   $ clang++ -std=c++11 -Wall -O3 self-pipe-trick.cpp -o self-pipe-trick
 *   $ ./self-pipe-trick
 *   hello
 *   hello
 *   ^C [SIGINT]
 *   $
 */
#include <cstdlib>
#include <cstdio>
#include <unistd.h>
#include <poll.h>
#include <csignal>
#include <cerrno>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

/* シグナル通知でパイプへ ^C を 1 バイト書き込みます */ 
static char const signal_notify_byte[] = "\x03";
/* シグナル通知用のパイプ fd: [0] が読み込み [1] が書き込み */
static int signal_notify_pipefd[2];

void
signal_handler (int signal)
{
    write (signal_notify_pipefd[1], signal_notify_byte, 1);
}

/* fd を非ブロッキングにします */
int
fcntl_setfl_nonblock (int fd)
{
    int flag;
    if ((flag = fcntl (fd, F_GETFL, 0)) < 0) {
        perror ("fcntl F_GETFL");
        return -1;
    }
    flag |= O_NONBLOCK;
    if ((flag = fcntl (fd, F_SETFL, flag)) < 0) {
        perror ("fcntl F_SETFL");
        return -1;
    }
    return 0;
}

int
main ()
{
    struct pollfd fds[2];
    int ttyin;
    int len;
    char buf[256];

    if (pipe (signal_notify_pipefd) < 0) {
        perror ("pipe");
        exit (1);
    }
    if ((ttyin = open ("/dev/tty", O_RDONLY)) < 0) {
        perror ("open /dev/tty");
        exit (1);
    }
    /* パイプと tty 入力を非ブロッキングにします */
    if (fcntl_setfl_nonblock (signal_notify_pipefd[0]) < 0
            || fcntl_setfl_nonblock (signal_notify_pipefd[1]) < 0
            || fcntl_setfl_nonblock (ttyin) < 0) {
        close (ttyin);
        close (signal_notify_pipefd[1]);
        close (signal_notify_pipefd[0]);
        exit (1);
    }

    std::signal (SIGINT, signal_handler);

    for (bool main_loop_quit = false; ! main_loop_quit;) {
        /* poll (2) はシグナル・ハンドラ呼び出しで必ず中断されて
         * errno に EINTR が入ります。
         */
        fds[0].fd = ttyin;
        fds[0].events = POLLIN;
        fds[1].fd = signal_notify_pipefd[0];
        fds[1].events = POLLIN;
        if (poll (fds, 2, 1000) < 0 && errno != EINTR) {
            perror ("poll");
            break;
        }
        /* /dev/tty から入力あり */
        if (fds[0].revents & POLLIN) {
            if ((len = read (ttyin, buf, 256)) < 0) {
                perror ("read tty");
            }
            else {
                fwrite (buf, 1, len, stdout);
            }
        }
        /* シグナル通知パイプから入力あり */
        if (fds[1].revents & POLLIN) {
            if ((len = read (signal_notify_pipefd[0], buf, 256)) < 0) {
                perror ("read signal notify from pipe");
            }
            else {
                puts (" [SIGINT]");
                main_loop_quit = true;
            }
        }
    }
    close (ttyin);
    close (signal_notify_pipefd[1]);
    close (signal_notify_pipefd[0]);

    return EXIT_SUCCESS;
}

スパム対策のためのダミーです。もし見えても何も入力しないでください
ゲスト


画像認証

トラックバック - http://d.hatena.ne.jp/tociyuki/20171013/1507878159
 |