【问题标题】:Does POSIX guarantee signals will not be delivered to a partially-initialized thread?POSIX 是否保证信号不会传递到部分初始化的线程?
【发布时间】:2010-11-19 16:55:47
【问题描述】:

在大多数 POSIX 线程实现中,新创建的线程需要进行一些初始化,然后才能处于能够运行应用程序代码的一致状态。这可能涉及解锁线程结构中的锁,在使用一个的实现中初始化“线程寄存器”,初始化线程本地数据(编译器级 TLS 或 POSIX 线程特定数据)等。我找不到明确的保证所有这些初始化将在线程可以接收任何信号之前完成;我能找到的最接近的是 2.4.3:

下表定义了一组异步信号安全的函数。因此,应用程序可以不受限制地从信号捕获函数中调用它们:

...

据推测,其中一些函数(至少 fork,它必须检查由 pthread_atfork 函数建立的全局状态)依赖于处于一致的初始化状态的线程。

困扰我的一件事是我已经阅读了很多 glibc/nptl 源代码,但找不到任何显式同步来阻止新创建的线程在完全初始化之前对其进行处理。我希望调用pthread_create 的线程在调用clone 之前阻塞所有信号,并让新线程在初始化完成后解除对它们的阻塞,但我找不到任何具有这种效果的代码,也没有在@ 中看到它987654325@输出。

【问题讨论】:

  • 哇,当pthread_atfork() 处理程序已设置时,从信号处理程序调用fork()...您真的必须知道自己在做什么(并相信你的图书馆实现)!特别是如果(通常情况下)prefork 处理程序抓取一堆锁以确保它们表示的数据在 fork 之前是一致的 - 原则上可以持有这些锁中的任何一个(或者更糟糕的是,在被获取的过程中)由线程处理信号,这意味着数据是不可挽回的不一致(或者进程可能死锁!)。一切都很有趣:-)
  • 好吧,fork 被列为异步信号安全函数之一,但我同意 pthread_atfork 注册函数可以做的几乎所有有用的事情都不是异步的信号安全。仍然有一些有效的用途,例如,如果您的 pthread_atfork 处理程序只是用固定值重新初始化数据,破坏控制互斥体并初始化新的互斥体(当然都是在子进程中)。
  • 更令人不安的是,如果一个库(不知道被调用应用程序线程化;甚至可能作为另一个库的依赖项间接动态加载)设置了pthread_atexit 处理程序,而这些处理程序不是异步信号安全。调用应用程序可以期望 fork 是异步信号安全的(如文档所述)并从信号处理程序中调用它。我想我通过这个思想实验得到的结果是,它暴露了引入创建的线程使用透明度模型 pthread_atfork 中的一个缺陷。
  • 完全同意您的两位 cmets - 只是为了强化我的观点,即您真的需要在冒险进入那些浑浊的水域之前知道自己在做什么 :-)
  • Modulo 一次不小心输入了pthread_atexit 而不是pthread_atfork.. :-)

标签: c linux pthreads posix signals


【解决方案1】:

(我不认为这是一个真正的答案,但它对于评论来说太大了)

这是一个非常有趣的问题。我已经查看了pthread_create 的 glibc 代码以了解它的行为方式,除非我完全遗漏了某些东西,否则似乎没有任何特殊行为可以阻止这种情况(例如在 clone 之前阻止所有信号并取消阻止它们在某些设置后的子进程中{在记录线程创建时间并设置 C++ 捕获所有异常处理程序后,即使在 C 代码中也会发生这种情况}。

我期待找到一个评论,提到这种情况的可能性,甚至可能提到 POSIX 说要做的事情(或者提到它没有说要做什么)。

也许您应该始终将pthread_create 包装在代码中以阻塞和恢复信号,并通过取消阻塞调用启动所有线程函数。

这很可能是 pthreads 中的一个过度站点(或 glibc 或我对代码的理解)。

【讨论】:

  • 我能收集到的最好的信息是,如果信号在初始化完成之前没有在新线程中被阻塞,那么实现必须至少确保这不会导致任何异步信号安全功能打破。由于__thread 还不是标准的,因此不是 POSIX 的一部分,因此当然不需要 TLS 变量可访问或具有正确的值。也许一种可能的实现(我还没有检查 glibc 是否这样做)是在调用应用程序的处理程序之前用一个完成线程初始化(如果需要)的处理程序包装所有信号处理程序。
  • 我怀疑 glibc 会像这样包装信号处理程序。我认为这样做将需要它复制内核所做的信号处理以及阻止和恢复对sigaction 的每次调用的信号,以防止引入竞争条件。我只是看了看,它似乎没有这样做。这也是一件笨拙的事情,非常困难,并且可能需要常规线程启动代码能够识别它已被中断并且不应该重做已经完成的事情。
  • 我不会对旧的 LinuxThreads 包装的信号处理程序感到惊讶。这是一个可怕的黑客攻击。 :-)
  • Linux 在内核的 sigaction 结构中具有 sa_restorer 函数指针,它包装了信号处理程序的下半部分(通过返回作为 glibc 一部分的 sa_restore 代码来发出 I 'm 通过系统调用)。我刚刚了解了这个问题,并意识到前几天我回答了另一个稍微错误的问题,因为我认为是内核负责处理这个问题。
  • 答案似乎是,信号处理程序完全依赖于它在哪个线程中运行的唯一方式是通过errno。所有其他处理线程或线程局部状态的函数都不是异步信号安全的。我想知道errno 是否真的在 glibc 上正常工作,尽管如果信号在线程完成初始化其 TLS 区域之前到达......
【解决方案2】:

根据我的理解,POSIX pthread_create specification 要求这样做:

新线程的信号状态初始化如下:

  • 信号掩码应从创建线程继承。
  • 新线程的待处理信号集应为空。

但是我没有足够的经验说在各种实现中都是这样的。

【讨论】:

  • 我认为这严格意味着新线程不会继承或共享来自其父级或创建者的待处理信号。
  • 确实如此。我看不出有任何方法可以将该语句解释为线程在处于一致状态之前不应接收信号。
  • @R - “一致的状态”是什么意思?在此一致状态之前和之后一微秒获取信号的实际影响是什么?大概它仍然会被处理或忽略或其他。我并不是在争论,而是试图评估实际影响。
  • @Duck:在我查看的 glibc 线程启动代码中,发生了一些操作。其中之一是初始化当前线程的异常处理默认捕获器(这是在 C 代码中完成的,并且是必要的,因为 pthreads 用于 C++ {并且可能也用于 Objective-C})。我不知道在信号处理程序中抛出异常的规则是什么,但如果确实发生了这种情况并且在该代码之前它不会像它应该的那样优雅地失败。
  • @Duck:我的意思是任何用于管理线程的数据结构的一致性。想到的一些事情是访问errno(早期信号处理程序在访问errno 时不应崩溃或覆盖另一个线程的errno)、forkpthread_atfork 处理程序,以及线程本地存储(但是这超出了任何当前规范的范围)。
【解决方案3】:

pthread_create 是一个阻塞调用。在调用之前没有(新的)线程将信号发送到,并且有 一个线程在调用之后将信号发送到 ,因此线程的 ID由调用返回。

因此,我得出的结论是线程必须在那时有效并初始化......

【讨论】:

  • 我不关注。我的问题不是关于pthread_create 向新线程发送信号,而是关于其他信号(由同一进程、其他进程或内核中的其他线程生成)在初始化之前传递给新线程。
  • 这不是真的,pthread_create (通常)不是原子调用。
最近更新 更多