讀書筆記(7) kernel 訊號量

2021-10-12 18:00:27 字數 3593 閱讀 9977

linux 核心中應用最廣泛的同步原語除了自旋鎖就是訊號量(semaphore)。可以說自旋鎖和訊號量在很大程度上是一種互補的關係,它們有各自適用的場景,兩者的場景加起來基本上可以覆蓋一切核心中的所有場景。

訊號量可以是多值的(多值訊號量),當其為二值訊號量(只有兩個值)時,類似於鎖:乙個值代表未鎖,另乙個值代表已鎖。其工作原理與自旋鎖最大的不同在於:獲取鎖的程序(核心的乙個執行路徑),若不能立即得到鎖,就會發生排程轉入睡眠;另外的程序釋放鎖時,喚醒等待該鎖的程序。

前面已經說過自旋鎖的使用的限制主要有兩點:

(1)持有自旋鎖的臨界區不允許排程和睡眠

(2)其次就是競爭激烈時整體效能不好

而訊號量恰好解決這兩個問題:因為鎖的競爭者 不是忙等,訊號量的臨界區允許排程和睡眠而不會導致死鎖;因為鎖的競爭者會轉入睡眠, 從而讓出 cpu 資源給別的程序,因此對鎖的競爭不會影響整體效能。有優點就有缺點,中斷上下文要求整體執行時間可**(不能太長),而訊號量臨界區可能發生排程,因此不能用於中斷上下文(只能用於程序上下文)。另外,如果臨界區的**很短,那麼用訊號量並不合算,因為程序睡眠-喚醒的代價太大,消耗的 cpu 資源可能遠遠大於短時間的忙等。和自旋鎖一樣,訊號量也分為普通訊號量和讀寫訊號量兩種。

(1)普通訊號量

在 linux-2.6.26 以前,普通訊號量的實現是體系結構相關的。考慮到訊號量的原語並不像自旋鎖那樣用在對效能要求很高的場景,linux-2.6.26 開始從可讀性出發實現了通用的普通訊號量。通用普通訊號量的資料型別定義如下:

struct semaphore ;
這裡面,count 是最重要的計數器字段,它標識了訊號量的狀態:0 表示忙已 鎖,值為正代表自由未鎖,允許競爭者進入臨界區。因此 count 的初值就是最大允許進入臨界區的程序數目,初值為 2 的訊號量就是二值訊號量。二值訊號量類似於乙個普通的鎖,而多值訊號量類似於乙個允許一定併發性的鎖。wait_list 欄位是當訊號量為忙時, 所有等待訊號量的程序列表,而 lock 則是保護 wait_list 的自旋鎖。

普通訊號量的主要 api 有:

/*靜態定義乙個名為 sem 訊號量*/

define_semaphore(sem):

/*初始化乙個訊號量 sem,計數器初值為 val,也就是count的值初始化為val*/

void sema_init(struct semaphore *sem, int val):

/*減少訊號量 sem 的計數器(類似於獲取鎖)。如果失敗(計數器已經是 0),那麼轉入睡眠(狀態為 task_uninterruptible,不會被任何訊號喚醒)並把當前程序掛到 wait_list;被喚醒後繼續嘗試獲取鎖*/

void down(struct semaphore *sem)

/*增加訊號量 sem 的計數器(類似於釋放鎖),然後喚醒 wait_list 裡面的第乙個程序(如果裡面有的話)*/

void up(struct semaphore *sem)

/*嘗試減少訊號量 sem 的計數器(類似於獲取鎖),如果成功就返回 0,如果失敗(計數器已經是 0)就返回 1,程序不會睡眠*/

int down_trylock(struct semaphore *sem)

/*減少訊號量 sem 的計數器(類似於獲取鎖)。如果失敗(計數器已經是 0),那麼轉入 睡眠(狀態為task_killable,會被致命訊號喚醒)並把當前程序掛到wait_list;被喚 醒後繼續嘗試獲取鎖。正常返回 0,被訊號喚醒則返回-eintr*/

int down_killable(struct semaphore *sem)

