C語言可重入函數和不可重入函數

可重入函數和不可重入函數的概念

  在函數中如果我們使用靜態變量了,導致產生中斷調用別的函數的 過程中可能還會調用這個函數,於是原來的 靜態變量被在這裏改變了,然後返回主體函數,用着的那個靜態變量就被改變了,導致錯誤。這類函數我們稱爲不可重入函數

  如果是在函數體內 動態申請內存的話,即便 新的線程調用這個函數也沒事,因爲新的線程使用的是新的函數的 新申請的動態內存(靜態變量只有一份,所以 多線程對於函數體內的靜態變量改變 會有無法修復的結果),所以這類函數就是可重入函數

     在 實時系統的設計中,經常會出現多個任務調用同一個函數的情況。如果這個函數不幸被設計成爲不可重入的函數的話,那麼不同任務調用這個函數時

可能修改其他任 務調用這個函數的數據,從而導致不可預料的後果。那麼什麼是可重入函數呢?所謂可重入是指一個可以被多個任務調用的過程,任務在

調用時不必擔心數據是否會 出錯。不可重入函數在實時系統設計中被視爲不安全函數。

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

1

2

3

4

5

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

 

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

 

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


如何寫出可重入的函數?在函數體內不訪問那些全局變量,不使用靜態局部變量,堅持只使用缺省態(auto)局部變量,寫出的函數就將是可重入的。如果

必須訪問全局變量,記住利用互斥信號量來保護全局變量。或者調用該函數前關中斷,調用後再開中斷。 

可重入函數可以被一個以上的任務調用,而不必擔心數據被破壞。可重入函數任何時候都可以被中斷,一段時間以後又可以運行,而相應的數據不會丟失。

可重入函數或者只使用局部變量,即保存在CPU寄存器中或堆棧中;或者使用全局變量,則要對全局變量予以保護。 

說法2:

一個可重入的函數簡單來說,就是:可以被中斷的函數。就是說,你可以在這個函數執行的任何時候中斷他的運行,在任務調度下去執行另外一段代 碼而

不會出現什麼錯誤。而不可重入的函數由於使用了一些系統資源,比如全局變量區,中斷向量表等等,所以他如果被中斷的話,可能出現問題,所以這類函

數是 不能運行在多任務環境下的。

基本上下面的函數是不可重入的

1

2

3

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

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

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

把一個不可重入函數變成可重入的唯一方法是用可重入規則來重寫他。

其實很簡單,只要遵守了幾條很容易理解的規則,那麼寫出來的函數就是可重入的。

第一,不要使用全局變量。因爲別的代碼很可能覆蓋這些變量值。

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

心”或者用OS_ENTER_KERNAL/OS_EXIT_KERNAL來描述。//這是臨界區保護

第三,不能調用任何不可重入的函數。

第四,謹慎使用堆棧。最好先在使用前先OS_ENTER_KERNAL。

還有一些規則,都是很好理解的,總之,時刻記住一句話:保證中斷是安全的! 

相信很多人都看過下面這個面試題

中斷是嵌入式系統中重要的組成部分,這導致了很多編譯開發商提供一種擴展—讓標準C支持中斷。具代表事實是,產生了一個新的關鍵字 __interrupt。下

 

面的代碼就使用了__interrupt關鍵字去定義了一箇中斷服務子程序(ISR),請評論一下這段代碼的。
 

1

2

3

4

5

6

__interrupt double compute_area (double radius)

{

    double area = PI * radius * radius;

    printf("\nArea = %f", area);

    return area;

}

這個函數有太多的錯誤了,以至讓人不知從何說起了:

 

1)ISR 不能返回一個值。如果你不懂這個,那麼你不會被僱用的。

 

2) ISR 不能傳遞參數。如果你沒有看到這一點,你被僱用的機會等同第一項。

 

3) 在許多的處理器/編譯器中,浮點一般都是不可重入的。有些處理器/編譯器需要讓額處的寄存器入棧,有些處理器/編譯器就是不允許在ISR中做浮點運

 

算。此外,ISR應該是短而有效率的,在ISR中做浮點運算是不明智的。

 

