內核鎖機制

一. 原子操作

 原子操作,就是代碼執行不會在執行完畢前被任何其他任務或事件打斷. 原子操作需要硬件的支持,因此是架構相關的, 它們都使用內嵌彙編語言實現,因爲C語言並不能實現這樣的操作.

  內核原子操作的理解:
  	a. 資源的使用情況計數, atomic_t的counter是int型的,可以記錄資源的使用情況;
  	b. 資源使用的互斥, 當 atomic_t的counter的取值情況來判斷資源的佔用情況;

(1) 定義
include/linux/types.h :
typedef struct {
int counter;
} atomic_t;

具體的體系架構中都有自己的實現:
	ARM平臺 : arch/arm/include/asm/atomic.h 

(2) 功能API
資源使用情況的counter的取值的計算和獲取, 來獲取資源佔用(使用)情況, 從而決定一些控制邏輯.

ATOMIC_INIT(i); //在聲明一個atomic_t變量時候,將他初始化爲i
int atomic_read(atomic_t *v); //讀取原子變量
void atomic_set(atomic *v,int i); //設置原子變量
void atomic_add(int i,atomic *v); //原子變量之+i
void atomic_inc(atomic *v); //原子變量自加操作
void atomic_dec(atomic *v); //原子變量自減操作
int atomic_sub_and_test(int i,atomic_t *v); //原子變量-i, 並判斷是否爲0
int atomic_add_negative(int i,atomic_t *v); //原子變量+i, 並判斷是否爲負
int atomic_add_return(int i,atomic_t *v); //原子變量+i, 並返回
int atomic_sub_return(int i,atomic_t *v); //原子變量-i, 並返回
int atomic_inc_return(atomic_t *v); //原子變量自加, 並返回
int atomic_dec_return(atomic_t * v); //原子變量自減, 並返回

(3) 使用場景
a. 驅動的安全性 :
一個驅動程序,有些時候, 不止一個進程在使用, 可以使用原子操作來使當前只要一個進程在使用, 對於字符設備, 其實只要在原來的字符設備驅動的 open 函數中增加了原子操作判斷相關的代碼,在關閉函數中增加釋放原子操作的代碼。 其餘的代碼基本不用改動,這樣就可以保證這個驅動只能被一個進程使用, 保證了驅動的安全性。
原子操作相關接口不僅僅是可以放在 open 函數上,可以根據需要放在不同的地方, 比如放在 write 函數中, 在同一時刻只能被一個進程寫操作, 讀操作可以被多進程讀, 因爲讀操作不會破壞數據, 而寫操作會.
因此, 可以根據具體需要, 靈活使用各個 API.

(4) 使用邊界
a. 是否會引起休眠?
執行過程中不能被打斷, 如果執行時間長的話, 涉及進程調度的話, 應該會引起休眠.

 		引起休眠的判斷方法:
 		睡眠 的本質是 該進程 處於一種特殊的不可執行狀態	(TASK_INTERRUPTIBLE 和TASK_UNINTERRUPTIBLE),
這樣, 調度程序就不會去執行他,而實現睡眠。
 		(1) 從代碼上分析,如果函數調用了其他函數,追朔回去只要有對scheduled的調用就可能引起睡眠;
		(2) 從操作系統原理解釋就是,當你需要獲取資源(硬件資源,內存資源,一個數據結構等),而資源不可得,此時不應該忙等, 本進程就應該睡眠,直到資源可用。因此, 可以試着分析是否有這種‘請求語境’出現,比如請求內存,而內存可能分配給了其他進程,要分配內存就意味着可能從其他進程把內存頁調來給你用,請求io,io設備有可能不可得,這些情況下都會帶來休眠。
		
 待續...

(5) 突破邊界(是否有)
a. 是否有規避邊界的方法?

待續...

二. spin lock

自旋鎖已經被別的執行單元保持時,調用者就一直循環等待是否該自旋鎖的保持者已經釋放了鎖,"自旋"一詞就是因此而得名。
在短期間內進行輕量級的鎖定。一個被爭用的自旋鎖使得請求它的線程在等待鎖重新可用的期間進行自旋(特別浪費處理器時間,這種特性下,不適用於長時間等待鎖的場景),所以自旋鎖不應該被持有時間過長。
自旋鎖內睡眠禁止睡眠問題:如果自旋鎖鎖住以後進入睡眠,而此時又不能進行處理器搶佔(鎖住會disable prempt),其他進程無法獲得cpu,這樣也不能喚醒睡眠的自旋鎖,因此不相應任何操作。
自旋鎖廣泛用於內核:
a. 效率高;
自旋鎖是一種輕量級的互斥鎖,不會涉及進程切換, 可以更高效的對互斥資源進行保護。所以自旋鎖的效率就遠高於互斥鎖;
b. 可使用的場景豐富;
不同場景,有不同的api.

