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

本文探讨了生产者-消费者问题的经典解决方案,通过共享内存和信号量机制实现进程间的同步与互斥,确保多进程环境下资源的安全访问。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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

有一个仓库(缓冲区),生产者生产出来的东西往里面扔,消费者从里面取东西。如何安排能够使两者之间不冲突。
一些基本的约束条件:
    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"

typedef	struct 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"

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

int main(int argc,char *argv[])
{
	shmfifo_t *fifo = shmfifo_init(1234,sizeof(STU),3);  // 接收块也为3
	STU 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"

// 进行删除操作



typedef	struct 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>

//这个基于信号量实现的先进先出管道是
//解决这个生产者消费者的核心结构
//首先有个头来指示这个管道的基本情况
typedef	struct shmfifo shmfifo_t;
typedef	struct 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;		    //共享内存id
	int 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"

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


/********************************
	input	
		key   可以是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要加1
	sem_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_UNDEFINED
union semun
{
	int val;
	struct semid_ds *buf;
	unsigned short *array;
	struct seminfo *__buf;
};
#endif

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

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

int
sem_open(key_t key)
{
	int semid;
	//打开其实就是后面两个选项不用关心
	semid = semget(key,0,0);
	//if(semid < 0) {
		//ERR_EXIT("semget");
	//}
	return semid;
}

//设置信号量集
//类似PV的原语中的初始化信号量
int
sem_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;
}

int
sem_getval(int semid)
{
	int ret;
	ret = semctl(semid,0,GETVAL,0);
	if(ret < 0) {
		ERR_EXIT("sem_getval");
	}
	return ret;
}

//删除信号量
int
sem_d(int semid)
{
	int ret;
	ret = semctl(semid,0,IPC_RMID,0);
	if(ret < 0) {
		ERR_EXIT("semctl");
	}
	return 0;
}

//P操作
//P操作小于0的时候就会阻塞
int
sem_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;
}

int
sem_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 all
CC=gcc
CFLAGS=-Wall -g
BIN=shmfifo_send shmfifo_recv shmfifo_free
OBJS1=shmfifo_send.o shmfifo.o ipc.o
OBJS2=shmfifo_recv.o shmfifo.o ipc.o
OBJS3=shmfifo_free.o shmfifo.o ipc.o
all:$(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.youkuaiyun.com/detail/u014304293/8742727

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


评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值