利用信号量机制实现生产者消费者问题(用进程通信进行模拟)

生产者消费者同步问题:生产者进程产生消息放在某个共享资源区,共享资源区的消息不为空时消费者进程从该共享资源区读取消息,为了避免脏读生产者和消费者不能同时访问共享资源,这就涉及到生产者和消费者进程之间的同步。利用信号量解决该问题:
定义两个信号量(不能是一个,否则生产者或者消费者可能会无限的访问共享资源,形成死循环)S1、S2,初始值为1,0。S1=1表示生产者进程可写共享资源,为0则不可;S2=1表示消费者进程可读共享资源,为0则不可。伪代码描述如下:

    生产者进程:
    whlie(true)
    {
        产生一个消息
    	P(S1)
    	消息写到共享资源
    	V(S2)
    }
     
    消费者进程:
    whlie(true)
    {
    	P(S2)
    	从共享资源读取消息
    	V(S1)
    	处理消息
    }

1.信号量
信号量可用于多进程的同步,为多个进程提供对共享资源的访问。

(1)获取信号量

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

semnum为信号数量,如果是新创建信号量,则大于0;如果是打开已有的信号量,则semnum可为0;

(2)对信号量进行操作

int semctl(int semid, int semnm, int cmd [, union semun arg]);

参数semnm是信号量集合中某个信号量的下标,从0开始,在0与nsems-1之间;
参数中联合体结构如下(有些系统需要自定义):

    union semun 
    {
       int val;                 /*for SETVAL*/
       struct semid_ds *buf;    /*for IPC_STAT  IPC_SET*/
       unsigned short *array;   /*for SETALL*/
    };

这里union semun arg是可选参数,例如cmd=IPC_RMID就不需要该参数,cmd为SETVAL或者SETALL的时候需要该参数。
以cmd=SETALL时,此时信号量集合的值由arg.array数组指定。

(3)自动执行信号量集合上的操作数组 semop是原子操作

int semctl(int semid, int semnm, int cmd [, union semun arg]);

其中结构体struct sembuf在(sys/sem.h)中定义如下:

    struct sembuf
    {
       unsigned short sem_num;      /*0~nsems-1*/
       short sem_op;                /*negative, 0, pasitive*/
       short sem_flg;               /*0 IPC_UNDO  IPC_NOWAIT*/
    };

输入域参数semoparray指向一个信号量操作结构体数组,参数nops是操作的信号量的个数。

这里可以看用semop可以包装P、V操作,以便实现进程之间的同步,这里回顾一下PV操作。
定义一个信号量sem,则有:
P操作:令sem=sem-1,若sem>=0,则进程继续执行,否则挂起等待,加入等待队列。
V操作:令sem=sem+1,若sem<0,唤醒等待队列中的一个进程。(注意:sem<0才需要唤醒,因为此时有阻塞; s>=0说明没阻塞也不需要唤醒)。

2.共享内存
共享内存是IPC的一种,共享内存为多个进程提供一个可以共同访问的存储区,共享内存的数据,不需要在各个进程间复制,因此是速度最快的IPC。多个进程访问共享内存时,为了避免脏读,需要实现进程的同步,通常用信号量实现共享内存的同步访问。
共享内存的接口函数如下:
(1)创建或者获得共享内存

int shmget(key_t key, size_t size, int flag);

(2)共享内存操作

int shmctl(int shmid, int cmd, struct shmid_ds *buf);

(3)链接共享内存

void *shmat(int shmid, const void *arr,  int flag);  //成功返回共享内存的指针,否则返回NULL

addr通常使用NULL,表示链接到内核选择的第一个可用地址上;
addr非NULL,且没有指定flag为SHM_RND则链接到arr指定的地址;
addr非NULL,且指定了flag为SHM_RND则连接到arr-(addr mod SHMLBA)表示的地址,就是向下取近1个SHMLBA的倍数数
flag如果为RDONLY,则共享内存只读

(4)结束共享内存段访问(并未删除)

int shmdt(const void *shmArr);   //分离链接的共享内存,成功返回0,失败-1

shmArr是shmat返回的值。

