原文地址:
http://blog.chinaunix.net/space.php?uid=20491906&do=blog&cuid=322733
由于cublog系統(tǒng)的緣故,將前段時(shí)間寫的一篇blog文章再次貼上。
可重入函數(shù)這一概念早有接觸,但一直未有系統(tǒng)的理解,最近閱讀《APUE》信號(hào)一章時(shí),其中講解很到位,故總結(jié)如下。
信號(hào)作為一種軟中斷,能夠被進(jìn)程給捕獲,因而也就中斷進(jìn)程的正常執(zhí)行,轉(zhuǎn)而去執(zhí)行信號(hào)處理程序,最后再返回到原進(jìn)程繼續(xù)正常執(zhí)行。然而,當(dāng)進(jìn)程正在執(zhí)行malloc()動(dòng)態(tài)內(nèi)存分配時(shí),信號(hào)產(chǎn)生從而轉(zhuǎn)入到信號(hào)處理程序,但當(dāng)信號(hào)處理程序中也用到了malloc()函數(shù)時(shí),問題就出來了?因?yàn)?/span>malloc()通常維護(hù)一個(gè)所有已分配內(nèi)存鏈表,當(dāng)信號(hào)發(fā)生時(shí),進(jìn)程可能正在修改鏈表指針,這時(shí)在信號(hào)處理程序中將又一次修改鏈表。當(dāng)然類似的情況還有不少,下文中將會(huì)談到。
因此,在進(jìn)行上層應(yīng)用程序設(shè)計(jì)過程中我們就必須明確哪些函數(shù)是可重入性函數(shù)(reentrant functions)。可重入性函數(shù)通常也一定能夠在信號(hào)處理程序(signal handler)中被調(diào)用。
圖1 能夠在信號(hào)處理程序中調(diào)用的可重入性函數(shù)(節(jié)自《APUE》)
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 |
縱觀上表,我們可以看出,有不少系統(tǒng)調(diào)用函數(shù)并沒有出現(xiàn),換言之也就是非可重入性函數(shù)。函數(shù)不可重入的原因主要如下:
<!--[if !supportLists]-->(1) <!--[endif]-->函數(shù)使用了static靜態(tài)數(shù)據(jù)結(jié)構(gòu)
如:struct passwd *getpwuid(uid_t uid);
struct passwd *getpwnam(const char *name);
struct passwd *getpwent(void);
以上3個(gè)函數(shù)都是返回一個(gè)指向passwd結(jié)構(gòu)的指針,而該passwd結(jié)構(gòu)通常都是函數(shù)中static變量,其內(nèi)容在每次調(diào)用以上函數(shù)時(shí)都會(huì)被重寫。因此,當(dāng)進(jìn)程主程序與信號(hào)處理程序中均調(diào)用了以上函數(shù)時(shí),沖突就產(chǎn)生了。
<!--[if !supportLists]-->(2) <!--[endif]-->函數(shù)調(diào)用了malloc和free函數(shù),正如文章最開始所提到的;
<!--[if !supportLists]-->(3) <!--[endif]-->函數(shù)為標(biāo)準(zhǔn)I/O的庫函數(shù),因?yàn)榇蠖鄶?shù)的標(biāo)準(zhǔn)I/O庫函數(shù)的實(shí)現(xiàn)都使用了global全局?jǐn)?shù)據(jù)結(jié)構(gòu);
因此,若要寫可重入性函數(shù)的做法通常是我們?cè)诤瘮?shù)中只修改局部變量,而不改變?nèi)肿兞浚虮M量不使用全局變量、靜態(tài)static變量。
事實(shí)上,與可重入性函數(shù)(reentrant function)對(duì)應(yīng)的還有可重入內(nèi)核(reentrant kernel),其區(qū)別和聯(lián)系在《深入理解Linux內(nèi)核》上有較詳細(xì)的講解。