如何正确使用 sigaction() 和 SA_RESTART?

如何正确使用 sigaction() 和 SA_RESTART?

我想开发一个 C 代码,它将同时等待键盘输入和键盘生成的信号,并且能够使其他 I/O(带有管道和文件)处于活动状态。

我有一个最小的信号处理程序,它会增加一个标志(并且当前提供调试):

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。该数组中的每个条目对应于程序有兴趣监视活动的文件描述符。数组中的第一个条目是标准输入的文件描述符。第二个是上述管道的读取端。如果您想扩展此示例以处理其他事件源(与文件描述符关联的事件),那么这就是执行此操作的位置,只需fds使用适当的文件描述符向数组添加其他元素即可。

然后它使用 进入事件循环poll。超时后-1poll将阻塞,直到 (1) 它在注册的文件描述符之一上获得活动或 (2) 信号中断它(例如收到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返回更少的可用字节。为简单起见,我没有检查该条件。根据您想要处理的事件源,您可能需要这样做。

相关内容