深入併發與同步
一、概念
所謂併發,是指多個核心路徑同時訪問和操作資料,可能發生 覆蓋共享資料的情況,造成被訪問資料的不一致。
在核心中發生併發訪問併發源主要有以下4種。
- 中斷和異常
- 軟中斷和tasklet:軟中斷和tasklet可能隨時會被排程執行,從而打斷當前正在執行程序的上下文。
- 核心搶佔:排程器支援核心搶佔。
- 多處理器併發執行
上述情況需要針對單核和多核系統進行區別對待。
對於單處理器的系統有以下併發源:
1. 中斷處理程式可以打斷軟中斷、tasklet和程序上下文的執行
2. 軟中斷和tasklet並不會併發,但是可以打斷程序上下文的執行
3. 在支援搶佔的核心中,程序上下文會併發
4. 在不支援搶佔的核心中,程序的上下文不會產生併發
對於SMP系統:
1. 同類型的中斷並不會併發,但是不同型別的中斷源可能會被送到不同的CPU上,因此可能會存在併發
2. 同類型的軟中斷會在不同的CPU上併發執行
3. 同類型的tasklet是序列執行,不會在多個CPU上併發
4. 不同CPU的程序上下文會併發
記住臨界區的保護原則:是保護資源或者資料,而不是保護程式碼。(靜態區域性變數,全域性變數,共享的資料結構,Buffer快取,連結串列,紅黑樹等)
二、原子操作和記憶體屏障
1.ARM處理器中如何實現獨佔訪問記憶體??
處理器中有Local monitor
和Global monitor
來實現ldrex
和strex
指令的獨佔訪問,並且ldrex
strex
保證的add操作的原子性。i++用原子操作還是加鎖的方式來保證它的原子性??
採用原子操作,加鎖開銷太大!
2.記憶體屏障
程式實際執行時記憶體訪問順序和程式程式碼編寫的訪問順序不一致,會導致記憶體亂序訪問.因此引入記憶體屏障以防止記憶體亂序訪問.
- 資料儲存屏障DMB(Data Memory Barrier)
- 資料同步屏障DSB(Data Sync Barrier)
- 指令同步屏障ISB(Instruction Sync Barrier)
記憶體屏障的使用場景舉例:
- 在網絡卡驅動程式中傳送資料包,網路資料包寫入buffer後交給DMA引擎傳送,
wmb()
- 在核心裡的睡眠和喚醒API也用到了記憶體屏障,在
set_current_state()
修改程序狀態時插入記憶體屏障函式smp_wmb()
.喚醒時會呼叫wake_up()
,在修改task狀態之前也會隱式的插入smp_wmb()
3.自旋鎖spinlock
1.spinlock的性質:
- 忙等待的所機制
- 同一時刻只能有一個程式碼路徑獲得該鎖
- 鎖持有者必須儘快完成臨界區的任務
2.存在的問題:
在很多CPU爭用同一個spinlock時,會導致嚴重的不公平性和效能下降。當該鎖釋放時,事實上可能剛剛釋放該鎖的CPU又會馬上獲得該鎖的使用權,沒有考慮那些已經在鎖外面等待了很久的CPU。因為剛剛釋放鎖的CPU的L1 cache中儲存了該鎖,它比其他鎖更快的獲得自旋鎖。
3spinlock鎖實現的關鍵:
關閉核心搶佔!!!
如果臨界區允許核心搶佔,那麼如果臨界區發生中斷,中斷返回時回去檢查搶佔排程。
因此就有兩個問題:
①搶佔排程相當於使得持有鎖的程序睡眠,違背了spinlock不允許睡眠和快速執行的設計初衷;
②搶佔排程程序也可能去申請獲得spinlock鎖,於是死鎖就產生了。
4.使用spinlock的重要原則:
擁有spinlock鎖的臨界區必須是原子執行,不能休眠和主動排程。
5.spin_lock
和raw_spin_lock
的區別
在絕對不允許被搶佔和睡眠的臨界區,應該使用raw_spin_lock
,否則使用spin_lock
4.訊號量
訊號量的可以同時允許任意數量的鎖持有者,sema_init(struct *sem,int count)
,其中count大於1,可以允許多個持有者,計數訊號量;count等於1,只允許一人持有鎖,互斥訊號量。訊號量允許睡眠。可以用於並行處理環境。
5.Mutex互斥體
Linux核心已經有了訊號量機制,為何還要單獨設定一個Mutex機制呢??
訊號量相當於多個廁所;Mutex相當於一個廁所,一次只允許一個人進去。Mutex比訊號量執行速度快,可擴充套件性更好,Mutex資料結構的定義比訊號量小。
Mutex實現了自旋等待的機制,更準確的說,他比讀寫訊號量更早的實現了自旋等待機制。在實現自旋等待機制時,核心實現了一套MCS鎖機制(一種自旋鎖優化方案)來保證只有一個人自旋等待持有者釋放鎖。
MCS避免多個CPU爭用鎖導致CPU快取記憶體行顛簸現象
1.Mutex鎖的實現
Mutex鎖的初始化有兩種方式:
- 靜態使用
DEFINE_MUTEX
巨集 - 動態使用
mutex_init()
函式
小結
Mutex使用場景:
- 同一時刻只有一個執行緒可以持有Mutex
- 只有鎖持有者可以解鎖.
- 不允許遞迴加鎖和解鎖
- 程序持有Mutex不能退出
- 必須使用官方API來初始化
- 可以睡眠,但是不允許在中斷處理程式或中斷下半部使用.
在實際工程中,如何使用spinlock和Mutex???
中斷上下文,毫不猶豫地使用spinlock,臨界區含有睡眠,隱含睡眠的動作及核心API,避免使用spinlock.訊號量和Mutex,優先使用Mutex.