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

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

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

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


  • 2
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
条消息。为了避免生产者消费者在同时访问缓冲池时出现竞争条件,我们可以使用多线程或进程以及 System V 信号量解决生产者消费者问题使用多线程和信号量实现生产者消费者模型的伪代码如下: ``` #include <stdio.h> #include <stdlib.h> #include <pthread.h> #include <semaphore.h> #define N 10 // 缓冲池大小 #define MAX_MSG 100 // 最大消息数量 pthread_t tid_producer, tid_consumer; // 生产者消费者线程 ID sem_t sem_empty, sem_full, sem_mutex; // 信号量:空闲空间、已用空间、互斥量 int buffer[N]; // 缓冲池 int msg_count = 0; // 当前消息数量 void* producer(void* arg) { int msg; while (msg_count < MAX_MSG) { msg = rand() % 1000; // 随机生成一个消息 sem_wait(&sem_empty); // 等待空闲空间 sem_wait(&sem_mutex); // 加锁 buffer[msg_count % N] = msg; // 将消息存入缓冲池 msg_count++; printf("Producer produced message %d\n", msg); sem_post(&sem_mutex); // 解锁 sem_post(&sem_full); // 发送已用空间信号 } pthread_exit(NULL); } void* consumer(void* arg) { int msg; while (msg_count < MAX_MSG) { sem_wait(&sem_full); // 等待已用空间 sem_wait(&sem_mutex); // 加锁 msg = buffer[(msg_count - 1) % N]; // 从缓冲池中取出消息 msg_count--; printf("Consumer consumed message %d\n", msg); sem_post(&sem_mutex); // 解锁 sem_post(&sem_empty); // 发送空闲空间信号 } pthread_exit(NULL); } int main() { sem_init(&sem_empty, 0, N); // 初始化空闲空间信号量 sem_init(&sem_full, 0, 0); // 初始化已用空间信号量 sem_init(&sem_mutex, 0, 1); // 初始化互斥信号量 pthread_create(&tid_producer, NULL, producer, NULL); // 创建生产者线程 pthread_create(&tid_consumer, NULL, consumer, NULL); // 创建消费者线程 pthread_join(tid_producer, NULL); // 等待生产者线程结束 pthread_join(tid_consumer, NULL); // 等待消费者线程结束 sem_destroy(&sem_empty); // 销毁空闲空间信号量 sem_destroy(&sem_full); // 销毁已用空间信号量 sem_destroy(&sem_mutex); // 销毁互斥信号量 return 0; } ``` 在上面的代码中,我们创建了三个 System V 信号量: - `sem_empty`:空闲空间信号量,初始值为缓冲池大小 N。 - `sem_full`:已用空间信号量,初始值为 0。 - `sem_mutex`:互斥信号量,用于对缓冲池的访问进行加锁,初始值为 1。 在生产者线程中,我们使用 `sem_wait()` 函数等待空闲空间信号量,表示缓冲池中有空闲空间可以存放消息。然后使用 `sem_wait()` 函数对互斥信号量进行加锁,保证生产者线程与其他线程的访问是互斥的。接着,将消息存入缓冲池,并将消息数量加一。最后使用 `sem_post()` 函数释放互斥信号量和发送已用空间信号量,表示缓冲池中已经使用了一个空间。 在消费者线程中,我们使用 `sem_wait()` 函数等待已用空间信号量,表示缓冲池中有消息可以被消费者取出。然后使用 `sem_wait()` 函数对互斥信号量进行加锁,保证消费者线程与其他线程的访问是互斥的。接着,从缓冲池中取出消息,并将消息数量减一。最后使用 `sem_post()` 函数释放互斥信号量和发送空闲空间信号量,表示缓冲池中又有一个空间可以被生产者使用。 在主函数中,我们先进行信号量的初始化,然后创建生产者消费者线程,并使用 `pthread_join()` 函数等待线程结束。最后销毁信号量并返回。 使用多进程和信号量实现生产者消费者模型的伪代码如下: ``` #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/wait.h> #include <sys/shm.h> #include <semaphore.h> #define N 10 // 缓冲池大小 #define MAX_MSG 100 // 最大消息数量 sem_t *sem_empty, *sem_full, *sem_mutex; // 信号量:空闲空间、已用空间、互斥量 int* buffer; // 缓冲池 int msg_count = 0; // 当前消息数量 void producer() { int msg; while (msg_count < MAX_MSG) { msg = rand() % 1000; // 随机生成一个消息 sem_wait(sem_empty); // 等待空闲空间 sem_wait(sem_mutex); // 加锁 buffer[msg_count % N] = msg; // 将消息存入缓冲池 msg_count++; printf("Producer produced message %d\n", msg); sem_post(sem_mutex); // 解锁 sem_post(sem_full); // 发送已用空间信号 } } void consumer() { int msg; while (msg_count < MAX_MSG) { sem_wait(sem_full); // 等待已用空间 sem_wait(sem_mutex); // 加锁 msg = buffer[(msg_count - 1) % N]; // 从缓冲池中取出消息 msg_count--; printf("Consumer consumed message %d\n", msg); sem_post(sem_mutex); // 解锁 sem_post(sem_empty); // 发送空闲空间信号 } } int main() { int shmid = shmget(IPC_PRIVATE, N * sizeof(int), 0666 | IPC_CREAT); // 创建共享内存 buffer = (int*) shmat(shmid, NULL, 0); // 将共享内存映射到进程地址空间 sem_empty = sem_open("/sem_empty", O_CREAT, 0644, N); // 创建空闲空间信号量 sem_full = sem_open("/sem_full", O_CREAT, 0644, 0); // 创建已用空间信号量 sem_mutex = sem_open("/sem_mutex", O_CREAT, 0644, 1); // 创建互斥信号量 pid_t pid_producer = fork(); // 创建生产者进程 if (pid_producer == 0) { producer(); exit(0); } pid_t pid_consumer = fork(); // 创建消费者进程 if (pid_consumer == 0) { consumer(); exit(0); } waitpid(pid_producer, NULL, 0); // 等待生产者进程结束 waitpid(pid_consumer, NULL, 0); // 等待消费者进程结束 sem_close(sem_empty); // 关闭空闲空间信号量 sem_close(sem_full); // 关闭已用空间信号量 sem_close(sem_mutex); // 关闭互斥信号量 sem_unlink("/sem_empty"); // 删除空闲空间信号量 sem_unlink("/sem_full"); // 删除已用空间信号量 sem_unlink("/sem_mutex"); // 删除互斥信号量 shmdt(buffer); // 将共享内存从进程地址空间中分离 shmctl(shmid, IPC_RMID, NULL); // 删除共享内存 return 0; } ``` 在上面的代码中,我们创建了三个 System V 信号量,和一个共享内存: - `sem_empty`:空闲空间信号量,初始值为缓冲池大小 N。 - `sem_full`:已用空间信号量,初始值为 0。 - `sem_mutex`:互斥信号量,用于对缓冲池的访问进行加锁,初始值为 1。 - `buffer`:缓冲池,使用共享内存实现。 在生产者进程中,我们使用 `sem_wait()` 函数等待空闲空间信号量,表示缓冲池中有空闲空间可以存放消息。然后使用 `sem_wait()` 函数对互斥信号量进行加锁,保证生产者进程与其他进程的访问是互斥的。接着,将消息存入缓冲池,并将消息数量加一。最后使用 `sem_post()` 函数释放互斥信号量和发送已用空间信号量,表示缓冲池中已经使用了一个空间。 在消费者进程中,我们使用 `sem_wait()` 函数等待已用空间信号量,表示缓冲池中有消息可以被消费者取出。然后使用 `sem_wait()` 函数对互斥信号量进行加锁,保证消费者进程与其他进程的访问是互斥的。接着,从缓冲池中取出消息,并将消息数量减一。最后使用 `sem_post()` 函数释放互斥信号量和发送空闲空间信号量,表示缓冲池中又有一个空间可以被生产者使用。 在主函数中,我们先创建共享内存信号量,然后创建生产者消费者进程,并使用 `waitpid()` 函数等待进程结束。最后删除信号量共享内存并返回。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值