內(nèi)核同步
在使用共享內(nèi)存的應(yīng)用程序中,程序員必須留意保護共享資源,防止對共享資源的并發(fā)訪問,內(nèi)核也不例外。如果多個執(zhí)行線程同時訪問和操作數(shù)據(jù),就有可能發(fā)生各線程之間相互覆蓋共享數(shù)據(jù)的情況,造成被訪問的數(shù)據(jù)處于不一致狀態(tài)。
1.臨界區(qū)和競爭條件
臨界區(qū)就是訪問和操作共享數(shù)據(jù)的代碼段。多個執(zhí)行線程并發(fā)訪問同一個資源是不安全的,因此通常對臨界區(qū)采用原子操作(顧名思義,這些操作看作一個原子,不可分的單元)。若兩個執(zhí)行線程處于同一個臨界區(qū)同時執(zhí)行,這是一個bug,稱這種情況為競爭條件。避免并發(fā)和防止競爭條件稱為同步(synchronization)。
2.造成并發(fā)執(zhí)行的原因
內(nèi)核中造成并發(fā)執(zhí)行的原因有:
- 中斷,中斷可能隨時打斷當(dāng)前執(zhí)行的代碼
- 軟中斷和tasklet
- 內(nèi)核搶占,內(nèi)核中的任務(wù)可能被另一個任務(wù)搶占
- 睡眠以及與用戶空間的同步,內(nèi)核執(zhí)行的進程可能睡眠,就會喚醒調(diào)度程序,從而導(dǎo)致調(diào)度一個新的用戶進程執(zhí)行
- 對此多處理,多個處理器同時執(zhí)行代碼
3.死鎖
死鎖需要四個必要條件:
- 互斥條件:一個資源每次只能被一個進程使用
- 請求與保持條件:一個進程因請求資源而阻塞時,不釋放已獲得資源
- 不剝奪條件:進程已獲得的資源,在未使用完之前,不能強行剝奪
- 循環(huán)等待條件:若干進程之間形成一種頭尾相接的循環(huán)等待資源關(guān)系
4.內(nèi)核同步方法
Linux內(nèi)核提供了一組相當(dāng)完備的同步方法。
原子操作
內(nèi)核提供了兩組原子操作接口——一組針對整數(shù)進行操作,另一組針對單獨的位進行操作。
自旋鎖
Linux內(nèi)核中最常見的鎖是自旋鎖,自旋鎖只能被一個可執(zhí)行線程持有。若一個執(zhí)行線程試圖獲得一個被占用的自旋鎖,那么該線程就會一直忙循環(huán)——旋轉(zhuǎn)——等待鎖重新可用。
用現(xiàn)實生活例子解釋,你去上廁所,廁所只有一個坑,先去的那人把門鎖了,你只有抽根煙,再去看看出來沒,沒出來再去抽根煙,在回來看看...只有持鎖的人才能開門,所以自旋鎖不適合長時間占用臨界區(qū)的執(zhí)行線程。
Linux中自旋鎖接口定義在<linux/spinlock.h>
中,基本形式如下:
DEFINE_SPINLOCK(mr_lock);
spin_lock(&mr_lock);
/*臨界區(qū)*/
spin_unlock(&mr_lock);
對于讀寫操作,還存在讀-寫自旋鎖:
- 一個或多個讀任務(wù)可以并發(fā)持有讀者鎖
- 寫的鎖最多只能被一個寫任務(wù)持有,而且此時不能有并發(fā)讀操作
信號量
Linux中的信號量是一種睡眠鎖。如果一個任務(wù)試圖獲得一個不可用的信號量時,信號量會將其推進一個等待隊列,然后讓其睡眠。此時處理器能重獲自由,從而執(zhí)行其他代碼。
同樣是上廁所,這個廁所升級了,具有一個記錄和通知功能,當(dāng)發(fā)現(xiàn)廁所坑被占用之后,到來的人就在冊子末尾添加自己的電話,你就可以繼續(xù)去抽煙泡妞。等蹲坑那哥們一出來,就挑選冊子上第一個電話號碼,打電話通知你,兄弟來上廁所。
計數(shù)信號量是高級版信號量,就是說允許多個執(zhí)行線程(多個但是有上限)同時訪問臨界區(qū)。通過PV操作執(zhí)行計數(shù)器的加減操作。
繼續(xù)上廁所,廁所擴容了,不止一個坑!!!因此這里就有一個計數(shù)器,最大值就是坑數(shù),來一個人就減一,走一個人就加一。計數(shù)器為0時,嘿嘿,小伙,再夾一會吧,有坑,哥會打電話給你的。
互斥體(mutex)
互斥體和信號量類似,但是互斥體的上鎖者必須負責(zé)給其解鎖,因此互斥體不適合內(nèi)核同用戶空間復(fù)雜的同步場景,更多用于同一上下文的上鎖和解鎖。
小結(jié)
同步是是Linux以及多線程編程等場景下非常重要的內(nèi)容,需要進一步了解同步的知識,需要動手實驗一下。