多年之前,在單一處理器的時候,隻有在中斷發生的時候,或者在核心代碼中顯示地請求重新排程,執行另一任務的時候,資料才有可能被并發通路。從2.0開始,核心開始支援對稱多處理器,這就意味着核心代碼可以運作在兩個或者更多的處理器上,是以,如果不加以保護,運作在兩個不同處理器上的核心代碼完全可能在同一時刻并發通路共享資料。随着2.6核心的出現,linux核心以發展成為搶占式核心,這意味這不加保護的情況下,排程程式可以在任何時刻搶占正在運作的核心代碼,重新排程其他程序執行。
并發與競态
并發(concurrency)環境中的多個程序如果需要通路同一個臨界資源,在不加以保護的情況下,很容易導緻競态(race condition)。
為了避免某臨界資源的臨界區被并發執行,程式設計者必須保證臨界區原子地執行。
競争狀态出現的幾率非常小,但是這種因競争引起的錯誤非常不易重視,是以調試這種錯誤才會非常困難。
避免并發和防止競态出現的機制被稱為同步(synchronization)。
鎖機制
任何需要進入臨界區的程序首先需要占住對應的鎖,這樣就能阻止來自其他線程的并發通路。需要注意的是,鎖的應用是自願的,非強制的。
鎖有多種多樣的形式,而且加鎖的粒度範圍也各不相同。Linux自身實作了幾種不同的鎖機制,各種鎖機制之間的差別主要在于鎖被争用時的行為表現 --- 一些鎖被争用時會簡單地執行忙等待,而有些鎖會使目前任務睡眠直到鎖可用為止。
到底是什麼造成了并發執行
核心中有以下可能造成并發執行的情況:
· 中斷 —— 中斷幾乎可以在任何時刻異步發生,也就可能随時打斷目前正在執行的代碼。(中斷服務程式通路被打斷程序正在通路的資源)
· 軟中斷和tasklet —— 核心能在任何時刻喚醒或排程軟中斷和tasklet,打斷目前正在執行的代碼。
· 核心搶占 —— 因為核心具有搶占性,是以核心中的任務可能會被另一任務搶占。
· 睡眠以及使用者空間的同步 —— 在核心執行的程序可能會睡眠,這就會喚醒排程程式,進而導緻排程一個新的使用者程序執行。(核心代碼在臨界區中睡眠)
· 對稱對處理器 —— 兩個或多個處理器可以同時執行代碼。(多個處理器絕對不能同時通路同一共享資源)
當我們清楚什麼樣的資料需要保護時,提供鎖來保護代碼的安全也就不難做到了。然而,真正困難的卻是發現上述這些潛在并發執行的可能,并有意識地采取某些措施來防止并發執行。
基本原則:在編寫代碼的開始階段就要設計恰當的鎖。
中斷安全代碼(interrupt-safe) —— 中斷處理程式中能避免并發通路的代碼。
SMP安全代碼(SMP-safe) —— 對稱多處理的機器中能避免并發通路的代碼。
搶占安全代碼(preempt-safe) —— 在核心搶占時能避免并發通路的安全代碼。
要保護些什麼
找出哪些資料需要保護是關鍵所在。
到底什麼資料需要加鎖呢?大多數核心資料結構都需要加鎖!有一條很好的經驗可以幫助我們判斷:如果有其他執行線程可以通路這些資料,那麼就給這些資料加上某種形式的鎖;如果任何其他什麼東西都能看見這個資料,那麼就要鎖住它。要給資料而不是代碼加鎖。
死鎖
死鎖的産生需要一定的條件:一個或多個執行線程和一個或多個資源,每個線程都在等待被其他線程持有的資源。
以下是一些簡單的規則對避免死鎖有很大幫助:
1. 加鎖的順序。使用嵌套的鎖時必須保證以相同的順序擷取鎖,這樣可以阻止緻命擁抱類型的死鎖。盡管是否鎖的順序和死鎖無關,但最好還是以獲得鎖相反的順序來釋放鎖。
2. 防止發生饑餓。
3. 不要重複請求同一個鎖。
4. 加鎖力求簡單。