核心同步方法
1.原子操作
原子操作可以保證指令以原子的方式執行——執行過程不被打斷。核心提供了兩組原子操作介面,一組針對整數進行操作,另一組針對單獨的位進行操作。
針對整數的原子操作只能對 atomic_t 類型的資料進行處理。
除了原子整數操作外,核心還提供了一組針對位這一級資料進行操作的函數。位操作函數是對普通的記憶體位址進行操作的,它的參數是一個指標和一個位號,第0位是給定地址的最低有效位。
核心還提供了一組與上述操作對應的非原子位函數。非原子位函數與原子位函數的操作完全相同,但是前者不保證原子性,且其名字首碼多兩個底線。例如,與 set_bit 對應的非原子形式是 __set_bit。如果你不需要原子性操作(比如說,如果你已經用鎖保護了自己的資料),那麼這些非原子的位函數相比原子的位函數可能會執行得更快些。
2.自旋鎖
Linux核心中最常見的鎖是自旋鎖(spin lock)。自旋鎖最多隻能被一個可執行線程持有。如果一個執行線程試圖獲得一個被爭用(已經被持有)的自旋鎖,那麼該線程就會一直進行忙迴圈——旋轉——等待鎖重新可用。
spinlock_t mr_lock = SPIN_LOCK_UNLOCKED;spin_lock( &mr_lock );/* 臨界區... */spin_unlock( &mr_lock );
Linux核心實現的自旋鎖是不可遞迴的。
自旋鎖可以使用在中斷處理常式中(此處不能使用訊號量,因為它們會導致睡眠)。在中斷處理常式中使用自旋鎖時,一定要在擷取鎖之前,首先禁止本地中斷(在當前處理器上的插斷要求),否則,中斷處理常式就會打斷正持有鎖的核心代碼,有可能會試圖去爭用這個已經被持有的自旋鎖,造成死結。注意,需要關閉的只是當前處理器上的中斷。如果中斷髮生在不同的處理器上,即使中斷處理常式在同一鎖上自旋,也不會妨礙鎖的持有人(在不同處理器上)最終釋放鎖。
核心提供了禁止中斷同時請求鎖的介面:
spinlock_t mr_lock = SPIN_LOCK_UNLOCKED;unsigned long flags;spin_lock_irqsave( &mr_lock, flags ); //儲存中斷目前狀態,並禁止中斷,擷取鎖。/* 臨界區... */spin_unlock_irqrestore( &mr_lock, flags ); //解鎖,並讓中斷恢複到加鎖前的狀態。
在與下半部配合使用時,必須小心地使用鎖機制。函數 spin_lock_bh 用於擷取指定鎖,同時它會禁止所有下半部的執行。相應的 spin_unlock_bh 函數執行相反的操作。
由於下半部可以搶佔進程內容相關的代碼,所以當下半部和進程上下文共用資料時,必須對進程上下文中的共用資料進行保護,需要加鎖的同時還要禁止下半部執行。同樣,由於中斷處理常式可以搶佔下半部,所以如果中斷處理常式和下半部共用資料,那麼就必須在擷取恰當的鎖的同時還要禁止中斷。
同類的 tasklet 不可能同時運行,所以對於同類 tasklet 中的共用資料不需要保護。但是當資料被兩個不同種類的 tasklet 共用時,就需要在訪問下半部中的資料前先獲得一個普通的自旋鎖,這裡不需要禁止下半部,因為在同一個處理器上決不會有 tasklet 相互強佔的情況。
對於非強制中斷,無論是否同種類型,如果資料被非強制中斷共用,那麼它必須得到鎖的保護,這是因為即使是同種類型的兩個非強制中斷也可以同時運行在一個系統的多個處理器上。但是,同一處理器上的一個非強制中斷絕不會搶佔另一個非強制中斷。因此,根本沒必須禁止下半部。
3.讀—寫自旋鎖
這種自旋鎖為讀和寫分別提供了不同的鎖。一個或多個讀任務可以並發的持有讀者鎖;相反,用於寫的鎖最多隻能被一個寫任務持有,而且此時不能有並發的讀操作。
rwlock_t mr_rwlock = RW_LOCK_UNLOCKED;read_lock( &mr_rwlock );/* 臨界區(唯讀) */read_unlock( &mr_rwlock );write_lock( &mr_rwlock );/* 臨界區(讀寫) */write_unlock( &mr_rwlock );
注意:不能把一個讀鎖“升級”為寫鎖
read_lock( &mr_rwlock );write_lock( &mr_rwlock );
將會帶來死結,因為寫鎖會不斷自旋,等待所有的讀者釋放鎖,其中也包括它自己。
這種鎖機制照顧讀比照顧寫要多一點。當讀鎖被持有時,寫操作為了互斥訪問只能等待,但是,讀者卻可以繼續成功地佔用鎖,而自旋等待的寫者在所有讀者釋放鎖之前是無法獲得鎖的。所以,大量讀者必定會使掛起的寫者處於饑餓狀態。