什么是可重入函數(shù)
可重入的概念
若一個(gè)程序或子程序可以“在任意時(shí)刻被中斷,然后操作系統(tǒng)調(diào)度執(zhí)行另外一段代碼,這段代碼又調(diào)用了該子程序不會(huì)出錯(cuò)”,則稱其為可重入(reentrant或re-entrant)的。
也就是說,當(dāng)該子程序正在運(yùn)行時(shí),執(zhí)行線程可以再次進(jìn)入并執(zhí)行它,仍然獲得符合設(shè)計(jì)時(shí)預(yù)期的結(jié)果。與多線程并發(fā)執(zhí)行的線程安全不同,可重入強(qiáng)調(diào)對(duì)單個(gè)線程執(zhí)行時(shí),重新進(jìn)入同一個(gè)子程序,仍然是安全的。
可重入函數(shù)可以簡(jiǎn)單地理解為: 函數(shù)可以在任意時(shí)刻被中斷并再次被調(diào)用(即重入),稍后再繼續(xù)執(zhí)行被中斷的那次調(diào)用,而不會(huì)丟失數(shù)據(jù)。
不可重入
為便于理解可重入的概念,先來考察一個(gè)不可重入的例子:
我們現(xiàn)在有一個(gè)鏈表,insert()函數(shù)可向鏈表中插入一個(gè)結(jié)點(diǎn)

- 在正在調(diào)用
insert()函數(shù)插入結(jié)點(diǎn)node1時(shí)(假設(shè)此時(shí)剛完成node1的指針指向,圖中1所示), - 被
sighandler()函數(shù)中斷,并在sighandler()函數(shù)中再次調(diào)用insert()函數(shù)向鏈表插入node2, - 完成后,此時(shí)
head指向了node2, - 繼續(xù)進(jìn)行被中斷的插入動(dòng)作(即繼續(xù)插入
node1),則完成該動(dòng)作之后head將指向node1,這樣就導(dǎo)致中間插入的node2丟失。
因此,函數(shù)insert()是不可重入的。
可重入函數(shù)的條件
若一個(gè)函數(shù)是可重入的,則該函數(shù)應(yīng)當(dāng)滿足下述條件:
- 不能含有靜態(tài)(全局)非常量數(shù)據(jù)。
- 不能返回靜態(tài)(全局)非常量數(shù)據(jù)的地址。
- 只能處理由調(diào)用者提供的數(shù)據(jù)。
- 不能依賴于單實(shí)例模式資源的鎖。
- 調(diào)用的函數(shù)也必需是可重入的。
上述條件就是要求可重入函數(shù)使用的所有變量都保存在調(diào)用棧的當(dāng)前函數(shù)幀(frame)上。因此,同一執(zhí)行線程重入執(zhí)行該函數(shù)時(shí) 加載了新的函數(shù)幀,與前一次執(zhí)行該函數(shù)時(shí)使用的函數(shù)幀不沖突、不互相覆蓋,從而保證了可重入執(zhí)行安全。
多“用戶/對(duì)象/進(jìn)程優(yōu)先級(jí)”以及多進(jìn)程,一般會(huì)使得對(duì)可重入代碼的控制變得復(fù)雜。同時(shí),IO代碼通常不是可重入的,因?yàn)樗麄円蕾囉谙翊疟P這樣共享的、單獨(dú)的資源(類似編程中的靜態(tài)Static、全域Global資源)。
因此,如果函數(shù)體內(nèi)調(diào)用了標(biāo)準(zhǔn)I/O函數(shù),則該函數(shù)是不可重入的。
可重入出現(xiàn)的背景
可重入概念是在單線程操作系統(tǒng)的時(shí)代提出的。
這也使得可重入函數(shù)未必是線程安全的;線程安全函數(shù)未必是可重入的。
一個(gè)子程序的重入,可能由于自身原因,如執(zhí)行了jmp或者call,類似于子程序的遞歸調(diào)用;或者由于操作系統(tǒng)的中斷響應(yīng)。UNIX系統(tǒng)的signal的處理,即子程序被中斷處理程序或者signal處理程序調(diào)用。所以,可重入也可稱作“異步信號(hào)安全”。這里的異步是指信號(hào)中斷可發(fā)生在任意時(shí)刻。 重入的子程序,按照后進(jìn)先出線性序依次執(zhí)行。
示例
下面兩個(gè)函數(shù)f1()和f2()都不是可重入函數(shù):
int global_var = 1;
int f1()
{
global_var += 2;
return global_var;
}
int f2()
{
return f1() + 2;
}
原因分析
函數(shù)f1()使用了全局變量global_var,所以 如果兩個(gè)或多個(gè)線程同時(shí)執(zhí)行它并訪問global_var,則返回的結(jié)果取決于執(zhí)行的時(shí)間。因此,f1()不可重入。而函數(shù)f2()調(diào)用了f1(),所以它也不可重入。
修改為可重入函數(shù)
不再使用全局變量,而是改為以參數(shù)的形式接收輸入,則兩個(gè)函數(shù)都是可重入的:
int f1(int i)
{
return i + 2;
}
int f2(int i)
{
return f1(i) + 2;
}
Linux提供的可重入函數(shù)
slot@slot-ubt:~$ man 7 signal
Async-signal-safe functions
A signal handler function must be very careful, since
processing elsewhere may be interrupted at some arbi‐
trary 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 Cor‐
rigendum 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()
POSIX.1-2008 Technical Corrigendum 1 (2013) adds the
following functions:
fchdir()
pthread_kill()
pthread_self()
pthread_sigmask()
聲明:本文部分整理自維基百科:可重入,本文所有文字遵從知識(shí)共享 署名-相同方式共享 3.0協(xié)議