Linux设备驱动并发控制之Linux信号量与互斥体

信号量与互斥体

  • 在linux设备驱动代码中,为了解决竞态问题,途径是:保证对共享资源的互斥访问;所谓互斥访问是指一个执行单元在访问共享资源的时候,其他的执行单元被禁止访问。
  • 访问共享资源的代码区域被称为临界区,临界区需要被以某种互斥机制加以保护。中断屏蔽、原子操作、自旋锁、信号量、互斥体等是 Linux 设备驱动中可采用的互斥途径。

信号量

信号量(Semaphore) 是操作系统中最典型的用于同步和互斥手段,信号量的值可以是0,1,n。信号量与操作系统中的PV操作对应。

  • P(S)
    (1)将信号量S的值减少1,即S = S - 1;
    (2)如果S >= 0,则该进程继续执行;否则该进程设置为等待状态,进入等待队列中。
  • V(S)
    (1)将信号量S的值增加1,即S = S + 1;
    (2)如果S > 0,唤醒队列中等待信号量的进程。

在Linux信号量的相关操作如下:

定义信号量

struct semaphore sem; //定义名称为sem的信号量

初始化信号量

void sema_init(struct semaphore *sem,int val); //该函数用于初始化信号量,并将信号量sem的值设置为val

获得信号量

void down(struct semaphore *sem);  //获得信号量,会导致睡眠,因此不能在中断上下文中使用
int down_interruptible(struct semaphore *sem); 
//功能与down函数类似,但是down()进入状态的进程不能被信号打断
//down_interruptible()进入睡眠状态的进程能被信号打断,信号也会导致该函数返回,这是函数返回值非0
int down_trylock(struct semaphore *sem); 
//尝试获取信号量sem,如果能够立即获得,它就获得该信号量并返回0,否则,返回非0值。
//它不会导致调用者睡眠,可以在中断上下文中使用

注意:在使用 down_interruptible() 函数获取信号量时,对返回值一般会进行检查,如果非0,通常立即返回 -ERESTARTSYS,如下:

if( down_interruptible(&sem) )
	return -ERESTARTSYS;

释放信号量

void up(struct semaphore *sem);  //释放信号量sem,唤醒等待者

作为一种可能的互斥手段,信号量可以保护临界区,它的使用方式和自旋锁类似。与自旋锁相同,只有得到信号量的进程才能执行临界区代码。但是,与自旋锁不同的是,当获取不到信号量时,进程不会在原地打转而是进入休眠等待状态。用作互斥时,信号量一般这样被使用:
在这里插入图片描述
在新的Linux内核倾向于直接使用 mutex(互斥体)作为互斥手段,信号量用作互斥不再被推荐使用。

信号量同步

信号量也可以用于同步,一个进程 A 执行 down() 等待信号量,另一个进程 B 执行up() 释放信号量,这样进程 A 就同步地等待进程 B 。过程类似:
在这里插入图片描述
对于关心具体数值的生产者/消费者问题,使用信号量较为合适。因为生产者/消费者问题也是一种同步问题。

互斥体

mutex (互斥体)在 Linux 内核中真实存在。
下面代码定义了互斥体并初始化它:

struct mutex my_mutex; //定义互斥体mutex
mutex_init(&my_mutex); //初始化互斥体mutex

下面代码用于获取互斥体:

void mutex_lock(struct mutex *lock); //获取互斥体,进入等待状态,不能被信号打断
int mutex_lock_interruptible(struct mutex *lock); //获取互斥体,进入等待状态,但是可以被信号打断
int mutex_trylock(struct mutex *lock); //用于尝试获取互斥体mutex,获取不到mutex时不会引起进程睡眠

下面代码用于释放互斥体:

void mutex_unlock(struct mutex *lock);  //释放互斥体

mutex 的使用方法和信号量用于互斥的场合完全一样,如下:

struct mutex my_mutex; //定义mutex
mutex_init(&my_mutex); //初始化mutex
mutex_lock(&my_mutex); //获取mutex
... //临界资源
mutex_unlock(&my_mutex); //释放mutex

自旋锁与互斥体的使用选择

  • 互斥体和自旋锁属于不同层次的互斥手段,前者的实现依赖于后者。在互斥体本身的实现上,为了保证互斥体结构存取的原子性,需要自旋锁来互斥,所以自旋锁属于更底层的手段。
  • 互斥体是进程级的,用于多个进程之间对资源的互斥,虽然也是在内核中,但是该内核执行路径是以进程的身份,代表进程来争夺资源的。如果竞争失败,会发生进程上下文交换,当前进程进入睡眠状态,CPU将运行其他进程。鉴于进程上下文切换的开销也很大,因此,只有当进程占用时间较长时,用互斥体才是较好的选择
  • 当所要保护的临界区访问时间较短时,用自旋锁是非常方便的,因为它可节省上下文切换的时间。但是CPU得不到自旋锁会在那里空转直到其他执行单元解锁为止,所以要求锁不能在临界区里长时间停留,否则会降低系统的效率。

因此,自旋锁和互斥体选用3个原则:

  1. 当锁不能被获取到时,使用互斥体的开销是进程上下文切换时间,使用自旋锁的开销是等待获取自旋锁(由临界区执行时间决定)。若临界区较小,选择自旋锁,若临界区很大,使用互斥体。
  2. **互斥体所保护的临界区可包含可能引起阻塞的代码,而自旋锁则绝对要避免用来保护包含这样代码的临界区。**因为阻塞意味着要进行进程的切换,如果进程被切换出去后,另一个进程企图获取本自旋锁,是说就会发生。
  3. 互斥体存在于进程上下文,因此,如果被保护的共享资源需要在中断或软中断情况西使用,则在互斥体和自旋锁之间只能选择自旋锁。 当然,如果一定要使用互斥体,则只能通过 mutex_trylock() 方式进行,不能获取就立即返回以避免阻塞。

本文查考书籍宋宝华—《Linux设备驱动开发详解-基于最新的Linux 4.0内核》

猜你喜欢

转载自blog.csdn.net/qq_41782149/article/details/105614655