並發是指多個執行單元同時、並行被執行,而並發的執行單位對於共享資源(硬件資源和軟件上的全局變量、靜態變量等)的訪問很容易導致競態(race conditions)
競態主要發生在如下幾種情況:
1、對稱多處理器(SMP)的多個CPU
2、單CPU內進程與搶占它的進程
3、中斷(硬中斷、軟中斷、Tasklet、底半部)與進程之間
4、中斷也可能被更高優先級的中斷打斷,因此,多個中斷之間也可能引起並發導致競態
解決競態問題的途徑是保證對共享資源的互斥訪問,所謂互斥訪問是指一個執行單元在訪問共享資源的時候,其他的執行單元被禁止訪問。
訪問共享資源的代碼區成為臨界區,臨界區需要被以某種互斥機制加以保護,中斷屏蔽、原子操作、自旋鎖和信號量是linux設備驅動中可采用的互斥途徑。
1、中斷屏蔽:只能禁止和使能本CPU內的中斷。
2、原子操作:是指在執行過程中不會被別的代碼路徑所中斷的操作。內核代碼可以安全的調用它們而不被打斷。分為針對位和整形變量進行原子操作兩類
3、自旋鎖:理解它最簡單的方法是把它當作一個變量看待,該變量把一個臨界區標記為”我當前在運行,請稍等一會“或者標記為”我當前不在運行,可以被使用“
使用自旋鎖可以保證臨界區不受別的CPU和本CPU內的搶占進程打擾,但是得到鎖的代碼路徑在執行臨界區的時候,還可能受到中斷和底半部的影響。
為防止這種影響,需要用到自旋鎖的衍生。可以避免突如其來的中斷對系統造成的傷害。
使用自旋鎖需注意如下:
1、實際上是忙等鎖,當鎖不可用時,CPU一直循環執行”測試並設置“該鎖直到可用而取得該鎖,CPU在等待自旋鎖時不做任何有用的工作,僅僅是等待。
因此,只有在占有鎖的時間極短的情況下,使用自旋鎖才是合理的。
2、自旋鎖可能導致死鎖。引發這個問題最常見的情況是遞歸使用一個自旋鎖,即如果一個已經擁有某個自旋鎖的CPU想第二次獲得這個鎖,則CPU將死鎖
3、自旋鎖鎖定期間不能調用可能引起進程調度的函數。如果進程獲取自旋鎖之后阻塞,如調用copy_from_user()\copy_to_user()\kmalloc()和msleep
等,則可能導致內核崩潰。
4、信號量:只有得到信號量的進程才能執行臨界區代碼,與自旋鎖不同的是,當獲取不到信號量時,進程不會原地打轉而是進入休眠等待狀態,不能用在中斷上下文中
如果信號量被初始化為0,則它可以用於同步,同步意味着一個執行單元的繼續執行需等待另一執行單元完成某事。
信號量和自旋鎖的區別:
1、信號量是進程級的,用於多個進程之間對資源的互斥,。鑒於進程上下文的切換開銷較大,只有當進程占用資源時間較長時,用信號量才是較好的選擇,
當所要保護的臨界區訪問時間較短時,用自旋鎖是較好的
2、信號量所保護的臨界區可包含可能引起阻塞的代碼,而自旋鎖要避免,因為阻塞意味着要進行進程的切換,如果進程切換出去后,另一個進程企圖獲得
本自旋鎖,死鎖將發生
3、信號量存在於進程上下文,因此,如果被保護的共享資源需要在中斷或軟中斷情況下使用,則在信號量和自旋鎖之間只能選擇自旋鎖。