System V信号量是不属于POSIX标准,它属于SUS(Single UNIX Specification)单一UNIX规范中的扩展定义。它和POSIX信号量一样都提供基本的信号量功能操作。
System V信号量相对于POSIX信号量最大的区别是在信号量的操作复杂度。
在POSIX信号量中说过,根据信号量取值(代表可用资源的数目)的不同,POSIX信号量可以分为:
相对于POSIX信号量,System V信号量增加了复杂度,我们称System V信号量之为:计数信号量集。计数信号量集:至少有一个信号量构成的集合,其中集合中的每个信号量都是计数信号量。对于信号量集中的信号量数目系统内核是存在限制的,由内核参数:SEMMSL决定。
[root@localhost ~]# grep SEMMSL -R /usr/include/* /usr/include/linux/sem.h:#define SEMMSL 250 /* <= 8 000 max num of semaphores per id */ ... ...
对于System V信号量,系统内核还有很多限制,譬如下面:
SEMMNS:系统中信号量的最大数目,等于SEMMNI*SEMMSL SEMOPM:一次semopt()操作的最大信号量数目 SEMMNI。系统内核中信号量的最大数目对于系统中的每个 System V 信号量,即每个信号量集,内核都会维护一个 semid_ds 的信息结构,如下是Linux 2.6.18下的定义 :
/* Data structure describing a set of semaphores. */ struct semid_ds { struct ipc_perm sem_perm; // IPC的操作权限,每个IPC结构都有 __time_t sem_otime; //上一次执行semop() 的时间 unsigned long int __unused1; //预留使用 __time_t sem_ctime; //上一次通过semctl()进行的修改时间 unsigned long int __unused2; unsigned long int sem_nsems; //信号量集中的信号量数目 unsigned long int __unused3; unsigned long int __unused4; };
在《APUE》P423提到,信号量集中每一个信号量由一个无名结构表示。但是我始终无法找到该结构体的定义,书中提到该结构的定义如下:
struct { unsigned short semval; //信号量的值 pid_t sempid; //最后一次semctl操作的进程id unsigned short semncnt; //等待semval变为大于当前值的线程数 unsigned short semzcnt; //等待semval变为0的线程数 };
#include <sys/sem.h> int semget(key_t key, int nsems, int semflg); //若成功就返回飞非负的标识符,否则返回-1
semget用于创建或打开一个已存在的信号量。
key:用于生成唯一信号量的key,主要的目的是使不同进程在同一该IPC汇合。key可以是事先不同的进程约定好的一个值,也可以不同进程通过相同的路径名和项目ID,调用ftok()函数,生成一个键。
nsems:表示信号量集中信号量的个数,如果创建一个信号量集,nsems必须是一个非0正整数,如果打开一个指定的信号量集,nsems可以指定为0。
semflag:IPC_CREAT, IPC_EXCL,以及IPC的指定权限位。如果为IPC_CREAT|IPC_EXCL,当该信号量集以及存在会返回错误。errno为EEXIST。
semget的返回值是被称为信号量标识符的整数,semop和semctl函数将通过该标识符对信号量集进行操作。
这里需要知道是调用semget()创建一个新的信号量集并没有对之初始化,需要调用后面要讲的semctl()函数进行初始化。这样System V信号量的创建和初始化就不是一个原子操作,这是一个很大的缺陷。会出现使用未初始化信号量集的问题。
#include <sys/sem.h> int semctl(int semid, int semnum, int cmd, .../* union semun arg*/); //若失败返回-1,并设置errno,成功具体返回值如下
semctl函数主要是对信号量集的一系列控制操作,根据操作命令cmd的不同,执行不同的操作,依赖于所请求的命令,第四个参数是可选的,
semid:System V信号量的标识符;
semnum:表示信号量集中的第semnum个信号量。它的取值范围:0~nsems-1。
cmd:操作命令;
arg:如果使用该参数,该参数的类型为 union semun,它是多个特定命令的联合。按照SUS明确规定,这个结构必须有用户自己定义,在Linux 2.6.18的系统头文件中也没有该结构的定义,但在<bits/sem.h>中有一段对该结构的定义的建议,不过是注释掉的。
include <bits/sem.h> /* The user should define a union like the following to use it for arguments for `semctl'. 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) }; */
semctl中cmd命令有10种,如下:
对于GETALL以外的所有GET命令,semctl都返回相应的值,其他命令的返回值为0;
#include <sys/sem.h> int semop(int semid, struct sembuf *sops, unsigned nsops); int semtimedop(int semid, struct sembuf *sops, unsigned nsops, struct timespec *timeout); //成功返回0,出错返回-1
semop函数主要是在已打开的信号量集上,对其中的一个或多个信号量的值进行操作。
semid:System V信号量的标识符,用来标识一个信号量集。
sops:是指向一个struct sembuf结构体数组的指针,该数组是一个信号量操作数组。
nsops:sops所指向sembuf结构体数组中元素的个数。
sembuf结构体的定义如下:
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 */ };
sem_num标识信号量集中的第几个信号量,0表示第1个,1表示第2个,nsems - 1表示最后一个。
sem_op标识对信号量的所进行的操作类型。对信号量的操作有三种类型:
sem_flag:信号量操作的属性标志,如果为0,表示正常操作,如果为IPC_WAIT,使对信号量的操作时非阻塞的。即指定了该标志,调用线程在信号量的值不满足条件的情况下不会被阻塞,而是直接返回-1,并将errno设置为EAGAIN。如果为SEM_UNDO,那么将维护进程对信号量的调整值,以便进程结束时恢复信号量的状态。
下面解释一下与单个信号量相关的几个值:
semval:信号量的当前值,在文章开头信号量的结构中已提到。
semncnt:等待semval变为大于当前值的线程数。在文章开头信号量的结构中已提到。
semzcnt:等待semval变为0的线程数。在文章开头信号量的结构中已提到。
semadj:指定信号量针对某个特定进程的调整值。只有sembuf结构的sem_flag指定为SEM_UNDO后,semadj才会随着sem_op而更新。讲简单一点:对某个进程,在指定SEM_UNDO后,对信号量semval值的修改都会反应到semadj上,当该进程终止的时候,内核会根据semadj的值,重新恢复信号量之前的值。
这里我们可以看到System V信号量可以对信号量集中的某一信号量进行加减sem_op,该值不仅仅为1,而在POSIX信号量中只能对信号量进行加减1的操作。
和POSIX的有名信号量一样,父进程中打开的信号量在子进程中仍然是保持着打开状态的。
(2)销毁
下面代码进行了测试:
#include <iostream> #include <fstream> #include <cstdlib> #include <cstring> #include <unistd.h> #include <fcntl.h> #include <errno.h> #include <sys/ipc.h> #include <sys/sem.h> using namespace std; #define SEM_PATHNAME "/tmp/sem_name" union semun { int val; struct semid_ds *buf; unsigned short int *array; struct seminfo *__buf; }; int CreateKey(const char * pathName) { int fd = open( pathName, O_CREAT , 0666); if (fd < 0) { cout<<"open file error..."<<strerror(errno)<<endl; return -1; } close(fd); return ftok(pathName, 0); } int main() { int semId; semun arg; //解决信号量的创建和初始化不是原子操作的一种方案 if ((semId = semget(CreateKey(SEM_PATHNAME), 1, IPC_CREAT | IPC_EXCL | 0666)) >= 0) { arg.val = 4; if (semctl(semId, 0, SETVAL, arg) < 0) { cout<<"semctl error "<<strerror(errno)<<endl; return -1; } } else if (errno == EEXIST) { semId = semget(CreateKey(SEM_PATHNAME), 1, 0666); } else { cout<<"semget error "<<strerror(errno)<<endl; return -1; } cout<<"parent:sem value:"<<semctl(semId, 0, GETVAL)<<endl; if (fork() == 0) { struct sembuf buffer; buffer.sem_num = 0; buffer.sem_op = -2; buffer.sem_flg = 0; semop(semId, &buffer, 1); cout<<"child:sem value:"<<semctl(semId, 0, GETVAL)<<endl; exit(0); } sleep(1); cout<<"parent:sem value:"<<semctl(semId, 0, GETVAL)<<endl; }执行结果如下:
parent:sem value:4 child:sem value:2 parent:sem value:2
下面代码测试信号量集的删除:
int main() { int semId; semun arg; //解决信号量的创建和初始化不是原子操作的一种方案 if ((semId = semget(CreateKey(SEM_PATHNAME), 1, IPC_CREAT | IPC_EXCL | 0666)) >= 0) { arg.val = 4; if (semctl(semId, 0, SETVAL, arg) < 0) { cout<<"semctl error "<<strerror(errno)<<endl; return -1; } } else if (errno == EEXIST) { semId = semget(CreateKey(SEM_PATHNAME), 1, 0666); } else { cout<<"semget error "<<strerror(errno)<<endl; return -1; } cout<<"parent:sem value:"<<semctl(semId, 0, GETVAL)<<endl; if (fork() == 0) { semctl(semId, 0, IPC_RMID); exit(0); } sleep(1); if (semctl(semId, 0, GETVAL) == -1) { cout<<"semctl error:"<<strerror(errno)<<endl; return -1; } }
测试结果如下:
parent:sem value:4 semctl error:Invalid argument
下面是测试代码,信号量集中只有一个信号量,该信号量的值被设成6,每次semop获取两个信号量才能对共享资源(这里用文件代替)进行访问;
#include <iostream> #include <fstream> #include <cstdlib> #include <cstring> #include <unistd.h> #include <fcntl.h> #include <errno.h> #include <sys/ipc.h> #include <sys/sem.h> using namespace std; #define SEM_PATHNAME "/tmp/sem_name" union semun { int val; struct semid_ds *buf; unsigned short int *array; struct seminfo *__buf; }; int CreateKey(const char * pathName) { int fd = open( pathName, O_CREAT , 0666); if (fd < 0) { cout<<"open file error..."<<strerror(errno)<<endl; return -1; } close(fd); return ftok(pathName, 0); } void semTest(int flag, int semId) { struct sembuf buffer; buffer.sem_num = 0; buffer.sem_op = -2; buffer.sem_flg = 0; semop(semId, &buffer, 1); ofstream fileStream("./test.txt", ios_base::app); for (int i = 0; i < 5; ++i) { sleep(1); fileStream<<flag; fileStream<<' '<<flush; } buffer.sem_op = 2; semop(semId, &buffer, 1); } int main() { int semId; semun arg; //解决信号量的创建和初始化不是原子操作的一种方案 if ((semId = semget(CreateKey(SEM_PATHNAME), 1, IPC_CREAT | IPC_EXCL | 0666)) >= 0) { arg.val = 4; if (semctl(semId, 0, SETVAL, arg) < 0) { cout<<"semctl error "<<strerror(errno)<<endl; return -1; } } else if (errno == EEXIST) { semId = semget(CreateKey(SEM_PATHNAME), 1, 0666); } else { cout<<"semget error "<<strerror(errno)<<endl; return -1; } for (int i = 0; i < 3; ++i) { if (fork() == 0) { semTest(i + 1, semId); sleep(1); exit(0); } } }执行结果如下:
./test.txt 1 2 1 2 1 2 2 1 1 2 3 3 3 3 3
Jul 4, 2013 AM 00:26 @dorm
有想法就要努力的去尝试---skywalker