分叉时哪些系统和库调用是安全的?

What system and library calls are safe while forking?

我正在研究一些代码,这些代码本质上实现了一个也调用 fork 的多线程守护进程,我确信这样做并不安全。重写是理想的方案,但我也在研究修改它以使其安全的最佳方法,这个想法很简单:

我可以找出我们自己代码中的不安全之处,但我不知道系统代码中的不安全之处。为此,我想知道在标准 libc 和系统调用的某个地方是否有一个详尽的列表,它们隐式地获取了引擎盖下的互斥锁。

signal(2) 联机帮助页中有一个系统调用列表,可以安全地调用信号处理程序:

       _Exit()  _exit()  abort()  accept()  access()  aio_error() aio_return()
       aio_suspend() alarm() bind() cfgetispeed() cfgetospeed()  cfsetispeed()
       cfsetospeed() chdir() chmod() chown() clock_gettime() close() connect()
       creat() dup() dup2() execle() execve() fchmod() fchown() fcntl() fdata-
       sync()   fork()   fpathconf()  fstat()  fsync()  ftruncate()  getegid()
       geteuid() getgid() getgroups() getpeername() getpgrp()  getpid()  getp-
       pid()   getsockname()  getsockopt()  getuid()  kill()  link()  listen()
       lseek() lstat()  mkdir()  mkfifo()  open()  pathconf()  pause()  pipe()
       poll()  posix_trace_event()  pselect() raise() read() readlink() recv()
       recvfrom()  recvmsg()  rename()  rmdir()  select()  sem_post()   send()
       sendmsg()  sendto()  setgid()  setpgid() setsid() setsockopt() setuid()
       shutdown()  sigaction()  sigaddset()  sigdelset()  sigemptyset()   sig-
       fillset()  sigismember() signal() sigpause() sigpending() sigprocmask()
       sigqueue() sigset() sigsuspend() sleep() socket()  socketpair()  stat()
       symlink()  sysconf()  tcdrain()  tcflow() tcflush() tcgetattr() tcgetp-
       grp() tcsendbreak() tcsetattr() tcsetpgrp()  time()  timer_getoverrun()
       timer_gettime()   timer_settime()   times()  umask()  uname()  unlink()
       utime() wait() waitpid() write()

而且我认为,由于这些可以安全地调用信号处理程序,因此它们不会尝试获取任何互斥锁或执行不可重入的操作。

我只是假设所有其他系统调用都是不安全的吗? libc 呢,我从 other threads 了解到,例如 malloc 会在后台进行一些锁定,某处是否有明确的列表?

编辑:提供一些背景来说明我为什么要问这个问题。

我认为您担心的是,如果您从线程 A 调用 fork(),则线程 B 可能位于包含互斥锁的库函数中。新进程将只有一个线程 运行(线程 A 的克隆),并且永远不会删除互斥量。如果你之后立即调用 exec(),你是安全的,因为互斥量(连同剩余的内存)将被清除。如果您之后不调用 exec(),那么这是一个有效的问题。但是,库作者应该知道它,并且应该使用 pthread_atfork 或类似的方式围绕它进行编码。

来自 pthread_atfork 的文档:

To understand the purpose of pthread_atfork, recall that fork(2) duplicates the whole memory space, including mutexes in their current locking state, but only the calling thread: other threads are not running in the child process. The mutexes are not usable after the fork and must be initialized with pthread_mutex_init in the child process. This is a limitation of the current implementation and might or might not be present in future versions.

因此,只要库编写得当,您就不必担心它是否具有互斥体。事实上,一些使用库的代码甚至不知道该库使用线程,并且本身并没有链接到线程库,因此 必须 是这种情况。