目录
1 介绍
2 信号量各种操作合集
1. 创建信号量
2. 初始化信号量集
3. 信号量控制
4. 关联结构
5 信号量操作
6. 信号量删除
3 二元信号量协议实现(可重用)
信号量提供一种访问机制,让一个临界区同一时间只有一个线程在访问它, 也就是说信号量是用来调协进程对共享资源的访问的,防止出现因多个程序同时访问一个共享资源而引发的一系列问题。其中共享内存的使用就要用到信号量。
一个信号量是一个由内核维护的整数,其值被限制为大于或等于0。在-一个信 号量上可以执行各种操作( 即系统调用),包括:
使用SystemV信号量的常规步骤如下。
#include
#include
#include
int semget(key_t key, int nsems, int semflg);
key参数是使用45.2节中描述的其中一种方法生成的键(通常使用值IPC_PRIVATE或由ftok()返回的键)。
如果使用semget()创建一个新信号量集,那么nsems会指定集合中信号量的数量,并且其值必须大于0。如果使用semget()来获取一个既有集的标识符,那么nsems必须要小于或等于集合的大小(否则会发生EINVAL错误)。无法修改一个既有集中的信号量数量。
semflg参数是一个位掩码,它指定了施加于新信号量集之上的权限或需检查的一个既有集合的权限。指定权限的方式与为文件指定权限的方式是一样的(表15-4)。此外,在semflg中可以通过对下列标记中的零个或多个取OR来控制semget()的操作。
IPC_CREAT
如果不存在与指定的key相关联的信号量集,那么就创建一个新集合。
IPC_EXCL
如果同时指定了IPC_CREAT并且与指定的key关联的信号量集已经存在,那么返回EEXIST错误。
semget()在成功时会返回新信号量集或既有信号量集的标识符。后续引用单个信号量的系统调用必须要同时指定信号量集标识符和信号量在集合中的序号。一个集合中的信号量从0开始计数。
根据SUSv3的要求,实现无需对由semget()创建的集合中的信号量值进行初始化。相反,程序员必须要使用semctl()系统调用显式地初始化信号量。(在Linux上,semget()返回的信号量实际上会被初始化为0,但为取得移植性就不能依赖于此。)前面曾经提及过,信号量的创建和初始化必须要通过单独的系统调用而不是单个原子步骤来完成的事实可能会导致在初始化一个信号量时出现竞争条件。
union semun arg;
struct sembuf sop;
arg.val = 0;
/* So initialize it to 0 */
if (semctl(semid, 0, SETVAL, arg) == -1)
errExit(" semctl" );
#include
#include
#include
int semctl(int semid, int semnum, int cmd, ...);
semid参数是操作所施加的信号量集的标识符。对于那些在单个信号量上执行的操作,semnum参数标识出了集合中的具体信号量。对于其他操作则会忽略这个参数,并且可以将其设置为0。cmd参数指定了需执行的操作。
一些特定的操作需要向semctl()传入第四个参数,在本节余下的部分中将这个参数命名为arg。这个参数是一个union semun, 程序清单47-2给出了其定义。在程序中必须要显式地定义这个union。
union semun {
int val; /* Value for SETVAL */
struct semid_ds *buf; /* Buffer for IPC_STAT, IPC_SET */
unsigned short *array; /* Array for GETALL, SETALL */
struct seminfo *__buf; /* Buffer for IPC_INFO
(Linux specific) */
};
常规控制操作
下面的操作与可应用于其他类型的System V IPC对象上的操作是一样的。 所有这些操作都会忽略semnum参数。45.3 节提供了有关这些操作的更多细节,包括调用进程所需的特权和权限。
IPC_RMID
立即删除信号量集及其关联的semid ds数据结构。所有因在semop()调用中等待这个集合中的信号量而阻塞的进程都会立即被唤醒,semop()会报告错误EIDRM。这个操作无需arg参数。
IPC_STAT
在arg.buf指向的缓冲器中放置一份与这个信号量集相关联的semid_ds数据结构的副本。
IPC_SET
使用arg.buf指向的缓冲器中的值来更新与这个信号量集相关联的semid_ds数据结构中选中的字段。
获取和初始化信号量值
下面的操作可以获取或初始化一个集合中的单个或所有信号量的值。获取一个信号量的值需具备在信号量上的读权限,而初始化该值则需要修改(写)权限。
GETVAL
semctl()返回由semid指定的信号量集中第semnum个信号量的值。这个操作无需arg参数。
SETVAL
将由semid指定的信号量集中第semnum个信号量的值初始化为arg.val。
GETALL
获取由semid指向的信号量集中所有信号量的值并将它们放在arg.array 指向的数组中。程序员必须要确保该数组具备足够的空间。(通过由IPC_STAT操作返回的semid_ds 数据结构中的sem_nsems 字段可以获取集合中的信号量数量。)这个操作将忽略semnum参数。
SETALL
使用arg.array 指向的数组中的值初始化semid指向的集合中的所有信号量。这个操作将忽略semnum参数。
获取单个信号量的信息
下面的操作返回(通过函数结果值) semid引用的集合中第semnum个信号量的信息。所有这些操作都需要在信号量集合中具备读权限,并且无需arg参数。
GETPID
返回上一个在该信号量上执行semop()的进程的进程ID;这个值被称为sempid 值。如果还没有进程在该信号量上执行过semop(),那么就返回0。
GETNCNT
返回当前等待该信号量的值增长的进程数;这个值被称为semnent值。
GETZCNT
返回当前等待该信号量的值变成0的进程数;这个值被称为semzcnt值。
struct semid_ds {
struct ipc_perm sem_perm; /* Ownership and permissions
time_t sem_otime; /* Last semop time */
time_t sem_ctime; /* Last change time */
unsigned short sem_nsems; /* No. of semaphores in set */
};
struct ipc_perm {
key_t key; /* Key supplied to semget() */
uid_t uid; /* Effective UID of owner */
gid_t gid; /* Effective GID of owner */
uid_t cuid; /* Effective UID of creator */
gid_t cgid; /* Effective GID of creator */
unsigned short mode; /* Permissions */
unsigned short seq; /* Sequence number */
};
sem_perm
在创建信号量集时按照45.3中所描述的那样初始化这个子结构中的字段。通过IPC_SET能够更新uid、gid 以及mode子字段。
sem_otime
在创建信号量集时会将这个字段设置为0,然后在每次成功的semop()调用或当信号量值因SEM_UNDO操作而发生变更时将这个字段设置为当前时间(参见47.8 节)。这个字段和sem_ctime的类型为time_t,它们存储自新纪元到现在的秒数。
sem_ctime
在创建信号量时以及每个成功的IPC_ SET、SETALL和SETVAL操作执行完毕之后将这个字段设置为当前时间。
sem_ nsems
在创建集合时将这个字段的值初始化为集合中信号量的数量。
#include
#include
#include
int semop(int semid, struct sembuf *sops, unsigned nsops);
int semtimedop(int semid, struct sembuf *sops, unsigned nsops, struct timespec *timeout);
sops参数是一个指向数组的指针, 数组中包含了需要执行的操作,nsops 参数给出了数组的大小(数组至少需包含一个元素)。操作将会按照在数组中的顺序以原子的方式被执行。sops数组中的元素是形式如下的结构。
struct sembuf{
unsigned short sem_num; /* semaphore number */
short sem_op; /* semaphore operation */
short sem_flg; /* operation flags */
}
sem_num字段标识出了在集合中的哪个信号量上执行操作。sem_op 字段指定了需执行的操作。
当semop()调用阻塞时,进程会保持阻塞直到发生下列某种情况为止。
semtimedop中的timeout参数是一个指向timespec 结构(参见23.4.2节)的指针,通过这个结构能够将一个时间间隔表示为秒数和纳秒数。如果在信号量操作完成之前所等待的时间已经超过了规定的时间间隔,那么semtimedop会返回EAGAIN错误。如果将timeout指定为NULL,那么semtimedop就与semop完全一样了。
semctl(semid, semnum, IPC_RMID)
头文件
/* binary_sems.h
Header file for binary_sems.c.
*/
#ifndef BINARY_SEMS_H /* Prevent accidental double inclusion */
#define BINARY_SEMS_H
#include "tlpi_hdr.h"
/* Variables controlling operation of functions below */
extern Boolean bsUseSemUndo; /* Use SEM_UNDO during semop()? */
extern Boolean bsRetryOnEintr; /* Retry if semop() interrupted by
signal handler? */
int initSemAvailable(int semId, int semNum);
int initSemInUse(int semId, int semNum);
int reserveSem(int semId, int semNum);
int releaseSem(int semId, int semNum);
#endif
源文件
/* binary_sems.c
Implement a binary semaphore protocol using System V semaphores.
*/
#include
#include
#include "semun.h" /* Definition of semun union */
#include "binary_sems.h"
Boolean bsUseSemUndo = FALSE;
Boolean bsRetryOnEintr = TRUE;
int /* Initialize semaphore to 1 (i.e., "available") */
initSemAvailable(int semId, int semNum)
{
union semun arg;
arg.val = 1;
return semctl(semId, semNum, SETVAL, arg);
}
int /* Initialize semaphore to 0 (i.e., "in use") */
initSemInUse(int semId, int semNum)
{
union semun arg;
arg.val = 0;
return semctl(semId, semNum, SETVAL, arg);
}
/* Reserve semaphore (blocking), return 0 on success, or -1 with 'errno'
set to EINTR if operation was interrupted by a signal handler */
int /* Reserve semaphore - decrement it by 1 */
reserveSem(int semId, int semNum)
{
struct sembuf sops;
sops.sem_num = semNum;
sops.sem_op = -1;
sops.sem_flg = bsUseSemUndo ? SEM_UNDO : 0;
while (semop(semId, &sops, 1) == -1)
if (errno != EINTR || !bsRetryOnEintr)
return -1;
return 0;
}
int /* Release semaphore - increment it by 1 */
releaseSem(int semId, int semNum)
{
struct sembuf sops;
sops.sem_num = semNum;
sops.sem_op = 1;
sops.sem_flg = bsUseSemUndo ? SEM_UNDO : 0;
return semop(semId, &sops, 1);
}