为什么只能安全地从信号处理程序中调用异步信号安全功能?

时间:2015-12-26 01:02:48

标签: c++ linux multithreading c++11 signals

我仍然有点困惑,为什么接收信号并从该信号处理程序中调用非异步安全函数是不安全的。有人可以解释这背后的原因,并可能尝试给我一些参考资料,我可以跟着自己阅读更多内容吗?

换句话说,我问为什么在信号处理程序中调用printf是不安全的。是因为内部进程问题和可能的竞争条件导致两个可能的printf没有保护调用,或者是因为进程争用到同一个资源(在本例中为stdout)。假设进程A中的线程正在调用printf而另一个线程接收该信号然后调用printf。可能是因为这里的内核不知道该怎么做,因为它无法区分这两个调用。

1 个答案:

答案 0 :(得分:7)

  

假设进程A中的线程正在调用printf和另一个线程   接收信号,然后调用printf。可能是因为   内核在这里不知道该做什么,因为它无法做到   区分这两个电话。

内核不会有问题。这是你的应用程序本身。 printf不是内核函数。它是您的应用程序使用的C库中的一个函数。 printf实际上是一个相当复杂的功能。它支持各种输出格式。

此格式化的最终结果是格式化的输出字符串,该字符串写入标准输出。这个过程本身也涉及一些工作。格式化的输出字符串将写入内部stdout文件句柄的输出缓冲区。无论何时发生某些定义的条件,即输出缓冲区已满,和/或每当换行符被写入时,输出缓冲区都会被刷新(并且只有此时内核才会接管并将定义的数据块写入文件)。输出流。

输出缓冲区的内部数据结构支持所有这些,您不必担心它,因为它是C库的工作。现在,信号可以在printf运行时随时到达。我的意思是,在任何时候。在更新输出缓冲区的内部数据结构时,它可能会在printf时到达,并且它们处于临时不一致状态,因为printf尚未完成更新。

示例:在现代C / C ++实现中,printf可能不是信号安全的,但它是线程安全的。多个线程可以使用printf写入标准输出。线程负责协调这个过程,以确保最终的输出实际上是有意义的,并且它不会随机地从多个线程的输出混乱,但这不是重点。

重点是printf是线程安全的,这通常意味着某个地方有一个mutex参与该过程。因此,可能发生的事件序列是:

  • printf获取内部互斥锁。

  • printf继续处理格式化字符串并将其写入stdout的输出缓冲区。

  • printf完成之前,可以释放获取的互斥锁,信号到达。

现在,内部mutex已被锁定。关于信号处理程序的事情是,它通常没有指定在一个进程中哪个线程处理信号。给定的实现可能会随机选择一个线程,或者它可能总是选择当前正在运行的线程。在任何情况下,它都可以选择锁定printf的线程,这里是为了处理信号。

现在,您的信号处理程序运行,并且它还决定调用printf。由于printf的内部互斥锁被锁定,因此线程必须等待互斥锁解锁。

等等。

等等。

因为,如果你正在跟踪事物:互斥锁被被中断以服务信号的线程锁定。在线程恢复运行之前,互斥锁不会被解锁。但是直到信号处理程序终止并且线程恢复运行才会发生这种情况,但信号处理程序现在正在等待互斥锁解锁。

你是个笨蛋。

现在,当然,printf可能会使用等效于std::recursive_mutex的C ++来避免这个问题,但即使这样也无法解决信号可能导致的所有可能死锁。

总而言之,根据定义,它“接收信号并从该信号处理程序中调用非异步安全功能不安全”的原因是因为它不是。从信号处理程序中调用非异步安全函数是不安全的“因为信号是异步事件,并且由于它不是异步安全函数,所以根据定义你不能。水是湿的,因为它是水,并且无法从异步信号处理程序调用异步不安全的函数。