System V信号量不是用来在进程间传输数据的,而是用来同步进程的动作。
信号量的一个常见用途是同步对一块共享内存的访问以防止出现一个进程在访问共享内存的同时另一个进程更新这块内存的情况,
在控制进程的动作方面,信号量本身并没有任何意义,它的意义仅由使用信号量的进程赋予其的关联关系来确定。
进程之间会达成协议将一个信号量与一种共享资源关联起来,如一块共享内存区域。信号量还有其他用途,如在 fork()之后同步父进程和子进程。
使用Svstem V信号量的常规操作步骤:
使用semget()
创建或者打开一个信号量集。
使用semctl() SETVAL 或 SETALL
操作初始化集合中的信号量(只有一个进程需要完成这个任务)。
使用semop()
操作信号量的值。使用信号量的进程通常会使用这些操作来标识一种共享资源的获取和释放。
当所有进程都不再需要使用信号量集之后semctl() IPC_RMID
操作删除这个集合(只有一个进程需要完成这个任务)。
System V信号量是以分配被称为信号量集的组为单位进行的。在使用semget()
系统调用创建集合的时候需要指定集合中的信号量数量。
虽然同一时刻通常只会操作一个信号量,但通过semop()系统调用可以原子的在同一个集合中的多个信号量之上执行一组操作。
信号量数据结构是信号量程序设计中经常使用的数据结构,由于在之后的函数经常用到,这里将结构的原型列出来,便于查找:
//每个信号量集,内核维护如下信息结构:
struct semid_ds
{
struct ipc_perm sem_perm; /* 操作权限结构 */
struct sem *sem_base; //指向sem结构数组指针
time_t sem_otime; /* 上次semop()时间 */
time_t sem_ctime; /* 上次由semctl()更改的时间 */
ushort sem_nsems; /* 集合中的信号量数量 */
};
//ipc_perm含有当前信号量访问权限
struct ipc_perm{
uid_t uid;
gid_t gid;
uid_t cuid;
gid_t cgid;
mode_t mode;
ulong_t seq;
key_t key;
}
//维护某个信号量的一组的内部数据结构,一个信号量集的每个成员如下这个结构描述:
struct sem{
ushort_t semval;
short sempid;
ushort_t semncnt;
ushort_t semzcnt;
}
除维护一 个信号量集内每个信号量的实际值之外,内核还给该集合中每个信号量维护另外三个信息:对其值执行最后 一 次操作的进程的进程ID 、等待其值增长的进程数计数以及等待其值变为0的进程数计数。
semget()函数用于创建一个新的信号量集合,或者访问现有的集合。
其原型如下,其中第1个参数key是ftok生成键值,第2个参数nsems参数可以指定新的集合中应该创建的信号量的数目,第3个参数semflsg是打开信号的方式。
#include
#include
int semget(key_t key,int nsems,int semflg);
//key是ftok生成键值
//nsems参数可以指定新的集合中应该创建的信号量的数目
//semflsg是打开信号的方式
semflsg属性值:
IPC_CREAT
: 如果内核中不存在这样的信号量集合,则把它创建出来。IPC_EXCL
:
IPC_CREAT
一起使用时,如果信号量集合早已存在,则操作将失败。IPC _ CREAT
, semget()或者返回新创建的信号量集合的信号量集合标识符;或者返回早已存在的具有同 个关键字值的集合的标识符。IPC_EXCL和IPC_CREAT
, 那么将有两种可能的结果:如果集合不存在,则创建一个新的集合;如果集合早已存在,则调用失败,并返回-1。IPC_EXCL本
身是没有什么用处的,但当与IPC_CREAT
组合使用时,它可以用于防止为了访问而打开现有的信号量集合。
//CreateSem()函数按照用户的键值生成一个信号量,把信号量的初始值设为用户输入的value。
typedef int sem_t;
union semun{
//信号量操作的联合结构
int val; //整型变量
struct semid_ds *buf;//semid_ds结构指针
unsigned short *array;//数组类型
}arg;//全局变量
sem_t CreateSem(key_t key,int value)//建立信号量,魔数key和信号量的初始值value
{
union semun sem; //信号量结构变量
sem_t semid;//信号量ID
sem.val = val;//设置初值
semid = semget(key,0,IPC_CREAT|0666);//获得信号量ID
if(-1 == semid)
{
printf("create semaphore error\n");
return -1;
}
semctl(semid,0,SETVAL,sem);//发送命令,建立value个初始值的信号量
return semid;
}
与文件操作的ioctl()函数类似,信号量的其他操作是通过函数semctl()来完成。函数semctl()的原型如下:
#include
#include
#inlcude<sys/sem.h>
int semctl(int semid,int semnum,int cmd,/*union semun arg*/...);
//第4个参数指向如下联合体:
union semun{
int val;//当执行SETVAL命令时将用到这个成员,它用于指定要把信号量设置成什么值。
struct semid_ds *buf;//在命令IPC_STAT/IPC_SET中使用。它代表内核中所使用的内部信号量数据结构的一 个复制。
ushort *array;//用在GETALL/SET ALL命令中的 一 个指针。它应当指向整数值的一 个数组。在设置或获取集合中所有信号量的值的过程中,将会用到该数组。
struct seminfo *buf;//信号量内部结构
};
cmd 参数代表将要在集合上执行的命令。其取值如下所述:
IPC_STAT: 获取某个集合的semid_ds结构,并把它存储在semun联合体的buf参数所指定的地址中。
IPC_SET:设置某个集合的semid_ds结构的ipc_perm成员的值。该命令所取的值是从semun联合体的buf参数中取到的。
IPC_RMID : 从内核删除该集合。
GETALL: 用于获取集合中所有信号量的值。整数值存放在无符号短整数的一个数组中,该数组由联合体的array成员所指定。
GETNCNT: 返回当前正在等待资源的进程的数目。
GETPID: 返回最后一 次执行 semop 调用的进程的PID 。
GETVAL: 返回集合中某个信号量的值。
GETZCNT: 返回正在等待资源利用率达到百分之百的进程的数目。
SETALL: 把集合中所有信号量的值,设置为联合体的array成员所包含的对应值。
SETVAL: 把集合中单个信号量的值设置为联合体的val成员的值。
例:利用semctl()函数设置和获得信号量的值构建同用的函数:
void SetvalueSem(sem_t semid,int value)//设置信号的值,通过SETVAL实现,所设置的值通过联合变量sem的val域实现
{
union semun sem;//信号量操作的结构
sem.val = value;//初始化
semctl(semid,0,SETVAL,sem);//设置信号量的值
}
int GetvalueSem(sem_t semid)//获得信号量的值
{
union semun sem;//信号量操作的结构
return semctl(semid,0,GETVAL,sem);//获得信号量的值,通过GETVAL会是其返回给定信号量的当前值
}
void DestroySem(sem_t semid