sigaction() と SA_RESTART を正しく使用するには?

sigaction() と SA_RESTART を正しく使用するには?

私はキーボード入力とキーボード生成信号の両方を待ち、他のI / O(パイプとファイルを含む)をアクティブに保つことができるCコードを開発したいと思います。

フラグを増やし、現在のデバッグを提供する最小限の信号ハンドラがあります。

void Handler (int signo)

{
    char msg[80];

    ss->nTstp++;
    sprintf (msg, "\n%s .. Called Handler %d sig %d ..\n",
        TS(), ss->nTstp, signo);
    write (STDERR_FILENO, msg, strlen (msg));
}

次のようにハンドラを設定しました。

sigemptyset (& ss->sa.sa_mask);
sigaddset (& ss->sa.sa_mask, SIGTSTP);
ss->sa.sa_flags = SA_RESTART | SA_NOCLDSTOP;
ss->sa.sa_handler = Handler;
rc = sigaction (SIGTSTP, & ss->sa, NULL);

私のジレンマは、信号が直接承認されますが、端末入力が自動的に再開されることです。 Enterを押すまでハンドラフラグを操作できません。

SA_RESTARTを設定しない場合は、端末入力からEINTRを受け取りますが、他のすべてのファイル記述子でもEINTRを期待する必要があります(そして再起動可能なトランスポートを作成する必要があります)。

特定のファイル記述子でSA_RESTARTを無効にする方法、または他のfdがEINTRを取得しないようにする方法はありますか?

ベストアンサー1

あなたの要件を理解したら、(1)キーボード入力、(2)信号、(3)潜在的に他のイベントソースを処理できるようにしたいと思います。これが正しいなら、これはあなたが求めていることの始まりかもしれません。

#include <errno.h>
#include <poll.h>
#include <signal.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>

static int pipe_fds[2];

static void handler(const int signo)
{
    #define msg "received SIGTSTP\n"
    write(pipe_fds[1], msg, strlen(msg));
    #undef msg
}

int main(void)
{
    if (pipe(pipe_fds) < 0) {
        perror("pipe");
    }

    struct sigaction sa = {
        .sa_handler = handler,
        .sa_flags = SA_RESTART | SA_NOCLDSTOP,
    };

    if (sigemptyset(&sa.sa_mask) < 0) {
        perror("sigemptyset");
        return 1;
    }

    if (sigaction(SIGTSTP, &sa, NULL) < 0) {
        perror("sigaction");
        return 1;
    }

    struct pollfd fds[] = {
        {
            .fd = STDIN_FILENO,
            .events = POLL_IN,
        },
        {
            .fd = pipe_fds[0],
            .events = POLL_IN,
        },
    };

    const int num_fds = 2;
    char buffer[1024] = {};

    for (;;) {
        const int ret = poll(fds, num_fds, -1);
        if (ret < 0) {
            if (errno == EINTR) {
                // Receiving SIGTSTP can cause poll() to return
                // -1 with errno = EINTR.  Ignore that.
                continue;
            }
            perror("poll");
            return 1;
        }

        for (int i = 0; i < num_fds; ++i) {
            if (fds[i].revents & POLL_IN) {
                const int count = read(fds[i].fd, buffer, sizeof(buffer) - 1);

                buffer[count - 1] = '\0';
                printf("Read '%s' from file descriptor %d\n", buffer, fds[i].fd);
            }
        }
    }

    return 0;
}

このmain()関数はまずパイプを生成します。プログラムはパイプを使用して、信号処理関数(信号コンテキストで)と基本プログラムとの間の通信を実行します。次に、SIGTSTP上記のようにシグナルのシグナルハンドラを設定します。

struct pollfd次に、という配列を作成しますfds。この配列の各エントリは、プログラムがアクティビティを監視することに興味があるファイル記述子に対応します。配列の最初の項目は標準入力のファイル記述子です。 2つ目は、上のパイプの読み取りの終わりです。他のイベントソース(ファイル記述子に関連するイベント)を処理するためにこの例を拡張したい場合は、ここで実行できます。fds適切なファイル記述子を使用して配列に追加の要素を追加するだけです。

その後、イベントループに入るために使用されますpoll。タイムアウト後、-1poll1)登録されたファイル記述子の1つでアクティブになるか(2)信号が中断されるまでブロックされます(例:received SIGTSTP)。したがって、プログラムはの戻り値をチェックしpoll、0より小さい場合はエラーを明示的にチェックして無視しますEINTR(システムコールによって中断されます)。

poll()アクティビティによって返された場合、revents関連フィールドはstruct pollfdそれに応じて表示されます。次に、関連するファイル記述子から読み取ってメッセージを印刷します。

実行例は次のとおりです。

$ ./a.out
Hello!
Read 'Hello!' from file descriptor 0
How are you?
Read 'How are you?' from file descriptor 0
^ZRead 'received SIGTSTP' from file descriptor 3
Good
Read 'Good' from file descriptor 0
^C
$

実行例では、キーボードHello!から読みます。How are you?どちらの場合も、プログラムは私が入力した内容を読み、応答を印刷して応答します。次に、パイプから読み取って応答を印刷するSIGTSTP信号を生成します。received SIGTSTP次に、Goodキーボードの応答を読み、印刷します。結局、プログラムを中断してCtrl-Cプログラムが終了しました。

read使用可能なバイト数を少なく返すこともできます。簡略化のため、その条件を確認していません。処理するイベントソースによっては、これを行う必要があります。

おすすめ記事