当前位置: 首页 > 知识库问答 >
问题:

sem_post、信号处理程序和未定义行为

蒯坚白
2023-03-14

在信号处理程序中使用sem_post()是否依赖于未定义的行为?

/* 
 * excerpted from the 2017-09-15 Linux man page for sem_wait(3)
 * http://man7.org/linux/man-pages/man3/sem_wait.3.html
 */
...
sem_t sem;
...
static void
handler(int sig)
{
    write(STDOUT_FILENO, "sem_post() from handler\n", 24);
    if (sem_post(&sem) == -1) {
        write(STDERR_FILENO, "sem_post() failed\n", 18);
        _exit(EXIT_FAILURE);
    }
}

如果信号处理程序引用除errno以外的任何具有静态存储持续时间的对象,而不是通过向声明为volatile sig_atomic_t的对象赋值,或者如果信号处理程序调用本标准中定义的任何函数,而不是[显式异步信号安全函数]之一,则行为未定义。

共有1个答案

姚鹤龄
2023-03-14

从技术上讲,是的;在某些情况下,行为是不确定的。

我自己也经常使用这种模式,我看过的几乎所有信号感知程序也是如此。它有望在实践中工作,并且可以跨系统移植,即使没有任何标准规定。

POSIX.1标准将其定义为未定义的行为,并不是因为它期望程序避免这种访问,而是因为定义安全访问情况将过于复杂,并可能限制未来的实现,而收益很少甚至没有,因为对于所有这种访问都有一个众所周知的解决方案:一个专用线程捕捉信号。

当进程只有一个线程时,信号在挂起时被阻塞,并且在解除阻塞信号的调用返回之前被传递。

这省略了最常见的情况:多线程进程,以及处理进程外部产生的信号的处理程序(例如,当进程在前台运行时使用SIGINT,用户按Ctrl+C;或者当运行进程的会话关闭时使用SIGHUP)。

我对这种情况的理解是,每个人都希望通过异步信号安全函数引用具有静态存储持续时间的对象的信号处理程序不会在任何正常的POSIXy体系结构上触发未定义的行为;如果对具有静态存储持续时间的对象使用multithread-safe(MT-safe)异步信号安全函数,它在多线程进程中的工作与在单线程进程中的工作完全相同;alarm()setitimer()timer_settime()触发的信号与raise()sigqueue()触发的信号行为相同;其他进程发送的信号与目标进程中raise()sigqueue()触发的信号行为相同;唯一的区别是siginfo结构中的一些字段具有不同的值。

甚至有一个很小的可能性,措辞应该有访问而不是参考。这确实允许将具有静态存储持续时间的任何对象的地址传递给异步信号安全函数,如sem_post(),即使是在多线程进程中,就像Carlo Wood的回答假设一样。

然而,我认为这种措辞的原因更加微妙,涉及到关于并发访问和执行信号处理程序的上下文的硬件实现的差异:在某些POSIX OSE可能有不同行为的情况下,行为太复杂了,无法标准化,所以干脆不定义。

对于那些希望生成可靠、健壮的程序并能在所有POSIXy系统上工作的开发人员,以及不理解POSIX.1规范中当前措辞的微妙之处的开发人员,我的回答的其余部分试图描述这些问题。

最难定义的部分是与并发访问和陷阱表示相关的细节。不仅由同一进程中的其他线程,而且由内核。(因为我们只考虑具有静态存储持续时间的对象,所以我们可以避免共享内存和所有相关的复杂性。)特别是,如果一个对象有陷阱表示,并且该对象被非原子地修改,那么分配的中间阶段可能会导致陷阱。尽管在某些架构上可能存在硬件限制,但陷阱本身可能会导致信号被提升。

因此,任何与陷阱表示有关的东西基本上都太复杂了,无法在标准中解决。

好的,让我们假设标准将安全读访问限制在静态存储持续时间的对象,这些对象没有被中断的线程、进程中的任何其他线程或内核并发修改;以及对具有静态存储持续时间的对象的写访问,这些对象没有被中断的线程、进程中的任何其他线程或内核同时读取或修改。并且正在访问的对象根本没有陷阱表示。

