可重入和不可重入函數

重入一般可以理解爲一個函數在同時多次調用,例如操作系統在進程調度過程中,或者單片機、處理器等的中斷的時候會發生重入的現象。
一般浮點運算都是由專門的硬件來完成,舉個例子假設有個硬件寄存器名字叫做FLOAT,用來計算和存放浮點數的中間運算結果
假設有這麼個函數
void fun()
{
//...這個函數對FLOAT寄存器進行操作
}
假如第一次執行,有個對浮點數操作運算的結果臨時存在FLOAT寄存器中,而就在這時被中斷了,而中斷函數或者另一個進程也調用fun函數,這時第二次調用的fun函數在執行的過程中就會破壞第一次FLOAT寄存器中的結果,這樣當返回到第一次fun函數的時候,結果就不正確了。

可以把fun函數理解爲printf()函數。
贊同

(轉)可重入和不可重入
2011-10-04 21:38

這種情況出現在多任務系統當中,在任務執行期間捕捉到信號並對其進行處理時,進程正在執行的指令序列就被信號處理程序臨時中斷。如果從信號處理程序返回,則繼續執行進程斷點處的正常指令序列,從重新恢復到斷點重新執行的過程中,函數所依賴的環境沒有發生改變,就說這個函數是可重入的,反之就是不可重入的。
衆所周知,在進程中斷期間,系統會保存和恢復進程的上下文,然而恢復的上下文僅限於返回地址,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後綴的同名可重入函數版本。如果實在沒有,不妨在可預見的發生錯誤的地方嘗試加上保護鎖同步機制等等。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章