中断读调用信号

Interrupt read call on signal

如果按下 ctrl-c,我必须使用信号中断读取调用。 我写了这个(简化的)示例代码:

#include <unistd.h>
#include <sys/wait.h>

int should_stop = 0;

void sighandler(int signal)
{
    write(1, "\nctrl-c has been pressed\n", 25);
    should_stop = 1;
}

void read_function()
{
    char c;

    while (!should_stop)
        read(0, &c, 1);
    //Do some stuff and return someting
}

int main()
{
    signal(SIGINT, &sighandler);
    read_function();
    write(1, "read_function is over\n", 22);
    return (0);
}

由于 read 是一个阻塞调用(据我所知),一旦 read 被调用,should_stop 全局变量将不会被评估。 所以我不知道如何通过按 ctrl-c 来中断读取调用。

另一个限制是我只能使用这些功能:

- write
- read
- fork
- wait
- signal
- kill
- exit

所以我不能使用select来设置超时值。 因为我还需要 read_function 的 return 值,所以我不能使用 fork,只能使用不同的信号处理程序退出进程。

还有其他方法可以中断读取调用吗?

这是当前发生的情况:当您从键盘发送中断信号时,信号处理程序开始运行,将 \nctrl-c has been pressed\n 消息写入您的控制台并设置 should_stop 变量。然后,控制返回到 read(0, &buf, 1) 语句。由于标准输入被缓冲, read 在遇到换行符之前不会结束。如果您之后按 Enterread 读取一位,然后 returns。之后,再次检查条件 should_stop,因为它现在包含 1 值 — 循环结束。

现在,我们要修改该行为,以便您的程序在 SIGINT 后正常关闭。

来自man 7 signal

If  a  blocked  call  to one of the following interfaces is interrupted by a
signal handler, then the call is automatically restarted  after  the  signal
handler  returns  if  the SA_RESTART flag was used; otherwise the call fails
with the error EINTR:

来自 man 2 signal:

certain  blocking  system calls are automatically
restarted if interrupted by a signal handler (see signal(7)).  The  BSD  se‐
mantics are equivalent to calling sigaction(2) with the following flags:

   sa.sa_flags = SA_RESTART;

所以,下面是我们如何为我们的案例使用 sigaction(2)

int main()
{
    struct sigaction sa;
    sigemptyset(&sa.sa_mask);
    sa.sa_handler = sighandler;
    sa.sa_flags = 0;

    sigaction(SIGINT, &sa, NULL);
    read_function();
    write(1, "read_function is over\n", 22);
    return (0);
}

这样,在被信号处理程序中断时,read(2) returns 出现 EINTR 错误并且不会重新启动。

在代码的可移植性方面,

signal(2) 通常不如 sigaction(2),您可以阅读 here

如果您将 should_stop 变量声明为 volatile,它应该可以工作。这将指示编译器在每次访问时从内存中重新读取它:

...
volatile int should_stop = 0;
...

仅取决于您的系统,read 调用可能会在发出信号后重新启动,您必须在 Ctrl-C 后按 return 才能结束程序。默认情况下,我的 FreeBSD 11 bos 的行为是这样的。

如果您不想重新启动 read 调用,您应该使用 siginterrupt:

明确要求该行为
...
signal(SIGINT, &sighandler);
siginterrupt(SIGINT, 1);
...

这样,程序将在 Ctrl-C 后立即停止