[臨界區和競爭條件]
所謂臨界區就是訪問和操作共享數據的代碼段。多個執行線程並發訪問同一個資源通常是不安全的,為了避免在臨界區中並發訪問,coder必須保證這些代碼原子執行。
如果兩個執行線程有可能處於同一個臨界區中同時執行,那麼這就是程序包含的一個bug。如果這種情況確實發生了,我們就稱它是競爭條件(race conditions)。避免並發和防止競爭條件稱為同步(synchronization)。
[造成並發執行的原因]
用戶空間之所以需要同步,是因為用戶程序會被調度程序搶占和重新調度。在內核中有類似可能造成並發執行的原因:
中斷:中斷幾乎可以在任何時刻異步發生,也就是隨時打斷當前正在執行的代碼;
軟中斷和tasklet:內核能在任何時刻喚醒或調度軟中斷和tasklet,打斷當前正在執行的代碼;
內核搶占:因為內核具有搶占性,所以內核中的任務可能會被另一任務搶占;
睡眠及與用戶空間的同步:在內核執行的進程可能會睡眠,這就會喚醒調度程序,從而導致調度一個新的用戶進程執行;
對稱處理器:兩個或多個處理器可以同時執行代碼。
[哪些代碼需要同步]
我們在編寫內核代碼時,你要問自己下面這些問題:
這個數據是不是全局的?除了當前線程外,其他線程能不能訪問它?
這個數據會不會在進程上下文和中斷上下文種共享?它是不是要在兩個不同的中斷處理程序中共享?
進程在訪問數據時可不可能被搶占?被調度的新程序會不會訪問同一數據?
當前進程是不是會睡眠(阻塞)在某些資源上,如果是,它會讓共享數據處於何種狀態?
怎樣防止數據失控?
如果這個函數又在另一個處理上被調度將會發生什麼呢?
如何確保代碼遠離並發威脅呢?
簡而言之,幾乎訪問所有的內核全局變量和共享數據都需要某種形式的同步方法。
[死鎖]
死鎖的產生需要一定的條件:要一個或多個執行線程和一個或多個資源,每個線程都在等待其中的一個資源,但所有的資源都已經被占用了。所有線程都在相互等待,但它們永遠不會釋放已經占有的資源,於是任何資源都無法繼續,這就意味著死鎖的發生。
Example:有兩個線程和兩把鎖
線程1 線程2
獲得鎖A 獲得鎖B
試圖獲得鎖B 試圖獲得鎖A
等待鎖B 等待鎖A
[原子操作]
原子操作可以保證指令以原子的方式執行-執行過程不被打斷。內核提供了兩組原子操作接口:一組針對整數進行操作,另一組針對單獨的位進行操作。
原子整數類型
[自旋鎖]
自旋鎖(spin lock)最多只能被一個可執行線程持有。如果一個執行線程試圖獲得一個被已經持有(即所謂的爭用)的自旋鎖,那麼該線程就會一直進行忙循環-旋轉-等待鎖重新可用。
spinlock結構體:
一個被爭用的自旋鎖是的請求它的線程在等待鎖重新可用時自旋,特別浪費處理器時間,這種行為是自旋鎖的要點。所以自旋鎖不應該被長時間持有。持有自旋鎖的時間最好小於完成兩次上下文切換的耗時。
自旋鎖可以用於中斷處理程序中,但是信號量不可以,信號量會導致睡眠。
使用鎖的時候一定要對症下藥,要有針對性。要知道需要保護的是數據而不是代碼。