分类:
2008-12-08 11:43:21
10.6 reentrant functions
这个话题总是与signal关联在一起。
发生reentrant问题的情况:
1.内存分配,或者处理全局数据时
当我们在malloc一段内存,当我们再用getpwnam来获取用户帐户的信息(这个函数会使用static缓冲),总之当我们在处理一段全局的数据结构的时候(这里的全局值得不仅仅是具有全局的作用域,还包括了全局的生命周期比如static缓冲),我们被一个signal打断,signal handler执行了,在这个handler内部也对该数据进行操作,这个数据就乱套了。Malloc维护的page的list会乱套,getpwnam的static 缓冲会乱套。
2.使用标准库
我们知道,printf等standard i/o library其实使用了自己的全局的缓冲,所以,当我们调用printf的时候,如果收到一个signal, 那么,如果,signal handler里面调用了printf,也会将standard i/o library的缓冲搅乱。
3.Errno
Errno本身是个全局量,所以说,你甚至可以认为说有使用了errno的函数都不是可重入的函数。当然这也要求太高了,所以,有一个建议,在你的signal handler里面,如果有操作要改变errno,那么在signal handler的开头,将errno保存一下,在退出handler之前,将其restore。
4.Longjmp和siglongjmp的作用
我们在处理一个全局数据,此时signal来了,在signal handler里面,没有return,而是调用longjmp或者siglongjmp调到别处执行去了,这样就会使正在被处理的全局数据成处理到了一半。如果我们的signal handler里面的确一定要调用siglongjmp这样的函数,那么我们应该保证我们处理全局数据的时候,该signal不会发生,即我们可以将该signal block一会儿。
上述函数都不是可reentrant的函数。
Single unix specification指定如下函数应该是reentrant函数:
|
Figure 10.4. Reentrant functions that may be called
from a signal handler |
||||
|
accept |
fchmod |
lseek |
sendto |
stat |
|
access |
fchown |
lstat |
setgid |
symlink |
|
aio_error |
fcntl |
mkdir |
setpgid |
sysconf |
|
aio_return |
fdatasync |
mkfifo |
setsid |
tcdrain |
|
aio_suspend |
fork |
open |
setsockopt |
tcflow |
|
alarm |
fpathconf |
pathconf |
setuid |
tcflush |
|
bind |
fstat |
pause |
shutdown |
tcgetattr |
|
cfgetispeed |
fsync |
pipe |
sigaction |
tcgetpgrp |
|
cfgetospeed |
ftruncate |
poll |
sigaddset |
tcsendbreak |
|
cfsetispeed |
getegid |
posix_trace_event |
sigdelset |
tcsetattr |
|
cfsetospeed |
geteuid |
pselect |
sigemptyset |
tcsetpgrp |
|
chdir |
getgid |
raise |
sigfillset |
time |
|
chmod |
getgroups |
read |
sigismember |
timer_getoverrun |
|
chown |
getpeername |
readlink |
signal |
timer_gettime |
|
clock_gettime |
getpgrp |
recv |
sigpause |
timer_settime |
|
close |
getpid |
recvfrom |
sigpending |
times |
|
connect |
getppid |
recvmsg |
sigprocmask |
umask |
|
creat |
getsockname |
rename |
sigqueue |
uname |
|
dup |
getsockopt |
rmdir |
sigset |
unlink |
|
dup2 |
getuid |
select |
sigsuspend |
utime |
|
execle |
kill |
sem_post |
sleep |
wait |
|
execve |
link |
send |
socket |
waitpid |
|
_Exit & _exit |
listen |
sendmsg |
socketpair |
write |
下面是一个例子,它使用每秒钟一次的alarm signal,调用getpwname函数:
#include "apue.h"#include static voidmy_alarm(int signo){ struct passwd *rootptr; printf("in signal handler\n"); if ((rootptr = getpwnam("root")) == NULL) err_sys("getpwnam(root) error"); alarm(1);} intmain(void){ struct passwd *ptr; signal(SIGALRM, my_alarm); alarm(1); for ( ; ; ) { if ((ptr = getpwnam("sar")) == NULL) err_sys("getpwnam error"); if (strcmp(ptr->pw_name, "sar") != 0) printf("return value corrupted!, pw_name = %s\n", ptr->pw_name); }}
过一段时间,就会出错,甚至会出现SIGSEGV。