我正在研究一些本质上实现多线程守护程序的代码,该代码也调用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在引擎盖下做了一些锁定,是否有某个确定的列表?
编辑:提供一些背景知道为什么我会问这个问题。
答案 0 :(得分:2)
我认为你担心的是,如果你从线程A调用fork()
,那么线程B可能在一个包含互斥锁的库函数中。然后,新进程将只运行一个线程(线程A的克隆),并且永远不会丢弃互斥锁。如果您之后立即致电exec()
,那么您将是安全的,因为互斥锁(以及其余内存)将被清除。如果您之后没有拨打exec()
,那么这是一个有效的问题。但是,图书馆作者应该了解它,并且应该使用pthread_atfork
或类似的方法对其进行编码。
来自pthread_atfork
的文档:
要理解
pthread_atfork
的目的,请记住fork(2)
复制整个内存空间,包括处于当前锁定状态的互斥锁,但仅限于调用线程:其他线程未在子进程中运行。在fork之后,互斥锁不可用,必须在子进程中使用pthread_mutex_init进行初始化。这是当前实现的限制,可能会或可能不会出现在将来的版本中。
因此,如果库已正确编写,您不必担心它是否具有互斥锁。实际上,一些使用库的代码甚至不知道库使用线程,并且本身并不链接到线程库,所以 就是这种情况。