System V共享内存与信号量综合应用之生产者与消费者问题解决

来源:互联网 发布:龙虎榜数据几点公布 编辑:程序博客网 时间:2024/05/16 10:48

一.生产者---消费者问题

有一个仓库(缓冲区),生产者生产出来的东西往里面扔,消费者从里面取东西。如何安排能够使两者之间不冲突。
一些基本的约束条件:
    1.仓库只有一个,且有固定大小。生产者有多个消费者有多个
    2.生产者占用仓库前要判断仓库是否满,满了就不能争用
    3.消费者使用仓库前要判断仓库是否空,空了就不能争用
    4.仓库中要么只有消费者要么只有生产者

PV原语伪代码如下(假定仓库一开始空的):没有死锁用的是AND信号量解决的,伪代码为:
 生产者
       P(sem_full)
       P(sem_mutex)
       生产产品
       V(sem_mutex)
       V(sem_empty)
 消费者
      P(sem_empty)
      P(sem_mutex)
      消费产品
      V(sem_mutex)
      V(sem_full)
在代码中,共享内存就是仓库,使用信号量来完成PV原语操作。同时在逻辑上将共享内存描述为环形的先进先出(FIFO)管道。来实现生产者和消费者之间的同步。
其它:
    一个PV操作一般就用一个信号量来实现。
    一个信号量可以有多个成员也就是说一个信号量可以等待多个资源满足条件,而不是加个信号量进行互斥。(关于这个在使用信号量解决哲学家就餐问题中有体现,哲学家要等待2个筷子都可用并不是要使用两个信号量来实现,本身该问题需要用到的PV操作也就只有一个)

二.用到的共享内存结构体

说明:

(1)实现一个共享内存的先进先出队列结构, shmfifo结构体包含指向头部的指针p_shm,指向有效起始地址的指针,以及其他4个参数. 

(2)生产者与消费者模型应该有3个信号量:互斥信号量(一次只有一个可以操作,即读写这个缓冲区),满信号量,空信号量.

(3)包头结构体包含块大小,总块数,读索引,写索引.

伪代码:

int in=0,out=0;
item buffer[n];
semaphore mutex=1,empty=n,full=0;
void proceducer()
{
    do{
        producer an item nextp;
        wait(empty);
        wait(mutex);
        buffer[in]=nextp;
        in:=(in+1)%n;
        signal(mutex);
        signal(full);
    }while(TRUE)
}
void consumer()
{
    do{
        wait(full);
        wait(mutex);
        nextc = buffer[out];
        out = (out+1)%n;
        signal(mutex);
        signal(empty);
        consumer the item in nextc;
    }while(TRUE)
}

void main()
{
    cobdgin
        proceducer();
        consumer();
    coend
}


三.相关核心代码:

shmfifo_send.c

#include <string.h>#include <stdio.h>#include <stdlib.h>#include "shmfifo.h"typedefstruct stu{char name[32];int age;} STU;int main(int argc,char *argv[]){shmfifo_t *fifo = shmfifo_init(1234,sizeof(STU),3);  // 这里的总块数为3//然后往仓库放5个学生STU s;memset(&s,0,sizeof(STU));int i;for(i = 0;i < 5;++i) {s.name[0] = 'A' + i;s.age = 20 + i;shmfifo_put(fifo,&s);printf("send ok\n");}return 0;}


shmfifo_recv.c

#include <stdio.h>#include <stdlib.h>#include <string.h>#include "shmfifo.h"typedefstruct stu{char name[32];int age;} STU;int main(int argc,char *argv[]){shmfifo_t *fifo = shmfifo_init(1234,sizeof(STU),3);  // 接收块也为3STU s;memset(&s,0,sizeof(STU));int i;for(i = 0;i < 5;++i) {shmfifo_get(fifo,(char *)&s);printf("stu name : %s age : %d\n",s.name,s.age);}shmfifo_destroy(fifo);return 0;}


shmfifo_free.c

