1. 程式人生 > >Linux系統程式設計——淺談可重入函式與不可重入函式

Linux系統程式設計——淺談可重入函式與不可重入函式

在實時系統的設計中,經常會出現多個任務呼叫同一個函式的情況。如果有一個函式不幸被設計成為這樣:那麼不同任務呼叫這個函式時可能修改其他任務呼叫這個函式的資料,從而導致不可預料的後果。這樣的函式是不安全的函式,也叫不可重入函式。

相反,肯定有一個安全的函式,這個安全的函式又叫可重入函式。那麼什麼是可重入函式呢?所謂可重入是指一個可以被多個任務呼叫的過程,任務在呼叫時不必擔心資料是否會出錯。

一個可重入的函式簡單來說就是可以被中斷的函式,也就是說,可以在這個函式執行的任何時刻中斷它,轉入OS排程下去執行另外一段程式碼,而返回控制時不會出現什麼錯誤;而不可重入的函式由於使用了一些系統資源,比如全域性變數區,中斷向量表等,所以它如果被中斷的話,可能會出現問題,這類函式是不能執行在多工環境下的。
  

也可以這樣理解,重入即表示重複進入,首先它意味著這個函式可以被中斷,其次意味著它除了使用自己棧上的變數以外不依賴於任何環境(包括 static),這樣的函式就是purecode(純程式碼)可重入,可以允許有該函式的多個副本在執行,由於它們使用的是分離的棧,所以不會互相干擾。如果確實需要訪問全域性變數(包括 static),一定要注意實施互斥手段。可重入函式在並行執行環境中非常重要,但是一般要為訪問全域性變數付出一些效能代價。


編寫可重入函式時,若使用全域性變數,則應通過關中斷、訊號量(即P、V操作)等手段對其加以保護。


說明:若對所使用的全域性變數不加以保護,則此函式就不具有可重入性,即當多個程序呼叫此函式時,很有可能使有關全域性變數變為不可知狀態。

示例:假設 Exam 是 int 型全域性變數,函式 Squre_Exam 返回 Exam 平方值。那麼如下函式不具有可重入性。

int Exam = 0;
unsigned int example( int para ) 
{ 
    unsigned int temp;
    Exam = para; // (**)
    temp = Square_Exam( );
    return temp;
}

此函式若被多個程序呼叫的話,其結果可能是未知的,因為當(**)語句剛執行完後,另外一個使用本函式的程序可能正好被啟用,那麼當新啟用的程序執行到此函式時,將使 Exam 賦與另一個不同的 para 值,所以當控制重新回到 “temp = Square_Exam( )” 後,計算出的temp很可能不是預想中的結果。此函式應如下改進。

int Exam = 0;
unsigned int example( int para ) 
{
    unsigned int temp;
    [申請訊號量操作] //(1)  加鎖
    Exam = para;
    temp = Square_Exam( );
    [釋放訊號量操作] //     解鎖 
    return temp;
}

申請不到“訊號量”,說明另外的程序正處於給 Exam 賦值並計算其平方過程中(即正在使用此訊號),本程序必須等待其釋放訊號後,才可繼續執行。若申請到訊號,則可繼續執行,但其它程序必須等待本程序釋放訊號量後,才能再使用本訊號。

保證函式的可重入性的方法:

1)在寫函式時候儘量使用區域性變數(例如暫存器、堆疊中的變數);

2)對於要使用的全域性變數要加以保護(如採取關中斷、訊號量等互斥方法)這樣構成的函式就一定是一個可重入的函式。

滿足下列條件的函式多數是不可重入(不安全)的:

1)函式體內使用了靜態的資料結構;

2)函式體內呼叫了malloc() 或者 free() 函式;

3)函式體內呼叫了標準 I/O 函式。

如何將一個不可重入的函式改寫成可重入函式呢?把一個不可重入函式變成可重入的唯一方法是用可重入規則來重寫它。其實很簡單,只要遵守了幾條很容易理解的規則,那麼寫出來的函式就是可重入的:

1)不要使用全域性變數。因為別的程式碼很可能改變這些變數值。
2)在和硬體發生互動的時候,切記執行類似 disinterrupt() 之類的操作,就是關閉硬體中斷。完成互動記得開啟中斷,在有些系列上,這叫做“進入/ 退出核心”。
3)不能呼叫其它任何不可重入的函式。
4)謹慎使用堆疊。

Linux常見的可重入函式