1.信号量简介
信号量(semaphore)是用于保护临界区的一种常用方法,只有得到信号量的进程才能执行临界区代码.与自旋锁不同的是,获取不到信号量时,进程不会原地打转而是进入睡眠状态.
2.内核API
2-1.定义信号量
Struct semaphore sem;
2-2.初始化信号量
Void sema_init(struct semaphore *sem,int val);
该函数初始化信号量,并设置信号量sem的值为val.尽管信号量可以被初始化为大于1的值从而成为一个计数信号量,但是它通常不被这样使用.
Void init_METEX(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.
2-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;
}
2-4.释放信号量
Void up(struct semaphore *sem);
该函数释放信号量sem,唤醒等待者.
信号量的使用示意代码如下:
DECLARE_MUTEX(mount_sem);
Down(&mount_sem);
...
Critical section
...
Up(&mount_sem);
3.信号量用于同步
信号量可用于同步,同步意味着一个执行单元的继续执行需要等待另一个执行单元完成某事,保证执行的先后顺序.示例例子如下:
Static DECLARE_MUTEX(xxx_lock);
Static int xxx_open(struct inode *inode,struct file *filp)
{
...
If(down_trylock(&xxx_lock))
Return -EBUSY;
...
}
Static int xxx_release(struct inode *inode,struct file *filp)
{
Up(&xxx_lock);
Return 0;
}
3-1.信号量VS完成量
完成量是比信号量更好的一种同步机制.它用于一个执行单元等待另一个执行单元执行完某事.
3-1-1 定义完成量
Struct completion my_completion;
3-1-2 初始化completion
下面代码初始化my_completion;
Init_completion(&my_completion);
完成上述两步,即定义并初始化完成变量,可用下面宏来实现:
DECLARE_COMPLETION(my_completion);
3-1-3 等待完成量
Void wait_for_completion(struct completion *c);
3-1-4 唤醒完成量
Void complete(struct completion *c);
Void complete_all(struct completion *c);
前者只唤醒一个等待执行单元,后者释放所有等待同一完成量的执行单元.
4. 信号量VS自旋锁
自旋锁和信号量的选用原则:
1).当锁不能获取时,获取信号量失败的进程会进入睡眠,这时候发生进程上下文切换的开销,标记此时间为Tsw;使用自旋锁一直自旋等待,直到锁可获取,标记此时间为Tcs.若Tcs < Tsw,用自旋锁;若Tcs >> Tsw,应使用信号量;
2).信号量保护的临界资源可包含可能引起阻塞的代码,而自旋锁则绝对要避免用来保护包含这样代码的临界资源.因为阻塞意味着要进行进行的切换,如果进行被切换出去后,另一个进程企图获取本自旋锁时,死锁就会发生;
3).信号量存在于进程上下文,因此,如果被保护的共享资源需要在中断或软中断情况下使用,则只能选择自旋锁.因为中断上下文里面不能阻塞或引起睡眠.如果一定要使用信号量,只能通过函数down_trylock()函数尝试获取.
5.读写信号量
读写信号量可以看作是信号量的一种优化--它允许N个读执行单元同时访问共享资源,而最多只能有一个写执行单元.
5-1 定义和初始化读写信号量
Struct rw_semaphore my_rws;
Void init_rwsem(struct rw_semaphore *sem);
5-2 读信号量获取
Void down_read(struct rw_semaphore *sem);
Int down_read_trylock(struct rw_semaphore *sem);
5-3 读信号量释放
Void up_read(struct rw_semaphore *sem);
5-4 写信号量获取
Void down_write(struct rw_semaphore *sem);
Int down_write_trylock(struct rw_semaphore *sem);
5-5 写信号量释放
Void up_write(struct rw_semaphore *sem);
读写信号量使用的示意代码:
Rw_semaphore rw_sem;
Init_rwsem(&rw_sem);
Down_read(&rw_sem);
Critical section;
Up_read(&rw_sem);
Down_write(&rw_sem);
Critical section;
Up_write(&rw_sem);