从信号处理程序调用fflush是否安全?

时间:2014-01-30 15:29:52

标签: c signals fflush

嗯,标题大部分内容都说明了。

假设我的应用程序正在登录到stdout / file。但是,当终止时,并不总是完全刷新。一种解决方案是在每次记录操作后进行刷新,但这会使程序放慢速度,这是不可接受的。

所以,问题是,fflush(file)从信号处理程序调用是否安全,甚至可能是fflush(NULL)

如果没有,请说明原因。这个问题还有其他解决方案吗?如果我知道我不在文件处理程序中,这可能是安全的吗?

2 个答案:

答案 0 :(得分:2)

您只能在信号处理程序中使用异步安全函数,并且不包括stdio库。 POSIX标准定义了一组异步安全的函数。您尚未指定平台,我不知道一般解决方案。

如果在POSIX / BSD系统上存储支持FILE的文件描述符 - 使用fileno() - 您可以使用异步安全函数来挽救某些内容:write(),{{1} }(flush),lseek()等。当然,如果stdio使用自己的缓冲区,这将无济于事。

(some Cert-C guidelines)

答案 1 :(得分:0)

@ heinrich5991:取决于程序,可能会有一种信号安全的方式来执行fflush()。并且,如果事情做得很仔细,那么您对最后一个问题的回答(在对布雷特·黑尔的回答的评论中)应该为是。但是,它可能会变得复杂。

为了说明,让我们从一个简单的cat程序开始:

int main(void)
{
  int c;

  while (1) {

    c = getchar();

    if (c < 0 || c > UCHAR_MAX) /* Includes EOF */
      break;

    putchar(c);
  }

  fflush(stdout);
  return 0;
}

getchar()返回EOF时,程序正常终止。

当此程序从永远不会发送EOF的流中获取输入时(例如,处于原始模式的终端),可能需要使用信号。在这种情况下,我们可以尝试通过以下方式实现正常终止 捕获到的信号,例如SIGUSR1

void sig_handler(int signo)
{
  if (signo == SIGUSR1)
    _exit(0);
}

int main(void)
{
  /* Code for installing SIGUSR1 signal handler here */

  /* Remaining code here as before */
}

此处理程序是安全的,因为_exit()是异步信号安全的。问题是SIGUSR1信号将终止程序而不会刷新输出缓冲区,这可能会导致输出丢失。我们可以尝试通过强制冲洗来解决此问题,如:

void sig_handler(int signo)
{
  if (signo == SIGUSR1) {
    fflush(stdout);
    _exit(0);
  }
}

但是它不再是信号安全的,因为fflush()不是。 putchar()库函数会定期刷新stdout。该信号可能到达对putchar()的调用的中间,而该信号可能处于刷新stdout的中间。然后,我们的处理程序再次调用fflush(stdout),而先前stdout(来自main的{​​{1}})的刷新还没有完成。根据标准库的实现,这可能导致putchar缓冲区覆盖其自身的一部分(破坏输出),或导致stdout缓冲区的其余部分被复制。

一种可能的解决方法是使处理程序仅设置一个全局标志变量,然后让主程序处理刷新:

stdout

这再次变为信号安全,但引入了死锁。假设程序已经收到了它将要获取的所有输入,并且我们发送了一个volatile int gotsignal = 0; void sig_handler(int signo) { if (signo == SIGUSR1) gotsignal = 1; } int main() { int c; /* Code for installing signal handler here */ while (1) { c = getchar(); if (c < 0 || c > UCHAR_MAX) break; putchar(c); if (gotsig == 1) break; } fflush(stdout); return 0; } 信号来正常终止它。当通过SIGUSR1的读取调用阻止该进程时,信号到达。该信号将由处理程序处理,getchar()将被设置为1,但是控制将返回到阻塞的gotsig调用,因此它将永远阻塞,永远不会(优雅地)终止。我们陷入僵局。

解决方案:

您引用的同一GNU C Libarary文档(“信号处理和不可重入函数”小节)说:

  

如果您在处理程序中调用一个函数,请确保该函数相对于信号是可重入的,否则请确保该信号不会中断对相关函数的调用。

我们可以从上面句子的“或其他”部分中得到一些有关中断对“相关函数”的调用的想法。我们的主要代码中哪些功能与getchar()相关?我们的主要代码仅调用两个函数(标准库)fflush(stdout)getchar()。合理地假设getchar()仅触摸putchar(),因此不会调用stdin或任何从属函数。因此,此处的“相关”功能为fflush(stodut),因为如前所述,它会定期触发putchar()或相关内容。

遵循GNU C Libarary建议,我们可以使处理程序执行此操作:

  • 如果在fflush(stdout)的{​​{1}}调用内调用处理程序,则不会putchar();只需返回并让main()进行刷新

  • 否则,从处理程序中fflush(stdout)安全应该

这解决了以上所有问题(刷新/不安全/死锁)。

要实现此目的,我们使用另一个全局标志main()

下面的最终代码应该是信号安全的,并且没有死锁,但是比上面的早期尝试更加复杂。

fflush(stdout)