在线程程序中,fork(应该是)对信号处理程序是否安全?

时间:2010-12-15 19:08:43

标签: c pthreads deadlock fork signals

我真的不确定POSIX在存在线程和信号时对fork的安全性的要求。 fork被列为异步信号安全函数之一,但如果库代码有可能注册了pthread_atfork处理程序,这些处理程序不是异步信号安全的,那么这会否定安全性fork?答案取决于运行信号处理程序的线程是否可以使用atfork处理程序所需的资源?或者换句话说,如果atfork处理程序使用同步资源(互斥体等),但是从一个永远不会访问这些资源的线程中执行的信号处理程序调用{​​{1}},程序是否符合要求?

基于这个问题,如果使用fork建议的习语在系统库内部实现“线程安全”分叉(获取prefork处理程序中的所有锁并释放父和子中的所有锁) postfork handlers),那么pthread_atfork是否可以安全地在线程程序中使用信号处理程序?处理信号的线程是否可能在调用forkmalloc / fopen并持有全局锁定的过程中,导致{{1}期间出现死锁}}?

最后,即使fclose在信号处理程序中是安全的,信号处理程序中的fork是安全的,然后从信号处理程序返回,或者在fork中调用fork在信号处理程序返回之前,信号处理程序总是需要后续调用fork_exit系列函数?

5 个答案:

答案 0 :(得分:12)

尽我所能回答所有子问题;我很抱歉,其中一些比理想情况更为模糊:

  

如果有   库代码有可能   注册pthread_atfork处理程序   这不是异步信号安全的   否定了叉子的安全性?

是。 fork documentation明确提到了这一点:

   When the application calls fork() from a signal handler and any of the
   fork handlers registered by pthread_atfork() calls a function that is
   not asynch-signal-safe, the behavior is undefined.

当然,这意味着您实际上不能将pthread_atfork()用于使多线程库对于认为它们是单线程的进程透明的目的,因为没有任何pthread同步函数是异步的 - 信号安全;这是规范中的缺陷,请参阅http://www.opengroup.org/austin/aardvark/latest/xshbug3.txt(搜索“L16723”)。

  

是否   答案取决于是否   信号处理程序所在的线程   跑步可能在中间   使用atfork的资源   处理程序需要吗?或者说不同   如果atfork处理程序使用的方式   同步资源(互斥量,   等)但是从a调用fork   在一个执行的信号处理程序   永远不会访问这些的线程   资源,程序符合吗?

严格来说答案是否定的,因为根据规范,功能要么是异步信号安全的,要么是不是;没有“在某些情况下安全”的概念。在实践中,你可能会侥幸逃脱它,但是你很容易受到一种笨重但正确的实现的影响,而这种实现并没有像你期望的那样对资源进行分区。

  

基于这个问题,如果   实现“线程安全”分叉   在系统库内部使用   pthread_atfork建议的习语   (获取prefork中的所有锁   处理程序并释放两者中的所有锁   父母和孩子的postfork   处理程序),然后叉是安全的   在线程中使用信号处理程序   程序?是不是有可能   处理信号的线程可能在   调用malloc或者中间   fopen / fclose并持有全球   锁定,导致死锁   叉?

如果以这种方式实现,那么你是对的,来自信号处理程序的fork()永远不会是安全的,因为如果调用线程已经持有它,尝试获取锁可能会死锁。但这意味着使用这种方法的实现将不符合要求。

以glibc为例,它不会这样做 - 相反,它需要两种方法:首先,它确实获得的锁是递归的(所以如果当前线程已经拥有它们,它们的锁定数将只是增加);此外,在子进程中,它只是单方面覆盖所有锁 - 请参阅nptl/sysdeps/unix/sysv/linux/fork.c的摘录:

  /* Reset the file list.  These are recursive mutexes.  */
  fresetlockfiles ();

  /* Reset locks in the I/O code.  */
  _IO_list_resetlock ();

  /* Reset the lock the dynamic loader uses to protect its data.  */
  __rtld_lock_initialize (GL(dl_load_lock));

