IPC(SystemV) 之 信号量

linux下面据我目前掌握的内容,一共有两类信号量,一类是POSIX信号量,一类是SystemV信号量.用法类似.名字也类似.不过范围不同.POSIX信号量分为两类,一类用于线程间,一类用于进程间,而SystemV只有一类,就是进程间通信.当然如果你喜欢也可以把用于进程间通信的信号量拿来放到线程间.

首先我们需要了解的一个概念应该是临界资源和临界区.所谓临界资源即多个进程同时访问的一个资源.此资源的特点是同一时间只能有一个进程对它进行访问.而临界区的概念,就是我们的程序中,负责访问临界资源的那段代码,一般也就是那么几行而已.

想要对一段代码进行独占式的访问,方法有很多.后续我将分享一篇自己写的自旋锁,也是独占式访问的一种方式.但是它的缺点也特别明显,由于这是一种忙等待的方式,如果等待时间过长,势必会影响到整个系统的效率和性能.并且这种方式并不支持进程间的互斥.还有很多其它的方式,由于本文分享的是SystemV信号量,所以其它的缺点自然就要罗列一下.比如通过open的原子调用结合O_EXCL来实现进程间互斥.的确可以达到效果.但是文件操作无疑要进行磁盘读写,这个效率相比内存就大打折扣了.

荷兰数学家迪杰斯特拉提出的信号量概念完美的解决了我们的问题.信号量是一个特殊的变量,只能取正值,且程序对其的访问是原子操作.涉及到了信号量,就不得不讲一下PV原语.

P : 如果信号量大于0,信号量-1,如果信号量为0, 则阻塞等待.

V : 如果没有进程等待,信号量+1, 如果此时存在阻塞,则解除一个等待.

头文件<sys/sem.h>

一般而言,这个头文件中已经包含了sys/types.h 和sys/ipc.h ,所以可以不用重复包含.

函数如下:

int semget(key_t key, int num, int flag);

int semctl(int semid, int sem_num, int command, ...);

int semop(int semid, struct sembuf, size_t num_sem_buf);

下面单独介绍:

semget   创建一个信号量独一无二的标记符.参数key_t为一个独一无二的键值.通过这个键值,我们可以实现进程间共享这个信号量.如果不想进程间使用,可以把这个键值指定为IPC_PRIVATE. 参数二指定信号量个数,注意是个数,不是值.这是SystemV信号量和POSIX的一个区别,严格意义上,SystemV的这组信号量API提供的是对一组信号量操作的方式.而不是一个.当然这个值大部分情况下都是1.flag参数指定了创建信号量的权限和操作,低九位表示权限.与IPC_CREAT 和 IPC_EXCL做或操作.如果指定了IPC_EXCL,表示如果信号量已经存在,则创建失败,如果没有这个选项,当信号量已经存在是,直接获取到这个信号量.

semget的返回值,如果成功,返回semid供semop和semctl来进行使用,如果失败返回-1


semop   这个就是我们所说的PV操作的执行者.首先得了解一下这个sembuf结构体.

struct sembuf{

int sem_num;    指定对信号量组的哪个进行操作,一般只有一个组,这个值取0

int op;   +/-1   可以看到,如果是+1就是V操作,如果是-1就是P操作.

int flag;     指定一些特殊的操作.我所知道的只有一个标记SEM_UNDO.即当进程意外终止时,由系统回收这个信号量,不至于浪费系统资源.一般来说,建议加上这个标记.因为信号量不会自动消失.

}

semop返回值,如果成功返回0,如果失败返回-1


semctl    提供了一组操作信号量的方式.比如设定信号量初值.删除信号量.获取信号量状态等.

首先第一个参数就是semget返回的信号量标记.

第二个参数,指定信号量组,一般情况只要取0就可以了.

第三个参数指定对这组信号量执行的操作.常用的有IPC_RMID(删除一个信号量), SETVAL(设定信号量初值)

当这个参数是SETVAL的时候需要第四个参数,union semun

union semun{

int val;     设定信号量初值,这个值才是用来PV操作的值.

struct sem_ds *buf;    没用过

unsigned short *array;   没有用过.

}

这个联合体体,有的头文件中包含了,有的没有.如果在编译的时候报了没有这个联合体的错误.需要自己定义一个.

semctl的返回值,根据第三个参数的不同而不同,不过SETVAL和IPC_RMID是一样的,成功返回0, 失败返回-1 


下面我们用信号量完成一个例子.我们首先创建一个文件,一个进程写,一个进程来读.刚刚启动时,读写进程执行P操作(这里有个细节,如果读进程获取到了资源怎么办,其实无所谓,就让他去读,反正读不到东西就执行V操作了),写进程写入一行以后执行V操作.循环执行,知道写进程输入end.ok,我们来试试吧.

好吧.被打脸了.看书的时候,书中的是单进程的例子.而我实验的是多进程的.从两点钟一直搞到四点.各种问题.

