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).从等待进程链表取出第一个进程,并从链表上移除。然后就是唤醒该进程。

本文转自:点击打开链接

猜你喜欢

转载自blog.csdn.net/caihaitao2000/article/details/80789363