其中每个resetlocklock_initialize函数最终调用glibc的内部等价物pthread_mutex_init(),无论是否有任何服务员,都会有效地重置互斥锁。

我认为理论是,通过获得(递归)锁定,可以保证没有其他线程会触及数据结构(至少以可能导致崩溃的方式),然后重置单个锁定可确保资源不会永久阻止。 (重置当前线程的锁是安全的,因为现在没有其他线程可以争用数据结构,实际上直到使用锁返回的任何函数都没有。)

我不是100%确信这涵盖了所有可能性(尤其是因为如果/当信号处理程序返回时,刚刚锁定其锁定的功能将尝试解锁它,并且内部递归解锁功能不会防止解锁太多次!) - 但似乎可以在异步信号安全的递归锁上构建一个可行的方案

  

最后,即使fork是安全的   信号处理程序,是否可以安全地进入   一个信号处理程序然后从中返回   信号处理程序,或者调用   总是在信号处理程序中分叉   需要随后调用_exit   或者一个exec系列的函数   在信号处理程序返回之前?

我假设你在谈论孩子的过程? (如果fork()是异步信号安全意味着什么,那么它应该可以在父母中返回!)

没有在规范中发现任何其他情况(尽管我可能已经错过了)我认为应该安全 - 至少,'安全'从信号处理程序返回的意义上虽然多线程进程刚刚分叉的事实可能意味着exec*()_exit()可能是最安全的行为,但在子进程中并不意味着未定义的行为本身。 / p>

答案 1 :(得分:4)

我正在添加此答案,因为看起来fork()可能不再被视为异步安全。至少glibc似乎就是这种情况,但POSIX中可能不再存在支持。目前标记为“已接受”的答案似乎得出结论认为它是安全的,但至少在glibc中可能并非如此。

  

在这个问题的基础上,如果使用pthread_atfork建议的习语在系统库内部实现“线程安全”分叉(获取prefork处理程序中的所有锁并释放父和子postfork处理程序中的所有锁),那么fork是否可以安全地在线程程序中使用信号处理程序?处理信号的线程是否可能在调用malloc或fopen / fclose并持有全局锁的过程中,导致fork期间出现死锁?

事实上!从The Open Group resolved to remove it列表中看起来好像是this very reason

IEEE 1003.1c-1995解释请求#37关于pthread_atfork

  

解释委员会认为......下面的解释性补充:Pg 78第864行“此外,pthread_atfork从一个从信号处理程序调用的分支中设置的fork处理程序的调用需要是异步安全的。”

glibc Bug 4737确定了fork()从异步安全功能列表中逐出的解决方案,posix_spawn()用于填补其位置。不幸的是,它被解决为WONTFIX,因此即使是联机帮助页也没有更新。

答案 2 :(得分:2)

在信号处理程序中使用fork()应该没问题。

pthread_atfork听起来好像是个坏主意。

为了回答你原来的问题,pthread不能保证调用任何pthread_atfork函数的安全是异步信号安全的,因为内核的信号实现使得这是不可能的。

哦,如果你在信号处理程序中分叉,不要让孩子从信号处理程序返回。这是未定义的。

答案 3 :(得分:0)

据我所知fork source in glibc,它使用信号状态临界区来确保分叉过程不会被信号中断。

  ss = _hurd_self_sigstate ();
  __spin_lock (&ss->critical_section_lock);

pthread_atfork处理程序在临界区锁定后执行时 - 它们会自动变为信号安全。

可能我错了,我会感谢你的修正。

答案 4 :(得分:0)

下面 https://www.securecoding.cert.org/confluence/display/seccode/SIG30-C.+Call+only+asynchronous-safe+functions+within+signal+handlers

fork被列为Async-signal safe,因此可以使用它。

  

POSIX

     

下表来自Open   集团基本规格[开放集团   2004],定义了一组函数   是异步信号安全的。   应用程序可以调用这些   功能,没有限制,来自   信号处理程序。

     

异步信号安全功能

     

<强>叉()