淺談可重入函數與不可重入函數【轉】

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

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

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

也可以這樣理解,重入即表示重複進入,首先它意味着這個函數可以被中斷,其次意味着它除了使用自己棧上的變量以外不依賴於任何環境(包括 static),這樣的函數就是purecode(純代碼)可重入,可以允許有該函數的多個副本在運行,由於它們使用的是分離的棧,所以不會互相干擾。如果確實需要訪問全局變量(包括 static),一定要注意實施互斥手段。可重入函數在並行運行環境中非常重要,但是一般要爲訪問全局變量付出一些性能代價。
編寫可重入函數時,若使用全局變量,則應通過關中斷、信號量(即P、V操作)等手段對其加以保護。
說明:若對所使用的全局變量不加以保護,則此函數就不具有可重入性,即當多個進程調用此函數時,很有可能使有關全局變量變爲不可知狀態。

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

  1. int Exam = 0;  
  2. unsigned int example( int para )   
  3. {   
  4.     unsigned int temp;  
  5.     Exam = para; // (**)  
  6.     temp = Square_Exam( );  
  7.     return temp;  
  8. }  

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

  1. int Exam = 0;  
  2. unsigned int example( int para )   
  3. {  
  4.     unsigned int temp;  
  5.     [申請信號量操作] //(1)  加鎖  
  6.     Exam = para;  
  7.     temp = Square_Exam( );  
  8.     [釋放信號量操作] //     解鎖   
  9.     return temp;  
  10. }  

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

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

1)在寫函數時候儘量使用局部變量(例如寄存器、堆棧中的變量);

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

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

1)函數體內使用了靜態的數據結構;

2)函數體內調用了malloc() 或者 free() 函數;

3)函數體內調用了標準 I/O 函數。

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

1)不要使用全局變量。因爲別的代碼很可能改變這些變量值。

2)在和硬件發生交互的時候,切記執行類似 disinterrupt() 之類的操作,就是關閉硬件中斷。完成交互記得打開中斷,在有些系列上,這叫做“進入/ 退出核心”。

3)不能調用其它任何不可重入的函數。

4)謹慎使用堆棧。

Linux常見的可重入函數

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