C 从信号处理程序调用非异步安全函数时是否总是不安全?

C 从信号处理程序调用非异步安全函数时是否总是不安全?,c,linux,signals,async-safe,C,Linux,Signals,Async Safe,我只是想知道是否可以在信号处理程序中调用非异步安全函数。 引用Linux手册页信号(7): 如果信号中断不安全函数的执行,并且处理程序调用不安全函数,则程序的行为是未定义的 以及: SUSv3注意到表21-1(异步安全函数列表)中未列出的所有函数都被认为是信号不安全的,但指出只有在调用信号处理程序中断不安全函数的执行时,函数才是不安全的,处理程序本身也调用不安全的函数 我对以上引用的解释是,只有在信号处理程序没有中断非异步安全函数的情况下,从信号处理程序调用非异步安全函数才是安全的 例如,我为S

我只是想知道是否可以在信号处理程序中调用非异步安全函数。
引用Linux手册页信号(7):

如果信号中断不安全函数的执行,并且处理程序调用不安全函数,则程序的行为是未定义的

以及:

SUSv3注意到表21-1(异步安全函数列表)中未列出的所有函数都被认为是信号不安全的,但指出只有在调用信号处理程序中断不安全函数的执行时,函数才是不安全的,处理程序本身也调用不安全的函数

我对以上引用的解释是,只有在信号处理程序没有中断非异步安全函数的情况下,从信号处理程序调用非异步安全函数才是安全的

例如,我为SIGINT安装了一个处理程序,它调用了一个不安全的函数,假设该函数是
crypt(3)
,它是不可重入的,即不安全的

sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_RESTART;
sa.sa_handler = handler;
sigaction(SIGINT, &sa, NULL);
我还在
main()
中的无限循环中调用
printf()
,我只运行主线程

对于这个简单的示例,当处理程序中断
printf()
的执行并调用不安全的函数时,我看不到任何不好的事情发生。
AFAK,
printf()
将获取控制台锁,并具有内部缓冲区来执行缓冲I/O,但在本例中,其状态是一致的。虽然
crypt()
返回一个静态分配的字符串,但它不与其他函数或线程共享


我是不是误解了什么?我希望有人向我澄清,让信号处理程序中断主程序中不安全函数的执行,并且它本身也调用不安全函数,这是否总是不安全的?或者在某些情况下这样做是安全的(例如上面的简单示例)?

确实如此,在任何情况下,从信号处理程序内部调用非异步信号安全函数都是不安全的(除非您深入研究实现代码,例如
libc
,或者您的编译器为其生成代码);然后你也许可以证明调用这样的函数实际上是安全的;但这样的证明可能是不可能的,或者需要数月或数年的时间,即使在静态分析仪的帮助下。。。并要求研究所有实施细节

具体来说,
crypt
很可能在内部调用
malloc
(对于某些内部数据等)。标准的
malloc
函数显然具有一些全局状态(例如,与以前
空闲的
-d内存区域相关的存储桶链表的向量,将来调用
malloc
时将重复使用)

请记住,这可能发生在每一个位置(不仅仅是C序列点,它具有一些定义良好的语义)。如果运气不好,在更新
malloc
全局状态的少数机器指令中可能会出现信号。然后,将来调用
malloc
——例如,从信号处理程序间接调用,可能会导致破坏(即)。这样的不幸可能不太可能发生(但评估其可能性实际上是不可能的),但您应该针对它编写代码

详细信息与实现密切相关,具体取决于编译器和优化标志、
libc
、内核、处理器体系结构等

通过打赌灾难不会发生,您可能不关心异步信号安全函数。这对于调试目的来说可能是可以接受的(例如,信号处理程序中的
printf
通常(但并非总是)在大多数情况下都能正常工作;编译器在信号处理程序中内部使用其“异步不安全”库)用于用
#ifndef NDEBUG
包装的代码,但这对生产代码没有好处;如果您真的必须在处理程序中添加这样的代码,请在注释中提到,您知道自己对非异步信号安全函数的调用是错误的,并且准备好被将来在同一代码库上工作的同事诅咒

处理这种情况的一个典型技巧是简单地在信号处理程序(阅读POSIX文档)中设置一个
volatile sig_atomic_t
标志,并在某个循环中的某个安全位置(处理程序外部)检查该标志,或者将其设置为应用程序初始化时的一个或几个字节,并让该管道的读取端被定期读取,然后由事件循环或其他线程读取

(我以
malloc
为例,但您可以想到其他广泛使用的非异步信号安全函数,甚至是特定于实现的例程,例如32位处理器上的64位算术等。).

如果信号中断主程序中的任何异步不安全函数,则调用信号处理程序中的任何异步不安全函数是不安全的。异步不安全函数不需要彼此有任何关系——结果是未定义的

因此,在信号处理程序中安全调用异步不安全函数的唯一方法是确保在调用aysnc不安全函数时信号永远不会出现。一种方法是使用适当的sigblock/sigsetmask调用包装对任何异步不安全函数的每个调用,以确保在不安全函数运行时不会传递信号。另一种方法是让主程序在调用异步不安全函数时设置/清除一个
sigatomic
标志,并让信号处理程序在尝试调用异步不安全函数之前检查该标志

使用同步信号(如
SIGFPE
SIGSEGV
)会更好一些,因为在那里,您可以确保异步不安全功能不会触发这些信号,并且您不允许(或