(三)进程通信(四)-System V信号量

目录

30System V信号量(一)

信号量

信号量集结构

信号量集函数

信号量示例

31System V信号量(二)

用信号集解决哲学家就餐问题

33System V共享内存与信号量综合

基于生产者-消费者模型实现先进先出的共享内存段


30System V信号量(一)


信号量

●二值信号量:其值或为0或为1的信号量。这与互斥锁类似。若资源被锁住则信号量为0,若资源可用则信号量值为1。

●计数信号量:其值在0和某个限制值之间的信号量。         

// 这两种类型的信号量中,等待操作都等待信号量的值变为大于0,然后将它减1。
//挂出(post)操作则只是将信号量的值加1,从唤醒在等待该信号量值变为大于0的任何线程。

●计数信号量集:一个或者多个信号量(构成一个集合),其中每个都是计数信号量。


信号量集结构

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

/*
1、semval :当前某信号量的资源数目

2、semzcnt:当sem_op(见 struct sembuf)为0,且semop 函数没有设置IPC_NOWAIT 标志,且当前semval 不为0,此时semzcnt 会加1,表示等待这个信号量的资源变为0的进程数加1,且进程会阻塞等待直到4个事件其中一个发生,具体可man 2 semop 一下。

3、semncnt:当sem_op(见 struct sembuf)< 0,且semop 函数没有设置IPC_NOWAIT 标志,且当前semval < |sem_op| ,此时semncnt 会加1,表示等待这个信号量的资源增加的进程数加1,且进程会阻塞等待直到4个事件其中一个发生,具体可man 2 semop 一下。

4、当正确执行了semop 函数,则信号量集中的每个信号量的sempid 参数都被设置为改变此信号量的进程pid。

*/

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

è¿éåå¾çæè¿°


信号量集函数

函数原型

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>

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

函数功能

//用来创建和访问一个信号量集

返回值

成功返回一个非负整数,即该信号量集的标识码;失败返回-1

参数说明

key: 信号量集的名字
nsems:信号量集中信号量的个数
semflg: 由九个权限标志构成,它们的用法和创建文件时使用的mode模式标志是一样的

 

函数原型

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

函数功能

用于控制信号量集

返回值

成功返回0;失败返回-1

参数说明

semid:由semget返回的信号量集标识码
semnum:信号量集中信号量的序号,从0开始编号
cmd:将要采取的动作(有三个可取值)
最后一个参数是 union semun,具体成员根据cmd 的不同而不同

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


cmd 取值如下:

SETVAL  设置信号量集中的信号量的计数值
GETVAL  获取信号量集中的信号量的计数值
IPC_STAT 把semid_ds结构中的数据设置为信号量集的当前关联值
IPC_SET 在进程有足够权限的前提下,把信号量集的当前关联值设置为semid_ds数据结构中给出的值
IPC_RMID 删除信号量集

 

函数原型

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

函数功能

用来创建和访问一个信号量集

返回值

成功返回0;失败返回-1

参数说明

semid:是该信号量集的标识码,也就是semget函数的返回值
sops:是个指向一个结构体的指针
nsops:信号量的个数


struct sembuf

{ 

unsigned short sem_num;  /* semaphore number */
           short          sem_op;   /* semaphore operation */
           short          sem_flg;  /* operation flags */

};


sem_num:是信号量的编号。


sem_op:是信号量一次PV操作时加减的数值,一般只会用到两个值,一个是“-1”,也就是P操作,等待信号量变得可用;另一个是“+1”,也就是我们的V操作,发出信号量已经变得可用。
当然+-n 和0 都是允许的。需要注意的是只有+n 才确保将semval +n 后马上返回,而-n 和 0 很可能是会阻塞的,见文章上面的分析,+-n 需要进程对信号量集有写的权限,而0 只需要读的权限。


sem_flag:的两个取值是IPC_NOWAIT或SEM_UNDO,设为前者如果当某个信号量的资源为0时进行P操作,此时不会阻塞等待,而是直接返回资源不可用的错误;
设为后者,当退出进程时对信号量资源的操作撤销;不关心时设置为0即可。


当要对一个信号量集中的多个信号量进行操作时,sops 是结构体数组的指针,此时nsops 不为1。
此时对多个信号量的操作是作为一个单元原子操作,要么全部执行,要么全部不执行。

 


信号量示例

