当我从信号处理程序调用非异步安全函数时,它总是不安全的吗?



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

如果一个信号中断了一个不安全函数的执行,并且处理程序调用了一个不安全函数,那么程序的行为是undefined

和TLPI:

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,也许你的编译器为它生成代码);然后你也许可以证明调用这样一个这样的函数实际上是安全的;但是这样的证明可能是不可能的,或者需要花费您数月或数年的时间,即使有静态分析器(la Frama-C)的帮助,……并且需要研究所有的实现细节。

具体来说,crypt很可能在内部调用malloc(对于一些内部数据,等等…)。标准的malloc函数显然有一些全局状态(例如,与以前的free -d内存区域相关的桶链表向量,将来调用malloc时可以重用)。 请记住,Unix信号可以出现在用户空间中的每一个机器指令(不仅仅是C序列点,它们有一些定义良好的语义)(一个系统调用就是一个单独的SYSENTER指令)。如果运气不好,在更新malloc全局状态的几个机器指令中可能会出现信号。然后将来调用malloc -例如间接从你的信号处理程序,可能会破坏破坏(即未定义的行为)。这样的不幸可能不太可能发生(但是评估它的概率实际上是不可能的),但是您应该针对它编写代码。

细节是非常具体的实现,取决于你的编译器和优化标志,libc,内核,处理器架构等…

你可能不关心异步信号安全函数,打赌灾难不会发生。这对于调试来说可能是可以接受的(例如,通常情况下,但并非总是如此,信号处理程序中的printf在大多数情况下实际上工作;并且GCC编译器在内部使用它的"async-unsafe"libbacktrace库(信号处理程序)用于用#ifndef NDEBUG包装的代码,但它不适用于生产代码;如果你真的必须在处理程序中添加这样的代码,在注释中提到你知道你正在错误地调用非异步信号安全函数,并准备好被将来使用相同代码库的同事诅咒。

处理这种情况的一个典型技巧是简单地在信号处理程序中设置一个volatile sig_atomic_t标志(请阅读POSIX signal.h文档),并在某个循环中的某个安全位置检查该标志(在处理程序之外),或者将(2)一个或几个字节写入先前在应用程序初始化时设置的管道(7),并使该管道的读端定期轮询(2)-ed,然后由事件循环或其他线程读取-)。

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

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

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

使用同步信号(如SIGFPESIGSEGV)情况会好一些,因为在那里您可以确保异步不安全的函数永远不会触发这些信号,并且您不允许(或关心)使用kill异步发送这些信号。然而,这需要一些注意——如果你有一个SIGSEGV的信号处理程序来捕获对写保护内存的写入,你需要确保你永远不会将写保护内存传递给一个async不安全的函数,因为它可能会触发你的处理程序。

最新更新