一、概述
怎么會有可重入和不可重入。
在多任務系統下,中斷可能在任務執行的任何時間發生;如果一個函數的執行期間被中斷后,到重新恢復到斷點進行執行的過程中,函數所依賴的環境沒有發生改變,那么這個函數就是可重入的,否則就不可重入。
在中斷前后不都要保存和恢復上下文嗎,怎么會出現函數所依賴的環境發生改變了呢?
我們知道中斷時確實保存一些上下文,但是僅限於返回地址,cpu寄存器等之類的少量上下文,而函數內部使用的諸如全局或靜態變量,buffer等並不在保護之列,所以如果這些值在函數被中斷期間發生了改變,那么當函數回到斷點繼續執行時,其結果就不可預料了。
滿足下面條件之一的多數是不可重入函數:
(1)使用了靜態數據結構;
(2)調用了malloc或free;
(3)調用了標准I/O函數;
(4)進行了浮點運算.
malloc/free是不可重入的,它們使用了全局變量來指向空閑區;
標准I/O庫的很多實現都使用了全局數據結構;
許多的處理器/編譯器中,不可重入的 (浮點運算大多使用協處理器或者軟件模擬來實現)。
在信號處理程序及多線程編程時,要特別注意。
考慮這種情況:
1) 信號處理程序A內外都調用了同一個不可重入函數B;B在執行期間被信號打斷,進入A (A中調用了B),完事之后返回B被中斷點繼續執行,這時B函數的環境可能改變,其結果就不可預料了。
2) 多線程共享進程內部的資源,如果兩個線程A,B調用同一個不可重入函數F,A線程進入F后,線程調度,切換到B,B也執行了F,那么當再次切換到線程A時,其調用F的結果也是不可預料的。
在信號處理程序中即使調用可重入函數也有問題要注意。作為一個通用的規則,當在信號處理程序中調用可重
入函數時,應當在其前保存errno,並在其后恢復errno。(要了解經常被捕捉到的信號是SIGCHLD,其信號處理程序通常要調用一種wait函數,而各種wait函數都能改變errno。)
二、實例
給出一段程序,這段程序從信號處理程序my_alarm調用非可重入函數getpwnam,而my_alarm每秒被調用一次。在該程序中調用alarm函數使得每秒產生一次SIGALRM信號。
#include <stdio.h> #include <pwd.h> #include <unistd.h> #include <string.h> #include <signal.h> static void my_alarm(int signo){ struct passwd * rootptr; printf("in signal handler\n"); if((rootptr = getpwnam("root")) == NULL){ fprintf(stderr, "getpwnam(root) error\n"); } alarm(1); } int main(void){ struct passwd *ptr; signal(SIGALRM, my_alarm); alarm(1); for(;;){ if((ptr = getpwnam("ives")) == NULL){ fprintf(stderr, "getpwnam(ives) error\n"); } if(strcmp(ptr->pw_name, "ives") != 0){ printf("return value corrupted!, pw_name = %s\n", ptr->pw_name); } } }
在大多數系統下,該代碼不會正常工作,只會輸出一串"in signal handler"就阻塞在那里不再運行。