3.信号量与共享内存解决生产者消费者问题
生产者进程负责从键盘读取消息写入到共享内存,消费者进程从共享内存读数据输出到屏幕,生产者进程写共享内存结束前,消费者进程不能读;消费者进程读共享内存时,生产者进程不能写共享内存。
(1)shm_sem.h

# ifndef _SHM_SEM_H
# define _SHM_SEM_H
 
# define SHM_KEY 38111
# define SEM_KEY 38222
# define SHM_SIZE 2048
# define SEM_NUM  2
 
int sem_p(int semid, int semnum);
int sem_v(int semid, int semnum);
int get_semval(int semid, int semnum);
 
union semun
{
	int val;                 /*for SETVAL*/
	struct semid_ds *buf;    /*for IPC_STAT  ans  IPC_SET*/
	unsigned short *array;   /*for GETALL  ans  SET_ALL*/
};
 
 
# endif

(2)pubfun.c

#include <stdio.h>  
#include <string.h>  
#include <stdlib.h>  
#include <unistd.h> 
#include <errno.h> 
#include <sys/types.h>  
#include <sys/ipc.h>  
#include <sys/sem.h> 
 
/*sem P*/ 
int sem_p(int semid, int semnum)    
{
	/*int semop(int semid, struct sembuf semoparray[], size_t cnts); */
	struct sembuf semOpr ;
	
	semOpr.sem_num = semnum;
	semOpr.sem_op  = -1;
	semOpr.sem_flg = SEM_UNDO;
	
	return semop(semid, &semOpr, 1);
}
 
/*sem V*/ 
int sem_v(int semid, int semnum)
{
	/*int semop(int semid, struct sembuf semoparray[], size_t cnts); */
	struct sembuf semOpr ;
	
	semOpr.sem_num = semnum;
	semOpr.sem_op  = 1;
	semOpr.sem_flg = SEM_UNDO;
	
	return semop(semid, &semOpr, 1);
}
 
/*get semval*/ 
int get_semval(int semid, int semnum)
{
	return semctl(semid, semnum, GETVAL);
}

(3)server.c 生产者进程

#include <stdio.h>  
#include <string.h>  
#include <stdlib.h>  
#include <unistd.h> 
#include <errno.h> 
#include <sys/types.h>  
#include <sys/ipc.h>  
#include <sys/shm.h> 
#include <sys/sem.h> 
 
#include "shm_sem.h"
 
# if 0
struct sembuf
{
   unsigned short sem_num;      /*0-nsems-1*/
   short sem_op;                /*negative, 0, pasitive*/
   short sem_flg;               /*0 IPC_UNDO  IPC_NOWAIT*/
}
#endif

