信号量(semaphore)是用于保护临界区的一种常用方法,它的使用方式与自旋锁类似
与自旋锁相同的是:只有得到信号量的进程才能执行临界区的代码
与自旋锁不同的是:当获取不到信号量的时候,进程不会在原地打转,而是进入休眠等待状态;
理解:
定义于#include<linux/semaphore.h>
struct semaphore { spinlock_t lock; unsigned int count; struct list_head wait_list; };
其中count是计数作用,lock变量实现对count变量的保护,而wait_list则是对申请信号量的进程维护的等待队列
从源码中可以看到信号量利用自旋锁的相关函数实现了对count变量的保护,通过判断变量是否大于0以及自增减来实现信号量的申请和释放。
操作:
struct semaphore sem; //定义信号量
void sema_init(struct semaphore* sem, int val)
该函数用于初始化信号量sem,并设置信号量sem的值为val;尽管信号量的值可以被初始化为大于1的值,从而成为一个计数信号量,但是通常并不这样用。
void init_MUTEX(struct semaphore* sem)
该函数用于初始化一个用作互斥的信号量sem,它把信号量sem的值设置为1,等同于sema_init(struct semaphore* sem, 1)。
void init_MUTEX_LOCKED(struct semaphore* sem)
该函数用于初始化一个信号量sem,但是它把信号量sem的值设置为0,等同于sema_init(struct semaphore* sem, 0)
此外,下面两个宏是用于定义并初始化信号量的快捷方式:
DECLARE_MUTEX(name); //定义名为name的信号量,并设置初始值为1
DECLARE_MUTEX_LOCKED(name); //定义名为name的信号量,并设置初始值为0
注意:
在linux3.x中已不存在init_MUTEX和init_MUTEX_LOCKED等初始化函数,同时也更换了名字等,这点请注意,因此建议以后在编程中遇到需要使用信号量的时候尽量采用sema_init(struct semaphore *sem, int val)函数,因为这个函数就目前为止从未发生变化。
下面是linux3.1.4中的定义
#define __SEMAPHORE_INITIALIZER(name, n)
\
{ \
.lock
= __SPIN_LOCK_UNLOCKED((name).lock), \
.count
= n, \
.wait_list= LIST_HEAD_INIT((name).wait_list),\
}
#define DEFINE_SEMAPHORE(name) \
struct semaphore name = __SEMAPHORE_INITIALIZER(name, 1)
void down(struct semaphore* sem)
该函数用于获得信号量sem,但是它会导致调用进程睡眠,因此,不能在中断上下文中使用。
该函数把信号量sem的值减1,如果信号量sem的值为非负(>0),则函数立即返回,否则将被永远挂起,直到其它执行单元释放该信号量sem之后,才能继续运行下去;
void down_interruptible(struct semaphore* sem)
该函数的功能与down()类似,也是用于获得信号量sem。
不同之处在于,因为down()函数而进入睡眠状态的进程不能被信号打断,而因为down_interruptible()函数而进入睡眠状态的进程能够被信号打断,信号也会导致该函数返回,这个时候函数的返回值为非0;如果函数返回0,则表示获得信号量正常返回;如果被信号打断,则返回-EINTR;
int down_killable(struct semaphore* sem)
该函数用于获得信号量sem;但是它可以被kill信号打断。
int down_timeout(struct semaphore* sem, long jiffies)
该函数用于获得信号量sem,它与down()一样,也会导致调用进程睡眠,因此,也不能用于中断上下文;但是它与down()不同的是,在得不到信号量时,该函数不会一直睡眠下去,它只会睡眠一个指定的超时时间jiffies,当该超时时间到达时,仍然没有获得信号量的话,该函数就返回;超时时间jiffies是以系统时钟滴答次数计算。
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,并唤醒等待者
该函数把信号量sem的值加1,如果信号量sem的值为非正数,则表示有其它执行单元在等待该信号量sem,因此唤醒这些等待者;
用例:
struct semaphore my_sem;
sema_init(&my_sem, val);
down(&sem); //获得信号量,保护临界区
...临界区...
up(&mount_sem); //释放信号量
注:如果信号量的值被初始化为0,则它可以用于同步
同步就意味着一个执行单元的继续执行需要等待另一个执行单元完成某事,保证执行的先后顺序。
执行单元A 执行单元B
…… ……
down(&sem);
…… up(&sem);
up函数激活down函数