# 7.3 POSIX 信号量

### 概述

SUSv3 规定了两种类型的 POSIX 信号量：

* 命名信号量：不相关的进程通过这个名字能够访问同一个信号量。
* 未命名信号量：没有名字，位于内存中一个约定的位置，可以在进程之间或一组线程之间共享。进程间共享中，需要位于一个共享内存区域中。

POSIX 信号量是一个整数，其值不能小于 0。如果一个进程试图将一个信号量的值减小到小于 0，那么取决于所使用的函数，调用会阻塞或返回一个表明当前无法执行相应操作的错误。Linux 2.6 并带有 NPTL 线程库的版本上，完整的 POSIX 信号量实现才可用。

### 命名信号量

#### 打开一个命名信号量

```c
#include <fcntl.h>
#include <sys/stat.h>
#include <semaphore.h>

/*
@brief 打开或创建一个命名信号量
@param name	标识了信号量，名字格式斜杠开头，如 /myobject
@param oflag	位掩码，标识创建还是打开一个信号量，可取
            	- O_CREAT   创建一个信号量
                - O_EXCL    配合 O_CREAT 使用，如果已存在则创建失败
                - 0	    打开一个信号量
@param mode	oflag 为 O_CREAT 使用，指定了权限可取
            	- O_RDONLY	仅读
                - O_WRONLY	仅写
                - O_RDWR	读写，默认
@param value	无符号整数。指定了新信号量的初值
@retrun	成功返回指向信号量的指针，否则返回 SEM_FAILED
*/
sem_t *sem_open(const char *name, int oflag, ...
                /* mode_t mode, unsigned int value */);
```

SUSv3 规定只能在原始返回的 sem\_t 指针上操作，其副本操作是未定义的，即不能：

```c
sem_t *sp, sem2;
sp = sem_open(...);
sem2 = *sp;
sem_wait(&sem2); // WRONG, Not Defined
```

#### 关闭一个信号量

```c
#include <semaphore.h>
int sem_close(sem_t *sem);
```

当一个进程打开一个命名信号量时，系统会记录进程与信号量之间的关联关系。sem\_close 会终止这种关联关系（即关闭信号量），释放系统为该进程关联到该信号量之上的所有资源，并递减引用该信号量的进程数。 在进程终止或者调用了 exec 时，会被自动关闭，关闭信号量并不代表删除信号量。

#### 删除一个信号量

```c
#include <semaphore.h>
// 删除 name 标识的信号量，当所有进程均使用完（close）这个信号量后销毁
int sem_unlink(const char *name);
```

### 信号量操作

#### 等待一个信号量

```c
#include <semaphore.h>
// 如果当前信号量大于 0，将信号量 -1，并立即返回；信号量为 0 则阻塞直到其大于 0
int sem_wait(sem_t *sem);
```

如果一个阻塞的 sem\_wait 调用被一个信号处理器中断了，那么它就会失败并返回 EINTR 错误，不管在使用 sigaction 建立这个信号处理器时是否采用了 SA\_RESTART 标记。 非阻塞版本：

```c
#include <semaphore.h>
// 如果递减操作不能被立即执行，失败并返回 EAGAIN 错误
int sem_trywait(sem_t *sem);
```

#### 发布一个信号量

```c
#include <semaphore.h>
// 递增一个信号量
int sem_post(sem_t *sem);
```

此操作会唤醒一个因此信号量而被阻塞的进程（或线程）。哪一个进程被唤醒则是不确定的。

#### 获取信号量当前值

```c
#include <semaphore.h>
// 获取 sem 指向的信号量的当前值.写入 sval 指向的内存，成功返回 0，失败返回 -1
int sem_getvalue(sem_t *sem, int *sval);
```

注意在 sem\_getvalue 返回时，sval 中的返回值可能已经过时了。

### 未命名信号量

未命名信号量的操作与信号量是一样的，它的创建与删除会用到额外的两个函数，这两个函数不应该用在命名信号量上。 在线程间共享的信号量不需要名字。在相关进程间共享的信号量不需要名字。如果一个父进程在一块共享内存区域中（如一个共享匿名映射）分配了一个未命名信号量，那么作为 fork 操作的一部分，子进程会自动继承这个映射，从而继承这个信号量。

#### 初始化一个未命名信号量

```c
#include <semaphore.h>

/*
@brief 初始化一个未命名信号量
@param sem 	信号量指针
@param pshared	表明信号量是在线程间共享还是在进程间共享
            	- 0:   信号量将在线程间共享
                - else:信号量在进程间共享
@param value	信号量初始值
@return	成功返回 0，失败返回 -1
*/
int sem_init(sem_t *sem, int pshared, unsigned int value);
```

对一个已经初始化的信号量再次初始化将导致未定义的行为。并且与命名信号量一样，必须通过 sem 指定的 sem\_t 变量上操作，在其副本上的操作是未定义的。

#### 销毁一个未命名信号量

```c
#include <semaphore.h>

// 销毁一个信号量，只有在不存在进程或线程等待一个信号量时才可以安全销毁这个信号量
int sem_destroy(sem_t *sem);
```

### 信号量限制

下面限制定义在 limits.h 中：

* SEM\_NSEMS\_MAX：一个进程能够拥有的 POSIX 信号量最大数目
* SEM\_VALUE\_MAX ：可取的信号值最大值
