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

__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);
}
在解锁的时候,会先把lock->count设置成1,

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

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

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

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

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


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

kernel/semaphore.c

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的时候,回去查看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则有顺序的保证,使得每个使用者都能依次获得他,但是相应的会损失一点效率。

  • 7
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值