SystemV信号量并不如Posix信号量那样“好用”,但相比之下它的年代更加久远,但是SystemV使用的却更加广泛(尤其是在老系统中)。在学习Posix信号量的时候,已经大概清楚了二值信号量
和计数信号量
是什么东西。在接触SystemV信号量之后,这里有一个新的概念叫做:计数信号量集。其实就是把信号量放入数组中,不过都用一些特别的结构封装。
函数接口就比较少了,书上介绍了三个:semget
,semctl
、semop
。
#include
#include
#include
int semget(key_t key, int nsems, int semflg);
第一个参数key通过ftok根据特定path获取
第二个参数表示信号量集中信号量数(可以理解为数组的大小)
第三个参数标志位比如:senflg=O_CREAT|O_EXCL|0644
systemV信号随内核持续的,semget
调用成功后,在内核会维护一个信息结构(可以理解为信号量集的表头,或者链表的头节点),里面包含了一些信息:
struct semid_ds {
struct ipc_perm sem_perm; //例如0644,0600,也有一些宏特别指定,不过还是数字好记
time_t sem_otime;
time_t sem_ctime;
unsigned long sem_nsems;
struct sem * sem_base;//这一项在man page中没有明确表示
//但man page提到信号量集中有这样的结构
};
struct sem
表示封装的信号量结构
struct sem
{
unsigned short semval; /* semaphore value */
unsigned short semzcnt; /* # waiting for zero */
unsigned short semncnt; /* # waiting for increase */
pid_t sempid; /* ID of process that did last op */
};
根据这两个结构体,在内核中某个特定信号量集可以图解为:
使用semget
打开一个信号量后,可以对其中一个或多个信号量操作使用semop
函数来执行。
#include
#include
#include
int semop(int semid, struct sembuf *sops, unsigned nsops);
对于struct sembuf
这个结构体来说,其结构定义如下:
struct sembuf{
unsigned short sem_num; /* semaphore number */
short sem_op; /* semaphore operation */
short sem_flg; /* operation flags */
};
sem_num指定特定信号量的操作。
sem_op的值分为3类:
a.sem_op > 0:
将值添加到semval上,对应与释放某个资源。
b.sem_op = 0:
希望等待到semval值变为0,如果已经是0,则立即返回,否则semzcnt+1,并线程阻塞。
c.sem_op < 0:
希望等待到semval值变为大于或等于 |sem_op| 。这对应分配资源。如果已经满足条件,则semval减去sem_op的绝对值,否则semncnt+1并且线程投入睡眠。
semop
函数通过命令执行了信号量操作。
对一个信号量执行各种控制操作。
#include
#include
#include
int semctl(int semid, int semnum, int cmd, ...);
semnum指定信号量集中的某个成员,类似于数组下标(0,1,2…直到nsems-1)。
下面列出了所有CMD对应的宏。
semnum值仅仅用于前5个命令。
命令 | 作用 |
---|---|
GETVAL | 返回semval |
SETVAL | 把semval设定为指定值 |
GETPID | 返回sempid |
GETNCNT | 返回semncnt |
GETZCNT | 返回semzcnt |
GETALL | 返回所有semval值,由array指针返回 |
SETALL | 设置所有semval |
IPC_RMID | 删除指定id信号量集 |
IPC_SET | 设置uid,gid和mode |
IPC_STAT | 返回semid_ds结构 |
而对于第四个参数来说,它是如下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) */
};
从注释中可以看见,有些成员仅仅针对某些命令,这也正是为什么这里用Union而不用Struct,可以节省空间,因为假设当前命令跟某个成员没关的时候,struct依然为这个成员分配空间。
使用semget
创建信号量集,创建之后可以在linux终端使用ipcs
来查看相应信息。
#include
#include
#include
#include
#include
#define SEM_R 0400 //用户(属主)读
#define SEM_A 0200 //用户(属主)写
#define SVSEM_MODE (SEM_R | SEM_A | SEM_R>>3 | SEM_R>>6)
int main(int argc,char *argv[])
{
int c,oflag,semid,nsems;
oflag = SVSEM_MODE | IPC_CREAT; //设置创建模式
//根据命令行参数e判断是否制定了IPC_EXCL模式
while((c = getopt(argc,argv,"e"))!= -1)
{
switch(c)
{
case 'e':
oflag |= IPC_EXCL;
break;
}
}
//判断命令行参数是否合法
if (optind != argc -2)
{
printf("usage: semcreate [-e] " );
exit(0);
}
//获取信号量集合中的信号量个数
nsems = atoi(argv[optind+1]);
//创建信号量,通过ftok函数创建一个key,返回信号量 标识符
semid = semget(ftok(argv[optind],0),nsems,oflag);
exit(0);
}
使用semop
对信号量集合操作
#include
#include
#include
#include
#include
int main(int argc,char *argv[])
{
int c,i,flag,semid,nops;
struct sembuf *ptr;
flag = 0;
//根据命令行参数设置操作模式
while( ( c = getopt(argc,argv,"nu")) != -1)
{
switch(c)
{
case 'n':
flag |= IPC_NOWAIT; //非阻塞
break;
case 'u':
flag |= SEM_UNDO; //不可恢复
break;
}
}
if(argc - optind < 2)
{
printf("usage: semops [-n] [-u] operation..." );
exit(0);
}
//打开一个已经存在的信号量集合
if((semid = semget(ftok(argv[optind],0),0,0)) == -1)
{
perror("semget() error");
exit(-1);
}
optind++; //指向当前第一个信号量的位置
nops = argc - optind; //信号量个数
ptr = calloc(nops,sizeof(struct sembuf));
for(i=0;i//信号量变换
ptr[i].sem_op = atoi(argv[optind+i]); //设置信号量的值
ptr[i].sem_flg = flag; //设置操作模式
}
//对信号量执行操作
if(semop(semid,ptr,nops) == -1)
{
perror("semop() error");
exit(-1);
}
exit(0);
}
使用semctl
对信号量集合发送命令
#include
#include
#include
#include
#include
//定义信号量操作共用体结构
union semun
{
int val;
struct semid_ds *buf;
unsigned short *array;
};
int main(int argc,char *argv[])
{
int semid,nsems,i;
struct semid_ds seminfo;
unsigned short *ptr;
union semun arg;
if(argc < 2)
{
printf("usage: semsetvalues [values ...]" );
exit(0);
}
//打开已经存在的信号量集合
semid = semget(ftok(argv[1],0),0,0);
arg.buf = &seminfo;
//获取信号量集的相关信息
semctl(semid,0,IPC_STAT,arg);
nsems = arg.buf->sem_nsems; //信号量的个数
if(argc != nsems + 2 )
{
printf("%s semaphores in set,%d values specified",nsems,argc-2);
exit(0);
}
//分配信号量
ptr = calloc(nsems,sizeof(unsigned short));
arg.array = ptr;
//初始化信号量的值
for(i=0;i2]);
//通过arg设置信号量集合
semctl(semid,0,SETALL,arg);
exit(0);
}
虽然systemV只有三个函数,但是设计的结构比较多,感觉用起来不如posix方便吧。
相比POSIX信号量,systemV信号量由一组值组成,并且除了PV操作外,信号量集中的每个成员有三个操作:测试值是否为0、加一个整数、减一个整数。等等
1.UNP卷2
2.linux man page
3.http://www.cnblogs.com/Anker/archive/2013/01/14/2859352.html