2015-12-26 46 views
6

我仍然有点困惑,为什么从信号处理程序中接收信号并调用非异步安全函数的确切原因是不安全的。有人可以解释这个背后的原因,并可能尝试给我一些参考,我可以按照自己更多的了解这个?为什么只能安全地从信号处理程序调用异步信号安全函数?

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

+1

简写形式是“因为标准是这么说的”,或者“因为你的图书馆是这样说的”,长形式是代码不是用异步访问能力:有很多方法可以编写代码,以便它不能很好地处理异步问题。几乎所有处理异步的方法在同步运行时都会产生成本,因此具有非异步处理功能会使性能常常受到影响 – Yakk

+0

感谢!如果我以这样的方式构造我的代码,以便不以并行方式调用非异步安全函数,但是在非返回信号处理程序的上下文中,那没关系吗? – Curious

+0

'malloc'特定版本:https://stackoverflow.com/questions/3366307/why-is-malloc-not-async-signal-safe –

回答

6

说一个进程内的线程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可能会使用C++等效的std::recursive_mutex来避免这个问题,但即使这样也不能解决可能由信号引入的所有可能的死锁。

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

+1

谢谢!这就说得通了。只要它是一个内部进程问题,我想我可以解决它 – Curious

+0

递归互斥体在某种意义上更糟糕:您正在修改数据结构,并且您现在正在同时再次修改它。 – Yakk