一篇文章帶你瞭解C語言函數的可重入性
一、不可重入函數。
在函數中如果我們使用靜態變量瞭,導致產生中斷調用別的函數的 過程中可能還會調用這個函數,於是原來的 靜態變量被在這裡改變瞭,然後返回主體函數,用著的那個靜態變量就被改變瞭,導致錯誤。這類函數我們稱為不可重入函數。
在 嵌入式系統的設計中,經常會出現多個任務調用同一個函數的情況。如果這個函數不幸被設計成為不可重入的函數的話,那麼不同任務調用這個函數時可能修改其他任 務調用這個函數的數據,從而導致不可預料的後果。
不可重入函數在實時系統設計中被視為不安全函數。
滿足下列條件的函數多數是不可重入的:
(1)函數體內使用瞭靜態的數據結構;
(2)函數體內調用瞭malloc()或者free()函數;
(3)函數體內調用瞭標準I/O函數。
(4)函數體內調用瞭不可中斷的硬件寄存器,如串口收發寄存器
二、可重入函數。
可重入函數可以被一個以上的任務調用,而不必擔心數據被破壞。可重入函數任何時候都可以被中斷,一段時間以後又可以運行,而相應的數據不會丟失。
可重入函數或者隻使用局部變量,即保存在CPU寄存器中或堆棧中;或者使用全局變量,則要對全局變量予以保護。
若一個函數是可重入的,則該函數必須滿足一下必要條件:
1、不能含有靜態(全局)非常量數據。
2、不能返回靜態(全局)非常量數據的地址。
3、隻能處理由調用者提供的數據。作為可重入函數的輸入參數,隻能由調用者提供,而且所提供的輸入數據必須滿足上面兩點要求
4、不能依賴於單實例模式資源的鎖。
5、不能調用不可重入的函數。 函數內部,盡量不能用 malloc 和 free 之類的方法進行內存分配和釋放,如果使用,一般情況下會造成該函數的不可重入
三、如何寫出可重入的函數
1、在寫函數時候盡量使用局部變量(例如寄存器、堆棧中的變量)。不訪問那些全局變量,不使用靜態局部變量
2、如果確實需要訪問全局變量(包括static),一定要註意實施互斥手段。可重入函數在並行運行環境中非常重要,但是一般要為訪問全局變量付出一些性能代價。編寫可重入函數時,若使用全局變量,則應通過關中斷、信號量(即P、V操作)等手段對其加以保護。
四、函數的可重入性和線程安全的關系
可重入與線程安全兩個概念都關系到函數處理資源的方式。但是,他們有一定的區別。可重入概念會影響函數的外部接口,而線程安全隻關心函數的實現。
大多數情況下,要將不可重入函數改為可重入的,需要修改函數接口,使得所有的數據都通過函數的調用者提供。要將非線程安全的函數改為線程安全的,則隻需要修改函數的實現部分。一般通過加入同步機制以保護共享的資源,使之不會被幾個線程同時訪問。
線程安全與可重入性是兩個不同性質的概念。
可重入是在單線程操作系統背景下,重入的函數或者子程序,按照後進先出的線性序依次執行完畢。多線程執行的函數或子程序,各個線程的執行時機是由操作系統調度,不可預期的,但是該函數的每個執行線程都會不時的獲得CPU的時間片,不斷向前推進執行進度。
可重入函數未必是線程安全的;線程安全函數未必是可重入的。例如,一個函數打開某個文件並讀入數據。這個函數是可重入的,因為它的多個實例同時執行不會造成沖突;但它不是線程安全的,因為在它讀入文件時可能有別的線程正在修改該文件,為瞭線程安全必須對文件加“同步鎖”。
函數在它的函數體內部訪問共享資源使用瞭加鎖、解鎖操作,所以它是線程安全的,但是卻不可重入。因為若該函數一個實例運行到已經執行加鎖但未執行解鎖時被停下來,系統又啟動該函數的另外一個實例,則新的實例在加鎖處將轉入等待。如果該函數是一個中斷處理服務,在中斷處理時又發生新的中斷將導致資源死鎖。
五、malloc和printf為什麼不可重入
malloc和printf通常使用全局結構,並在內部使用基於鎖的同步。這就是為什麼它們不可重入。
malloc函數可以是線程安全的,也可以是線程不安全的。兩者都不可重入:
malloc在全局堆上操作,同時發生的兩個不同的malloc調用可能返回相同的內存塊。(第二個malloc調用應該在獲取塊的地址之前發生,但塊沒有標記為不可用)。這違反瞭malloc的後置條件,因此此實現不會重新進入。
printf函數也對全局數據進行操作。任何輸出流通常都使用一個附加到資源數據的全局緩沖區(用於終端或文件的緩沖區)。打印過程通常是將數據復制到緩沖區,然後刷新緩沖區的序列。
總結
本篇文章就到這裡瞭,希望能給你帶來幫助,也希望您能夠多多關註WalkonNet的更多內容!