int main(void)
{
	int ret;
	int shmid;
	int semid;
	void *ptrShm = NULL;
	struct shmid_ds shm_ds;
	struct semid_ds sem_ds;
	char buf[1024] = {0};
	union semun arg;
	unsigned short semvalArr[SEM_NUM] = {1, 0};
	
	memset(&shm_ds, 0x00, sizeof(shm_ds));
	memset(&sem_ds, 0x00, sizeof(sem_ds));
	
	/*semget*/
	semid = semget((key_t)SEM_KEY, SEM_NUM, 0660|IPC_CREAT|IPC_EXCL);   /*int semget(key_t key, int nsems, int flag);*/
	if(semid == -1)
	{
		if(errno == EEXIST) /*no need to init sem*/
		{
			printf("semget() warning: %s\n", strerror(errno));
			semid = semget((key_t)SEM_KEY, 0, 0660|IPC_CREAT); /*nsems can be 0 when semid already exists*/
			if(semid == -1)
			{
				printf("semget() error: %s\n", strerror(errno));
				return -1;
			}
			printf("semget() success. semid=[%d]\n", semid);
		}
		else
		{
			printf("semget() error: %s\n", strerror(errno));
			return -1;
		}
	}
	else  /*need to init sem*/
	{
		printf("semget() success. semid=[%d]\n", semid);
				
		/*semctl(): init sem, set semvalArr[0]=1 semvalArr[1]=0*/
        arg.array = semvalArr;		
		ret = semctl(semid, 0, SETALL, arg);  /*int semctl(int semid, int semnm, int cmd [, union semun arg]); semnum between 0~nsems-1*/ 
		if(ret == -1)
		{
			printf("semctl() SETALL error: %s\n", strerror(errno));
			printf("init sem error.\n");
			
		   /*semctl IPC_RMID*/
	       ret = semctl(semid, 0, IPC_RMID);
	       if(ret == -1)
	       {
	       	printf("semctl() error: %s\n", strerror(errno));
	       }
	       printf("semctl() success. Sem is deleted.\n"); 
		   return -1;
		   
		}
		printf("semget() success.\n");
		
	}
	printf("init sem success. semval[0]=[%d] semval[1]=[%d].\n", get_semval(semid, 0), get_semval(semid, 1));
	
 
	/*shmget*/
	shmid = shmget((key_t)SHM_KEY, SHM_SIZE, 0660|IPC_CREAT|IPC_EXCL);   /*int shmget(key_t key, size_t size, int flag);*/
	if(shmid == -1)
	{
		if(errno == EEXIST)
		{
			printf("shmget() warning: %s\n", strerror(errno));
			shmid = shmget((key_t)SHM_KEY, SHM_SIZE, 0660|IPC_CREAT);
			if(shmid == -1)
			{
				printf("shmget() error: %s\n", strerror(errno));
				return -1;
			}
		}
		else
		{
			printf("shmget() error: %s\n", strerror(errno));
			return -1;
		}
	}
	printf("shmget() success. shmid=[%d]\n", shmid); 
		
	
	/*shmat*/
	ptrShm = shmat(shmid, NULL, !SHM_RDONLY);   /*void *shmat(int shmid, const void *shmarr,  int flag);*/
	if(ptrShm == NULL)
	{
		printf("shmat() error: %s\n", strerror(errno));
		goto FAILED;
	}
	printf("shmat() success.\n"); 
	
	/*Begin to write*/
	printf("Begin to write shared memory.\n"); 
	while(1)
	{
		
	    /*input*/
		memset(&buf, 0x00, sizeof(buf));
		
		
		printf("server: ");
		if(fgets(buf, sizeof(buf)-1, stdin) < 0)
		{
			printf("fgets() error\n");
			fflush(stdin);
			continue;
		}
		fflush(stdin);
		
		/*P*/
		ret = sem_p(semid, 0);
		if(ret == -1)
		{
			printf("sem_p() error\n");
			goto FAILED;
			
		}
		
		/*write into shm*/
		memset(ptrShm, 0x00, SHM_SIZE);
		memcpy((char*)ptrShm, buf, strlen(buf));
				
		/*V*/
		ret = sem_v(semid, 1);
		if(ret == -1)
		{
			printf("sem_v() error\n");
			goto FAILED;
			
		}
		if(memcmp(buf, "exit", 4) == 0)
		{
			break;
		}
		
	}
	
	
	/*shmdt*/
	ret = shmdt(ptrShm);
	if(ret == -1)
	{
		printf("shmdt() error: %s\n", strerror(errno));
	}
	printf("shmdt() success.\n"); 
	
	return 0;
	
FAILED:
	/*shmctl IPC_RMID*/
	ret = shmctl(shmid, IPC_RMID, &shm_ds);
	if(ret == -1)
	{
		printf("shmctl() error: %s\n", strerror(errno));
	}
	printf("shmctl() success. Shm is deleted.\n"); 
	
	
	/*semctl IPC_RMID*/
	ret = semctl(semid, 0, IPC_RMID);
	if(ret == -1)
	{
		printf("semctl() error: %s\n", strerror(errno));
	}
	printf("semctl() success. Sem is deleted.\n"); 
	
	return -1;
	
}

(4)client.c 消费者进程

#include <stdio.h>  
#include <string.h>  
#include <stdlib.h>  
#include <unistd.h> 
#include <errno.h> 
#include <sys/types.h>  
#include <sys/ipc.h>  
#include <sys/shm.h> 
#include <sys/sem.h> 
 
#include "shm_sem.h"

