重入一般可以理解爲一個函數在同時多次調用,例如操作系統在進程調度過程中,或者單片機、處理器等的中斷的時候會發生重入的現象。 一般浮點運算都是由專門的硬件來完成,舉個例子假設有個硬件寄存器名字叫做FLOAT,用來計算和存放浮點數的中間運算結果 假設有這麼個函數 void fun() { //...這個函數對FLOAT寄存器進行操作 } 假如第一次執行,有個對浮點數操作運算的結果臨時存在FLOAT寄存器中,而就在這時被中斷了,而中斷函數或者另一個進程也調用fun函數,這時第二次調用的fun函數在執行的過程中就會破壞第一次FLOAT寄存器中的結果,這樣當返回到第一次fun函數的時候,結果就不正確了。 可以把fun函數理解爲printf()函數。
這種情況出現在多任務系統當中,在任務執行期間捕捉到信號並對其進行處理時,進程正在執行的指令序列就被信號處理程序臨時中斷。如果從信號處理程序返回,則繼續執行進程斷點處的正常指令序列,從重新恢復到斷點重新執行的過程中,函數所依賴的環境沒有發生改變,就說這個函數是可重入的,反之就是不可重入的。
衆所周知,在進程中斷期間,系統會保存和恢復進程的上下文,然而恢復的上下文僅限於返回地址,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的第二次調用可能會覆蓋第一次的輸出。死鎖的話還沒研究啦。
不可重入函數指的是該函數在被調用還沒有結束以前,再次被調用可能會產生錯誤。可重入函數不存在這樣的問題。
不可重入函數在實現時候通常使用了全局的資源,在多線程的環境下,如果沒有很好的處理數據保護和互斥訪問,就會發生錯誤。
常見的不可重入函數有:
printf --------引用全局變量stdout
malloc --------全局內存分配表
free --------全局內存分配表
在unix裏面通常都有加上_r後綴的同名可重入函數版本。如果實在沒有,不妨在可預見的發生錯誤的地方嘗試加上保護鎖同步機制等等。