封装一个信号量集操作函数的工具

#include <sys/types.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>

#define ERR_EXIT(m) \
        do \
        { \
                perror(m); \
                exit(EXIT_FAILURE); \
        } while(0)

union semun
{
    int val;                  /* value for SETVAL */
    struct semid_ds *buf;     /* buffer for IPC_STAT, IPC_SET */
    unsigned short *array;    /* array for GETALL, SETALL */
    /* Linux specific part: */
    struct seminfo *__buf;    /* buffer for IPC_INFO */
};

int sem_create(key_t key)
{
    int semid = semget(key, 1, 0666 | IPC_CREAT | IPC_EXCL);
    if (semid == -1)
        ERR_EXIT("semget");

    return semid;
}

int sem_open(key_t key)
{
    int semid = semget(key, 0, 0);
    if (semid == -1)
        ERR_EXIT("semget");

    return semid;
}

int sem_p(int semid)
{
    struct sembuf sb = {0, -1, /*IPC_NOWAIT*/SEM_UNDO};
    int ret = semop(semid, &sb, 1);
    if (ret == -1)
        ERR_EXIT("semop");

    return ret;
}

int sem_v(int semid)
{
    struct sembuf sb = {0, 1, /*0*/SEM_UNDO};
    int ret = semop(semid, &sb, 1);
    if (ret == -1)
        ERR_EXIT("semop");

    return ret;
}

int sem_d(int semid)
{
    int ret = semctl(semid, 0, IPC_RMID, 0);
    if (ret == -1)
        ERR_EXIT("semctl");
    return ret;
}

int sem_setval(int semid, int val)
{
    union semun su;
    su.val = val;
    int ret = semctl(semid, 0, SETVAL, su);
    if (ret == -1)
        ERR_EXIT("semctl");

    printf("value updated...\n");
    return ret;
}

int sem_getval(int semid)
{
    int ret = semctl(semid, 0, GETVAL, 0);
    if (ret == -1)
        ERR_EXIT("semctl");

    printf("current val is %d\n", ret);
    return ret;
}

int sem_getmode(int semid)
{
    union semun su;
    struct semid_ds sem;
    su.buf = &sem;
    int ret = semctl(semid, 0, IPC_STAT, su);
    if (ret == -1)
        ERR_EXIT("semctl");

    printf("current permissions is %o\n", su.buf->sem_perm.mode);
    return ret;
}

int sem_setmode(int semid, char *mode)
{
    union semun su;
    struct semid_ds sem;
    su.buf = &sem;

    int ret = semctl(semid, 0, IPC_STAT, su);
    if (ret == -1)
        ERR_EXIT("semctl");

    printf("current permissions is %o\n", su.buf->sem_perm.mode);
    sscanf(mode, "%o", (unsigned int *)&su.buf->sem_perm.mode);
    ret = semctl(semid, 0, IPC_SET, su);
    if (ret == -1)
        ERR_EXIT("semctl");

    printf("permissions updated...\n");

    return ret;
}

void usage(void)
{
    fprintf(stderr, "usage:\n");
    fprintf(stderr, "semtool -c\n");
    fprintf(stderr, "semtool -d\n");
    fprintf(stderr, "semtool -p\n");
    fprintf(stderr, "semtool -v\n");
    fprintf(stderr, "semtool -s <val>\n");
    fprintf(stderr, "semtool -g\n");
    fprintf(stderr, "semtool -f\n");
    fprintf(stderr, "semtool -m <mode>\n");
}

int main(int argc, char *argv[])
{
    int opt;

    opt = getopt(argc, argv, "cdpvs:gfm:");
    if (opt == '?')
        exit(EXIT_FAILURE);
    if (opt == -1)
    {
        usage();
        exit(EXIT_FAILURE);
    }

    key_t key = ftok(".", 's');
    int semid;
    switch (opt)
    {
    case 'c':
        sem_create(key);
        break;
    case 'p':
        semid = sem_open(key);
        sem_p(semid);
        sem_getval(semid);
        break;
    case 'v':
        semid = sem_open(key);
        sem_v(semid);
        sem_getval(semid);
        break;
    case 'd':
        semid = sem_open(key);
        sem_d(semid);
        break;
    case 's':
        semid = sem_open(key);
        sem_setval(semid, atoi(optarg));
        break;
    case 'g':
        semid = sem_open(key);
        sem_getval(semid);
        break;
    case 'f':
        semid = sem_open(key);
        sem_getmode(semid);
        break;
    case 'm':
        semid = sem_open(key);
        sem_setmode(semid, argv[2]);
        break;
    }

    return 0;
}