int main(void)
{
	int ret;
	int shmid;
	int semid;
	void *ptrShm = NULL;
	struct shmid_ds shm_ds;
	struct semid_ds sem_ds;
	char buf[1024] = {0};
	union semun arg;
	unsigned short semvalArr[SEM_NUM];
		
	memset(&shm_ds, 0x00, sizeof(shm_ds));
	memset(&sem_ds, 0x00, sizeof(sem_ds));
	
	/*semget*/
	semid = semget((key_t)SEM_KEY, SEM_NUM, 0660|IPC_CREAT|IPC_EXCL);   /*int semget(key_t key, int nsems, int flag);*/
	if(semid == -1)
	{
		if(errno == EEXIST) /*no need to init sem*/
		{
			printf("semget() warning: %s\n", strerror(errno));
			semid = semget((key_t)SEM_KEY, 0, 0660|IPC_CREAT); /*nsems can be 0 when semid already exists*/
			if(semid == -1)
			{
				printf("semget() error: %s\n", strerror(errno));
				return -1;
			}
			printf("semget() success. semid=[%d]\n", semid);
		}
		else
		{
			printf("semget() error: %s\n", strerror(errno));
			return -1;
		}
	}
	else  /*need to init sem*/
	{
		printf("semget() success. semid=[%d]\n", semid);
				
		/*semctl(): init sem, set semvalArr[0]=1 semvalArr[1]=0*/
		semvalArr[0]= (unsigned short)1 ;
		semvalArr[1]= (unsigned short)0 ;
        arg.array = semvalArr;		
		ret = semctl(semid, 0, SETALL, arg);  /*int semctl(int semid, int semnm, int cmd [, union semun arg]); semnum between 0~nsems-1*/ 
		if(ret == -1)
		{
			printf("semctl() SETALL error: %s\n", strerror(errno));
			printf("init sem error.\n");
			
		   /*semctl IPC_RMID*/
	       ret = semctl(semid, 0, IPC_RMID);
	       if(ret == -1)
	       {
	       	printf("semctl() error: %s\n", strerror(errno));
	       }
	       printf("semctl() success. Sem is deleted.\n"); 
		   return -1;
		   
		}
		printf("semget() success.\n");
		
	}
	printf("semval[0]=[%d] semval[1]=[%d].\n", get_semval(semid, 0), get_semval(semid, 1));
	
	/*shmget*/
	shmid = shmget((key_t)SHM_KEY, SHM_SIZE, 0660|IPC_CREAT|IPC_EXCL);   /*int shmget(key_t key, size_t size, int flag);*/
	if(shmid == -1)
	{
		if(errno == EEXIST)
		{
			printf("shmget() warning: %s\n", strerror(errno));
			shmid = shmget((key_t)SHM_KEY, SHM_SIZE, 0660|IPC_CREAT);
			if(shmid == -1)
			{
				printf("shmget() error: %s\n", strerror(errno));
				return -1;
			}
		}
		else
		{
			printf("shmget() error: %s\n", strerror(errno));
			return -1;
		}
	}
	printf("shmget() success. shmid=[%d]\n", shmid);   
	
	/*shmat*/
	ptrShm = shmat(shmid, NULL, SHM_RDONLY);   /*void *shmat(int shmid, const void *shmarr,  int flag);*/
	if(ptrShm == NULL)
	{
		printf("shmat() error: %s\n", strerror(errno));
		goto FAILED;
	}
	printf("shmat() success.\n"); 
	printf("Begin to read shared memory.\n"); 
	
    //防止消费者在生产者之前启动,导致死锁
	while(1)
	{
		/*test semvalArr[0]*/
		ret = get_semval(semid, 1);
		if(ret < 0) /**/
		{
			printf("get_semval() error.\n");
			goto FAILED;
		}
		else if(ret == 0)   /*It suggests that the server hasn't start yet*/
			continue;
		else
			break;
	}
	
	/*Begin to read*/
	while(1)
	{
		/*P*/
		ret = sem_p(semid, 1);
		if(ret == -1)
		{
			printf("sem_p() error\n");
			goto FAILED;
			
		}
		
	    /*read shm*/
		memset(&buf, 0x00, sizeof(buf));
		memcpy(buf, (char*)ptrShm, sizeof(buf));
		
		
		/*V*/
		ret = sem_v(semid, 0);
		if(ret == -1)
		{
			printf("sem_v() error\n");
			goto FAILED;
			
		}
		
		printf("client: %s", buf);
		if(buf[0]=='\0')
			printf("\n");
			
		if(memcmp(buf, "exit", 4) == 0)
		{
			break;
		}
		
	}
	
	
	/*shmdt*/
	ret = shmdt(ptrShm);
	if(ret == -1)
	{
		printf("shmdt() error: %s\n", strerror(errno));
	}
	printf("shmdt() success.\n"); 
	
	/*shmctl IPC_RMID*/
	ret = shmctl(shmid, IPC_RMID, &shm_ds);
	if(ret == -1)
	{
		printf("shmctl() error: %s\n", strerror(errno));
	}
	printf("shmctl() success. Shm is deleted.\n"); 
	
	/*semctl IPC_RMID*/
	ret = semctl(semid, 0, IPC_RMID);
	if(ret == -1)
	{
		printf("semctl() error: %s\n", strerror(errno));
	}
	printf("semctl() success. Sem is deleted.\n"); 
	
	return 0;
	
FAILED:
	/*shmctl IPC_RMID*/
	ret = shmctl(shmid, IPC_RMID, &shm_ds);
	if(ret == -1)
	{
		printf("shmctl() error: %s\n", strerror(errno));
	}
	printf("shmctl() success. Shm is deleted.\n"); 
	
	/*semctl IPC_RMID*/
	ret = semctl(semid, 0, IPC_RMID);
	if(ret == -1)
	{
		printf("semctl() error: %s\n", strerror(errno));
	}
	printf("semctl() success. Sem is deleted.\n"); 
	
	return -1;
}

