编程接口指南

System V 信号量

使用信号量,进程可以查询或更改状态信息。 通常使用信号量来监视和控制系统资源(如共享内存段)的可用性。 信号量既可以作为单个单元处理,也可以作为集中的元素处理。

由于 System V IPC 信号量可以存在于大型数组中,因此它们的开销非常大,线程库提供了更轻量级的信号量。 此外,POSIX 信号量是 System V 信号量的最新实现(请参见POSIX 信号量)。 线程库信号量必须与映射内存一起使用(请参见内存管理接口)。

信号量集由一个控制结构以及一个包含各个信号量的数组组成。 一个信号量集最多可以包含 25 个元素。 必须使用 semget(2) 初始化信号量集。 信号量创建者可以使用 semctl(2) 更改信号量的拥有权或权限。 任何具有相应权限的进程均可使用 semctl(2) 执行控制操作。

信号量操作通过 semop(2) 执行。 此接口采用指向信号量操作结构数组的指针。 此数组中的每个结构都包含有关针对信号量所执行的操作的数据。 任何具有读取权限的进程均可测试信号量的值是否为零。 递增或递减信号量的操作需要写入权限。

当操作失败时,不会更改任何信号量。 除非设置了 IPC_NOWAIT 标志,否则进程将阻塞,直到出现以下情况之一:

一次只有一个进程能更新一个信号量。 不同进程同时发出的请求可以按任意顺序执行。 当通过 semop(2) 调用提供操作数组时,在针对数组的所有操作均成功完成之前,不会进行更新。

如果以独占方式使用信号量的进程异常终止,并且无法撤消操作或释放信号量,则信号量将在内存中保持锁定在进程使其处于的状态。 为了防止出现这种情况,SEM_UNDO 控制标志使 semop(2) 为每个信号量操作分配撤消结构,其中包含将信号量返回到其先前状态的操作。 如果进程中止,则系统将应用撤消结构中的操作。 这样可防止异常中止的进程使信号量集处于不一致的状态。

如果进程共享受信号量控制的资源的访问权限,则不应在 SEM_UNDO 生效时对信号量执行操作。 如果当前控制资源的进程异常终止,则假定资源不一致。 其他进程必须能够识别这种情况,以将资源恢复为一致状态。

如果在 SEM_UNDO 生效时执行信号量操作,则在执行反向操作的调用时也必须使 SEM_UNDO 生效。 当进程正常运行时,反向操作会使用补充值更新撤消结构。 这样可确保将应用于撤消结构的值抵消为零,除非进程异常中止。 当撤消结构达到零时,便会被删除。

以不一致的方式使用 SEM_UNDO 可能会导致内存泄漏,因为在重新引导系统之前可能不会释放已分配的撤消结构。

初始化信号量集

semget(2) 可初始化信号量或获取其访问权限。 此调用成功时,会返回信号量 ID (semid)。 密钥参数是与信号量 ID 关联的值。nsems 参数指定信号量数组中的元素数。 如果 nsems 大于现有数组中的元素数,则此调用会失败。 如果不知道正确的计数,则为此参数提供 0 可确保此调用会成功。 semflg 参数指定初始访问权限和创建控制标志。

SEMMNI 系统配置选项可确定信号量数组的最大数目。 SEMMNS 选项可确定所有信号量集中的最大信号量数目。 由于信号量集之间存在分段,因此可能无法分配所有可用信号量。

以下代码说明了 semget(2)

#include			<sys/types.h>

#include			<sys/ipc.h>

#include			<sys/sem.h>

...

 	key_t		key;			/* key to pass to semget() */

 	int		semflg;			/* semflg to pass to semget() */

 	int		nsems;			/* nsems to pass to semget() */

 	int		semid;			/* return value from semget() */

		...

 	key = ...

 	nsems = ...

 	semflg = ...

 	...

 	if ((semid = semget(key, nsems, semflg)) == –1) {

 		perror("semget: semget failed");

 		exit(1);

 	} else

 		exit(0);

 ...

控制信号量

semctl(2) 可更改信号量集的权限和其他特性。 必须使用有效的信号量 ID 调用信号量。semnum 值可根据信号量索引选择数组内的信号量。 cmd 参数为以下控制标志之一。

GETVAL

返回单个信号量的值。

SETVAL

设置单个信号量的值。 在这种情况下,arg 被视为 arg.val,其类型为 int

GETPID

返回针对信号量或数组执行上次操作的进程的 PID

GETNCNT

返回等待信号量值增加的进程数。

GETZCNT

返回等待特定信号量的值达到零的进程数。

GETALL

返回信号量集中所有信号量的值。 在这种情况下,arg 被视为 arg.array,即指向数组的指针(值的类型为 unsigned short)。

SETALL

设置信号量集中所有信号量的值。 在这种情况下,arg 被视为 arg.array,即指向数组的指针(值为 unsigned short)。

IPC_STAT

从控制结构中返回信号量集的状态信息,并将其放入 arg.buf(指向类型为 semid_ds 的缓冲区的指针)指向的数据结构中。

IPC_SET

设置有效的用户和组标识以及权限。 在这种情况下,arg 被视为 arg.buf

IPC_RMID

删除指定的信号量集。

进程的属主、创建者或超级用户必须具有有效的用户标识,才能执行 IPC_SETIPC_RMID 命令。 与其他控制命令相同,执行上述两个命令也需要读写权限。

以下代码说明了 semctl(2)

#include <sys/types.h>

#include <sys/ipc.h>

#include <sys/sem.h>

...

 	register int				i;

 ...

 	i = semctl(semid, semnum, cmd, arg);

 	if (i == –1) {

 		perror("semctl: semctl failed");

 		exit(1);

...

信号量操作

semop(2) 可针对信号量集执行操作。 semid 参数是先前 semget(2) 调用所返回的信号量 ID。 sops 参数是指向结构数组的指针,其中每种结构都包含以下有关信号量操作的信息:

sembuf 结构指定信号量操作,如 sys/sem.h 中所定义。 nsops 参数指定数组的长度,其最大大小由 SEMOPM 配置选项确定。 此选项可确定单个 semop(2) 调用所允许的最大操作数,并缺省设置为 10。

要执行的操作按如下方式确定:

可与 semop(2) 一起使用的两个控制标志是 IPC_NOWAITSEM_UNDO

IPC_NOWAIT

可以针对数组中的任何操作设置。 如果接口无法执行任何设置了 IPC_NOWAIT 的操作,则使接口返回且不更改任何信号量值。 如果接口尝试以大于信号量当前值的数量递减信号量,或者测试非零信号量等于零,则接口会失败。

SEM_UNDO

当进程存在时,允许撤消数组中的单个操作。

以下代码说明了 semop(2)

#include				<sys/types.h>

#include				<sys/ipc.h>

#include				<sys/sem.h>

...

 	int				i;			/* work area */

 	int				nsops;	/* number of operations to do */

 	int				semid;	/* semid of semaphore set */

 	struct sembuf	*sops;	/* ptr to operations to perform */

 	...

 	if ((i = semop(semid, sops, nsops)) == –1) {

 		perror("semop: semop failed");

 	} else

 		(void) fprintf(stderr, "semop: returned %d\n", i);

 ...