#include <stdio.h>#include <stdlib.h>#include <string.h>#include "shmfifo.h"// 进行删除操作typedefstruct stu{char name[32];int age;} STU;int main(int argc,char *argv[]){shmfifo_t *fifo = shmfifo_init(1234,sizeof(STU),3);shmfifo_destroy(fifo);return 0;}


fhmfifo.h

#ifndef_SHM_FIFO_H_#define_SHM_FIFO_H_#include <stdint.h>//这个基于信号量实现的先进先出管道是//解决这个生产者消费者的核心结构//首先有个头来指示这个管道的基本情况typedefstruct shmfifo shmfifo_t;typedefstruct shmhead shmhead_t;//头部信息struct shmhead{uint32_t blksize;//块大小uint32_t blocks;//总块数uint32_t rd_index;//读索引uint32_t wr_index;//写索引};//管道基本结构信息struct shmfifo{shmhead_t *p_shm;//头部指针信息char *p_payload;//有效负载起始地址int shmid;    //共享内存idint sem_mutex;//用来互斥量的信号量,生产者消费者用来占用仓库的信号量int sem_full;//仓库已满的信号量int sem_empty;//仓库已空的信号量};//关于这个生产者和消费者之间的关系整理如下//1.仓库只有一个,且有固定大小。生产者有多个消费者有多个//2.生产者占用仓库前要判断仓库是否满,满了就不能争用//3.消费者使用仓库前要判断仓库是否空,空了就不能争用//4.仓库中要么只有消费者要么只有生产者//仓库初始化shmfifo_t *shmfifo_init(int key,int blksize,int blocks);//往仓库放东西void shmfifo_put(shmfifo_t *fifo,const void *buf);//从仓库取东西void shmfifo_get(shmfifo_t *fifo,void *buf);//销毁仓库void shmfifo_destroy(shmfifo_t *fifo);#endif


fhmfifo.c

#include <stdio.h>#include <stdlib.h>#include <string.h>#include <assert.h>#include <errno.h>#include <unistd.h>#include <sys/ipc.h>#include <sys/shm.h>#include "ipc.h"#include "shmfifo.h"#defineERR_EXIT(m) \do { \perror(m); \exit(EXIT_FAILURE); \} while(0)/********************************inputkey   可以是ftok的返回值blksize  块大小bkocks   总块数**********************************///仓库初始化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 < 0)    {    //创建新的共享内存区,返回共享内存标识符        fifo->shmid = shmget(key, size, IPC_CREAT | 0666);        if (fifo->shmid == -1)            ERR_EXIT("shmget err");  //shmat连接到地址空间,返回值为void*类型,需要强转为头部指针信息  //fino->p_shm 指向共享内存头部指针        fifo->p_shm = (shmhead_t *)shmat(fifo->shmid, NULL, 0);        //文档中有说明 "error (void *) -1 is returned"        if (fifo->p_shm == (shmhead_t *) - 1)              ERR_EXIT("shmat err");//+1指针偏移,得到有效负载起始地址        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;//创建3个信号量, 信号量的key和共享内存的key可以是一样的        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);  //为1        sem_setval(fifo->sem_full, blocks);  //块的大小        sem_setval(fifo->sem_empty, 0);  // 空信号量为0    }    // 如果已经创建了共享内存    else    {        fifo->shmid = shmid;        //连接 shared memory operations        fifo->p_shm = (shmhead_t *)shmat(fifo->shmid, NULL, 0);        if (fifo->p_shm == (shmhead_t *) - 1)            ERR_EXIT("shmat err");//+1指针偏移,得到有效负载起始地址        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){//存放一个数据,数据大小必须是blksize的大小,需要注意 P V操作sem_p(fifo->sem_full);sem_p(fifo->sem_mutex);//这里开始生产产品这里就是把buf拷贝到缓冲区// wr_index* blksize 代表块大小*写索引//函数原型 void *memcpy(void *dest, const void *src, size_t n);memcpy(fifo->p_payload + fifo->p_shm->wr_index * fifo->p_shm->blksize/*生产产品的位置*/,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){//这个代码和生产者基本差不多,区别在于PV的顺序,消费者先是等待判断仓库是否//是空,消费了就不会满所以sem_full要加1sem_p(fifo->sem_empty);sem_p(fifo->sem_mutex);//这里就是消费者具体代码,这里就是将一个块复制给buf//函数原型 void *memcpy(void *dest, const void *src, size_t n);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){//对共享内存进行销毁//解除映射shmdt(fifo->p_shm);//删除共享内存shmctl(fifo->shmid,IPC_RMID,NULL);//对信号量进行销毁sem_d(fifo->sem_mutex);sem_d(fifo->sem_full);sem_d(fifo->sem_empty);free(fifo);}


