Linux设备驱动开发详解-Note(15)--- Linux 设备驱动中的并发控制(2)

Linux 设备驱动中的并发控制(2)

成于坚持,败于止步

信号量

信号量的使用

信号量(semaphore)是用于保护临界区的一种常用方法,它的使用方式和自旋锁类似。与自旋锁相同,只有得到信号量的进程才能执行临界区代码。但是,与自旋锁不同的是,当获取不到信号量时,进程不会原地打转而是进入休眠等待状态。 Linux 系统中与信号量相关的操作主要有如下 4 种。

1.定义信号量

下列代码定义名称为 sem 的信号量。

struct semaphore sem;

2.初始化信号量

void sema_init (struct semaphore *sem, int val);

该函数初始化信号量,并设置信号量 sem 的值为 val。尽管信号量可以被初始化为大于 1 的值从而成为一个计数信号量,但是它通常不被这样使用。

void init_MUTEX(struct semaphore *sem);

该函数用于初始化一个用于互斥的信号量,它把信号量 sem 的值设置为 1,等同于sema_init (struct semaphore *sem, 1)。

void init_MUTEX_LOCKED (struct semaphore *sem);

该函数也用于初始化一个信号量,但它把信号量 sem 的值设置为 0,等同于sema_init (struct semaphore *sem, 0)。

此外,下面两个宏是定义并初始化信号量的“快捷方式”。

DECLARE_MUTEX(name)

DECLARE_MUTEX_LOCKED(name)

前者定义一个名为 name 的信号量并初始化为 1,后者定义一个名为 name 的信号量并初始化为 0。

3.获得信号量

void down(struct semaphore * sem);

该函数用于获得信号量 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;

}

4.释放信号量

void up(struct semaphore * sem);

该函数释放信号量 sem,唤醒等待者。 信号量一般这样被使用,如下所示:

//定义信号量
DECLARE_MUTEX(mount_sem);
down(&mount_sem);//获取信号量,保护临界区

critical section //临界区

up(&mount_sem);//释放信号量
Linux 自旋锁和信号量所采用的“获取锁—访问临界区—释放锁”的方式存在于几乎所有的多任务操作系统之中。 
下面代码给出了使用信号量实现设备只能被一个进程打开的例子

1 static DECLARE_MUTEX(xxx_lock);//定义互斥锁
2
3 static int xxx_open(struct inode *inode, struct file filp)
4 {
5 …
6 if (down_trylock(&xxx_lock)) //获得打开锁
7 return - EBUSY; //设备忙
8 …
9 return 0; /
成功 */
10 }
11
12 static int xxx_release(struct inode *inode, struct file *filp)
13 {
14 up(&xxx_lock); //释放打开锁
15 return 0;
16 }
信号量用于同步

如果信号量被初始化为 0,则它可以用于同步,同步意味着一个执行单元的继续执行需等待另一执行单元完成某事,保证执行的先后顺序。如图 7.4 所示,执行单元A 执行代码区域 b 之前,必须等待执行单元 B 执行完代码单元 c,信号量可辅助这一同步过程。

完成量用于同步

Linux 系统提供了一种比信号量更好的同步机制,即完成量(completion,关于这个名词,至今没有好的翻译,笔者将其译为“完成量”),它用于一个执行单元等待另一个执行单元执行完某事。 Linux 系统中与 completion 相关的操作主要有以下 4 种。

1.定义完成量

下列代码定义名为 my_completion 的完成量。

struct completion my_completion;

2.初始化 completion

下列代码初始化 my_completion 这个完成量。

init_completion(&my_completion);

对 my_completion 的定义和初始化可以通过如下快捷方式实现。

DECLARE_COMPLETION(my_completion);

3.等待完成量

下列函数用于等待一个 completion 被唤醒。

void wait_for_completion(struct completion *c);

4.唤醒完成量

下面两个函数用于唤醒完成量。

void complete(struct completion *c);

void complete_all(struct completion *c);

前者只唤醒一个等待的执行单元,后者释放所有等待同一完成量的执行单元。 下面的图描述了使用完成量实现的同步功能。

自旋锁 vs 信号量

自旋锁和信号量都是解决互斥问题的基本手段,面对特定的情况,应该如何进行选择呢?选择的依据是临界区的性质和系统的特点。

从严格意义上说,信号量和自旋锁属于不同层次的互斥手段,前者的实现依赖于后者。在信号量本身的实现上,为了保证信号量结构存取的原子性,在多CPU 中需要自旋锁来互斥。

