53 POSIX 信号量
大概特点
POSIX 信号量有两种:命名信号量和匿名信号量,前者和 System V 信号量比较相似(System V 的 IPC 都用 key 来标识,因此相当于是命名的)。
POSIX 信号量使用了 futex(2) 来实现,在没有争抢的情况下,不会发生系统调用,因此效率比 System V 实现更高。在争抢频繁的情况下,两者性能差不多。
和 POSIX 消息队列类似,Linux 上的 POSIX 信号量被挂载在 /dev/shm 目录这个 tmpfs 文件系统下。该文件系统具有内和持久性。
(二元)信号量和 pthreads mutex 相比:
- 前者是异步信号安全的,后者不是。但是处理信号还是建议用
sigwaitinfo()
。 - 前者可以由任何线程释放资源(
sem_post()
),后者只能由锁的持有者释放,否则是未定义行为。
命名信号量
可以用 sem_open()
来打开或创建命名信号量。这个过程和打开文件很像,如果需要创建命名信号量,则需要额外提供 mode 和 value 参数。这保证了命名信号量创建和初始化过程的原子化,这是 System V 没有的保证。
sem_open()
的返回值是 sem_t *
,这意味着我们要固定地址来使用信号量,不能复制它的值。如果打开 / 创建命名信号量失败,则会返回 SEM_FAILED
。
关闭信号量用 sem_close()
,进程结束时也会自动关闭。删除信号量用 sem_unlink()
,命名信号量和文件一样有引用计数:尽管已经调用 sem_unlink()
,只有所有进程都关闭命名信号量后,该信号量才会真正被删除。
信号量操作
As with a System V semaphore, a POSIX semaphore is an integer that the system never allows to go below 0.
信号量的值永远不会小于 0。不要以为可以先扣到负数再增加!
POSIX 信号量的 sem_wait()
和 sem_post()
操作每次只能操作一个信号量,而且只能将该信号量值的绝对值改变 1(如果操作成功)。这是和 SysV 不同的。
sem_wait()
如果被中断会失败并设置 EINTR 错误(英文书上说的是 fails with the error EINTR,翻译的是“返回 EINTR 错误”,让人以为是设置了返回值,实际上是设置了 errno)。即便信号量处理器建立时启用了 SA_RESTART
标记,Linux 也不会自动重启这个系统调用 —— 但是有的 UNIX 实现会重启。
除了 sem_wait()
和 sem_post()
之外,还能用 sem_getvalue()
获得信号量的当前值。如果有进程正在阻塞等待该信号量,Linux 等实现获取到的信号量值是 0,而有些 UNIX 实现获取到的信号量值是负数。
未命名(Unamed)信号量
为什么不说是匿名信号量呢?
未命名信号量可以使用和命名信号量一样的操作,但是创建和销毁的方式不同。
创建用 sem_init()
。在创建之前要先分配好信号量所需的内存。
SYNOPSIS
#include <semaphore.h>
int sem_init(sem_t *sem, int pshared, unsigned int value);
如果 pshared
非 0,则信号量可以用于进程间共享,这个时候需要将信号量放在共享内存段上(否则实际上是不共享的)。如果 pshared
为 0,则信号量只能在此进程使用(线程间可以共享)。尽管很多系统包括带有 NTPL 实现的 Linux 上 pshared
没有什么用,但为了可移植性还是要认真写。
一个信号量只能被初始化一次,初始化多次是未定义行为。
用 sem_destroy()
可以销毁信号量。很多系统上未命名信号量在进程结束之后(随着共享内存段)一起释放,但是有些系统上信号量是一种需要小心管理的资源,因此为了可移植性最好显式销毁。
SYNOPSIS
#include <semaphore.h>
int sem_destroy(sem_t *sem);
未命名信号量没有访问权限的设置,控制其访问权限可以通过修改其所在共享内存段的访问权限来完成。
信号量的限制
SEM_NSEMS_MAX
:一个进程能拥有的最大信号量数目。SUSv3 要求这个值至少为 256,Linux 上这个值受限于可用的内存。(没有整个系统上的限制!)
SEM_VALUE_MAX
:一个 POSIX 信号量可取的最大值。SUSv3 要求这个值至少为 32767,Linux 上这个值为 INT_MAX。