4) 與第三點一脈相承,printf()經常有重入和性能上的問題。如果你丟掉了第三和第四點,我不會太爲難你的。不用說,如果你能得到後兩點,那麼你的被

 

僱用前景越來越光明瞭。

 

 

-->

如果說中斷服務程序有返回值,那麼它的值返回給誰呢?

 

在系統的運行過程中,一定是某種中斷源出發了相應的中斷,系統上掛接的中斷服務程序進行現場的處理,例如告警等操作,然後清中斷。

 

也就是說中斷服務程序鏈接在某一類中斷源上,而這些中斷源的產生是隨機的,所以,中斷服務程序並沒有一個固定的調用者,也沒有固定的返回地址,所

 

以返回值也沒有用
 

我的問題是,這裏所說的printf()經常有重入的問題,具體是指什麼?有人能給解釋一下麼這個概念在嵌入式操作系統中比較重要,由於存在任務的調度,它

 

實時系統,可剝奪型內核中是危險的,如同一個安靜的水雷。可能會被觸發,也可能安然無恙。由於它運行結果的不可預期性,會使系統帶來隱患。


下面引用一段別人的解釋:

這主要在多任務環境中使用,一個可重入的函數簡單來說,就是:可以被中斷的函數。就是說,你可以在這個函數執行的任何時候中斷他的運行,在OS的

 

調度下去執行另外一段代碼而不會出現什麼錯誤。而不可重入的函數由於使用了一些系統資源,比如全局變量區,中斷向量表等等,所以他如果被中斷的

 

話,可能出現問題,所以這類函數是不能運行在多任務環境下的。

把一個不可重入函數變成可重入的唯一方法是用可重入規則來重寫他。


其實很簡單,只要遵守了幾條很容易理解的規則,那麼寫出來的函數就是可重入的。

第一,不要使用全局變量。因爲別的代碼很可能覆蓋這些變量值。

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

 

心”或者用OS_ENTER_KERNAL/OS_EXIT_KERNAL來描述。

第三,不能調用任何不可重入的函數。

第四,謹慎使用堆棧。最好先在使用前先OS_ENTER_KERNAL。

還有一些規則,都是很好理解的,總之,時刻記住一句話:保證中斷是安全的!

通俗的來講吧:由於中斷是可能隨時發生的,斷點位置也是無法預期的。所以必須保證每個函數都具有不被中斷髮生,壓棧,轉向ISR,彈棧後繼續執行影

 

響的穩定性。也就是說具有不會被中斷影響的能力。既然有這個要求,你提供和編寫的每個函數就不能拿公共的資源或者是變量來使用,因爲該函數使用的

 

同時,ISR(中斷服務程序)也可那會去修改或者是獲取這個資源,從而有可能使中斷返回之後,這部分公用的資源已經面目全非。


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

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

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

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

下面舉例加以說明:

可重入函數:

1

2

3

4

5

6

7

8

9

void strcpy(char* lpszDest, char* lpszSrc)

 

{

 

     while(*lpszDest++ = *lpszSrc++);

 

     *dest=0;

 

}

非可重入函數1:

1

2

3

4

5

6

7

8

9

10

11

12

13

char cTemp;            // 全局變量

 

void SwapChar1(char* lpcX, char* lpcY)

 

{

 

     cTemp = *lpcX;

 

     *lpcX = *lpcY;

 

     lpcY = cTemp;     // 訪問了全局變量,在分享內存的多個線程中可能造成問題

 

}

非可重入函數2:

1

2

3

4

5

6

7

8

9

10

11

12

13

void SwapChar2(char* lpcX, char* lpcY)

 

{

 

     static char cTemp;  // 靜態局部變量

 

     cTemp = *lpcX;

 

     *lpcX = *lpcY;

 

     lpcY = cTemp;   // 使用了靜態局部變量,在分享內存的多個線程中可能造成問題

 

}

 如何寫出可重入的函數?在函數體內不訪問那些全局變量,不使用靜態局部變量,堅持只使用局部變量,寫出的函數就將是可重入的。如果必須訪問全局

變量,記住利用互斥信號量來保護全局變量。 

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