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

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

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

最后,即使fork在信号处理程序中是安全的,在信号处理程序中fork然后从信号处理程序返回是安全的,或者在信号处理程序中调用fork总是需要后续调用_exit或其中一个exec信号处理程序返回之前的函数族?

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

如果库代码有可能注册了非异步信号安全的pthread_atfork处理程序,这会否定fork的安全性吗?

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

  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()用于使multithreading库对于认为它们是单线程的进程透明的目的,因为没有任何pthread同步函数是异步信号安全的。 这是规范中的缺陷,请参见http://www.opengroup.org/austin/aardvark/latest/xshbug3.txt (搜索“L16723”)。

答案取决于运行信号处理程序的线程是否正在使用atfork处理程序所需的资源? 或者换句话说,如果atfork处理程序使用同步资源(互斥体等),但是从一个永远不会访问这些资源的线程中执行的信号处理程序调用fork,程序是否符合要求?

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

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

如果以这种方式实现,那么你是对的,来自信号处理程序的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%确信这涵盖了所有可能性(尤其是因为如果/当信号处理程序返回时,刚被锁定的函数将尝试解锁它,并且内部递归解锁function无法防止解锁太多次!) – 但似乎可以在异步信号安全的递归锁之上构建一个可行的方案。

最后,即使fork在信号处理程序中是安全的,在信号处理程序中分叉然后从信号处理程序返回是安全的,或者在信号处理程序中调用fork也总是需要后续调用_exit或其中一个exec信号处理程序返回之前的函数族?

我假设你在谈论孩子的过程? (如果fork()是async-signal-safe意味着什么,那么它应该可以在父级中返回!)

没有在规范中发现任何其他情况(虽然我可能已经错过了)我相信它应该是安全的 – 至少,’安全’在某种意义上说,从孩子的信号处理程序返回并不意味着未定义的行为虽然multithreading进程刚刚分叉的事实可能意味着exec*()_exit()可能是最安全的行动方案。

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

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

确实! 由于这个原因 ,看起来Open Group决定将其从列表中删除 。

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

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

glibc错误4737标识了一个解析, fork()从异步安全函数列表中被逐出, posix_spawn()用于填充它的位置。 不幸的是,它被解决为WONTFIX因此甚至连WONTFIX没有更新。

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

pthread_atfork听起来像个坏主意。

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

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

正如我从glibc中的fork源理解的那样,它使用信号状态临界区来确保分叉过程不会被信号中断。

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

由于pthread_atfork处理程序在关键部分锁定后执行 – 它们会自动变为信号安全。

可能是我错了,我会很感激修正。

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

fork被列为异步信号安全,因此可以使用它。

POSIX

Open Group Base Specifications [Open Group 2004]中的下表定义了一组异步信号安全的函数。 应用程序可以无限制地从信号处理程序调用这些函数。

异步信号安全function

叉子()