世界杯海报_u20世界杯德国 - jjswlx.com

为什么只能从信号处理程序中安全地调用异步信号安全函数?
2026-07-01 19:13:25

假设进程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来避免这个问题,但是即使这样也不能解决所有可能由信号引入的死锁。

总之,“从信号处理程序内部接收一个信号并调用一个非异步安全函数是不安全的”的原因是,根据定义,它不是。在信号处理程序中调用非异步安全函数是不安全的“,因为信号是一个异步事件,而且由于它不是异步安全函数,因此您不能,根据定义,水是湿的,因为它是水,而异步不安全的函数不能从异步信号处理程序调用。