SystemV信号量是不属于POSIX标准,它属于SUS(SingleUNIXSpecification)单一规范中的扩展定义。它和POSIX信号量一样都提供基本的信号量功能操作。
SystemV信号量相对于POSIX信号量最大的区别是在信号量的操作复杂度。
在POSIX信号量中说过,根据信号量取值(代表可用资源的数目)的不同,POSIX信号量可以分为:
相对于POSIX信号量,SystemV信号量增加了复杂度,我们称SystemV信号量之为:计数信号量集。计数信号量集:至少有一个信号量构成的集合,其中集合中的每个信号量都是计数信号量。对于信号量集中的信号量数目系统内核是存在限制的,由内核参数:SEMMSL决定。
[root@localhost ~]# grep SEMMSL -R /usr/include/*
/usr/include/linux/sem.h:#define SEMMSL 250 /* <= 8 000 max num of semaphores per id */
... ...
对于SystemV信号量,系统内核还有很多限制,譬如下面:
SEMMNS:系统中信号量的最大数目,等于SEMMNI*SEMMSL
SEMOPM:一次semopt()操作的最大信号量数目
SEMMNI。系统内核中信号量的最大数目
对于系统中的每个
SystemV
信号量,即每个信号量集,内核都会维护一个
semid_ds
的信息结构,如下是Linux2.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 sem {
unsigned short semval; //信号量的值
pid_t sempid; //最后一次semctl操作的进程id
unsigned short semncnt; //等待semval变为大于当前值的线程数
unsigned short semzcnt; //等待semval变为0的线程数
};
#include
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()函数进行初始化。这样SystemV信号量的创建和初始化就不是一个原子操作,这是一个很大的缺陷。会出现使用未初始化信号量集的问题。
#include
int semctl(int semid, int semnum, int cmd, .../* union semun arg*/);
//若失败返回-1,并设置errno,成功具体返回值如下
semctl函数主要是对信号量集的一系列控制操作,根据操作命令cmd的不同,执行不同的操作,依赖于所请求的命令,第四个参数是可选的,
semid:SystemV信号量的标识符;
semnum:表示信号量集中的第semnum个信号量。它的取值范围:0~nsems-1。
cmd:操作命令;
arg:如果使用该参数,该参数的类型为unionsemun,它是多个特定命令的联合。按照SUS明确规定,这个结构必须有用户自己定义,在Linux2.6.18的系统头文件中也没有该结构的定义,但在
include
/* 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
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:SystemV信号量的标识符,用来标识一个信号量集。
sops:是指向一个structsembuf结构体数组的指针,该数组是一个信号量操作数组。
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的值,重新恢复信号量之前的值。
这里我们可以看到SystemV信号量可以对信号量集中的某一信号量进行加减sem_op,该值不仅仅为1,而在POSIX信号量中只能对信号量进行加减1的操作。
和POSIX的有名信号量一样,父进程中打开的信号量在子进程中仍然是保持着打开状态的。
(2)销毁
下面代码进行了测试:
#include
#include
#include
#include
#include
#include
#include
#include
#include
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..."<= 0)
{
arg.val = 4;
if (semctl(semId, 0, SETVAL, arg) < 0)
{
cout<<"semctl error "<
执行结果如下:
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 "<
测试结果如下:
parent:sem value:4
semctl error:Invalid argument
下面是测试代码,信号量集中只有一个信号量,该信号量的值被设成6,每次semop获取两个信号量才能对共享资源(这里用文件代替)进行访问;
#include
#include
#include
#include
#include
#include
#include
#include
#include
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..."<= 0)
{
arg.val = 4;
if (semctl(semId, 0, SETVAL, arg) < 0)
{
cout<<"semctl error "<
执行结果如下:
./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