/*減少訊號量 sem 的計數器(類似於獲取鎖)。如果失敗(計數器已經是 0),那麼轉入睡眠(狀態為 task_interruptible,會被任意訊號喚醒)並把當其程序掛到 wait_list; 被喚醒後繼續嘗試獲取鎖。正常返回 0,被訊號喚醒則返回-eintr*/

int down_interruptible(struct semaphore *sem)

/*減少訊號量 sem 的計數器(類似於獲取鎖)。如果失敗(計數器已經是 0),那麼轉入 睡眠(狀態為 task_ uninterruptible,但睡眠時間達到超時值 jiffies 後會被喚醒)並 把當其程序掛到 wait_list;被喚醒後繼續嘗試獲取鎖。正常返回 0,被超時喚醒則返回 -etime*/

int down_timeout(struct semaphore *sem, long jiffies)

(2)讀寫訊號量

讀寫訊號量的引入原因類似於讀寫自旋鎖,是為了區分不同的競爭者(讀者和寫者), 以便允許讀者共享而寫者互斥。讀寫訊號量既有通用版本,也有各種體系結構自己實現的版本。龍芯使用的是通用版本。

struct rw_semaphore {

long count;

struct list_head wait_list;

raw_spinlock_t wait_lock;

#ifdef config_rwsem_spin_on_owner

struct optimistic_spin_queue osq;

struct task_struct *owner;

#endif

主要字段 count、wait_list 和 wait_lock 的含義與普通訊號量的含義基本相同,用法也相同。而 config_ rwsem_spin_on_owner 是 linux-3.16 開始引入的,通過 mcs 鎖來優化讀寫訊號量的效能,其原理類似於 queued spinlock。

讀寫訊號量的主要 api 如下:

/*靜態宣告乙個名為 sem 訊號量*/

declare_rwsem(sem)

/*初始化乙個讀寫訊號量 sem*/

init_rwsem(sem)

/*讀者減少訊號量 sem 的計數器(類似於獲取鎖)*/

down_read(struct rw_semaphore *sem)

/*讀者增加訊號量 sem 的計數器(類似於釋放鎖)*/

up_read(struct rw_semaphore *sem)

/*寫者減少訊號量 sem 的計數器(類似於獲取鎖)*/

down_write(struct rw_semaphore *sem)

/*寫者增加訊號量 sem 的計數器(類似於釋放鎖)*/

up_write(struct rw_semaphore *sem)

/*讀者嘗試減少訊號量 sem 的計數器(類似於獲取鎖),如果count的值已經為0,立即返回不睡眠*/

down_read_trylock(struct rw_semaphore *sem)

/*寫者嘗試減少訊號量 sem 的計數器(類似於獲取鎖),如果count的值已經為0,立即返回不睡眠*/

down_write_trylock(struct rw_semaphore *sem):

/*寫者鎖降級,即將寫鎖轉換成讀鎖*/

downgrade_write(struct rw_semaphore *sem)

具體的**實現請看include/linux/semaphore.h中的實現,這裡不做詳細的介紹。

讀書筆記(10) kernel 完成量

完成量特點 自旋鎖訊號量在內的各種鎖類機制都有乙個共同的特點即加鎖者和解鎖者必須是同乙個程序,而完成量不同,a程序加鎖,b程序解鎖的同步原語操作,主要用於程序間通訊。資料結構 struct completion其中done欄位表示完成狀態,wait欄位代表等待佇列。工作原理就是 a程序將done 設...

筆記 訊號量集

訊號量集,主要作用是保證共享記憶體的使用不發生衝突。訊號量 semaphore 實際是乙個整數,它的值由多個程序進行測試 test 和設定 set 就每個程序所關心的測試和設定操作而言,這兩個操作是不可中斷的,或稱 原子 操作,即一旦開始直到兩個操作全部完成。測試和設定操作的結果是 訊號量的當前值和...

工作筆記 訊號量

訊號量用來各執行緒中通訊。我簡單的將訊號量想象成烽火台發出的煙,向遠處傳達訊號。嘗試將globvar增加20000次,乙個執行緒增加10000次。用訊號量來代替互斥鎖,使一次只有乙個執行緒對globvar變數進行操作。經過dev c 測試多次,其中5次中有一次錯誤的獲得19995 include i...