關於可重入函數

1 前言

最近在公司維護的項目中碰到一個解決了定位好久的 bug , bug 找到的時候發現犯了很低級的錯誤——在中斷處理函數中調用了 printf 函數,由於中斷處理函數的調用了不可重入函數,致使中斷丟失和系統位置錯誤,這裏直接致使嵌入式 linux 系統應用進程中的全部線程停掉,進而致使看門狗進程得不到喂狗,設備重啓。linux

  • 那什麼是不可重入函數呢?
  • 爲何中斷處理函數不能直接調用不可重入函數?
  • 怎樣寫可重入函數?

就以上三個問題展開小短文:c++

2 什麼是不可重入函數?

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

知足下列條件的函數多數是不可重入的:數據結構

  • 函數體內使用了靜態(static)的數據結構;
  • 函數體內調用了 malloc() 或者 free() 函數;
  • 函數體內調用了標準 I/O 函數;

A. 可重入函數

void strcpy(char *lpszDest, char *lpszSrc) 
{
    while(*lpszDest++=*lpszSrc++);///< 使用的局部變量
    *dest=0;
}

B. 不可重入函數1

char cTemp;    ///< 全局變量
void SwapChar1(char *lpcX, char *lpcY) 
{
    cTemp=*lpcX;
    *lpcX=*lpcY;
    lpcY=cTemp;   ///< 訪問了全局變量
}

C. 不可重入函數2

void SwapChar2(char *lpcX,char *lpcY)
{
    static char cTemp;  ///< 靜態局部變量
    cTemp=*lpcX;
    *lpcX=*lpcY;
    lpcY=cTemp;   ///< 使用了靜態局部變量
}

3 爲何中斷處理函數不能直接調用不可重入函數?

在多任務系統下,中斷可能在任務執行的任什麼時候間發生;若是一個函數的執行期間被中斷後,到從新恢復到斷點進行執行的過程當中,函數所依賴的環境沒有發生改變,那麼這個函數就是可重入的,不然就不可重入。多線程

在中斷先後不都要保存和恢復上下文嗎,怎麼會出現函數所依賴的環境發生改變了呢?咱們知道中斷時確實保存一些上下文,可是僅限於返回地址,cpu 寄存器等之類的少許上下文,而函數內部使用的諸如全局或靜態變量,buffer 等並不在保護之列,因此若是這些值在函數被中斷期間發生了改變,那麼當函數回到斷點繼續執行時,其結果就不可預料了。併發

在中斷處理函數中調用有互斥鎖保護的全局變量,若是剛好該變量正在被另外一個線程調用,會致使中斷處理函數不能及時返回,致使中斷丟失等嚴重問題。ide

而且在多線程環境中使用,在沒有加鎖的狀況下,對同一段內存塊進行併發讀寫,就會形成 segmentfault/coredump 之類的問題。函數

總而言之,中斷處理函數作的事情越簡單越好。url

4 如何寫出可重入的函數?

  • 在函數體內不訪問那些全局變量;
  • 若是必須訪問全局變量,記住利用互斥信號量來保護全局變量。或者調用該函數前關中斷,調用後再開中斷;
  • 不使用靜態局部變量;
  • 堅持只使用缺省態(auto)局部變量;
  • 在和硬件發生交互的時候,切記關閉硬件中斷。完成交互記得打開中斷,在有些系列上,這叫作「進入/退出核心」或者用 OS_ENTER_KERNAL/OS_EXIT_KERNAL 來描述;
  • 不能調用任何不可重入的函數;
  • 謹慎使用堆棧。最好先在使用前先 OS_ENTER_KERNAL;
相關文章
相關標籤/搜索