Linux信号量
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linux信号量相关的知识,希望对你有一定的参考价值。
参考技术A信号量是包含一个非负整数型的变量,并且带有两个原子操作wait和signal。Wait还可以被称为down、P或lock,signal还可以被称为up、V、unlock或post。在UNIX的API中(POSIX标准)用的是wait和post。
对于wait操作,如果信号量的非负整形变量S大于0,wait就将其减1,如果S等于0,wait就将调用线程阻塞;对于post操作,如果有线程在信号量上阻塞(此时S等于0),post就会解除对某个等待线程的阻塞,使其从wait中返回,如果没有线程阻塞在信号量上,post就将S加1.
由此可见,S可以被理解为一种资源的数量,信号量即是通过控制这种资源的分配来实现互斥和同步的。如果把S设为1,那么信号量即可使多线程并发运行。另外,信号量不仅允许使用者申请和释放资源,而且还允许使用者创造资源,这就赋予了信号量实现同步的功能。可见信号量的功能要比互斥量丰富许多。
POSIX信号量是一个sem_t类型的变量,但POSIX有两种信号量的实现机制: 无名信号量 和 命名信号量 。无名信号量只可以在共享内存的情况下,比如实现进程中各个线程之间的互斥和同步,因此无名信号量也被称作基于内存的信号量;命名信号量通常用于不共享内存的情况下,比如进程间通信。
同时,在创建信号量时,根据信号量取值的不同,POSIX信号量还可以分为:
下面是POSIX信号量函数接口:
信号量的函数都以sem_开头,线程中使用的基本信号函数有4个,他们都声明在头文件semaphore.h中,该头文件定义了用于信号量操作的sem_t类型:
【sem_init函数】:
该函数用于创建信号量,原型如下:
该函数初始化由sem指向的信号对象,设置它的共享选项,并给它一个初始的整数值。pshared控制信号量的类型,如果其值为0,就表示信号量是当前进程的局部信号量,否则信号量就可以在多个进程间共享,value为sem的初始值。
该函数调用成功返回0,失败返回-1。
【sem_destroy函数】:
该函数用于对用完的信号量进行清理,其原型如下:
成功返回0,失败返回-1。
【sem_wait函数】:
该函数用于以原子操作的方式将信号量的值减1。原子操作就是,如果两个线程企图同时给一个信号量加1或减1,它们之间不会互相干扰。其原型如下:
sem指向的对象是sem_init调用初始化的信号量。调用成功返回0,失败返回-1。
sem_trywait()则是sem_wait()的非阻塞版本,当条件不满足时(信号量为0时),该函数直接返回EAGAIN错误而不会阻塞等待。
sem_timedwait()功能与sem_wait()类似,只是在指定的abs_timeout时间内等待,超过时间则直接返回ETIMEDOUT错误。
【sem_post函数】:
该函数用于以原子操作的方式将信号量的值加1,其原型如下:
与sem_wait一样,sem指向的对象是由sem_init调用初始化的信号量。调用成功时返回0,失败返回-1。
【sem_getvalue函数】:
该函数返回当前信号量的值,通过restrict输出参数返回。如果当前信号量已经上锁(即同步对象不可用),那么返回值为0,或为负数,其绝对值就是等待该信号量解锁的线程数。
【实例1】:
【实例2】:
之所以称为命名信号量,是因为它有一个名字、一个用户ID、一个组ID和权限。这些是提供给不共享内存的那些进程使用命名信号量的接口。命名信号量的名字是一个遵守路径名构造规则的字符串。
【sem_open函数】:
该函数用于创建或打开一个命名信号量,其原型如下:
参数name是一个标识信号量的字符串。参数oflag用来确定是创建信号量还是连接已有的信号量。
oflag的参数可以为0,O_CREAT或O_EXCL:如果为0,表示打开一个已存在的信号量;如果为O_CREAT,表示如果信号量不存在就创建一个信号量,如果存在则打开被返回,此时mode和value都需要指定;如果为O_CREAT|O_EXCL,表示如果信号量存在则返回错误。
mode参数用于创建信号量时指定信号量的权限位,和open函数一样,包括:S_IRUSR、S_IWUSR、S_IRGRP、S_IWGRP、S_IROTH、S_IWOTH。
value表示创建信号量时,信号量的初始值。
【sem_close函数】:
该函数用于关闭命名信号量:
单个程序可以用sem_close函数关闭命名信号量,但是这样做并不能将信号量从系统中删除,因为命名信号量在单个程序执行之外是具有持久性的。当进程调用_exit、exit、exec或从main返回时,进程打开的命名信号量同样会被关闭。
【sem_unlink函数】:
sem_unlink函数用于在所有进程关闭了命名信号量之后,将信号量从系统中删除:
【信号量操作函数】:
与无名信号量一样,操作信号量的函数如下:
命名信号量是随内核持续的。当命名信号量创建后,即使当前没有进程打开某个信号量,它的值依然保持,直到内核重新自举或调用sem_unlink()删除该信号量。
无名信号量的持续性要根据信号量在内存中的位置确定:
很多时候信号量、互斥量和条件变量都可以在某种应用中使用,那这三者的差异有哪些呢?下面列出了这三者之间的差异:
Linux并发与同步专题 信号量
关键词:Semaphore、down()/up()。
1. 信号量数据结构
数据机构struct semaphore用于描述信号量。
/* Please don\'t access any members of this structure directly */
struct semaphore {
raw_spinlock_t lock;-----------------------------spinlock变量,用于对信号量数据结构里count和wait_list成员的保护。
unsigned int count;------------------------------用于表示允许进入临界区的内核执行路径个数。
struct list_head wait_list;--------------------------用于管理所有在该信号量上睡眠的进程,没有成功获取锁的进程会睡眠在这个链表上。
};
数据结构struct semaphore_waiter用于描述将在信号量等待队列山等待的进程。
struct semaphore_waiter { struct list_head list;---------------------------------链表项 struct task_struct *task;------------------------------将要放到信号量等待队列上的进程结构 bool up; };
2. 信号量的初始化
信号量的初始化有两种,一种是通过sema_init()动态初始化一个信号量,另一种是通过DEFINE_SEMAPHORE()静态定义一个信号量。
这两者都通过__SEMAPHORE_INITIALIZER()完成初始化工作。区别是sema_init()提供了lockdep调试跟踪,而且sema_init()可以指定持锁路径个数;而DEFINE_SEMAPHORE()默认为1。
#define __SEMAPHORE_INITIALIZER(name, n) \\ { \\ .lock = __RAW_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)---------------和sema_init()区别在于此处只有1. static inline void sema_init(struct semaphore *sem, int val) { static struct lock_class_key __key; *sem = (struct semaphore) __SEMAPHORE_INITIALIZER(*sem, val); lockdep_init_map(&sem->lock.dep_map, "semaphore->lock", &__key, 0); }
3. down()/up()
信号量的使用较简单,down_xxx()持有信号量,up()释放信号量。
down()有很多变种,基本上遵循一致的规则:首先判断sem->count是否大于0,如果大于0,则sem->count--;否则调用__down_xxx()函数。
__down_xxx()最终都会调用__down_common()函数,他们之间的区别就是参数不一样。
down()变种 | flag | timeout | 说明 |
down() | TASK_UNINTERRUPTIBLE | MAX_SCHEDULE_TIMEOUT | 争用信号量失败时进入不可中断的睡眠状态。 |
down_interruptible() | TASK_INTERRUPTIBLE | MAX_SCHEDULE_TIMEOUT | 争用信号量失败时进入可中断的睡眠状态。 |
down_killable() | TASK_KILLABLE | MAX_SCHEDULE_TIMEOUT | 争用信号量失败时进入不可中断睡眠状态,但是在收到致命信号时唤醒睡眠进程。 |
down_timeout() | TASK_UNINTERRUPTIBLE | timeout | 争用信号量失败时进入不可中断的睡眠状态,超时则唤醒当前进程。 |
down_trylock()是个特例,并不会等待,只是单纯的去获取锁。返回0表示获取锁成功,返回1表示获取锁失败。
void down(struct semaphore *sem) { unsigned long flags; raw_spin_lock_irqsave(&sem->lock, flags);-----------------获取spinlock并关本地中断来保护count数据。 if (likely(sem->count > 0))-------------------------------如果大于0则表明当前进程可以成功获取信号量。 sem->count--; else __down(sem);------------------------------------------获取失败,等待。 raw_spin_unlock_irqrestore(&sem->lock, flags);------------恢复中断寄存器,打开本地中断,并释放spinlock。 } static noinline void __sched __down(struct semaphore *sem) { __down_common(sem, TASK_UNINTERRUPTIBLE, MAX_SCHEDULE_TIMEOUT); } int down_interruptible(struct semaphore *sem) { unsigned long flags; int result = 0; raw_spin_lock_irqsave(&sem->lock, flags); if (likely(sem->count > 0)) sem->count--; else result = __down_interruptible(sem); raw_spin_unlock_irqrestore(&sem->lock, flags); return result; } static noinline int __sched __down_interruptible(struct semaphore *sem) { return __down_common(sem, TASK_INTERRUPTIBLE, MAX_SCHEDULE_TIMEOUT); } int down_killable(struct semaphore *sem) { unsigned long flags; int result = 0; raw_spin_lock_irqsave(&sem->lock, flags); if (likely(sem->count > 0)) sem->count--; else result = __down_killable(sem); raw_spin_unlock_irqrestore(&sem->lock, flags); return result; } static noinline int __sched __down_killable(struct semaphore *sem) { return __down_common(sem, TASK_KILLABLE, MAX_SCHEDULE_TIMEOUT); } int down_trylock(struct semaphore *sem) { unsigned long flags; int count; raw_spin_lock_irqsave(&sem->lock, flags); count = sem->count - 1; if (likely(count >= 0))-------------------------------判断当前sem->count的减1后是否大于等于0。如果小于0,则表示无法获取信号量;如果大于等于0,表示可以成功获取信号量,并更新sem->count的值。 sem->count = count; raw_spin_unlock_irqrestore(&sem->lock, flags); return (count < 0);-----------------------------------如果count<0,表示无法获取信号量;如果count<0不成立,则表示获取信号量失败。 } int down_timeout(struct semaphore *sem, long timeout) { unsigned long flags; int result = 0; raw_spin_lock_irqsave(&sem->lock, flags); if (likely(sem->count > 0)) sem->count--; else result = __down_timeout(sem, timeout); raw_spin_unlock_irqrestore(&sem->lock, flags); return result; } static noinline int __sched __down_timeout(struct semaphore *sem, long timeout) { return __down_common(sem, TASK_UNINTERRUPTIBLE, timeout); }
static inline int __sched __down_common(struct semaphore *sem, long state, long timeout) { struct task_struct *task = current;-------------------得到当前进程结构 struct semaphore_waiter waiter;-----------------------struct semaphore_waiter数据结构用于描述获取信号量失败的进程,每个进程会有一个semaphore_waiter数据结构,并把当前进程放到信号量sem的成员变量wait_list链表中。 list_add_tail(&waiter.list, &sem->wait_list);---------将waiter加入到信号量sem->waiter_list尾部 waiter.task = task;-----------------------------------waiter.task指向当前正在运行的进程。 waiter.up = false; for (;;) { if (signal_pending_state(state, task))------------根据不同state和当前信号pending情况,决定是否进入interrupted处理。 goto interrupted; if (unlikely(timeout <= 0))-----------------------timeout设置错误 goto timed_out; __set_task_state(task, state);--------------------设置当前进程task->state。 raw_spin_unlock_irq(&sem->lock);------------------下面即将睡眠,这里释放了spinlock锁,和down()中的获取spinlock锁对应。 timeout = schedule_timeout(timeout);--------------主动让出CPU,相当于当前进程睡眠。 raw_spin_lock_irq(&sem->lock);--------------------重新获取spinlock锁,在down()会重新释放锁。这里保证了schedule_timeout()不在spinlock环境中。 if (waiter.up)------------------------------------waiter.up为true时,说明睡眠在waiter_list队列中的进程被该信号量的up操作唤醒。 return 0; } timed_out: list_del(&waiter.list); return -ETIME; interrupted: list_del(&waiter.list); return -EINTR; }
static inline int signal_pending_state(long state, struct task_struct *p)
{
if (!(state & (TASK_INTERRUPTIBLE | TASK_WAKEKILL)))---------------------对于TASK_UNINTERRUPTIBLE,返回0,继续睡眠。TASK_INTERRUPTIBLE和TASK_WAKEKILL则往下继续判断。
return 0;
if (!signal_pending(p))--------------------------------------------------TASK_INTERRUPTIBLE和TASK_WAKEKILL情况,如果没有信号pending,则返回0,继续睡眠.
return 0;
return (state & TASK_INTERRUPTIBLE) || __fatal_signal_pending(p);--------如果是TASK_INTERRUPTIBLE或有SIGKILL信号未处理,则返回1,中断睡眠等待。
}
signed long __sched schedule_timeout(signed long timeout) { struct timer_list timer; unsigned long expire; switch (timeout) { case MAX_SCHEDULE_TIMEOUT: schedule();-------------------------------------------------------MAX_SCHEDULE_TIMEOUT并不设置一个具体的时间,仅是睡眠。 goto out; default: if (timeout < 0) { printk(KERN_ERR "schedule_timeout: wrong timeout " "value %lx\\n", timeout); dump_stack(); current->state = TASK_RUNNING; goto out; } } expire = timeout + jiffies; setup_timer_on_stack(&timer, process_timeout, (unsigned long)current); __mod_timer(&timer, expire, false, TIMER_NOT_PINNED);----------------这时一个timer,超时函数为process_timeout(),超时后wake_up_process()唤醒当前进程current。 schedule(); del_singleshot_timer_sync(&timer);-----------------------------------删除timer /* Remove the timer from the object tracker */ destroy_timer_on_stack(&timer);--------------------------------------销毁timer timeout = expire - jiffies;------------------------------------------还剩多少jiffies达到超时点。 out: return timeout < 0 ? 0 : timeout;------------------------------------timeout<0表示已超过超时点;timeout>0表示提前了timeout个jiffies唤醒了。 }
void up(struct semaphore *sem) { unsigned long flags; raw_spin_lock_irqsave(&sem->lock, flags); if (likely(list_empty(&sem->wait_list)))---------------------------如果信号量上的等待队列sem->wait_list为空,说明没有进程在等待该信号来那个,那么直接sem->count加1。 sem->count++; else __up(sem);-----------------------------------------------------如果不为空,说明有进程在等待队列里睡眠,调用__up()唤醒。 raw_spin_unlock_irqrestore(&sem->lock, flags); } static noinline void __sched __up(struct semaphore *sem) { struct semaphore_waiter *waiter = list_first_entry(&sem->wait_list, struct semaphore_waiter, list); list_del(&waiter->list);--------------------------------------------将waiter从信号量等待队列列表删除。 waiter->up = true;--------------------------------------------------修改该信号量等待队列上waiter->up变量。 wake_up_process(waiter->task);--------------------------------------唤醒该信号量等待队列上的进程。 } int wake_up_process(struct task_struct *p) { WARN_ON(task_is_stopped_or_traced(p)); return try_to_wake_up(p, TASK_NORMAL, 0); }
4. 信号量和spinlock的对比
spinlock临界区不允许睡眠,是一种忙等待;信号量允许进程进入睡眠状态。
spinlock同一时刻只能被一个内核代码路径持有;信号量可以同时允许任意数量的持有者。
spinlock适用于一些快速完成的简单场景;信号量适用于一些情况复杂、加锁时间较长的应用场景。
以上是关于Linux信号量的主要内容,如果未能解决你的问题,请参考以下文章