[臨界區(qū)和競爭條件]
所謂臨界區(qū)就是訪問和操作共享數(shù)據(jù)的代碼段。多個執(zhí)行線程并發(fā)訪問同一個資源通常是不安全的,為了避免在臨界區(qū)中并發(fā)訪問,coder必須保證這些代碼原子執(zhí)行。
如果兩個執(zhí)行線程有可能處于同一個臨界區(qū)中同時執(zhí)行,那么這就是程序包含的一個bug。如果這種情況確實發(fā)生了,我們就稱它是競爭條件(race conditions)。避免并發(fā)和防止競爭條件稱為同步(synchronization)。
?
[造成并發(fā)執(zhí)行的原因]
用戶空間之所以需要同步,是因為用戶程序會被調(diào)度程序搶占和重新調(diào)度。在內(nèi)核中有類似可能造成并發(fā)執(zhí)行的原因:
中斷:中斷幾乎可以在任何時刻異步發(fā)生,也就是隨時打斷當前正在執(zhí)行的代碼;
軟中斷和tasklet:內(nèi)核能在任何時刻喚醒或調(diào)度軟中斷和tasklet,打斷當前正在執(zhí)行的代碼;
內(nèi)核搶占:因為內(nèi)核具有搶占性,所以內(nèi)核中的任務可能會被另一任務搶占;
睡眠及與用戶空間的同步:在內(nèi)核執(zhí)行的進程可能會睡眠,這就會喚醒調(diào)度程序,從而導致調(diào)度一個新的用戶進程執(zhí)行;
對稱處理器:兩個或多個處理器可以同時執(zhí)行代碼。
?
[哪些代碼需要同步]
我們在編寫內(nèi)核代碼時,你要問自己下面這些問題:
這個數(shù)據(jù)是不是全局的?除了當前線程外,其他線程能不能訪問它?
這個數(shù)據(jù)會不會在進程上下文和中斷上下文種共享?它是不是要在兩個不同的中斷處理程序中共享?
進程在訪問數(shù)據(jù)時可不可能被搶占?被調(diào)度的新程序會不會訪問同一數(shù)據(jù)?
當前進程是不是會睡眠(阻塞)在某些資源上,如果是,它會讓共享數(shù)據(jù)處于何種狀態(tài)?
怎樣防止數(shù)據(jù)失控?
如果這個函數(shù)又在另一個處理上被調(diào)度將會發(fā)生什么呢?
如何確保代碼遠離并發(fā)威脅呢?
?
簡而言之,幾乎訪問所有的內(nèi)核全局變量和共享數(shù)據(jù)都需要某種形式的同步方法。
?
[死鎖]
死鎖的產(chǎn)生需要一定的條件:要一個或多個執(zhí)行線程和一個或多個資源,每個線程都在等待其中的一個資源,但所有的資源都已經(jīng)被占用了。所有線程都在相互等待,但它們永遠不會釋放已經(jīng)占有的資源,于是任何資源都無法繼續(xù),這就意味著死鎖的發(fā)生。
Example:有兩個線程和兩把鎖
線程1????????????????? 線程2
獲得鎖A??????????????? 獲得鎖B
試圖獲得鎖B??????????? 試圖獲得鎖A
等待鎖B??????????????? 等待鎖A
?
[原子操作]
原子操作可以保證指令以原子的方式執(zhí)行-執(zhí)行過程不被打斷。內(nèi)核提供了兩組原子操作接口:一組針對整數(shù)進行操作,另一組針對單獨的位進行操作。
原子整數(shù)類型
?
?
[自旋鎖]
自旋鎖(spin lock)最多只能被一個可執(zhí)行線程持有。如果一個執(zhí)行線程試圖獲得一個被已經(jīng)持有(即所謂的爭用)的自旋鎖,那么該線程就會一直進行忙循環(huán)-旋轉(zhuǎn)-等待鎖重新可用。
spinlock結(jié)構(gòu)體:
?
?
一個被爭用的自旋鎖是的請求它的線程在等待鎖重新可用時自旋,特別浪費處理器時間,這種行為是自旋鎖的要點。所以自旋鎖不應該被長時間持有。持有自旋鎖的時間最好小于完成兩次上下文切換的耗時,也就是兩次調(diào)用schedule()的時間。
自旋鎖可以用于中斷處理程序中,但是信號量不可以,信號量會導致睡眠。
使用鎖的時候一定要對癥下藥,要有針對性。要知道需要保護的是數(shù)據(jù)而不是代碼。
?
[信號量]
Linux中的信號量是一種睡眠鎖。如果有一個任務試圖獲得一個不可用(已經(jīng)被占用)的信號量時,信號量會將其推進一個等待隊列中,然后讓其睡眠。這時處理器能重獲自由,從而去執(zhí)行其他代碼。當持有的信號量可用(被釋放)后,處于等待列隊中的那個任務將被喚醒,并獲得該信號量。
?semaphore結(jié)構(gòu)體:
?
?
使用信號量應注意的地方:
由于爭用信號量的進程在等待鎖重新變?yōu)榭捎脮r會睡眠,所以信號量適合用于鎖會被長時間持有的情況;
相反,鎖被短時間持有時,使用信號量就不能太適合了。因為睡眠、維護等待隊列以及喚醒所花費的開銷可能比鎖被占用的全部時間還要長;
由于執(zhí)行線程在鎖被爭用時會睡眠,所以只能在進程上下文中才能獲取信號量鎖,因為在中斷上下文是不能進行調(diào)度的;
你可以在持有信號量時去睡眠,因為當其他進程試圖獲得同一信號量時不會因此而死鎖。
在你占用信號量的同時不能占用自旋鎖。因為在你等待信號量時可能會睡眠,而在持有自旋鎖時是不允許睡眠的。
?
[互斥體]
Linux最新的linux內(nèi)核中,互斥體mutex是一種實現(xiàn)互斥的特定睡眠鎖。Mutex在內(nèi)核中對應數(shù)據(jù)結(jié)構(gòu)mutex,其行為和使用計數(shù)為1的信號量類似,但操作接口更簡單,實現(xiàn)也更高效,而且使用限制更強。
?mutex結(jié)構(gòu)體:
?
?
Mutex使用限制:
任何時候中只有一個任務可以持有mutex,也就是說,mutex的使用計數(shù)永遠是1;
給mutex上鎖者必須負責給其再解鎖,你不能在一個上下文中鎖定一個mutex,而在另一個上下文中給它解鎖。這個限制使得mutex不合適內(nèi)核同用戶空間復雜的同步場景。最常使用的方式是:在同一上下文中上鎖和解鎖。
遞歸地上鎖和解鎖是不允許的。也就是說,你不能遞歸地持有同一個鎖,同樣你也不能再去解鎖一個已經(jīng)被解開的mutex;
Mutex不能在中斷或者下半部中使用;
Mutex只能通過官方API管理
?
[信號量和互斥體]
互斥鎖和信號量很相似,內(nèi)核中兩者共存會令人混淆。所幸,它們的標準使用方式都有很簡單的規(guī)范:除非mutex的某個約束妨礙你使用,否則相比信號量要優(yōu)先使用mutex。當你寫新代碼時,只有碰到特殊場合才會需要使用信號量。因此建議首選mutex。
?
[自旋鎖和互斥體]
了解何時使用自旋鎖,何時使用互斥體或者信號量對編寫優(yōu)良代碼很重要,但是多數(shù)情況下,并不需要太多的考慮,因為在中斷上下文中只能使用自旋鎖,而在任務睡眠時只能使用互斥體。
需求
建議加鎖方法
低開銷加鎖
優(yōu)先使用自旋鎖
短期鎖定
優(yōu)先使用自旋鎖
長期加鎖
優(yōu)先使用互斥體
中斷上下文中加鎖
使用自旋鎖
下半部加鎖
使用自旋鎖
持有鎖需要睡眠
使用互斥體
?
評論
查看更多