1、概述
我们在第10章中讲述信号量的概念时,现在讨论:
- 二值信号量(binary semaphore):其值或为0或为1的信号量。这与互斥锁类似,若资源被锁住则信号量值为0,若资源可用则信号量值为1。
接着把这种信号量扩展为
- 计数信号量(counting semaphore):其值在0和某个限制值(对于Posix信号量,该值必须至少为32767)之间的信号量。
这两种类型的信号量中,等待(wait)操作都等待信号量的值变为大于0,然后将它减1。挂出(post)操作则只是将信号量的值加1,从而唤醒正在等待该信号量值变为大于0的任意线程。
System V信号量通过定义如下概念给信号量增加了另外一级复杂度。
- 计数信号量集(set of counting semaphores):一个或多个信号量(构成一个集合),其中每个都是计数信号量。每个集合的信号量数存在一个限制,一般在25个的数量级上
对于系统中的每个信号量集,内核维护一个如下的信息结构,它定义在<sys/sem.h>头文件中。
struct semid_ds {
struct ipc_perm sem_perm; /* operation permission struct */
struct sem *sem_base; /* ptr to array of semaphores in set */
ushort sem_nsems; /* # of semaphores in set */
time_t sem_otime; /* time of last semop() */
time_t sem_ctime; /* time of creation or last IPC_SET */
};
sem结构是内核用于维护某个给定信号量的一组值的内部数据结构。一个信号量集的每个成员由如下这个结构描述:
struct sem (
ushort_t semval; /* semaphore value, nonnegative */
short sempid; /* PID of last successful semop(), SEIVAL, SETALL */
ushort_t semncnt; /* awaiting semval > current value */
ushort_t semzcnt; /* # awaiting semval = 0 */
};
2、semget 函数
semget 函数创建一个信号量集或访问一个已存在的信号量集。
#include <sys/sem.h>
int semget(key_t key, int nsems, int oflag);
// 返回:若成功则为非负标识符,若出错则为-1
nsems参数指定集合中的信号量数。如果我们不创建一个新的信号量集,而只是访问一个已存在的集合,那就可以把该参数指定为0。一旦创建完一个信号量集,我们就不能改变其中的信号量数。
3、semop函数
使用semget打开一个信号量集后,对其中一个或多个信号量的操作就使用semop函数来执行。
#include <sys/sem.h>
int semop(int semid, struct sembuf *opsptr, size_t nops);
// 返回:若成功则为0,若出错则为-1
其中opsptr指向一个如下结构的数组:
struct sembuf {
short sem_num; /* semaphore number: o, 1. .... nsems-1 */
short sem_op; /* semaphore operation: <0, 0, >0 */
short sem_flg; /* operation flags: 0, IPC_NOWAIT, SEMUNDO */
};
nops参数指出由opsptr指向的sembuf结构数组中元素的数目。该数组中的每个元素给目标信号量集内某个特定的信号量指定一个操作。这个特定的信号量由sen_num指定, 0代表第一个元素, 1代表第二个元素,依次类推,直到nsems-1,其中nsems是目标信号量集内成员信号量的数目(也就是创建该集合时传递给semget的第二个参数)。
4、sectl 函数
semctl 函数对一个信号量执行各种控制操作。
#include <sys/sem.h>
int semctl(int semid, int semnm, int cmd, .. /* union semun arg */ );
// 返回:若成功则为非负值(见正文),若出错!
第一个参数semid标识其操作待控制的信号量集,第二个参数semnum标识该信号量集内的某个成员(0,1等等,直到nsems-1), semnum值仅仅用于GETVAL. SETVAL GETINCNT、GETZCNT和GETPID命令。
第四个参数是可选的,取决于第三个参数cmd (参见下面给出的联合中的注释)。
union semun {
int val; /* used for SETVAL only */
struct semid_ds *buf; /* used for IPC_SET and IPC_STAT */
ushort *array; /* used for GETALL and SETALL */
};
5、实例
5.1 semcreate 程序,创建一个System V信号量集
// semcreate.c
#include "unpipc.h"
int main(int argc, char **argv)
{
int c, oflag, semid, nsems;
oflag = SVSEM_MODE | IPC_CREAT;
while ( (c = Getopt(argc, argv, "e")) != -1)
{
switch (c)
{
case 'e':
oflag |= IPC_EXCL;
break;
}
}
if (optind != argc - 2)
err_quit("usage: semcreate [ -e ] <pathname> <nsems>");
nsems = atoi(argv[optind + 1]);
printf("main optind:%d,argv[optind]:%s\n",optind,argv[optind]);
semid = Semget(Ftok(argv[optind], 0), nsems, oflag);
exit(0);
}
5.2 semrmid 程序,从系统中删除一个信号集
// semrmid.c
#include "unpipc.h"
int main(int argc, char **argv)
{
int semid;
if (argc != 2)
err_quit("usage: semrmid <pathname>");
semid = Semget(Ftok(argv[1], 0), 0, 0);
Semctl(semid, 0, IPC_RMID);
exit(0);
}
5.3 semsetvalues 程序,设置某个信号量集中的所有值
// semsetvalue.c
#include "unpipc.h"
int main(int argc, char **argv)
{
int semid, nsems, i;
struct semid_ds seminfo;
unsigned short *ptr;
union semun arg;
if (argc < 2)
err_quit("usage: semsetvalues <pathname> [ values ... ]");
/* first get the number of semaphores in the set */
semid = Semget(Ftok(argv[1], 0), 0, 0);
arg.buf = &seminfo;
Semctl(semid, 0, IPC_STAT, arg);
nsems = arg.buf->sem_nsems;
/* now get the values from the command line */
if (argc != nsems + 2)
err_quit("%d semaphores in set, %d values specified", nsems, argc-2);
/* allocate memory to hold all the values in the set, and store */
ptr = Calloc(nsems, sizeof(unsigned short));
arg.array = ptr;
for (i = 0; i < nsems; i++)
ptr[i] = atoi(argv[i + 2]);
Semctl(semid, 0, SETALL, arg);
exit(0);
}
5.4 semgetvalue 程序,获取并输出某个信号量集中的所有值
// semgetvalue.c
#include "unpipc.h"
int main(int argc, char **argv)
{
int semid, nsems, i;
struct semid_ds seminfo;
unsigned short *ptr;
union semun arg;
if (argc != 2)
err_quit("usage: semgetvalues <pathname>");
/* first get the number of semaphores in the set */
semid = Semget(Ftok(argv[1], 0), 0, 0);
arg.buf = &seminfo;
Semctl(semid, 0, IPC_STAT, arg);
nsems = arg.buf->sem_nsems;
/* allocate memory to hold all the values in the set */
ptr = Calloc(nsems, sizeof(unsigned short));
arg.array = ptr;
/* fetch the values and print */
Semctl(semid, 0, GETALL, arg);
for (i = 0; i < nsems; i++)
printf("semval[%d] = %d\n", i, ptr[i]);
exit(0);
}
5.5 semops 程序,对某个信号集执行一数组的操作
// semops.c
#include "unpipc.h"
int main(int argc, char **argv)
{
int c, i, flag, semid, nops;
struct sembuf *ptr;
flag = 0;
while ( (c = Getopt(argc, argv, "nu")) != -1)
{
printf("c:%d\n", c);
switch (c)
{
case 'n':
flag |= IPC_NOWAIT; /* for each operation */
break;
case 'u':
flag |= SEM_UNDO; /* for each operation */
break;
}
}
if (argc - optind < 2)
err_quit("usage: semops [ -n ] [ -u ] <pathname> operation ...");
semid = Semget(Ftok(argv[optind], 0), 0, 0);
optind++;
nops = argc - optind;
ptr = Calloc(nops, sizeof(struct sembuf));
for (i = 0; i < nops; i++)
{
ptr[i].sem_num = i;
ptr[i].sem_op = atoi(argv[optind + i]); /* <0, 0, or >0 */
ptr[i].sem_flg = flag;
printf("11 sem_num:%d, sem_op:%d, sem_flg:%d\n",ptr[i].sem_num,ptr[i].sem_op,ptr[i].sem_flg);
}
Semop(semid, ptr, nops);
exit(0);
}
5.6 执行结果
6、小结
从Posix信号量到System V信号量发生了如下变动。
(1) System V信号量由一组值构成。当指定应用到某个信号量集的一组信号量操作时,要么所有操作都执行,要么一个操作都不执行。
(2)可应用到一个信号量集的每个成员的操作有三种:测试其值是否为0、往其值加一个整数以及从其值中减掉一个整数(假设结果值仍然非负),Posix信号量所允许的操作只是将其值加1或减1 (假设结果值仍然非负)。
(3)创建一个System V信号量集需要技巧,因为创建该集合并随后初始化其各个值需要两个操作,从而可能导致竞争状态。
(4) System V信号量提供“复旧”特性,该特性保证在进程终止时逆转某个信号量操作。