5

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

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

TLPI

SUSv3 指出,表 21-1(异步安全函数列表)中未列出的所有函数都被认为对信号不安全,但指出只有在调用信号处理程序中断一个函数的执行时,该函数才是不安全的。 unsafe 函数,并且处理程序本身也调用了 unsafe 函数

我对上述引用的解释是,只有当信号处理程序没有中断非异步安全函数时,才可以安全地从信号处理程序调用非异步安全函数

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

sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_RESTART;
sa.sa_handler = handler;
sigaction(SIGINT, &sa, NULL);

而且我还调用printf()了一个无限循环main(),我只有主线程在运行。

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

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

4

2 回答 2

5

是的,确实,在所有情况下从信号处理程序内部调用非异步信号安全函数都是不安全的(除非您深入了解您的实现代码 - 例如libc,也许您的编译器会为它生成代码);那么您也许可以证明调用这样的函数实际上是安全的;但是这样的证明可能是不可能的,或者需要几个月或几年的时间,即使在静态分析器 la Frama-C的帮助下,......并且需要研究所有实现细节。

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

请记住,Unix 信号可以出现在用户空间中的每条机器指令处(不仅在 C 序列点,它们具有一些明确定义的语义)(系统调用是单个SYSENTER指令)。运气不好,可能会在更新全局状态的少数机器指令中出现信号malloc然后,将来从您的信号处理程序间接调用malloc-eg可能会造成严重破坏(即未定义的行为)。这样的不幸可能不太可能发生(但评估其概率实际上是不可能的),但您应该针对它进行编码。

详细信息在很大程度上是特定于实现的,具体取决于您的编译器和优化标志libc、内核、处理器架构等...

您可能不关心异步信号安全功能,因为打赌不会发生灾难。对于调试目的,这可能是可以接受的(例如,很多时候,但并非总是,printf信号处理程序内部的一个实际上大部分时间都可以工作;并且GCC编译器在信号处理程序中内部使用其“异步不安全” libbacktrace库)用于代码用 包裹#ifndef NDEBUG,但不适合生产代码;如果您确实必须在处理程序中添加此类代码,请在评论中提及您知道您错误地调用了非异步信号安全函数,并准备好被未来从事相同工作的同事诅咒代码库。

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

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

于 2015-08-24T15:25:31.890 回答
4

如果信号中断主程序中的任何异步不安全函数,则在信号处理程序中调用任何异步不安全函数都是不安全的。async-unsafe 函数不需要相互关联——结果是未定义的。

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

使用同步SIGFPE信号(和之类的东西SIGSEGV)情况可能会好一些,因为您可以确保异步不安全的函数永远不会触发这些信号,并且您不允许(或关心)与kill. 然而,这需要一些小心——如果你有一个信号处理程序来SIGSEGV捕获对写保护内存的写入,你需要确保你永远不会以可能触发你的处理程序的方式将写保护内存传递给异步不安全函数.

于 2015-08-24T18:00:46.573 回答