分叉时哪些系统和库调用是安全的?
What system and library calls are safe while forking?
我正在研究一些代码,这些代码本质上实现了一个也调用 fork
的多线程守护进程,我确信这样做并不安全。重写是理想的方案,但我也在研究修改它以使其安全的最佳方法,这个想法很简单:
- 创建读写锁
- 分叉前获取写锁
- 在做任何事情之前获取读锁"unsafe"(可能会获取锁等...)
我可以找出我们自己代码中的不安全之处,但我不知道系统代码中的不安全之处。为此,我想知道在标准 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.
因此,只要库编写得当,您就不必担心它是否具有互斥体。事实上,一些使用库的代码甚至不知道该库使用线程,并且本身并没有链接到线程库,因此 必须 是这种情况。
我正在研究一些代码,这些代码本质上实现了一个也调用 fork
的多线程守护进程,我确信这样做并不安全。重写是理想的方案,但我也在研究修改它以使其安全的最佳方法,这个想法很简单:
- 创建读写锁
- 分叉前获取写锁
- 在做任何事情之前获取读锁"unsafe"(可能会获取锁等...)
我可以找出我们自己代码中的不安全之处,但我不知道系统代码中的不安全之处。为此,我想知道在标准 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 thatfork(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.
因此,只要库编写得当,您就不必担心它是否具有互斥体。事实上,一些使用库的代码甚至不知道该库使用线程,并且本身并没有链接到线程库,因此 必须 是这种情况。