linux中mutex和semaphore的区别

来源:互联网 发布:mac finder打不开 编辑:程序博客网 时间:2024/04/30 13:09

很多编程的书里在介绍mutex和semaphore的时候都会说,mutex是一种特殊的semaphore.

当semaphore的N=1时,就变成了binary semaphore,也就等同与mutex了。

但是实际上,在linux中,他们的实现什有区别的,导致最后应用的行为也是有区别的。

先看下面这个例子,这是一段linux kernel的代码:

[cpp] view plaincopy
  1. #include <linux/init.h>  
  2. #include <linux/module.h>  
  3. #include <linux/mutex.h>  
  4. #include <linux/semaphore.h>  
  5. #include <linux/sched.h>  
  6. #include <linux/delay.h>  
  7.   
  8. static DEFINE_MUTEX(g_mutex);  
  9. static DEFINE_SEMAPHORE(g_semaphore);  
  10.   
  11. static int fun1(void *p)  
  12. {  
  13.     while (true) {  
  14.         mutex_lock(&g_mutex);  
  15.         msleep(1000);  
  16.         printk("1\n");  
  17.         mutex_unlock(&g_mutex);  
  18.     }  
  19.     return 0;  
  20. }  
  21.   
  22. static int fun2(void *p)  
  23. {  
  24.     while (true) {  
  25.         mutex_lock(&g_mutex);  
  26.         msleep(1000);  
  27.         printk("2\n");  
  28.         mutex_unlock(&g_mutex);  
  29.     }  
  30.     return 0;  
  31. }  
  32.   
  33. static int fun3(void *p)  
  34. {  
  35.     while (true) {  
  36.         down(&g_semaphore);  
  37.         msleep(1000);  
  38.         printk("3\n");  
  39.         up(&g_semaphore);  
  40.     }  
  41.     return 0;  
  42. }  
  43.   
  44. static int fun4(void *p)  
  45. {  
  46.     while (true) {  
  47.         down(&g_semaphore);  
  48.         msleep(1000);  
  49.         printk("4\n");  
  50.         up(&g_semaphore);  
  51.     }  
  52.     return 0;  
  53. }  
  54.   
  55. static int hello_init(void)  
  56. {  
  57.     kernel_thread(fun1, NULL, 0);  
  58.     kernel_thread(fun2, NULL, 0);  
  59.     kernel_thread(fun3, NULL, 0);  
  60.     kernel_thread(fun4, NULL, 0);  
  61.     return 0;  
  62. }  
  63.   
  64. 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

[cpp] view plaincopy
  1. __mutex_unlock_common_slowpath(atomic_t *lock_count, int nested)  
  2. {  
  3.     struct mutex *lock = container_of(lock_count, struct mutex, count);  
  4.     unsigned long flags;  
  5.   
  6.     spin_lock_mutex(&lock->wait_lock, flags);  
  7.     mutex_release(&lock->dep_map, nested, _RET_IP_);  
  8.     debug_mutex_unlock(lock);  
  9.   
  10.     /* 
  11.      * some architectures leave the lock unlocked in the fastpath failure 
  12.      * case, others need to leave it locked. In the later case we have to 
  13.      * unlock it here 
  14.      */  
  15.     if (__mutex_slowpath_needs_to_unlock())  
  16.         atomic_set(&lock->count, 1);  
  17.   
  18.     if (!list_empty(&lock->wait_list)) {  
  19.         /* get the first entry from the wait-list: */  
  20.         struct mutex_waiter *waiter =  
  21.                 list_entry(lock->wait_list.next,  
  22.                        struct mutex_waiter, list);  
  23.   
  24.         debug_mutex_wake_waiter(lock, waiter);  
  25.   
  26.         wake_up_process(waiter->task);  
  27.     }  
  28.   
  29.     spin_unlock_mutex(&lock->wait_lock, flags);  
  30. }  
在解锁的时候,会先把lock->count设置成1,

然后从等待这个mutex的队列里取出第一个任务,并wake_up这个任务。

这里要注意,wake_up_process只是把这个任务设置成可调度,并不是直接就进行调度了。

所以当一个线程unlock mutex之后,只要在自己还没有被调度出去之前再次很快的lock mutex的话,他依旧会成功。

于是,这就出现了一开始那个程序的结果。

在代码本身没有死锁的情况下,不合适得使用mutex,会造成饥饿的发生。


那semaphore到底是如何避免这样的情况发生的哪?

kernel/semaphore.c

[cpp] view plaincopy
  1. void down(struct semaphore *sem)  
  2. {  
  3.     unsigned long flags;  
  4.   
  5.     raw_spin_lock_irqsave(&sem->lock, flags);  
  6.     if (likely(sem->count > 0))  
  7.         sem->count--;  
  8.     else  
  9.         __down(sem);  
  10.     raw_spin_unlock_irqrestore(&sem->lock, flags);  
  11. }  
  12.   
  13. static inline int __sched __down_common(struct semaphore *sem, long state,  
  14.                                 long timeout)  
  15. {  
  16.     struct task_struct *task = current;  
  17.     struct semaphore_waiter waiter;  
  18.   
  19.     list_add_tail(&waiter.list, &sem->wait_list);  
  20.     waiter.task = task;  
  21.     waiter.up = false;  
  22.   
  23.     for (;;) {  
  24.         if (signal_pending_state(state, task))  
  25.             goto interrupted;  
  26.         if (unlikely(timeout <= 0))  
  27.             goto timed_out;  
  28.         __set_task_state(task, state);  
  29.         raw_spin_unlock_irq(&sem->lock);  
  30.         timeout = schedule_timeout(timeout);  
  31.         raw_spin_lock_irq(&sem->lock);  
  32.         if (waiter.up)  
  33.             return 0;  
  34.     }  
  35.   
  36.  timed_out:  
  37.     list_del(&waiter.list);  
  38.     return -ETIME;  
  39.   
  40.  interrupted:  
  41.     list_del(&waiter.list);  
  42.     return -EINTR;  
  43. }  
  44.   
  45. void up(struct semaphore *sem)  
  46. {  
  47.     unsigned long flags;  
  48.   
  49.     raw_spin_lock_irqsave(&sem->lock, flags);  
  50.     if (likely(list_empty(&sem->wait_list)))  
  51.         sem->count++;  
  52.     else  
  53.         __up(sem);  
  54.     raw_spin_unlock_irqrestore(&sem->lock, flags);  
  55. }  
  56.   
  57. static noinline void __sched __up(struct semaphore *sem)  
  58. {  
  59.     struct semaphore_waiter *waiter = list_first_entry(&sem->wait_list,  
  60.                         struct semaphore_waiter, list);  
  61.     list_del(&waiter->list);  
  62.     waiter->up = true;  
  63.     wake_up_process(waiter->task);  
  64. }  
首先在down的时候,回去查看sem->count的值,假如大于0,就进行--操作。

这就好比第一个线程去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则有顺序的保证,使得每个使用者都能依次获得他,但是相应的会损失一点效率。


0 0
原创粉丝点击