信号量是进程级的,用于多个进程之间对资源的互斥,虽然也是在内核中,但是该内核执行路径是以进程的身份,代表进程来争夺资源的。如果竞争失败,会发生进程上下文切换,当前进程进入睡眠状态,CPU 将运行其他进程。鉴于进程上下文切换的开销也很大,因此,只有当进程占用资源时间较长时,用信号量才是较好的选择。 当所要保护的临界区访问时间比较短时,用自旋锁是非常方便的,因为它节省上下文切换的时间。但是 CPU 得不到自旋锁会在那里空转直到其他执行单元解锁为止,所以要求锁不能在临界区里长时间停留,否则会降低系统的效率。

由此,可以总结出自旋锁和信号量选用的 3 项原则。

当锁不能被获取时,使用信号量的开销是进程上下文切换时间 Tsw,使用自旋锁的开销是等待获取自旋锁(由临界区执行时间决定)Tcs,若 Tcs 比较小,应使用自旋锁,若 Tcs 很大,应使用信号量。

信号量所保护的临界区可包含可能引起阻塞的代码,而自旋锁则绝对要避免用来保护包含这样代码的临界区。因为阻塞意味着要进行进程的切换,如果进程被切换出去后,另一个进程企图获取本自旋锁,死锁就会发生。

信号量存在于进程上下文,因此,如果被保护的共享资源需要在中断或软中断情况下使用,则在信号量和自旋锁之间只能选择自旋锁。当然,如果一定要使用信号量,则只能通过 down_trylock()方式进行,不能获取就立即返回以避免阻塞。

读写信号量

读写信号量与信号量的关系与读写自旋锁和自旋锁的关系类似,读写信号量可能引起进程阻塞,但它可允许 N 个读执行单元同时访问共享资源,而最多只能有一个写执行单元。因此,读写信号量是一种相对放宽条件的粒度稍大于信号量的互斥机制。

读写自旋锁涉及的操作包括如下 5 种。

1.定义和初始化读写信号量

struct rw_semaphore my_rws; /定义读写信号量/

void init_rwsem(struct rw_semaphore *sem); /初始化读写信号量/

2.读信号量获取

void down_read(struct rw_semaphore *sem);

int down_read_trylock(struct rw_semaphore *sem);

3.读信号量释放

void up_read(struct rw_semaphore *sem);

4.写信号量获取

void down_write(struct rw_semaphore *sem);

int down_write_trylock(struct rw_semaphore *sem);

5.写信号量释放

void up_write(struct rw_semaphore *sem);

读写信号量一般这样被使用,如下所示:

rw_semaphore rw_sem; //定义读写信号量
init_rwsem(&rw_sem); //初始化读写信号量
//读时获取信号量
down_read(&rw_sem);
… //临界资源
up_read(&rw_sem);

//写时获取信号量
down_write(&rw_sem);
… //临界资源
up_write(&rw_sem);
互斥体

尽管信号量已经可以实现互斥的功能,而且包含 DECLARE_MUTEX() 、init_MUTEX ()等定义信号量的宏或函数,从名字上看就体现出了互斥体的概念,但是mutex 在 Linux 内核中还是真实地存在的。

下面代码定义名为 my_mutex 的互斥体并初始化它。

struct mutex my_mutex;

mutex_init(&my_mutex);

下面的两个函数用于获取互斥体。

void fastcall mutex_lock(struct mutex *lock);

int fastcall mutex_lock_interruptible(struct mutex *lock);

int fastcall mutex_trylock(struct mutex *lock);

mutex_lock()与 mutex_lock_interruptible()的区别和 down()与 down_trylock()的区别完全一致,前者引起的睡眠不能被信号打断,而后者可以。mutex_trylock()用于尝试获得 mutex,获取不到 mutex 时不会引起进程睡眠。

下列函数用于释放互斥体。
void fastcall 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
就到这里了,O(∩_∩)O~

我的专栏地址:http://blog.csdn.net/column/details/linux-driver-note.html

待续。。。。

作者:Ela–学海无涯
来源:CSDN
原文:https://blog.csdn.net/xinyuwuxian/article/details/9358967
版权声明:本文为博主原创文章,转载请附上博文链接!

猜你喜欢

转载自blog.csdn.net/wang2425559/article/details/89707650
今日推荐