(5)makefile
编译

gcc -c pubfun.c
gcc -c server.c
gcc -c client.c
gcc pubfun.o server.o -o server
gcc pubfun.o client.o -o client

运行

./server
./client

测试结果
在这里插入图片描述在这里插入图片描述
在这里插入图片描述server进程输入exit则结束进程,client读取到exit后结束进程并删除共享内存和信号量。

  • 5
    点赞
  • 46
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
1. 目的: 调试、修改、运行模拟程序,通过形象化的状态显示,使学生理解进程的概念,了解同步和通信的过程,掌握进程通信和同步的机制,特别是利用缓冲区进行同步和通信的过程。通过补充新功能,使学生能灵活运用相关知识,培养创新能力。 2. 内容及要求: 1) 调试、运行模拟程序。 2) 发现并修改程序中不完善的地方。 3) 修改程序,使用随机数控制创建生产者消费者的过程。 4) 在原来程序的基础上,加入缓冲区的写互斥控制功能,模拟多个进程存取一个公共缓冲区,当有进程正在写缓冲区时,其他要访问该缓冲区的进程必须等待,当有进程正在读取缓冲区时,其他要求读取的进程可以访问,而要求写的进程应该等待。 5) 完成1)、2)、3)功能的,得基本分,完成4)功能的加2分,有其它功能改进的再加2分 3. 程序说明:   本程序是模拟两个进程,生产者(producer)和消费者(Consumer)工作。生产者每次产生一个数据,送入缓冲区中。消费者每次从缓冲区中取走一个数据。缓冲区可以容纳8个数据。因为缓冲区是有限的,因此当其满了时生产者进程应该等待,而空时,消费者进程应该等待;当生产者向缓冲区放入了一个数据,应唤醒正在等待的消费者进程,同样,当消费者取走一个数据后,应唤醒正在等待的生产者进程。就是生产者消费者之间的同步。   每次写入和读出数据时,都将读和写指针加一。当读写指针同样时,又一起退回起点。当写指针指向最后时,生产者就等待。当读指针为零时,再次要读取的消费者也应该等待。 为简单起见,每次产生的数据为0-99的整数,从0开始,顺序递增。两个进程的调度是通过运行者使用键盘来实现的。 4. 程序使用的数据结构 进程控制块:包括进程名,进程状态和执行次数。 缓冲区:一个整数数组。 缓冲区说明块:包括类型,读指针,写指针,读等待指针和写等待指针。 5. 程序使用说明   启动程序后,如果使用'p'键则运行一次生产者进程,使用'c'键则运行一次消费者进程。通过屏幕可以观察到两个进程的状态和缓冲区变化的情况。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值