《Linux核心設計與實現》學習【5】—— 核心同步

陽光下的咖啡發表於2020-10-18

1 基本概念

  臨界區:訪問和操作共享資料的程式碼段
  競爭條件:多個執行執行緒處於同一臨界區中同時執行
  同步:避免併發和防止競爭條件

2 造成併發執行的原因

  (1)中斷:中斷隨時會發生,也就會隨時打斷當前執行的程式碼。
  (2)軟中斷和tasklet:軟中斷和tasklet也會隨時被核心喚醒執行,也會像中斷一樣打斷正在執行的程式碼
  (3)核心搶佔:核心具有搶佔性,發生搶佔時,如果搶佔的執行緒和被搶佔的執行緒在相同的臨界區,就產生了競爭條件
  (4)睡眠及使用者空間的同步:使用者程式睡眠後,排程程式會喚醒一個新的使用者程式,新的使用者程式和睡眠的程式可能在同一個臨界區中
  (5)對稱多處理:2個或多個處理器可以同時執行相同的程式碼
  注意:要給資料加鎖而不是給程式碼加鎖

3 死鎖

(1)產生原因
  一個或多個執行執行緒和一個或多個資源,每個執行緒都在等待其中一個資源,但所有資源已經都被佔了。所有執行緒相互等待,但他們永遠不會釋放已經佔有的資源。
(2)避免死鎖
  1)按順序加鎖,使用巢狀的鎖時,必須保證以相同的順序獲取鎖。
  2)防止發生飢餓。即設定一個超時時間,防止一直等待下去。
  3)不要重複請求同一個鎖。
  4)設計應力求簡單。加鎖的方案越複雜就越容易出現死鎖。

4 核心同步方法

4.1 原子操作

  atomic_t型別定義在<linux/types.h>
  32位:atomic_t
  64位:atomic64_t

4.2 自旋鎖

  自旋鎖(spin lock),只能被一個執行緒持有,某個執行緒試圖獲取鎖,該執行緒會忙-旋轉-等待。
  基本使用:

DEFINE_SPINLOCK(mr_lock);
spin_lock(&mr_lock);
//臨界區
spin_unlock(&mr_lock);

注意:
  1)可用在中斷處理程式中
  2)在獲取鎖前,首先禁止當前處理器上的本地中斷

4.3 讀寫鎖

  多個讀任務可以併發地持有鎖,只能有一個任務可以持有寫鎖
  使用

DEFINE_RWLOCK(mr_rwlock);

read_lock(&mr_rwlock);
/* 臨界區(只讀).... */
read_unlock(&mr_rwlock);

write_lock(&mr_lock);
/* 臨界區(讀寫)... */
write_unlock(&mr_lock);

4.4 訊號量

  是一種睡眠鎖,當任務不能獲取訊號量時,睡眠。
  適用於長時間持有的情況
  兩種:計數訊號量、二值訊號量

/* 定義並宣告一個訊號量,名字為mr_sem,用於訊號量計數 */
static DECLARE_MUTEX(mr_sem);

/* 試圖獲取訊號量....*/
if(down_interruptible(&mr_sem)){
	...
}

/* 臨界區 ... */

/* 釋放給定的訊號量 */
up(&mr_sem);

4.5 讀寫訊號量

  與讀寫自旋鎖類似。

4.6 互斥體

  類似二值訊號量。
  相對訊號量,優先選擇互斥體。
  自旋鎖和互斥量

需求建議的加鎖方法
低開銷加鎖優先使用自旋鎖
短期鎖定優先使用自旋鎖
長期加鎖優先使用互斥體
中斷上下文中加鎖使用自旋鎖
持有鎖需要睡眠使用互斥體

4.7 完成變數

  一個任務執行工作,另一個任務在完成變數上等待,類似訊號量。
  方法

方法描述
init_completion(struct completion *)初始化指定的動態建立的完成變數
wait_for_completion(struct completion *)等待指定的完成變數接受訊號
complete(struct completion *)發訊號喚醒任何等待任務

4.8 BLK:大核心鎖

  不鼓勵使用,部分程式碼中依然沿用。
  特性:
  1)持有BLK的任務可以睡眠
  2)遞迴鎖
  3)只可以用在程式上下文中

4.9 順序鎖

  讀鎖被獲取時,寫鎖仍然可以被獲取。依靠一個序列計數器,當有疑義的資料被寫入時,序列值增加。
  使用

//定義
seqlock_t mr_seq_lock = DEFINE_SEQLOCK(mr_seq_lock);

//寫
write_seqlock(&mr_seq_lock);
/*寫鎖被獲取*/
write_sequnlock(&mr_seq_lock);

//讀
unsigned long seq;
do
{
    seq = read_seqbegin(&mr_seq_lock);
	...
} while(read_seqretry(&mr_seq_lock, seq)); 

  適用:資料存在多個讀者,寫者很少,希望寫優先於讀,資料簡單

4.10 禁止搶佔

  對於SMP,某些情況不需要自旋鎖,但仍需要關閉核心搶佔。

preempt_disable()
/*搶佔被禁止*/
preempt_enable()

4.11 順序和屏障

  目的:確保指令順序執行,不被處理器重排序

相關文章