System V的信号量与Posix的信号量略有差异
在原有的二值信号量与计数信号量基础上, System V信号量多了一种计数 信号量集:
一个或多个信号量(构成的集合), 其中每个都是计数信号量. 每个集合的信号量数存在一个限制.
所以, 一般我们谈论Posix信号量, 指的是单个信号量; System V信号量则表示多个信号量集
操作函数:
关于信号量的初始化, 这里必须注意的是:
在函数的声明中, 并没有让我们指定信号量的初始值. 所以 初始值是未定义的
因此, 我们就认为它并不初始化各个信号量. 若要设置信号量的值, 必须通过其他函数. 所以创建一个信号量与初始化它就需要两个函数来完成
这样一来, 我们只能如下解决:
在调用semget时指定IPC_CREAT | IPC_EXCL标志, 成功返回表明该进程是创建者进程, 随后调用函数初始化; semget返回失败EEXIST就勉强承认已被初始化, 于是再次调用semget, 只是这次不再指定IPC_CREAT和IPC_EXCL标志了
但是, 这就出现这竞争状态. 若是在创建者进程成功创建信号量之后,在调用初始化函数之前, 另一个进程突然也调用semget, 那么另一个进程得到的信号量仍旧是未初始化的
不过有办法绕过这个问题, 在semget创建一个新的信号量时, semid_ds的sem_otime成员(该成员用于指示上一次操作的时间)被初始化为0的, 只有在semop成功调用时才被设置为新的值
因此, 另一个进程在调用第二次semget成功返回后, 必须用IPC_STAT命令调用semctl获取sem_otime的值, 等待其变为非0值, 就可已断定信号量已被初始化过了
所以, 这就要求信号量的创建者必须初始化它的值
最后一个参数指定sembuf数组中元素的个数
每个信号量特定的操作是由sem_op来确定的, sem_op可以是负数, 正数, 0
第二个参数指定信号量集中的某个成员(0, 1, ...)
第三个参数可选:
操作结果:
最后放上一个书上的 文件锁例子:
Posix信号量与System V信号量之间的一些区别:
System V信号量由一组值构成, 当指定应用到某个信号量集的一组信号量操作时, 要么所有的操作都执行, 要么一个都不执行
可以应用到一个System V信号量集的每个成员的操作有3种 : 测试其值是否为0, 往其值加一个整数或是剪掉一个整数(值非负). 然而Posix信号量的操作只能是加一或是减一
创建一个信号量集需要技巧, 因为创建集合后初始化其各个值需要两个操作, 可能导致竞争状态
System V信号量提供"复旧"特性, 此特性保证在进程终止时逆转某个信号量的操作
在原有的二值信号量与计数信号量基础上, System V信号量多了一种计数 信号量集:
一个或多个信号量(构成的集合), 其中每个都是计数信号量. 每个集合的信号量数存在一个限制.
所以, 一般我们谈论Posix信号量, 指的是单个信号量; System V信号量则表示多个信号量集
操作函数:
int semget(key_t key, int nsems, int semflg);
第二个参数指明集合中的信号量的数量, 如果是访问已存在的信号量, 那么值填为0. 在创建完一个信号量集后, 就不能再修改其中的信号量数量了
关于信号量的初始化, 这里必须注意的是:
在函数的声明中, 并没有让我们指定信号量的初始值. 所以 初始值是未定义的
因此, 我们就认为它并不初始化各个信号量. 若要设置信号量的值, 必须通过其他函数. 所以创建一个信号量与初始化它就需要两个函数来完成
这样一来, 我们只能如下解决:
在调用semget时指定IPC_CREAT | IPC_EXCL标志, 成功返回表明该进程是创建者进程, 随后调用函数初始化; semget返回失败EEXIST就勉强承认已被初始化, 于是再次调用semget, 只是这次不再指定IPC_CREAT和IPC_EXCL标志了
但是, 这就出现这竞争状态. 若是在创建者进程成功创建信号量之后,在调用初始化函数之前, 另一个进程突然也调用semget, 那么另一个进程得到的信号量仍旧是未初始化的
不过有办法绕过这个问题, 在semget创建一个新的信号量时, semid_ds的sem_otime成员(该成员用于指示上一次操作的时间)被初始化为0的, 只有在semop成功调用时才被设置为新的值
因此, 另一个进程在调用第二次semget成功返回后, 必须用IPC_STAT命令调用semctl获取sem_otime的值, 等待其变为非0值, 就可已断定信号量已被初始化过了
所以, 这就要求信号量的创建者必须初始化它的值
int semop(int semid, struct sembuf *sops, unsigned nsops);
此函数用于操作信号量集中的每个信号量
最后一个参数指定sembuf数组中元素的个数
struct sembuf
{
unsigned short int sem_num; /* semaphore number */
short int sem_op; /* semaphore operation */
short int sem_flg; /* operation flag */
};
sem_num为sembuf数组中元素的下标, 0代表第一个元素, 1代表第二个...直到第nsems-1个(这里的nsems是semget的第二个参数)
每个信号量特定的操作是由sem_op来确定的, sem_op可以是负数, 正数, 0
如果sem_op是正数, 其值就加到semval上, 对应与释放由某个信号量控制的资源
如果sem_op为0, 那么调用着希望等到semval为0. 如果semval就是0, 那么立即返回
如果为负数, 那么调用者希望等待到semval变为大于或等于sem_op的绝对值 , 对应与分配走资源
int semctl(int semid, int semnum, int cmd, ...);
对一个信号量进行各种操作
第二个参数指定信号量集中的某个成员(0, 1, ...)
第三个参数可选:
IPC_STAT :获取此信号量集合的 semid_ds 结构,存放在第四个参数 arg 的 buf 中;
IPC_SET :通过 arg.buf 来设定信号量集相关联的 semid_ds 中信号量集合权限为 sem_perm 中的 uid , gid , mode 。
IPC_RMID :从系统中删除该信号量集合。这种删除立即发生,仍在使用该信号量集的其他进程,在下次对该信号量集进行操作的时候,会发生错误并返回 EIDRM 。这和 POSIX 信号量是不一样的。 POSIX 信号量 sem_unlink 只是会立即删除信号量的在文件系统中的文件,而信号量的析构是在最后一个 sem_close 发生是进行的。
GETVAL :返回第 semnum 个信号量的值;
SETVAL :设置第 semnum 个信号量的值,该值由第四个参数 arg 中的 val 指定;
GETPID :返回第 semnum 个信号量的 sempid ,最后一个操作的 pid ;
GETNCNT :返回第 semnum 个信号量的 semncnt 。等待 semval 变为大于当前值的线程数;
GETZCNT :返回第 semnum 个信号量的 semzcnt 。等待 semval 变为 0 的线程数。
GETALL :去信号量集合中所有信号量的值,将结果存放到 arg 中的 array 所指向的数组。
SETALL :按 arg.array 所指向的数组中的值,设置集合中所有信号量的值。
第四个参数:
struct sembuf
{
unsigned short int sem_num; /* 信号量的序号从0~nsems-1 */
short int sem_op; /* 对信号量的操作,>0, 0, <0 */
short int sem_flg; /* 操作标识:0, IPC_WAIT, SEM_UNDO */
};
//创建
#define FILE_MODE (S_IRUSR | S_IWUSR | S_IROTH)
int main(int ac, char *av[])
{
int flag, sems, sid;
if(ac < 3){
fprintf(stderr, "Usage : semc <pathname> count\n");
exit(1);
}
sems = atoi(av[2]);
flag = FILE_MODE | IPC_CREAT | IPC_EXCL;
sid = semget(ftok(av[1], 0), sems, flag);
if(sid < 0){
perror("semget error");
exit(-1);
}
return 0;
}
//删除
int main(int ac, char *av[])
{
if(ac == 1){
fprintf(stderr, "Usage <pathname>\n");
exit(1);
}
int semid = semget(ftok(av[1], 0), 0, 0);
semctl(semid, 0, IPC_RMID);
return 0;
}
//设置信号量的值
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) */
};
int main(int ac, char *av[])
{
int semid;
struct semid_ds seminfo;
union semun arg;
unsigned short *ptr;
if(ac < 2){
fprintf(stderr, "Usage : setvalue <pathname> [values]");
exit(-1);
}
semid = semget(ftok(av[1], 0), 0, 0);
if(semid < 0){
perror("semget error:");
exit(-1);
}
arg.buf = &seminfo;
if(semctl(semid, 0, IPC_STAT, arg) < 0){
perror("semctl error");
exit(-1);
}
unsigned short nsems = seminfo.sem_nsems;
if(ac-2 != nsems){
fprintf(stderr, "not enough values set to ...\n");
exit(-1);
}
ptr = calloc(nsems, sizeof(unsigned short));
int i=0;
for(; i<nsems; i++)
ptr[i] = atoi(av[i+2]);
arg.array = ptr;
semctl(semid, 0, SETALL, arg);
return 0;
}
//获取信号量的值
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) */
};
//大体步骤就是从semid_ds中取出信号量集中信号量的数目, 之后分配足够的数组空间去接收这些值
int main(int ac, char *av[])
{
int semid;
struct semid_ds seminfo;
union semun arg;
unsigned short *ptr;
if(ac < 2){
fprintf(stderr, "Usage : setvalue <pathname> \n");
exit(-1);
}
semid = semget(ftok(av[1], 0), 0, 0);
if(semid < 0){
perror("semget error:");
exit(-1);
}
arg.buf = &seminfo;
if(semctl(semid, 0, IPC_STAT, arg) < 0){
perror("semctl error");
exit(-1);
}
unsigned short nsems = seminfo.sem_nsems;
ptr = calloc(nsems, sizeof(unsigned short));
arg.array = ptr;
semctl(semid, 0, GETALL, arg);
int i;
for(i=0; i<nsems; i++)
printf("semval[%d] = %d\n", i, arg.array[i]);
return 0;
}
//对信号量的操作
int main(int ac, char *av[])
{
int c, ret;
int flag = 0;
while((c=getopt(ac, av, "nu")) != -1){
switch(c){
case 'n':
flag |= IPC_NOWAIT;
break;
case 'u':
flag |= SEM_UNDO;
break;
}
}
if(ac - optind < 2){
fprintf(stderr, "Usage : semops [-n] [-u] <pathname> option\n");
exit(-1);
}
int semid = semget(ftok(av[optind], 0), 0, 0);
if(semid < 0){
perror("semget error:");
exit(-1);
}
optind ++;
int nops = ac - optind;
struct sembuf *ptr = calloc(nops, sizeof(struct sembuf));
int i;
for(i=0; i<nops; i++)
{
ptr[i].sem_num = i;
ptr[i].sem_op = atoi(av[optind++]);
ptr[i].sem_flg = flag;
}
ret = semop(semid, ptr, nops);
if(ret < 0){
perror("semop error");
exit(-1);
}
return 0;
}
操作结果:
$ ./semr /path/to/file 3
$ ./setvalues /home/Ben/workspace/UNP2/Chapter11/used_for_ftok 1 2 3
$ ./getvalues /path/to/file
semval[0] = 1
semval[1] = 2
semval[2] = 3
$ ./semop /path/to/file -- -1 -2 -3
$ ./getvalues /path/to/file
semval[0] = 0
semval[1] = 0
semval[2] = 0
$ ./semop /path/to/file -- -1 -2 -3
semop error: Resource temporarily unavailable
最后放上一个书上的 文件锁例子:
#define LOCK_PATH /xxx/xxx
#define MAX_TRIES 10
int semid, initflag;
struct sembuf postop, waitop;
void my_lock(int fd)
{
int oflag, i;
union smeun arg;
struct semid_ds seminfo;
if(initflag == 0){
oflag = IPC_CREAT | IPC_EXCL | FILE_MODE;
if((semid=semget(ftok(LOCK_PATH, 0), 1, oflag)) >= 0){
arg.val = 1;
semctl(semid, 0, SETVAL, arg);
}
else if(errno == EEXIST){
semid = semget(ftok(LOCK_PATH, 0), 1, FILE_MODE);
arg.buf = &seminfo;
for(i=0; i<MAX_TRIES; i++) //之前提到, 如果信号量没有被初始化,那么它的sem_otime变量为0. 我们要等到被初始化后才能进行操作, 但这里我们限制了尝试等待的次数
{
semctl(semid, 0, IPC_STAT, arg);
if(arg.buf->sem_otime != 0)
goto init;
sleep(1);
}
fprintf(stderr,"not be initialized\n");
}
else{
perror("semget error");
exit(-1);
}
init:
initflag = 1;
postop.sem_num = 0; //只有一个信号量, 所以PV操作的都是下标为0的信号量
postop.sem_op = 1;
postop.sem_flg = SEM_UNDO; //设置这个变量, 如果进程在持锁期间终止了, 那么内核会释放掉
waitop.sem_num = 0;
waitop.sem_op = -1;
waitop.sem_flg = SEM_UNDO;
}
semop(semid, &waitop, 1);
}
void my_unlock(int fd)
{
semop(semid, &postop, 1);
}
//不过, 书上,网上提到关于此信号量的一些讨论:
//对于SEM_UNDO来说, 内核记录信号是根据进程相关的,一个进程在lock的时候设置一个UNDO, 那么对应该进程的UNDO计数就多一个, unlock的时候设置一个UNDO, 那么计数就减少一个. 对于临界区互斥的应用来说, lock和unlock是在一个进程中完成的, UNDO确实可以发挥作用.
//然而, 如果是一个进程lock, 另一个进程unlock, 那么UNDO就不起作用了, 导致UNDO对单一进程而言, 只朝着一个方向发展, 最后必定超过内核限定的值, 最后出现ERANGE错误
Posix信号量与System V信号量之间的一些区别:
System V信号量由一组值构成, 当指定应用到某个信号量集的一组信号量操作时, 要么所有的操作都执行, 要么一个都不执行
可以应用到一个System V信号量集的每个成员的操作有3种 : 测试其值是否为0, 往其值加一个整数或是剪掉一个整数(值非负). 然而Posix信号量的操作只能是加一或是减一
创建一个信号量集需要技巧, 因为创建集合后初始化其各个值需要两个操作, 可能导致竞争状态
System V信号量提供"复旧"特性, 此特性保证在进程终止时逆转某个信号量的操作