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的list成員是當程序無法獲取訊號量的時候掛入semaphore的wait_list成員。task成員就是記錄後續被喚醒的程序資訊。struct semaphore_waiter { struct list_head list; struct task_struct *task; };
一切準備就緒,現在就可以實現訊號量的申請函式。
(1).如果訊號量標記的資源還有剩餘,自然可以成功獲取訊號量。只需要遞減可用資源計數。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 */ }
(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).從等待程序連結串列取出第一個程序,並從連結串列上移除。然後就是喚醒該程序。