进程间通信之信号量semaphore--linux内核剖析

什么是信号量信号量的使用主要是用来保护共享资源 , 使得资源在一个时刻只有一个进程(线程)所拥有 。
信号量的值为正的时候 , 说明它空闲 。 所测试的线程可以锁定而使用它 。 若为0 , 说明它被占用 , 测试的线程要进入睡眠队列中 , 等待被唤醒 。
为了防止出现因多个程序同时访问一个共享资源而引发的一系列问题 , 我们需要一种方法 , 它可以通过生成并使用令牌来授权 , 在任一时刻只能有一个执行线程访问代码的临界区域 。
临界区域是指执行数据更新的代码需要独占式地执行 。 而信号量就可以提供这样的一种访问机制 , 让一个临界区同一时间只有一个线程在访问它 , 也就是说信号量是用来调协进程对共享资源的访问的 。
信号量是一个特殊的变量 , 程序对其访问都是原子操作 , 且只允许对它进行等待(即P(信号变量))和发送(即V(信号变量))信息操作 。
最简单的信号量是只能取0和1的变量 , 这也是信号量最常见的一种形式 , 叫做二进制信号量 。 而可以取多个正整数的信号量被称为通用信号量 。 这里主要讨论二进制信号量 。
信号量的工作原理由于信号量只能进行两种操作等待和发送信号 , 即P(sv)和V(sv),他们的行为是这样的:

  • P(sv):如果sv的值大于零 , 就给它减1;如果它的值为零 , 就挂起该进程的执行
  • V(sv):如果有其他进程因等待sv而被挂起 , 就让它恢复运行 , 如果没有进程因等待sv而挂起 , 就给它加1.
举个例子 , 就是两个进程共享信号量sv , 一旦其中一个进程执行了P(sv)操作 , 它将得到信号量 , 并可以进入临界区 , 使sv减1 。 而第二个进程将被阻止进入临界区 , 因为当它试图执行P(sv)时 , sv为0 , 它会被挂起以等待第一个进程离开临界区域并执行V(sv)释放信号量 , 这时第二个进程就可以恢复执行 。
信号量的分类在学习信号量之前 , 我们必须先知道——Linux提供两种信号量:
  • 内核信号量 , 由内核控制路径使用
  • 用户态进程使用的信号量 , 这种信号量又分为POSIX信号量和SYSTEM V信号量 。
POSIX信号量又分为有名信号量和无名信号量 有名信号量 , 其值保存在文件中, 所以它可以用于线程也可以用于进程间的同步 。 无名信号量 , 其值保存在内存中 。
POSIX 信号量与SYSTEM V信号量的比较
  1. 对POSIX来说 , 信号量是个非负整数 。 常用于线程间同步 。而SYSTEM V信号量则是一个或多个信号量的集合 , 它对应的是一个信号量结构体 , 这个结构体是为SYSTEM V IPC服务的 , 信号量只不过是它的一部分 。 常用于进程间同步 。
  2. POSIX信号量的引用头文件是 , 而SYSTEM V信号量的引用头文件是
  3. 从使用的角度 , System V信号量是复杂的 , 而Posix信号量是简单 。 比如 , POSIX信号量的创建和初始化或PV操作就很非常方便 。
内核信号量Linux内核的信号量在概念和原理上与用户态的System V的IPC机制信号量是一样的 , 但是它绝不可能在内核之外使用 , 它是一种睡眠锁 。
如果有一个任务想要获得已经被占用的信号量时 , 信号量会将其放入一个等待队列(它不是站在外面痴痴地等待而是将自己的名字写在任务队列中)然后让其睡眠 。
当持有信号量的进程将信号释放后 , 处于等待队列中的一个任务将被唤醒(因为队列中可能不止一个任务) , 并让其获得信号量 。
这一点与自旋锁不同 , 处理器可以去执行其它代码 。
它与自旋锁的差异:由于争用信号量的进程在等待锁重新变为可用时会睡眠 , 所以信号量适用于锁会被长时间持有的情况;
相反 , 锁被短时间持有时 , 使用信号量就不太适宜了 , 因为睡眠、维护等待队列以及唤醒所花费的开销可能比锁占用的全部时间表还要长;
由于执行线程在锁被争用时会睡眠 , 所以只能在进程上下文中才能获得信号量锁 , 因为在中断上下文中是不能进行调试的;持有信号量的进行也可以去睡眠 , 当然也可以不睡眠 , 因为当其他进程争用信号量时不会因此而死锁;不能同时占用信号量和自旋锁 , 因为自旋锁不可以睡眠而信号量锁可以睡眠 。 相对而来说信号量比较简单 , 它不会禁止内核抢占 , 持有信号量的代码可以被抢占 。
信号量还有一个特征 , 就是它允许多个持有者 , 而自旋锁在任何时候只能允许一个持有者 。
当然我们经常遇到也是只有一个持有者 , 这种信号量叫二值信号量或者叫互斥信号量 。 允许有多个持有者的信号量叫计数信号量 , 在初始化时要说明最多允许有多少个持有者(Count值) 信号量在创建时需要设置一个初始值 , 表示同时可以有几个任务可以访问该信号量保护的共享资源 , 初始值为1就变成互斥锁(Mutex) , 即同时只能有一个任务可以访问信号量保护的共享资源 。当任务访问完被信号量保护的共享资源后 , 必须释放信号量 , 释放信号量通过把信号量的值加1实现 , 如果信号量的值为非正数 , 表明有任务等待当前信号量 , 因此它也唤醒所有等待该信号量的任务 。