1、System V信号量概述
system V信号量增加了另外一级复杂度:
计数信号量集:一个或多个信号量(构成一个集合),其中每个都是计数信号量。每个集合的信号量数存在一个限制。一般在25个数量以上。于是谈及system V信号量时,均指是计数信号量集。而谈及Posix信号量时,均指单个计数信号量。(说实话真搞不懂system V为什么要这么定义信号量,还是Posix信号量好理解多了)
系统中的每个信号量集,内核维护一个相关的信息struct semid_ds结构,如下所示:
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 creatioin or last IPC_SET */
};
struct ipc_perm {
uid_t uid; /* owner's user id */
gid_t gid; /* owner's group id*/
uid_t cuid;/* creator's user id*/
gid_t cgid;/* creatot's group id*/
mode_t mode;/* read-write permissions*/
ulong_t seq; /* slot usage sequence number*/
key_t key; /* IPC key*/
};
sem结构是内核用于维护某个给定信号量的一组值的内部数据结构。一个信号量集的每个成员如下struct结构描述:
struct sem {
ushort_t semval; /* semaphore value,nonnegative */
short sempid; /* PID of last successful semop(), SETVAL, SETALL*/
ushort_t semncnt;/* # awaiting semval > current value */
ushort_t semzcnt;/* # awaiting semval =0 */
};
在struct semid_ds{}中的成员sem_base是指向某个sem结构数组的指针:当前信号量集中的每个信号量对应其中一个数组元素。
由两组值构成的某个信号量集的内核数据结构如下图所示:
2、System V信号量相关函数及其实现的相关简单程序
semget函数创建一个信号量集或访问一个已存在的信号量集。
#include<sys/sem.h>
int semget(key_t key, int nsems, int oflag);
//返回:若成功则为非负标识符,若出错则为-1
nsems参数指定集合中的信号量数。如果不创建一个新的信号量集,而只是访问一个已存在的集合,那就可以把参数指定为0。
oflag值可以SEM_R和SEM_A等,还可以和IPC_CREAT或IPC_CREAT|IPC_EXCL按位或等。
当实际操作为创建一个新的信号量集时,相应的semid_ds结构的以下成员(只有sem_base所指向对象不被初始化)被初始化:
i、sem_perm结构的uid和cuid成员被置为调用进程有效用户ID,gid和cgid成员被置为调用进程的有效组ID。
ii、oflag参数中的读写权限位存入sem_perm。mode。
iii、sem_otime被置为0,sem_ctime则被置为当前时间。
iv、sem_nsems被置为nsems参数的值。
v、与该集合中每个信号量关联的各个sem结构并不初始化。这些结构是在以SET_VAL或SETALL命令调用semctl时初始化。
使用semget打开一个信号量集后,对其中一个或多个信号量的操作使用semop函数来执行,且semop具有原子性操作。
#include<sys/sem.h>
int semop(int semid, struct sembuf *opstr, size_t nops);
//返回:若成功则为0,若出错则为-1
struct sembuf {//此结构不能静态初始化
short sem_num; /* semaphore number: 0, 1, ..., nsems-1 */
short sem_op; /* semaphore operation: <0, 0, >0 */
short sem_flg; /* operation flags: 0, IPC_NOWAIT, SEM_UNDO */
};
nops参数指出opsptr向的sembuf结构数组中元素的数目。sembuf结构数组中的每个元素给目标信号量内某个特定的信号量指定一个操作。该特定的信号量由sem_num指定。
由内核保证传递给semop函数的操作数组(opsptr)被原子地执行。内核要么完成所有指定的操作,要么什么操作都不做。
每个特定的操作是由sem_op的值确定的,它可以是负数、0或正数。
现在基于每个具体指定的sem_op操作的三类可能值——正数、0或负数——来描述semop的操作:
i、如果sem_op是正数,其值就加到semval上。这对应于释放由某个信号控制的资源。
如果指定了SEM_UNDO标志,那就从相应信号量的semadj值(概念性变量,由内核管理)中减掉sem_op的值。
ii、如果sem_op是0,那么调用者希望等待到semval变为0。如果semval已经是0,那就立即返回。
如果semval不为0,相应信号量的semzcnt就加1,调用线程则被阻塞到semval变为0(到那时,相应信号量的semzcnt值再减1)。如果指定了IPC_NOWAIT标志,调用线程就不会被投入睡眠。如果某个被捕获的信号中断了引起睡眠的semop函数,或者相应的信号量被删除了,那么该函数将过早地返回一个错误。
iii、如果sem_op是负数,那么调用者希望等待semval变为大于或等于sem_op的绝对值。这对应于分配资源。
如果semval大于或等于sem_op的绝对值,那就从semval中减掉sem_op的绝对值。如果指定了SEM_UNDO标志,那么sem_op的绝对值就加到相应信号量的semadj值上。
如果semval小于sem_op的绝对值,相应信号量的semncnt值就加1,调用线程则被阻塞到semval变为大于或等于sem_op的绝对值。到那时该线程将被阻塞,还将从semval中减掉sem_op的绝对值,相应信号量的semncnt值将减1。如果指定了SEM_UNDO标志,那sem_op的绝对值将加到相应信号量的semadj值上。如果指定了IPC_NOWAIT标志,调用线程就不被投入睡眠。另外,如果某个被捕获的信号中断了引起睡眠的sem_op函数,或者相应的信号量被删除了,那么该函数将过早地返回一个错误。
综上一句话:System V信号量确实比Posix信号量复杂多了。
semctl函数对一个信号量执行各种控制操作。
#include<sys/sem.h>
int semctl(int semid, int semnum, int cmd, .../* union semun arg */);
//返回:若成功则为非负值,若出错则为-1
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 */
};
第一个参数semid标识其操作待控制的信号量集,第二个参数semnum标识该信号量集内的某个成员(0、1等,直到nsems-1)。而semnum值仅仅用于GETVAL、SETVAL、GETNCNT、GETZCNT和GETPID命令。
System V支持下例cmd值。除非另外声明,否则返回值为0表示成功,返回值为-1表示出错。
GETVAL:把semval的当前值作为函数返回值返回。既然信号量决不会是负数,那么成功的返回值总是非负数。
SETVAL:把semval值设置为arg.val。如果操作成功,那么相应信号量在所有进程中的信号量调整值(semadj)将被置为0。
GETPID:把sempid的当前值作为函数返回值返回。
GETNCNT:把semncnt的当前值作为函数返回值返回。
GETZCNT:把semzcnt的当前值作为函数返回值返回。
GETALL:返回所指定信号量集内每个成员的semval值。这些值通过arg.array指针返回,函数本身的返回值则为0。
SETALL:设置所指定信号量集中每个成员的semval值。这些值是通过arg.array指针指定的。
IPC_RMID:把由semid指定的信号量集从系统中删除掉。
IPC_SET:设置所指定信号量集的semid_ds结构中的以下三个成员:sem_perm.uid、sem_perm.gid和sem_perm.mode,这些值来自由arg.buf参数指向的结构中的相应成员。semid_ds结构中sem_ctime成员也被设置成当前时间。
IPC_STAT:(通过arg.buf参数)返回所指定信号量集当前的semid_ds{}。注意,调用者必须首先分配一个semid_ds结构,并把arg.buf设置成指定向这个结构。
利用以上函数实现的简单函数:
semcreate程序
#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]);
//ftok函数可以理解成filename to key_t,即把一个已存在的路径名和一个整数标识符转换成一个key_t值,即IPC键
semid = Semget(Ftok(argv[optind], 0), nsems, oflag);
exit(0);
}
semrmid程序
#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);
}
semsetvalues程序
#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 ... ]");
/* 4first 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);//获取指定信号集中信号量的数量,即seminfo.sem_nsems
nsems = arg.buf->sem_nsems;
/* 4now get the values from the command line */
if (argc != nsems + 2)
err_quit("%d semaphores in set, %d values specified", nsems, argc-2);
/* 4allocate 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);
}
semgetvalues程序
#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>");
/* 4first 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;
/* 4allocate memory to hold all the values in the set */
ptr = Calloc(nsems, sizeof(unsigned short));
arg.array = ptr;
/* 4fetch 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);
}
semopst程序(对某个信号量集执行一数组的操作)
#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) {
switch (c) {
case 'n':
flag |= IPC_NOWAIT; /* for each operation */
break;
case 'u':
flag |= SEM_UNDO; /* for each operation */
break;
}
}
if (argc - optind < 2) /* argc - optind = #args remaining */
err_quit("usage: semops [ -n ] [ -u ] <pathname> operation ...");
semid = Semget(Ftok(argv[optind], 0), 0, 0);//打开指定信号量集
optind++;
nops = argc - optind;//表示剩余选项的数量
/* 4allocate memory to hold operations, store, and perform */
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;
}
Semop(semid, ptr, nops);
exit(0);
}
小结讲解从Posix信号量到System V信号量的变动非常清晰(注意哈!Posix信号量诞生的比System V信号量晚)
i、System V信号量由一组值构成。当指定应用到某个信号量集的一组信号量操作时,要么所有操作都执行,要么一个操作都不执行(即原子性)。
ii、可应用到一个信号量集的每个成员的操作有三种:测试其值是否为0、往其值加一个整数以及从其值中减掉一个整数(假设结果值仍然为负)。Posix信号量所允许的操作只是将其值加1或减1(假设结果值仍然非负)。
iii、创建一个System V信号量集需要技巧,因为创建该集合并随后初始化其各个值需要两个操作,从而可能导致竞争状态。
iv、System V信号量提供“复旧”特性,该特性保证进程终止时逆转某个信号量操作。
以上知识点来均来自steven先生所著UNP卷二(version2),刚开始学习网络编程,如有不正确之处请大家多多指正。