第10章内核同步方法

10.4 信号量

Linux中的信号量是一种睡眠锁。如果有一个任务试图获得一个不可用(已经被占用)的信号量时,信号量会将其推进一个等待队列,然后让其睡眠。这时处理器能重获自由,从而去执行其他代码。当持有的信号量可用(被释放)后,处于等待队列中的那个任务将被唤醒,并获得该信号量。

可以从信号量的睡眠特性得出一些有意思的结论:

由于争用信号量的进程在等待锁重新变为可用时会睡眠,所以信号量适用于锁会被长时间持有的情况。

相反,锁被短时间持有时,使用信号量不太适宜了。因为睡眠、维护等待队列以及唤醒所花费的开销可能比锁被占用的全部时间还要长。

由于执行线程在锁被争用时会睡眠,所以只能在进程上下文中才能获取信号量锁,因为在中断上下文中是不能进行调度的。

可以在持有信号量时去睡眠,因为当其他进程试图获得同一信号量时不会因此而死锁。

在占用信号量的同时不能占用自旋锁。因为在等待信号量时可能会睡眠,而在持有自旋锁时是不允许睡眠的。

以上结论阐明信号量和自旋锁在使用上的差异。在使用信号量的大多数时候,选择余地并不大。往往在需要和用户空间同步时,代码会需要睡眠,此时使用信号量是唯一的选择。由于不受睡眠的限制,使用信号量通常来说更加容易一些。如果需要在自旋锁和信号量中做选择,应该根据锁被持有的时间长短做判断。理想情况是所有的锁定操作都应该越短越好。但如果用的是信号量,那么锁定的时间长一点也能够接受。另外,信号量不同于自旋锁,它不会禁止内核抢占,所以持有信号量的代码可以被抢占。这意味着信号量不会对调度的等待时间带来负面影响。

1、计数信号量和二值信号量

讨论信号量的一个有用特性,它可以同时允许任意数量的锁持有者,而自旋锁在一个时刻最多允许一个任务持有它。信号量同时允许的持有者数量可以在声明信号量时指定。这个值称为使用者数量或简单地叫数量。通常情况下,信号量和自旋锁一样,在一个时刻仅允许有一个锁持有者。这时计数等于1,这样的信号量被称为二值信号量或者称为互斥信号量。另一方面,初始化时也可以把数量设置为大于1的非0值。这种情况,信号量被称为计数信号量,它允许在一个时刻至多有count个锁持有者。计数信号量不能用来进行强制互斥,因为它允许多个执行线程同时访问临界区。相反,这种信号量用来对特定代码加以限制,内核中使用它的机会不多。在使用信号量时,基本上用到的都是互斥信号量。

信号量在1968年由Edsger Wybe Dijkstra提出,逐渐成为一种常用的锁机制。信号量支持两个原子操作P和V,这两个名字来自荷兰语Proberen和Vershogen。前者叫做测试操作,或者叫做增加操作。后来的系统把两种操作分别叫做down()和up(),Linux也遵从这种叫法。down()操作通过对信号量计数减1来请求获得一个信号量。如果结果是0或大于0,获得信号量锁,任务就可以进入临界区。如果结果是负数,任务会被放入等待队列,处理器执行其他任务。相反,当临界区中的操作完成后,up()操作用来释放信号量,因为它会增加信号量的计数值。如果在该信号量上的等待队列不为空,那么处于队列中等待的任务在被唤醒的同时会获得该信号量。

2、创建和初始化信号量

信号量的实现与体系结构相关,具体实现定义在文件<asm/semaphore.h>中。struct semaphore类型用来表示信号量。可以通过如下方式静态地声明信号量——其中name是信号量变量名,count是信号量的使用数量:

struct semaphore name;

sema_init(&name, count);

创建更为普通的互斥信号量可以使用以下快捷方式,name仍然是互斥信号量的变量名:

static DECLARE_MUTEX(name);

更常见的情况是,信号量作为一个大数据结构的一部分动态创建。此时,只有指向该动态创建的信号量的间接指针,可以使用如下函数来对它进行初始化:

sema_init(sem, count);

sem是指针,count是信号量的使用者数量。

初始化一个动态创建的互斥信号量时使用如下函数:

init_MUTEX(sem);

3、使用信号量

down_interruptible()试图获取指定的信号量,如果信号量不可用,它将把调用进程设置成TASK_INTERRUPTIBLE状态——进入睡眠。这种进程状态意味着任务可以被信号唤醒。如果进程在等待获取信号量的时候接收到了信号,那么该进程就会被唤醒,而函数down_interruptible()会返回-EINTR。另外一个函数down()会让进程在TASK_UNINTERRUPTIBLE状态下睡眠。应该不希望这种情况发生,因为这样一来,进程在等待信号量的时候就不再响应信号了。因此,使用down_interruptible()比使用down()更为普遍。

使用down_trylock()函数,可以尝试以堵塞方式来获取指定的信号量。在信号量已被占用时,它立刻返回非0值;否则,它返回0,而且成功持有信号量锁。

要释放指定的信号量,需要调用up()函数。例如:

//定义并声明一个信号量

static DECLARE_MUTEX(sem);

//试图获取信号量

if(down_interruptible(&sem)){

}

//临界区

//释放信号量

up(&sem);

表10-6给出针对信号量的方法的完整列表。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值