进程间通信笔记(7)—SystemV信号量

1.概述

SystemV信号量并不如Posix信号量那样“好用”,但相比之下它的年代更加久远,但是SystemV使用的却更加广泛(尤其是在老系统中)。在学习Posix信号量的时候,已经大概清楚了二值信号量计数信号量是什么东西。在接触SystemV信号量之后,这里有一个新的概念叫做:计数信号量集。其实就是把信号量放入数组中,不过都用一些特别的结构封装。

2. systemV信号量编程

函数接口就比较少了,书上介绍了三个:semgetsemctlsemop

2.1 semget函数

#include 
#include 
#include 
int semget(key_t key, int nsems, int semflg);

第一个参数key通过ftok根据特定path获取
第二个参数表示信号量集中信号量数(可以理解为数组的大小)
第三个参数标志位比如:senflg=O_CREAT|O_EXCL|0644

systemV信号随内核持续的,semget调用成功后,在内核会维护一个信息结构(可以理解为信号量集的表头,或者链表的头节点),里面包含了一些信息:

 struct semid_ds {
    struct ipc_perm sem_perm;  //例如0644,0600,也有一些宏特别指定,不过还是数字好记
    time_t          sem_otime; 
    time_t          sem_ctime; 
    unsigned long   sem_nsems; 

    struct sem * sem_base;//这一项在man page中没有明确表示
    //但man page提到信号量集中有这样的结构
};

struct sem表示封装的信号量结构

struct sem
{
    unsigned short  semval;   /* semaphore value */
    unsigned short  semzcnt;  /* # waiting for zero */
    unsigned short  semncnt;  /* # waiting for increase */
    pid_t           sempid;   /* ID of process that did last op */
};

根据这两个结构体,在内核中某个特定信号量集可以图解为:

进程间通信笔记(7)—SystemV信号量_第1张图片

2.2 semop函数

使用semget打开一个信号量后,可以对其中一个或多个信号量操作使用semop函数来执行。

#include 
#include 
#include 
int semop(int semid, struct sembuf *sops, unsigned nsops);

对于struct sembuf这个结构体来说,其结构定义如下:

struct sembuf{
    unsigned short sem_num;  /* semaphore number */
    short          sem_op;   /* semaphore operation */
    short          sem_flg;  /* operation flags */
};

sem_num指定特定信号量的操作。
sem_op的值分为3类:
a.sem_op > 0:将值添加到semval上,对应与释放某个资源。
b.sem_op = 0:希望等待到semval值变为0,如果已经是0,则立即返回,否则semzcnt+1,并线程阻塞。
c.sem_op < 0:希望等待到semval值变为大于或等于 |sem_op| 。这对应分配资源。如果已经满足条件,则semval减去sem_op的绝对值,否则semncnt+1并且线程投入睡眠。

semop函数通过命令执行了信号量操作。

2.3 semctl函数

对一个信号量执行各种控制操作。

#include 
#include 
#include 
int semctl(int semid, int semnum, int cmd, ...);

semnum指定信号量集中的某个成员,类似于数组下标(0,1,2…直到nsems-1)。

下面列出了所有CMD对应的宏。
semnum值仅仅用于前5个命令。

命令 作用
GETVAL 返回semval
SETVAL 把semval设定为指定值
GETPID 返回sempid
GETNCNT 返回semncnt
GETZCNT 返回semzcnt
GETALL 返回所有semval值,由array指针返回
SETALL 设置所有semval
IPC_RMID 删除指定id信号量集
IPC_SET 设置uid,gid和mode
IPC_STAT 返回semid_ds结构

而对于第四个参数来说,它是如下union类型

 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) */
};

从注释中可以看见,有些成员仅仅针对某些命令,这也正是为什么这里用Union而不用Struct,可以节省空间,因为假设当前命令跟某个成员没关的时候,struct依然为这个成员分配空间。


3. systemV程序示例