31System V信号量(二)

用信号集解决哲学家就餐问题

我们尝试解决这个问题的方法是:仅当一个哲学家两边筷子都可用时才允许他拿筷子。

 

#include<stdio.h>
#include<stdlib.h>
#include<sys/ipc.h>
#include<sys/msg.h>
#include<sys/types.h>
#include<unistd.h>
#include<errno.h>
#include<sys/ipc.h>
#include<sys/sem.h>
#include<sys/wait.h>

#define ERR_EXIT(m) \
    do { \
        perror(m); \
        exit(EXIT_FAILURE); \
    } while(0)

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

int semid;

#define DELAY (rand() % 5 + 1)

void wait_for_2fork(int no)
{
    int left = no;
    int right = (no + 1) % 5;

    struct sembuf buf[2] =
    {
        {left, -1, 0},
        {right, -1, 0}
    };

    semop(semid, buf, 2);
}

void free_2fork(int no)
{
    int left = no;
    int right = (no + 1) % 5;

    struct sembuf buf[2] =
    {
        {left, 1, 0},
        {right, 1, 0}
    };

    semop(semid, buf, 2);
}

void philosopere(int no)
{
    srand(getpid());
    for (; ;)
    {

        printf("%d is thinking\n", no);
        sleep(DELAY);
        printf("%d is hungry\n", no);
        wait_for_2fork(no);
        printf("%d is eating\n", no);
        sleep(DELAY);
        free_2fork(no);
    }
}


int main(void)
{

    semid = semget(IPC_PRIVATE, 5, IPC_CREAT | 0666);
    if (semid == -1)
        ERR_EXIT("semget");
    union semun su;
    su.val = 1;
    int i;
    for (i = 0; i < 5; i++)
    {
        semctl(semid, i, SETVAL, su);
    }

    int no = 0;
    pid_t pid;
    for (i = 1; i < 5; i++)
    {
        pid = fork();
        if (pid == -1)
            ERR_EXIT("fork");

        if (pid == 0)
        {
            no = i;
            break;
        }
    }

    philosopere(no);

    return 0;
}

 

 

33System V共享内存与信号量综合

基于生产者-消费者模型实现先进先出的共享内存段

 可以用信号量解决生产者消费者问题,如下图:

定义3个信号量,sem_full 和 sem_empty 用于生产者进程和消费者进程之间同步,即缓冲区为空才能生产,缓冲区不为空才能消费。由于共享同一块缓冲区,在生产一个产品过程中不能生产/消费产品,在消费一个产品的过程中不能生产/消费产品,故再使用一个 sem_mutex 信号量来约束行为,即进程间互斥。
 

下面基于生产者消费者模型,来实现一个先进先出的共享内存段:

如上图所示,定义两个结构体,shmhead 是共享内存段的头部,保存了块大小,块数,读写索引。shmfifo 保存了共享内存头部的指针,有效负载的起始地址,创建的共享内存段的shmid,以及3个信号量集的semid。

封装几个函数:

#include "shmfifo.h"
#include <assert.h>

shmfifo_t *shmfifo_init(int key, int blksize, int blocks)
{
    shmfifo_t *fifo = (shmfifo_t *)malloc(sizeof(shmfifo_t));
    assert(fifo != NULL);
    memset(fifo, 0, sizeof(shmfifo_t));


    int shmid;
    shmid = shmget(key, 0, 0);
    int size = sizeof(shmhead_t) + blksize * blocks;
    if (shmid == -1)
    {
        fifo->shmid = shmget(key, size, IPC_CREAT | 0666);
        if (fifo->shmid == -1)
            ERR_EXIT("shmget");

        fifo->p_shm = (shmhead_t *)shmat(fifo->shmid, NULL, 0);
        if (fifo->p_shm == (shmhead_t *) - 1)
            ERR_EXIT("shmat");

        fifo->p_payload = (char *)(fifo->p_shm + 1);

        fifo->p_shm->blksize = blksize;
        fifo->p_shm->blocks = blocks;
        fifo->p_shm->rd_index = 0;
        fifo->p_shm->wr_index = 0;

        fifo->sem_mutex = sem_create(key);
        fifo->sem_full = sem_create(key + 1);
        fifo->sem_empty = sem_create(key + 2);

        sem_setval(fifo->sem_mutex, 1);
        sem_setval(fifo->sem_full, blocks);
        sem_setval(fifo->sem_empty, 0);
    }
    else
    {
        fifo->shmid = shmid;
        fifo->p_shm = (shmhead_t *)shmat(fifo->shmid, NULL, 0);
        if (fifo->p_shm == (shmhead_t *) - 1)
            ERR_EXIT("shmat");

        fifo->p_payload = (char *)(fifo->p_shm + 1);

        fifo->sem_mutex = sem_open(key);
        fifo->sem_full = sem_open(key + 1);
        fifo->sem_empty = sem_open(key + 2);
    }

    return fifo;
}