(1) 使用場景
a. 適合於保持時間非常短的情況,可以在任何上下文使用;
b. 在任何時刻最多隻能有一個執行單元獲得鎖;
c. 各個接口使用, 不同的上下文, 有不同的函數接口:
1. spin_lock_bh / spin_unlock_bh
在得到自旋鎖的同時失效/使能本地軟中斷(tasklet和timer是用軟中斷實現).
可能發生軟中斷的場景,使用最恰當, 軟中斷的場景最高效;(使用spin_lock_irq和spin_unlock_irq以及spin_lock_irqsave和spin_unlock_irqrestore也可以,它們失效了本地硬中斷,失效硬中斷隱式地也失效了軟中斷)
同一個tasklet或timer只能在一個CPU上運行,即使是在SMP環境下也是如此.
2. spin_lock_irq / spin_unlock_irq
禁止內核搶佔, 同時禁止本地中斷;
如果確定在獲取鎖之前本地中斷是開啓的; 解鎖的時候直接將本地中斷啓用就可以, 不保存中斷狀態;
3. spin_lock / spin_unlock
禁止內核搶佔;
4. spin_lock_irqsave/spin_unlock_irqrestore
禁止內核搶佔, 同時禁止本地中斷, 同時保存CPU所有的中斷狀態, 解鎖時通過 spin_unlock_irqrestore完成釋放鎖、恢復本地中斷到之前的狀態等工作;
a. 保存本地中斷狀態;
b. 關閉本地中斷;
c. 獲取自旋鎖;
5. spin_is_locked / spin_can_locked
判斷是否可獲取鎖;
每種場景下, 都有適合的接口使用, 可根據場景,選擇合適的接口, 也有對應的try接口.

(2) 邊界
a. 中斷上下文, 必須使用自旋鎖;
中斷上下文不能休眠, 因此被保護的共享資源需要在中斷上下文訪問(包括底半部即中斷處理句柄和頂半部即軟中斷)時, 就必須使用自旋鎖.
b. 自旋鎖保持期間是搶佔失效的;
自旋鎖只有在內核可搶佔或SMP的情況下才真正需要,在單CPU且不可搶佔的內核下,自旋鎖的所有操作都是空操作; (信號量和讀寫信號量保持期間是可以被搶佔的)

(3) 比較
a. 鎖保持時間;
自旋鎖適合於保持時間比較短的情況;
信號量和讀寫信號量適合於保持時間較長的情況,它們會導致調用者睡眠;
b. 是否可搶佔;
自旋鎖保持期間搶佔失效;
信號量和讀寫信號量保持期間可以被搶佔;
c. 是否引起休眠;
自旋鎖, 調用進程不會睡眠;
信號量和讀寫信號量, 會導致調用者睡眠;

三. 互斥鎖

互斥鎖主要用於實現內核中的互斥訪問功能。內核互斥鎖是在原子 操作基礎上實現的,獲取互斥鎖的過程中,是在自旋鎖的保護下進行, 但這對於內核用戶是不可見的。 互斥鎖比當前的內核信號量選項更快,並且更加緊湊。
對它的訪問必須遵循一些規則:
a. 同一時間只能有一個任務持有互斥鎖,而且只有這個任務可以對互斥鎖進行解鎖;
b. 互斥鎖不能進行遞歸鎖定或解鎖;
c. 一個互斥鎖對象必須通過其API初始化,而不能使用memset或複製初始化;
d. 一個任務在持有互斥鎖的時候是不能結束;
e. 互斥鎖所使用的內存區域是不能被釋放;
f. 使用中的互斥鎖是不能被重新初始化;
g. 並且互斥鎖不能用於中斷上下文。

a, d, e這樣的特性, 決定了他適用於長時間持鎖的場景.

(1) 定義
include/linux/mutex.h
kernel/locking/mutex.c
1. mutex_init/ mutex_lock / mutex_unlock;
初始化/獲取/釋放 鎖
2. mutex_trylock();
試圖獲取互斥鎖,如果成功獲取則返回1,否則返回0,不等待;
3. mutex_lock_interruptible();
在獲得了互斥鎖或進入睡眠直到獲得互斥鎖之後會返回0。如果在等待獲取鎖的時候進入睡眠狀態收到一個信號(被信號打斷睡眠),則返回_EINIR;

四. 信號量

信號量在創建時需要設置一個初始值,表示同時可以有幾個任務可以訪問該信號量保護的共享資源,初始值爲1就變成互斥鎖(Mutex),即同時只能有一個任務可以訪問信號量保護的共享資源。
一個任務要想訪問共享資源,首先必須得到信號量,獲取信號量的操作將把信號量的值減1,若當前信號量的值爲負數,表明無法獲得信號量,該任務必須掛起在該信號量的等待隊列等待該信號量可用;若當前信號量的值爲非負數,表示可以獲得信號量,因而可以立刻訪問被該信號量保護的共享資源。當任務訪問完被信號量保護的共享資源後,必須釋放信號量,釋放信號量通過把信號量的值加1實現,如果信號量的值爲非正數,表明有任務等待當前信號量,因此它也喚醒所有等待該信號量的任務。

(1) 定義
include/linux/semaphore.h
/* Please don’t access any members of this structure directly */
struct semaphore {
raw_spinlock_t lock;
unsigned int count;
struct list_head wait_list;
};

kernel/locking/semaphore.c

信號量的實現和spin lock的實現, 底層的接口是一樣的.
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章