linux 信号处理程序中的 malloc 导致死锁

malloc inside linux signal handler cause deadlock

首先很抱歉在信号处理程序中调用 malloc :)。我也明白我们不应该在信号处理程序中做任何耗时的 task/this 类讨厌的事情。

但我很想知道它崩溃的原因?

 #0  0x00006e3ff2b60dce in _lll_lock_wait_private () from /lib64/libc.so.6
 #1  0x00006e3ff2aec138 in _L_lock_9164 () from /lib64/libc.so.6
 #2  0x00006e3ff2ae9a32 in malloc () from /lib64/libc.so.6
 #3  0x00006e3ff1f691ad in ?? () from ..

我在 https://access.redhat.com/solutions/48701 中得到了类似的核心报告。

操作系统:RHEL

malloc() 不是可以从信号处理程序安全调用的函数。它不是异步信号安全功能。 所以,你永远不应该从信号处理程序中调用 malloc() 。您只能从信号处理程序调用一组有限的函数。 请参阅 man signal-safety 以获取可以从信号处理程序安全调用的函数列表。

查看您的 GDB 输出,似乎在 malloc() 持有锁时,您再次调用 malloc() 导致死锁。

只能从信号处理程序中安全地调用异步信号安全函数。

根据 the POSIX standard:

Any function not in the above [replicated below]table may be unsafe with respect to signals. Implementations may make other interfaces async-signal-safe. In the presence of signals, all functions defined by this volume of POSIX.1-2008 shall behave as defined when called from or interrupted by a signal-catching function, with the exception that when a signal interrupts an unsafe function or equivalent (such as the processing equivalent to exit() performed after a return from the initial call to main()) and the signal-catching function calls an unsafe function, the behavior is undefined. Additional exceptions are specified in the descriptions of individual functions such as longjmp().

如果您从信号处理程序中调用 "unsafe function","behavior is undefined"。

Linux signal.7 man page 状态:

Async-signal-safe functions

A signal handler function must be very careful, since processing elsewhere may be interrupted at some arbitrary point in the execution of the program. POSIX has the concept of "safe function". If a signal interrupts the execution of an unsafe function, and handler either calls an unsafe function or handler terminates via a call to longjmp() or siglongjmp() and the program subsequently calls an unsafe function, then the behavior of the program is undefined.

Linux 手册页在 Linux 上提供了一个异步信号安全函数列表。它们可能与 POSIX 规范中列出的不同——我没有比较它们,标准和实现确实会随着时间而改变。 上面第一个引述中 POSIX "above table" 中的 "safe functions" 仅包含以下函数:

_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()
execl()
execle()
execv()
execve()
faccessat()
fchdir()
fchmod()
fchmodat()
fchown()
fchownat()
fcntl()
fdatasync()
fexecve()
ffs()
fork()
fstat()
fstatat()
fsync()
ftruncate()
futimens()
getegid()
geteuid()
getgid()
getgroups()
getpeername()
getpgrp()
getpid()
getppid()
getsockname()
getsockopt()
getuid()
htonl()
htons()
kill()
link()
linkat()
listen()
longjmp()
lseek()
lstat()
memccpy()
memchr()
memcmp()
memcpy()
memmove()
memset()
mkdir()
mkdirat()
mkfifo()
mkfifoat()
mknod()
mknodat()
ntohl()
ntohs()
open()
openat()
pause()
pipe()
poll()
posix_trace_event()
pselect()
pthread_kill()
pthread_self()
pthread_sigmask()
raise()
read()
readlink()
readlinkat()
recv()
recvfrom()
recvmsg()
rename()
renameat()
rmdir()
select()
sem_post()
send()
sendmsg()
sendto()
setgid()
setpgid()
setsid()
setsockopt()
setuid()
shutdown()
sigaction()
sigaddset()
sigdelset()
sigemptyset()
sigfillset()
sigismember()
siglongjmp()
signal()
sigpause()
sigpending()
sigprocmask()
sigqueue()
sigset()
sigsuspend()
sleep()
sockatmark()
socket()
socketpair()
stat()
stpcpy()
stpncpy()
strcat()
strchr()
strcmp()
strcpy()
strcspn()
strlen()
strncat()
strncmp()
strncpy()
strnlen()
strpbrk()
strrchr()
strspn()
strstr()
strtok_r()
symlink()
symlinkat()
tcdrain()
tcflow()
tcflush()
tcgetattr()
tcgetpgrp()
tcsendbreak()
tcsetattr()
tcsetpgrp()
time()
timer_getoverrun()
timer_gettime()
timer_settime()
times()
umask()
uname()
unlink()
unlinkat()
utime()
utimensat()
utimes()
wait()
waitpid()
wcpcpy()
wcpncpy()
wcscat()
wcschr()
wcscmp()
wcscpy()
wcscspn()
wcslen()
wcsncat()
wcsncmp()
wcsncpy()
wcsnlen()
wcspbrk()
wcsrchr()
wcsspn()
wcsstr()
wcstok()
wmemchr()
wmemcmp()
wmemcpy()
wmemmove()
wmemset()
write()

malloc 的实现可能正在获取内部 glibc 锁。我们知道信号处理程序是异步调用的。如果线程在正常执行期间有 malloc' 并且被中断以处理信号,那么如果信号处理函数使用 malloc,我们就会遇到问题。信号处理程序 malloc 将尝试获取锁,但它不可用,因为同一个线程在其正常执行期间已获取它。你有僵局。出于这个原因,信号处理程序应该精简,并且不应调用非 AS 安全的函数。

直接回答OP的问题。一些 glibc 包装器(例如 malloc arenas 和 printf 文件访问)使用低级锁来实现并发。信号处理程序进入函数调用,获取 "lll_",被中断,重新进入函数调用并死锁。

可能的解决方案: 1)第一个已经在上面讨论过 2) 不要使用 glibc 包装器——直接进入内核系统调用。例如。不要使用 printf,使用 write。不要使用 glibc malloc,使用 syscall(sbrk...) - 可能不是一个好主意,除非你真的必须...... 3) 不要在处理程序中进行任何动态内存分配,在主任务中分配它并在处理程序中访问它