我的理解是,通常,如果您从信号处理程序调用非异步信号安全函数,则行为是未定义的,但我听说 linux 允许您安全地调用任何系统调用。这是真的?此外,SIGSEGV 处理程序的唯一可移植行为是中止或退出,但我知道如果您返回,linux 实际上会恢复执行,对吗?
3 回答
有关可以从信号处理程序内部安全调用的异步信号安全函数列表,请参见 signal(7)。
第 7 节signals
手册列出了以下函数和/或系统调用以及非常清晰的描述:
异步信号安全功能
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 calls an unsafe function, then the behavior of
the program is undefined.
POSIX.1-2004 (also known as POSIX.1-2001 Technical Corrigendum 2) requires an
implementation to guarantee that the following functions can be safely called
inside a signal handler:
_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()
fdatasync()
fork()
fpathconf()
fstat()
fsync()
ftruncate()
getegid()
geteuid()
getgid()
getgroups()
getpeername()
getpgrp()
getpid()
getppid()
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()
sigfillset()
sigismember()
signal()
sigpause()
sigpending()
sigprocmask()
sigqueue()
sigset()
sigsuspend()
sleep()
sockatmark()
socket()
socketpair()
stat()
symlink()
sysconf()
tcdrain()
tcflow()
tcflush()
tcgetattr()
tcgetpgrp()
tcsendbreak()
tcsetattr()
tcsetpgrp()
time()
timer_getoverrun()
timer_gettime()
timer_settime()
times()
umask()
uname()
unlink()
utime()
wait()
waitpid()
write()
POSIX.1-2008 removes fpathconf(), pathconf(), and sysconf() from the above
list, and adds the following functions:
execl()
execv()
faccessat()
fchmodat()
fchownat()
fexecve()
fstatat()
futimens()
linkat()
mkdirat()
mkfifoat()
mknod()
mknodat()
openat()
readlinkat()
renameat()
symlinkat()
unlinkat()
utimensat()
utimes()
我相信这些信息比我们有时在某处听到的信息更可靠。所以 Linux 确实只允许一些系统调用,但不是全部。所以你的问题的答案很简单——不。
是和否
是的:
您可以在信号处理程序中调用任何真实/原始系统调用。内核有责任确保它是安全的(在内核看来)。
1)内核不知道用户空间的上下文,或者说内核在传递信号时将状态保存到用户空间后故意忘记了它。(注意:执行恢复是由用户通过系统调用在保存状态的帮助下完成的,而不是真正由内核完成,内核已经忘记了)
2)一些线程库是通过单线程实现的,因此线程已经在“信号处理程序”中,但是这些线程可以调用任何系统调用。
不:
但是用户空间函数有其自身的目的和副作用。有些不是重入安全的,这些函数不能从信号处理程序中调用。man 7 signal
将帮助您找出哪些是重新进入安全的。
举个例子,你可以sys_futex()
在任何地方调用,包括信号处理程序,但是如果你sys_futex()
用来实现互斥锁,sys_futex()
当信号中断互斥锁的关键部分时,内部信号处理程序可能会永远阻塞。
此外,SIGSEGV 处理程序的唯一可移植行为是中止或退出,但我知道如果您返回,linux 实际上会恢复执行,对吗?
是的,如果你找不到原因。一些用户可能将 SIGSEGV 用于他们自己的按需映射目的(例如,在 JIT 中,您可以翻译 SIGSEGV 信号处理程序中的代码并将翻译后的代码映射到内存然后返回),他们可以调用 mmap() 或 mprotect () ...ETC。
我相信任何真正的系统调用都可以从信号处理程序中调用。真正的系统调用在<asm/unistd.h>
(或<asm/unistd_64.h>
)中有一个数字。
手册页第 2 节中的一些 posix 函数是通过“多路复用”系统调用实现的,因此在我看来它们不是“真正的系统调用”
从应用程序的角度来看,系统调用是原子操作;它几乎就像一条机器指令(来自应用程序内部)。看到这个答案。
如果您的问题是:处理程序可以通过或SIGSEGV
更改错误的地址映射吗?mprotect
mmap
那么我相信答案是肯定的(至少在 x86-64 和 x86-32 架构上),正如您在引用的问题中所说的,但我没有尝试。我读过这样做效率很低(SIGSEGV
处理速度不是很快,mprotect
或者mmap
也有点慢)。特别是,以这种方式模仿Hurd/Mach 外部寻呼机可能效率低下。