1. 进程卡在了semop 的P操作.原因是我把第三个参数搞错了.第三个参数是第二个参数的个数,不是第二个参数的大小.

2. semop产生invalid argument  错误.这个问题让我郁闷了半天.本以为一个进程已经给了信号量初值了.第二个应该就不要给了吧.可是没想到,还是得给.

3. semget   初次调用的时候,要指定信号量的组数.后面再次调用semget的时候就不要再指定了,所以这个参数置成0

4. 权限问题.如果一个权限高的用户创建了其中一个进程并且创建了信号量,那么当另外一个进程尝试执行P操作的时候就会失败,提示没有权限.

ok.上面就是我遇到的问题.下面贴出例子代码.当然有点小BUG .主线功能并没有影响.

write.c

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/sem.h>
#include <errno.h>

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

#define FILENAME "alai"

int main(int argc, char ** argv)
{
    int ret;
    int runing = 1;
    int fd;
    char buffer[BUFSIZ] = {0};

    int sem_id;
    struct sembuf sem_p;
    struct sembuf sem_v;
    union semun sem_un;

    sem_id = semget((key_t)123456789, 1, 0666|IPC_CREAT|IPC_EXCL);
    if (sem_id > 0)
    {
        sem_un.val = 1;
        ret = semctl(sem_id, 0, SETVAL, sem_un);
        if (ret < 0 )
        {
            perror("\n");
            exit(EXIT_FAILURE);
        }
    }
    else if (errno == EEXIST)
    {
        sem_id = semget((key_t)123456789, 0, 0666|IPC_CREAT);
    }
    else
    {
        perror("\n");
        exit(EXIT_FAILURE);
    }

    ret = semctl(sem_id, 0, GETVAL, &sem_un);

    sem_p.sem_num = 0;
    sem_p.sem_op = -1;
    sem_p.sem_flg = SEM_UNDO;

    sem_v.sem_num = 0;
    sem_v.sem_op = +1;
    sem_v.sem_flg = SEM_UNDO;

    while (runing)
    {
        ret = semop(sem_id, &sem_p, 1);
        if (ret == -1 )
        {
            perror("\n");
            exit(EXIT_FAILURE);
        }

        fd = open(FILENAME, O_WRONLY | O_CREAT | O_TRUNC);

        printf("please input some text:\n");
        ret = read(fileno(stdin), buffer, BUFSIZ);
        if (0 != ret)
        {
            write(fd, buffer, ret);
        }


        if (0 == strncmp(buffer, "end", 3))
        {
            runing = 0;
        }

        bzero(buffer, BUFSIZ);

        close(fd);

        semop(sem_id, &sem_v, 1);
    }

    ret = semctl(sem_id, 0, IPC_RMID, sem_un);

    return 0;
}


read.c

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/sem.h>
#include <errno.h>

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

#define FILENAME "alai"

int main(int argc, char ** argv)
{
    int ret;
    int runing = 1;
    int fd;
    char buffer[BUFSIZ] = {0};

    int sem_id;
    struct sembuf sem_p;
    struct sembuf sem_v;
    union semun sem_un;

    sem_id = semget((key_t)123456789, 1, 0666 | IPC_CREAT | IPC_EXCL);
    if (sem_id > 0)
    {
        sem_un.val = 1;
        ret = semctl(sem_id, 0, SETVAL, sem_un);
        if (ret == -1 )
        {
            perror("\n");
            exit(EXIT_FAILURE);
        }
    }
    else if (EEXIST == errno)
    {
        sem_id = semget((key_t)123456789, 0, 0666|IPC_CREAT);
    }
    else
    {
        perror("\n");
        exit(EXIT_FAILURE);
    }

    sem_p.sem_num = 0;
    sem_p.sem_op = -1;
    sem_p.sem_flg = SEM_UNDO;

    sem_v.sem_num = 0;
    sem_v.sem_op = +1;
    sem_v.sem_flg = SEM_UNDO;

    while (runing)
    {
        ret = semop(sem_id, &sem_p, 1);
        if (ret == -1 )
        {
            perror("\n");
            exit(EXIT_FAILURE);
        }

        fd = open(FILENAME, O_RDONLY);
        if (-1 == fd)
        {
            perror("\n");
            exit(EXIT_FAILURE);
        }

        read(fd, buffer, BUFSIZ);

        if (strncmp(buffer, "end", 3) == 0)
        {
            runing = 0;
        }
        printf("I have read %s\n", buffer);

        bzero(buffer, BUFSIZ);
        close(fd);

        semop(sem_id, &sem_v, 1);
    }

    ret = semctl(sem_id, 0, IPC_RMID, sem_un);

    return 0;
}


运行结果如下:

IPC(SystemV) 之 信号量_第1张图片


关于信号量,就分享这么多.相信同学们看了这篇文章应该能够简单的入门.不过建议例子代码不要复制粘贴看结果,而是真正自己敲一遍,这样才能够理解的更透彻.

你可能感兴趣的:(ipc,信号量,sem)