這種情況出現在多任務系統當中,在任務執行期間捕捉到訊號並對其進行處理時,進程正在執行的指令序列就被訊號處理常式臨時中斷。如果從訊號處理常式返回,則繼續執行進程斷點處的正常指令序列,從重新恢複到斷點重新執行的過程中,函數所依賴的環境沒有發生改變,就說這個函數是可重新進入的,反之就是不可重新進入的。
眾所周知,在進程中斷期間,系統會儲存和恢複進程的上下文,然而恢複的上下文僅限於返回地址,cpu寄存器等之類的少量上下文,而函數內部使用的諸如全域或靜態變數,buffer等並不在保護之列,所以如果這些值在函數被中斷期間發生了改變,那麼當函數回到斷點繼續執行時,其結果就不可預料了。打個比方,比如malloc,將如一個進程此時正在執行malloc分配堆空間,此時程式捕捉到訊號發生中斷,執行訊號處理常式中恰好也有一個malloc,這樣就會對進程的環境造成破壞,因為malloc通常為它所分配的儲存區維護一個連結資料表,插入執行訊號處理函數時,進程可能正在對這張表進行操作,而訊號處理函數的調用剛好覆蓋了進程的操作,造成錯誤。
滿足下麵條件之一的多數是不可重新進入函數:
(1)使用了待用資料結構;
(2)調用了malloc或free;
(3)調用了標準I/O函數;標準io庫很多實現都以不可重新進入的方式使用全域資料結構。
(4)進行了浮點運算.許多的處理器/編譯器中,浮點一般都是不可重新進入的 (浮點運算大多使用副處理器或者軟體類比來實現。
1) 訊號處理常式A內外都調用了同一個不可重新進入函數B;B在執行期間被訊號打斷,進入A (A中調用了B),完事之後返回B被中斷點繼續執行,這時B函數的環境可能改變,其結果就不可預料了。
2) 多線程共用進程內部的資源,如果兩個線程A,B調用同一個不可重新進入函數F,A線程進入F後,線程調度,切換到B,B也執行了F,那麼當再次切換到線程A時,其調用F的結果也是不可預料的。
在訊號處理常式中即使調用可重新進入函數也有問題要注意。作為一個通用的規則,當在訊號處理常式中調用可重新進入函數時,應當在其前儲存errno,並在其後恢複errno。(因為每個線程只有一個errno變數,訊號處理函數可能會修改其值,要瞭解經常被捕捉到的訊號是SIGCHLD,其訊號處理常式通常要調用一種wait函數,而各種wait函數都能改變errno。)
可重新進入函數列表:
_exit()、 access()、alarm()、cfgetispeed()、cfgetospeed()、cfsetispeed()、cfsetospeed ()、chdir()、chmod()、chown()、close()、creat()、dup()、dup2()、execle()、 execve()、fcntl()、fork()、fpathconf ()、fstat()、fsync()、getegid()、 geteuid()、getgid()、getgroups()、getpgrp()、getpid()、getppid()、getuid()、 kill()、link()、lseek()、mkdir()、mkfifo()、 open()、pathconf()、pause()、pipe()、raise()、read()、rename()、rmdir()、setgid ()、setpgid()、setsid()、setuid()、 sigaction()、sigaddset()、sigdelset()、sigemptyset()、sigfillset()、 sigismember()、signal()、sigpending()、sigprocmask()、sigsuspend()、sleep()、 stat()、sysconf()、tcdrain()、tcflow()、tcflush()、tcgetattr()、tcgetpgrp()、 tcsendbreak()、tcsetattr()、tcsetpgrp()、time()、times()、 umask()、uname()、unlink()、utime()、wait()、waitpid()、write()。
書上關於訊號處理常式中調用不可重新進入函數的例子:
#include <stdlib.h>
#include <stdio.h>
#include <pwd.h>
static void func(int signo)
{
struct passwd *rootptr;
if( ( rootptr = getpwnam( "root" ) ) == NULL )
{
err_sys( "getpwnam error" );
}
signal(SIGALRM,func);
alarm(1);
}
int main(int argc, char** argv)
{
signal(SIGALRM,func);
alarm(1);
for(;;)
{
if( ( ptr = getpwnam("sar") ) == NULL )
{
err_sys( "getpwnam error" );
}
}
return 0;
}
signal了一個SIGALRM,而後設定一個定時器,在for函數運行期間的某個時刻,也許就是在getpwnam函數運行期間,相應訊號發生中斷,進入訊號處理函數func,在運行func期間又收到alarm發出的訊號,getpwnam可能再次中斷,這樣就很容易發生不可預料的問題。
=================================================================================
不可重新進入函數不可以在它還沒有返回就再次被調用。例如printf,malloc,free等都是不可重新進入函數。因為中斷可能在任何時候發生,例如在printf執行過程中,因此不能在中斷處理函數裡調用printf,否則printf將會被重入。
函數不可重新進入大多數是因為在函數中引用了全域變數。例如,printf會引用全域變數stdout,malloc,free會引用全域的記憶體配置表。
個人理解:如果中斷髮生的時候,當運行到printf的時候,假設發生了中斷嵌套,而此時stdout資源被佔用,所以第二個中斷printf等待第一個中斷的stdout資源釋放,第一個中斷等待第二個中斷返回,造成了死結,不知這樣理解對不對。
不可重新進入函數指的是該函數在被調用還沒有結束以前,再次被調用可能會產生錯誤。可重新進入函數不存在這樣的問題。
不可重新進入函數在實現時候通常使用了全域的資源,在多線程的環境下,如果沒有很好的處理資料保護和互斥訪問,就會發生錯誤。
常見的不可重新進入函數有:
printf --------引用全域變數stdout
malloc --------全域記憶體配置表
free --------全域記憶體配置表
在unix裡面通常都有加上_r尾碼的同名可重新進入函數版本。如果實在沒有,不妨在可預見的發生錯誤的地方嘗試加上保護鎖同步機制等等。