冷酷的事实是,对于所有需要访问具有静态存储持续时间的对象的模式,都有一个符合POSIX的解决方案:一个单独的线程,专门通过sigwaitinfo()处理信号,所有这些信号都被阻止在所有其他线程中。该线程并不局限于使用异步信号安全函数,其他信号处理程序的限制也不适用于它。(如果我们考虑信号传递和它所中断的代码之间的交互,即使使用SA_restart标志定义的处理程序,也可以认为基于线程的方法是两种方法中更好的一种。)

简单地说:由于存在已知的变通方法,并且定义安全访问用例过于复杂并限制了未来的实现,所以POSIX标准根本没有对这种传统用例进行标准化。这并不是因为它预计不起作用--恰恰相反;它在所有当前的POSIXy系统中都工作得很好--但是因为定义安全访问案例(除了errnovolatile sig_atomic_t,它们都需要POSIX C编译器的支持)的复杂性和可能的限制是不值得的。

 类似资料:
  • 问题内容: 我需要在接收到任何终止命令(如SIGTERM和SIGKILL)时写入日志文件。 我可以注册SIGTERM,但是如何处理SIGKILL信号? 问题答案: 您不能,至少不是因为进程被杀死。 您 可以 做的是安排父进程监视子进程的死亡,并采取相应的措施。任何体面的过程监控系统(例如daemontools)都内置了这样的工具。

  • 主要内容:1. 定义条件,2. 定义处理程序在程序的运行过程中可能会遇到问题,此时我们可以通过定义条件和处理程序来事先定义这些问题。 定义条件是指事先定义程序执行过程中遇到的问题,处理程序定义了在遇到这些问题时应当采取的处理方式和解决办法,保证存储过程和函数在遇到警告或错误时能继续执行,从而增强程序处理问题的能力,避免程序出现异常被停止执行。 下面将详细讲解如何定义条件和处理程序。 1. 定义条件 MySQL 中可以使用 DECLARE 关

  • 我正在开发一个用PHP编写的预分叉TCP套接字服务器。 守护进程(父进程)分叉一定数量的子进程,然后等待,直到它被告知退出并且子进程都走了或者它收到信号。 SIGINT和SIGTERM使其将SIGTERM发送给所有子级。 孩子们建立了他们自己的信号处理器:SIGTERM导致一个干净的退出。SIGUSR1导致它转储一些状态信息(只需在下面的示例代码中打印出它收到了信号)。 如果子异常退出,则父级启动

  • 可以通过实现接口或扩展任何现有的实现来创建自己的自定义处理程序。 在下面的示例中,我们通过扩展类创建了自定义处理程序。 为了理解上述与DBUtils相关的概念,让我们编写一个将运行读取查询的示例。 创建一个示例应用程序。 更新在DBUtils入门程序中创建的文件:MainApp.java。 按照下面的说明编译并运行应用程序。 以下是的内容。 以下是文件的内容。 以下是文件的内容。 完成创建源文件后

  • 问题内容: 我只是在Mac OS X中玩信号。 为什么在我的信号处理程序完成后,以下代码为什么没有产生SIGSEGV的默认行为?在Linux下,代码可以正常工作。 编辑: 我得到的输出如下: 问题是我希望程序在输出之后终止,但是它永远运行了,我不得不中断它。 问题答案: 这实际上使我的大脑冻结了几分钟,而在今天和这个年龄段中永远不使用的原因在我体内变得越来越强大。 首先,从手册页 signal()

  • 信号 信号是一种进程间通信(IPC)机制,主要用于处理异步事件。 不同的Unix衍生版所支持的信号类型并不完全相同。除了支持POSIX规定的信号外,还支持其他信号。 术语解释 术语 解释 生成信号 发生了一个需要引起进程注意的事件而导致信号出现时。也叫发送信号 信号交付 被发送信号的那个进程识别到了信号并采取了适当动作。也叫接收信号 信号句柄 当信号出现时调用进行专门处理的函数。这个函数称为捕获函