1. 程式人生 > >Linux核心同步機制之訊號量和互斥體

Linux核心同步機制之訊號量和互斥體

訊號量:

訊號量(semaphore)是程序間通訊處理同步互斥的機制。是在多執行緒環境下使用的一種措施,它負責協調各個程序,以保證他們能夠正確、合理的使用公共資源。 它和spin lock最大的不同之處就是:無法獲取訊號量的程序可以睡眠,因此會導致系統排程。

原理

訊號量一般可以用來標記可用資源的個數。老規矩,還是舉個例子。假設圖書館有2本《C語言從入門到放棄》書籍。A同學想學C語言,於是發現這本書特別的好。於是就去學校的圖書館借書,A同學成功的從圖書館借走一本。這時,A同學室友B同學發現A同學竟然在偷偷的學習武功祕籍(C語言)。於是,B同學也去借一本。此時,圖書館已經沒有書了。C同學也想借這本書,可能是這本書太火了。圖書館管理員告訴C同學,圖書館這本書都被借走了。如果有同學換回來,會第一時間通知你。於是,管理員就把C同學的資訊登記先來,以備後續通知C同學來借書。所以,C同學只能悲傷的走了(如果是自旋鎖的原理的話,那麼C同學將會端個小板凳坐在圖書館,一直要等到A同學或者B同學還書並借走)。

實現
為了記錄可用資源的數量,我們肯定需要一個count計數,標記當前可用資源數量。當然還要一個可以像圖書管理員一樣的筆記本功能。用來記錄等待借書的同學。所以,一個雙向連結串列即可。因此只需要一個count計數和等待程序的連結串列頭即可。描述訊號量的結構體如下。

struct semaphore {
	unsigned int		count;
	struct list_head	wait_list;
};

在linux中,每個程序就相當於是每個借書的同學。通知一個同學,就相當於喚醒這個程序。因此,我們還需要一個結構體記錄當前的程序資訊(task_struct)。

struct semaphore_waiter {
	struct list_head list;
	struct task_struct *task;
}; 
struct semaphore_waiter的list成員是當程序無法獲取訊號量的時候掛入semaphore的wait_list成員。task成員就是記錄後續被喚醒的程序資訊。

一切準備就緒,現在就可以實現訊號量的申請函式。

void down(struct semaphore *sem)
{
	struct semaphore_waiter waiter;
 
	if (sem->count > 0) {
		sem->count--;                           /* 1 */
		return;
	}
 
	waiter.task = current;                          /* 2 */
	list_add_tail(&waiter.list, &sem->wait_list);   /* 2 */
	schedule();                                     /* 3 */
} 
(1).如果訊號量標記的資源還有剩餘,自然可以成功獲取訊號量。只需要遞減可用資源計數。
(2).既然無法獲取訊號量,就需要將當前程序掛入訊號量的等待佇列連結串列上。

(3).schedule()主要是觸發任務排程的示意函式,主動讓出CPU使用權。在讓出之前,需要將當前程序從執行佇列上移除。

互斥量(mutex):
前文提到的semaphore在初始化count計數的時候,可以分為計數訊號量和互斥訊號量(二值訊號量)。mutex和初始化計數為1的二值訊號量有很大的相似之處。他們都可以用做資源互斥。但是mutex卻有一個特殊的地方:只有持鎖者才能解鎖。但是,二值訊號量卻可以在一個程序中獲取訊號量,在另一個程序中釋放訊號量。如果是應用在嵌入式應用的RTOS,針對mutex的實現還會考慮優先順序反轉問題。

原理
既然mutex是一種二值訊號量,因此就不需要像semaphore那樣需要一個count計數。由於mutex具有“持鎖者才能解鎖”的特點,所以我們需要一個變數owner記錄持鎖程序。釋放鎖的時候必須是同一個程序才能釋放。當然也需要一個連結串列頭,主要用來便利睡眠等待的程序。原理和semaphore及其相似,因此在程式碼上也有體現。

實現
mutex的實現程式碼和linux中實現會有差異,但是依然可以為你呈現設計的原理。下面的設計程式碼更像是部分RTOS中的程式碼。mutex和semaphore一樣,我們需要兩個類似的結構體分別描述mutex。

struct mutex_waiter {
	struct list_head   list;
	struct task_struct *task;
};
 
struct mutex {
    long   owner;
    struct list_head wait_list;
}; 
struct mutex_waiter的list成員是當程序無法獲取互斥量的時候掛入mutex的wait_list連結串列。
首先實現申請互斥量的函式。
void mutex_take(struct mutex *mutex)
{
	struct mutex_waiter waiter;
 
	if (!mutex->owner) {
		mutex->owner = (long)current;           /* 1 */
		return;
	}
 
	waiter.task = current;
	list_add_tail(&waiter.list, &mutex->wait_list); /* 2 */
	schedule();                                     /* 2 */
}
(1).當mutex->owner的值為0的時候,代表沒有任何程序持有鎖。因此可以直接申請成功。然後,記錄當前申請鎖程序的task_struct。

(2).既然不能獲取互斥量,自然就需要睡眠等待,掛入等待連結串列。  

互斥量的釋放程式碼實現也同樣和semaphore有很多相似之處。不信,你看。

int mutex_release(struct mutex *mutex)
{
	struct mutex_waiter waiter;
 
	if (mutex->owner != (long)current)                         /* 1 */
		return -1;
 
	if (list_empty(&mutex->wait_list)) {
		mutex->owner = 0;                                      /* 2 */
		return 0;
	}
 
	waiter = list_first_entry(&mutex->wait_list, struct mutex_waiter, list);
	list_del(&waiter->list);
	mutex->owner = (long)waiter->task;                         /* 3 */
	wake_up_process(waiter->task);                             /* 4 */
 
	return 0;
}
(1).mutex具有“持鎖者才能解鎖”的特點就是在這行程式碼體現。
(2).如果等待連結串列沒有程序,那麼自然只需要將mutex->owner置0,代表沒有鎖是釋放狀態。
(3).mutex->owner的值改成當前可以持鎖程序的task_struct。

(4).從等待程序連結串列取出第一個程序,並從連結串列上移除。然後就是喚醒該程序。