中斷處理程序不能使用printf的本質
vxworks 中斷處理程序之所以不用printf,本質在於printf是將信息輸出到標準輸出設備(STDOUT)中, 整個標準輸出設備是一個全局變量,由於有semTake操作,那麼就會發生阻塞,vxworks屬於硬實時操作係統,不能在規定的時間內完成操作即會死機或複位。所以vxworks不用printf的原因在於阻塞。 網上說printf 因為引用全局變量stdout,所以是不可重入的。這個稍微解釋一下。如果用到了全局變量,但是用信號量保護,是線程安全的,但是不可重入的(會導致死鎖,譬如一個任務或中斷處理程序調用了printf,被另一個高優先級中斷打斷,那麼就會形成死鎖而導致係統複位)。 所以這裏的阻塞和不可重入都是因為對共享變量的保護而采用互斥鎖引起的,而這裏的阻塞是不可重入的一個真子集。(例如:可能有些函數對靜態或全局變量沒有鎖保護,因此是非線程安全,也是非可重入的,此時並沒有阻塞在靜態或全局變量上,所以不可重入的概念要大。)。因此printf不能用在中斷處理程序中的根本原因在於使用了全局變量後采用了鎖機製,而鎖機製會導致阻塞,阻塞是不可重入的真子集。 所以網上說printf因為不可重入,也會說得過去的(但不可重入還有其他非阻塞的場景)。更準確的說法是因為阻塞在全局變量STDOUT上)。關於可重入和線程安全的區別,下文會有詳細解釋:線程安全函數
• 概念:
線程安全的概念比較直觀。一般說來,一個函數被稱為線程安全的,當且僅當被多個並發線程反複調用時,它會一直產生正確的結果。
• 確保線程安全:
要確保函數線程安全,主要需要考慮的是線程之間的共享變量。屬於同一進程的不同線程會共享進程內存空間中的全局區和堆,而私有的線程空間則主要包括棧和寄 存器。因此,對於同一進程的不同線程來說,每個線程的局部變量都是私有的,而全局變量、局部靜態變量、分配於堆的變量都是共享的。在對這些共享變量進行訪 問時,如果要保證線程安全,則必須通過加鎖的方式。
• 線程不安全的後果:
線程不安全可能導致的後果是顯而易見的——共享變量的值由於不同線程的訪問,可能發生不可預料的變化,進而導致程序的錯誤,甚至崩潰。
可重入函數
• 概念:
可重入的概念基本沒有比較正式的完整解釋,多數的文檔都隻是說明什麼樣的情況才能保證函數可重入,但沒有完整定義。按照Wiki上的說法,“A computer program or routine is described as reentrant if it can be safely executed concurrently; that is, the routine can be re-entered while it is already running.”根據筆者的經驗,所謂“重入”,常見的情況是,程序執行到某個函數foo()時,收到信號,於是暫停目前正在執行的函數,轉到信號處理 函數,而這個信號處理函數的執行過程中,又恰恰也會進入到剛剛執行的函數foo(),這樣便發生了所謂的重入。此時如果foo()能夠正確的運行,而且處理完成後,之前暫停的foo()也能夠正確運行,則說明它是可重入的。
• 確保可重入:
要確保函數可重入,需滿足以下幾個條件:
1、不在函數內部使用靜態或全局數據
2、不返回靜態或全局數據,所有數據都由函數的調用者提供。
3、使用本地數據,或者通過製作全局數據的本地拷貝來保護全局數據。
4、不調用不可重入函數。
• 不可重入的後果:
不可重入的後果主要體現在象信號處理函數這樣需要重入的情況中。如果信號處理函數中使用了不可重入的函數,則可能導致程序的錯誤甚至崩潰。
可重入與線程安全
可重入與線程安全並不等同。一般說來,可重入的函數一定是線程安全的,但反過來不一定成立。
- 如果一個函數中用到了全局或靜態變量,那麼它不是線程安全的,也不是可重入的;
- 如果我們對它加以改進,在訪問全局或靜態變量時使用互斥量或信號量等方式加鎖,則可以使它變成線程安全的,但此時它仍然是不可重入的,因為通常加鎖方式是針對不同線程的訪問,而對同一線程可能出現問題;這裏舉例說明:假設函數func() 在執行過程中需要訪問某個共享資源,因此為了實現線程安全,在使用該資源前加鎖,在不需要資源解鎖。
假設該函數在某次執行過程中,在已經獲得資源鎖之後,有異步信號發生,程序的執行流轉交給對應的信號處理函數;再假設在該信號處理函數中也需要調用函數 func() ,那麼func() 在這次執行中仍會在訪問共享資源前試圖獲得資源鎖,然而我們知道前一個func() 實例已然獲得該鎖,因此信號處理函數阻塞。另一方麵,信號處理函數結束前被信號中斷的線程是無法恢複執行的,當然也沒有釋放資源的機會,這樣就出現了線程和信號處理函數之間的死鎖局麵。
因此,func() 盡管通過加鎖的方式能保證線程安全,但是由於函數體對共享資源的訪問,因此是非可重入。如果將函數中的全局或靜態變量去掉,改成函數參數等其他形式,則有可能使函數變成既線程安全,又可重入。比如:strtok函數是既不可重入的,也不是線程安全的;加鎖的strtok不是可重入的,但線程安全;而strtok_r既是可重入的,也是線程安全的。
最後更新:2017-04-03 07:57:10