ipc.h

#ifndef_IPC_H_#define_IPC_H_#include <sys/types.h>//封装的一些信号量基本操作函数int sem_create(key_t key);int sem_open(key_t key);int sem_setval(int semid,int val);int sem_getval(int semid);int sem_d(int semid);int sem_p(int semid);int sem_v(int semid);#endif


ipc.c

#include <stdio.h>#include <stdlib.h>#include <string.h>#include <stdint.h>#include <stdbool.h>#include <errno.h>#include <unistd.h>#include <sys/types.h>#include <sys/stat.h>#include <sys/ipc.h>#include <sys/sem.h>#ifdef_SEM_SEMUN_UNDEFINEDunion semun{int val;struct semid_ds *buf;unsigned short *array;struct seminfo *__buf;};#endif#defineERR_EXIT(m) \do { \perror(m); \exit(EXIT_FAILURE); \} while(0)intsem_create(key_t key){int semid;semid = semget(key,1,IPC_CREAT | IPC_EXCL | 0666);if(semid < 0) {ERR_EXIT("semget");}return semid;}intsem_open(key_t key){int semid;//打开其实就是后面两个选项不用关心semid = semget(key,0,0);//if(semid < 0) {//ERR_EXIT("semget");//}return semid;}//设置信号量集//类似PV的原语中的初始化信号量intsem_setval(int semid,int val){//设置信号量的初始值union semun su;su.val = val;int ret;ret = semctl(semid,0,SETVAL,su);if(ret < 0) {ERR_EXIT("sem_setval");}return 0;}intsem_getval(int semid){int ret;ret = semctl(semid,0,GETVAL,0);if(ret < 0) {ERR_EXIT("sem_getval");}return ret;}//删除信号量intsem_d(int semid){int ret;ret = semctl(semid,0,IPC_RMID,0);if(ret < 0) {ERR_EXIT("semctl");}return 0;}//P操作//P操作小于0的时候就会阻塞intsem_p(int semid){//PV操作主要是操作这3个变量//sem_flag的SEM_UNDO是撤销//含义表示当前的操作取消//也就是当前进程结束后其对//信号量做出的操作将会被撤销//如果是IPC_NOWAIT的表示即使//资源被申请完了调用P操作不//阻塞然后返回EAGAIN的错误。struct sembuf sb = {0,-1,0};int ret;ret = semop(semid,&sb,1);if(ret < 0) {ERR_EXIT("semop");}return ret;}intsem_v(int semid){struct sembuf sb = {0,1,0};int ret;ret = semop(semid,&sb,1);if(ret < 0) {ERR_EXIT("semop");}return ret;}

Makefile


.PHONY:clean allCC=gccCFLAGS=-Wall -gBIN=shmfifo_send shmfifo_recv shmfifo_freeOBJS1=shmfifo_send.o shmfifo.o ipc.oOBJS2=shmfifo_recv.o shmfifo.o ipc.oOBJS3=shmfifo_free.o shmfifo.o ipc.oall:$(BIN)%.O:%.c$(CC) $(CFLAGS) -c $< -o $@shmfifo_send:$(OBJS1)$(CC) $(CFLAGS) $^ -o $@shmfifo_recv:$(OBJS2)$(CC) $(CFLAGS) $^ -o $@shmfifo_free:$(OBJS3)$(CC) $(CFLAGS) $^ -o $@clean:rm -f *.o $(BIN)

代码下载地址: http://download.csdn.net/detail/u014304293/8742727

先关参考:<<计算机操作系统>>


0 1
原创粉丝点击