void shmfifo_put(shmfifo_t *fifo, const void *buf)
{
    sem_p(fifo->sem_full);
    sem_p(fifo->sem_mutex);

    memcpy(fifo->p_payload + fifo->p_shm->blksize * fifo->p_shm->wr_index,
           buf, fifo->p_shm->blksize);
    fifo->p_shm->wr_index = (fifo->p_shm->wr_index + 1) % fifo->p_shm->blocks;
    sem_v(fifo->sem_mutex);
    sem_v(fifo->sem_empty);
}

void shmfifo_get(shmfifo_t *fifo, void *buf)
{
    sem_p(fifo->sem_empty);
    sem_p(fifo->sem_mutex);

    memcpy(buf, fifo->p_payload + fifo->p_shm->blksize * fifo->p_shm->rd_index,
           fifo->p_shm->blksize);
    fifo->p_shm->rd_index = (fifo->p_shm->rd_index + 1) % fifo->p_shm->blocks;
    sem_v(fifo->sem_mutex);
    sem_v(fifo->sem_full);
}

void shmfifo_destroy(shmfifo_t *fifo)
{
    sem_d(fifo->sem_mutex);
    sem_d(fifo->sem_full);
    sem_d(fifo->sem_empty);

    shmdt(fifo->p_shm);
    shmctl(fifo->shmid, IPC_RMID, 0);
    free(fifo);
}



/*
1、shmfifo_init:先分配shmfifo 结构体的内存,
如果尝试打开共享内存失败则创建,创建的共享内存段大小 = shmhead大小 + 块大小×块数目,
然后shmat将此共享内存段映射到进程地址空间,
然后使用sem_create 创建3个信号量集,每个信号集只有一个信号量,即上面提到的3个信号量,设置每个信号量的资源初始值。
如果共享内存已经存在,则直接shmat映射下即可,此时3个信号量集也已经存在,sem_open 打开即可。


2、shmfifo_put:参照第一个生产者消费者的图,除去sem_p,sem_v 操作之外,中间就将buf 的内容memcpy 到对应缓冲区块,然后移动wr_index。

3、shmfifo_get:与shmfifo_put 类似,执行的是相反的操作。

4、shmfifo_destroy:删除3个信号量集,将共享内存段从进程地址空间剥离,删除共享内存段,释放shmfifo 结构体的内存。

*/

下面是生产者程序和消费者程序:

shmfifo_send.c

#include "shmfifo.h"

typedef struct stu
{
    char name[32];
    int age;
} STU;
int main(void)
{
    shmfifo_t *fifo = shmfifo_init(1234, sizeof(STU), 3);

    STU s;
    memset(&s, 0, sizeof(STU));
    s.name[0] = 'A';
    int i;

    for (i = 0; i < 5; i++)
    {
        s.age = 20 + i;
        shmfifo_put(fifo, &s);
        s.name[0] = s.name[0] + 1;

        printf("send ok\n");
    }

    free(fifo);

    return 0;
}

shmfifo_recv.c

#include "shmfifo.h"

typedef struct stu
{
    char name[32];
    int age;
} STU;

int main(void)
{
    shmfifo_t *fifo = shmfifo_init(1234, sizeof(STU), 3);

    STU s;
    memset(&s, 0, sizeof(STU));
    int i;

    for (i = 0; i < 5; i++)
    {
        shmfifo_get(fifo, &s);
        printf("name = %s age = %d\n", s.name, s.age);
    }

    shmfifo_destroy(fifo);

    return 0;
}

 

 

 

 

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值