linux中mutex和semaphore的區別
很多程式設計的書裡在介紹mutex和semaphore的時候都會說,mutex是一種特殊的semaphore.
當semaphore的N=1時,就變成了binary semaphore,也就等同與mutex了。
但是實際上,在linux中,他們的實現什有區別的,導致最後應用的行為也是有區別的。
先看下面這個例子,這是一段linux kernel的程式碼:
#include <linux/init.h> #include <linux/module.h> #include <linux/mutex.h> #include <linux/semaphore.h> #include <linux/sched.h> #include <linux/delay.h> static DEFINE_MUTEX(g_mutex); static DEFINE_SEMAPHORE(g_semaphore); static int fun1(void *p) { while (true) { mutex_lock(&g_mutex); msleep(1000); printk("1\n"); mutex_unlock(&g_mutex); } return 0; } static int fun2(void *p) { while (true) { mutex_lock(&g_mutex); msleep(1000); printk("2\n"); mutex_unlock(&g_mutex); } return 0; } static int fun3(void *p) { while (true) { down(&g_semaphore); msleep(1000); printk("3\n"); up(&g_semaphore); } return 0; } static int fun4(void *p) { while (true) { down(&g_semaphore); msleep(1000); printk("4\n"); up(&g_semaphore); } return 0; } static int hello_init(void) { kernel_thread(fun1, NULL, 0); kernel_thread(fun2, NULL, 0); kernel_thread(fun3, NULL, 0); kernel_thread(fun4, NULL, 0); return 0; } module_init(hello_init);
這段程式碼很簡單,4個執行緒,2個去獲取mutex,2個去獲取semaphore。
我們可以先只enable thread1和thread2。
我開始預期的結果什輸出1212121212...
但是實際的結果是111111111...22222222222...11111111111...
假設enable thread3和thread4,輸出則變成了34343434343434...
顯然,mutex和semaphore使用的結果不一樣。
為什麼會造成這樣的結果哪?
我們分析一下kernel裡mutex和semaphore的實現就可以明白了
kernel/mutex.c
在解鎖的時候,會先把lock->count設定成1,__mutex_unlock_common_slowpath(atomic_t *lock_count, int nested) { struct mutex *lock = container_of(lock_count, struct mutex, count); unsigned long flags; spin_lock_mutex(&lock->wait_lock, flags); mutex_release(&lock->dep_map, nested, _RET_IP_); debug_mutex_unlock(lock); /* * some architectures leave the lock unlocked in the fastpath failure * case, others need to leave it locked. In the later case we have to * unlock it here */ if (__mutex_slowpath_needs_to_unlock()) atomic_set(&lock->count, 1); if (!list_empty(&lock->wait_list)) { /* get the first entry from the wait-list: */ struct mutex_waiter *waiter = list_entry(lock->wait_list.next, struct mutex_waiter, list); debug_mutex_wake_waiter(lock, waiter); wake_up_process(waiter->task); } spin_unlock_mutex(&lock->wait_lock, flags); }
然後從等待這個mutex的佇列裡取出第一個任務,並wake_up這個任務。
這裡要注意,wake_up_process只是把這個任務設定成可排程,並不是直接就進行排程了。
所以當一個執行緒unlock mutex之後,只要在自己還沒有被排程出去之前再次很快的lock mutex的話,他依舊會成功。
於是,這就出現了一開始那個程式的結果。
在程式碼本身沒有死鎖的情況下,不合適得使用mutex,會造成飢餓的發生。
那semaphore到底是如何避免這樣的情況發生的哪?
kernel/semaphore.c
首先在down的時候,回去檢視sem->count的值,假如大於0,就進行--操作。void down(struct semaphore *sem) { unsigned long flags; raw_spin_lock_irqsave(&sem->lock, flags); if (likely(sem->count > 0)) sem->count--; else __down(sem); raw_spin_unlock_irqrestore(&sem->lock, flags); } static inline int __sched __down_common(struct semaphore *sem, long state, long timeout) { struct task_struct *task = current; struct semaphore_waiter waiter; list_add_tail(&waiter.list, &sem->wait_list); waiter.task = task; waiter.up = false; for (;;) { if (signal_pending_state(state, task)) goto interrupted; if (unlikely(timeout <= 0)) goto timed_out; __set_task_state(task, state); raw_spin_unlock_irq(&sem->lock); timeout = schedule_timeout(timeout); raw_spin_lock_irq(&sem->lock); if (waiter.up) return 0; } timed_out: list_del(&waiter.list); return -ETIME; interrupted: list_del(&waiter.list); return -EINTR; } void up(struct semaphore *sem) { unsigned long flags; raw_spin_lock_irqsave(&sem->lock, flags); if (likely(list_empty(&sem->wait_list))) sem->count++; else __up(sem); raw_spin_unlock_irqrestore(&sem->lock, flags); } static noinline void __sched __up(struct semaphore *sem) { struct semaphore_waiter *waiter = list_first_entry(&sem->wait_list, struct semaphore_waiter, list); list_del(&waiter->list); waiter->up = true; wake_up_process(waiter->task); }
這就好比第一個執行緒去down semaphore。
等到第二個執行緒再去down的時候,count為0了,就進入了__down_common函式。
這個函式裡面會一直檢查waiter.up,知道為true了才會退出。
至此,第二個執行緒就被block在了down函式裡。
等到第一個執行緒up semaphore,假如這個semaphore的等待佇列裡還有任務,則設定waiter->up為true並喚醒任務。
這裡用的也是wake_up_process,貌似和mutex的實現什一樣的,但是接下來就不一樣了。
假設第一個執行緒之後又很快的去down semaphore,會發生什麼哪?
由於sem->count還是為0,這個執行緒在down的時候就會被block住而發生排程。
第二個執行緒此時就可以獲得semaphore而繼續執行程式碼了。
一直直到沒有任何執行緒在等待佇列裡了,sem->count才會被++。
所以,semaphore就變成了這樣的行為。
總結:mutex在使用時沒有任何順序的保證,它僅僅是保護了資源,但是效率會比較高。
而semaphore則有順序的保證,使得每個使用者都能依次獲得他,但是相應的會損失一點效率。