使用semget创建信号量集,创建之后可以在linux终端使用ipcs来查看相应信息。

#include 
#include 
#include 
#include 
#include 
#define SEM_R    0400   //用户(属主)读
#define SEM_A    0200   //用户(属主)写
#define SVSEM_MODE (SEM_R | SEM_A | SEM_R>>3 | SEM_R>>6)

int main(int argc,char *argv[])
{
    int   c,oflag,semid,nsems;
    oflag = SVSEM_MODE | IPC_CREAT;   //设置创建模式
    //根据命令行参数e判断是否制定了IPC_EXCL模式
    while((c = getopt(argc,argv,"e"))!= -1)   
    {
        switch(c)
        {
            case 'e':
                oflag |= IPC_EXCL;
                break;
        }
    }
    //判断命令行参数是否合法
    if (optind != argc -2)
    {
        printf("usage: semcreate [-e]  ");
        exit(0);
    }
    //获取信号量集合中的信号量个数
    nsems = atoi(argv[optind+1]);
    //创建信号量,通过ftok函数创建一个key,返回信号量 标识符
    semid = semget(ftok(argv[optind],0),nsems,oflag);
    exit(0);
}

使用semop对信号量集合操作

#include 
#include 
#include 
#include 
#include 

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;   //非阻塞
                break;
            case 'u':
                flag |= SEM_UNDO;   //不可恢复
                break;
        }
    }
    if(argc - optind < 2)
    {
        printf("usage: semops [-n] [-u]  operation...");
        exit(0);
    } 
    //打开一个已经存在的信号量集合
    if((semid = semget(ftok(argv[optind],0),0,0)) == -1)
    {
        perror("semget() error");
        exit(-1);
    }
    optind++;  //指向当前第一个信号量的位置
    nops = argc - optind;   //信号量个数
    ptr = calloc(nops,sizeof(struct sembuf));
    for(i=0;i//信号量变换
        ptr[i].sem_op = atoi(argv[optind+i]);   //设置信号量的值
        ptr[i].sem_flg = flag;   //设置操作模式
    }
    //对信号量执行操作
    if(semop(semid,ptr,nops) == -1)  
    {
        perror("semop() error");
        exit(-1);
    }
    exit(0);
}

使用semctl对信号量集合发送命令

#include 
#include 
#include 
#include 
#include 

//定义信号量操作共用体结构
union semun
{
    int                val;
    struct semid_ds    *buf;
    unsigned short     *array;
};

int main(int argc,char *argv[])
{
    int semid,nsems,i;
    struct semid_ds seminfo;
    unsigned short *ptr;
    union semun arg;
    if(argc < 2)
    {
            printf("usage: semsetvalues [values ...]");
            exit(0);
    }
    //打开已经存在的信号量集合
    semid = semget(ftok(argv[1],0),0,0);
    arg.buf = &seminfo;
        //获取信号量集的相关信息
    semctl(semid,0,IPC_STAT,arg);
    nsems = arg.buf->sem_nsems;  //信号量的个数
    if(argc != nsems + 2 )
    {
        printf("%s semaphores in set,%d values specified",nsems,argc-2);
        exit(0);
    }
    //分配信号量
    ptr = calloc(nsems,sizeof(unsigned short));
    arg.array = ptr;
    //初始化信号量的值
    for(i=0;i2]);
    //通过arg设置信号量集合
    semctl(semid,0,SETALL,arg);
    exit(0);
}

4. 总结

虽然systemV只有三个函数,但是设计的结构比较多,感觉用起来不如posix方便吧。
相比POSIX信号量,systemV信号量由一组值组成,并且除了PV操作外,信号量集中的每个成员有三个操作:测试值是否为0、加一个整数、减一个整数。等等


5.参考

1.UNP卷2
2.linux man page
3.http://www.cnblogs.com/Anker/archive/2013/01/14/2859352.html

你可能感兴趣的:(UNIX网络编程(卷2))