一、实验要求:

用P,V操作和信号量实现进程同步和互斥生产者消费者问题 。

二、代码思路:

1.同步关系:缓冲区不为空时才能消费,缓冲区满时就不能生产;
2.互斥关系:缓冲区是一个临界资源,因此必须互斥使用;
设置两个同步信号量FULL,EMPTY,一个互斥信号量MUTEX。

*信号量操作使用到的函数:semget(),semctl(),semop():
用semget()函数,semctl()函数来对信号量进行创建和初始化
程序最后使用semctl()函数删除信号量来释放内存。

3.缓冲区的实现:使用共享内存代表缓冲区。
共享内存的原理是将进程的地址空间映射到一个共享存储段。

*共享内存操作使用到的函数:shmget(),shmat(),shmctl():
使用shmget()函数实现共享主存段的创建,shmget()返回共享内存区的ID。
对于已经申请到的共享段,进程需用shmat()函数把它附加到自己的虚拟空间中才能对其进行读写
对共享内存操作完毕后使用shmctl()函数撤销共享内存段。

4.缓冲区的数据结构:缓冲区采用环形队列表示,利用头、尾指针来存放读取数据,
以及判断队列是否为空。

5.P,V操作:使用semop()函数对信号量进行+1,-1操作来实现P,V操作。
6.创建进程:使用循环创建2个生产者和2个消费者进程,用fork()函数来创建;
7.生产过程:利用rand()函数产生A~Z的随机字符来作为生产内容,存放到缓冲区中(环形队列的对尾)。
8.消费过程:从环形队列的队首取出存放的字符表示消费。

三、代码

#include <stdio.h>//支持printf
#include <stdlib.h>//支持exit
#include <unistd.h>//支持sleep
#include <time.h>
#include <sys/ipc.h>
#include <sys/shm.h>//支持shmget shmat等
#include <sys/sem.h>//支持semget等 
#include <sys/types.h>
#include <sys/wait.h>//支持wait

#define MAX_BUFFER_SIZE 3//缓冲区最大值
/*信号量的索引,用于semctl函数参数*/
#define SEM_FULL 0//同步信号量,表示满缓冲区的值,初始值0
#define SEM_EMPTY 1//同步信号量,表示空缓冲区的值初始值为MAX_BUFFER_SIZE
#define MUTEX 2 //互斥信号量,用于对缓冲区进行操作
struct my_buffer//缓冲区采用环形队列表示
{
	int head;//头
	int tail;//尾
	char str[MAX_BUFFER_SIZE];//用来存放字母的数组
	int num;  /*缓冲区里字母数量*/
	int is_empty;//是否为空,=1空
};

const int N_CONSUMER = 2;/*消费者数量*/
const int N_PRODUCER = 2;/*生产者数量*/
const int N_WORKTIME = 5;/*工作次数*/

pid_t pid_c;//消费者
pid_t pid_p;//生产者

/*得到A~Z的一个随机字母*/ 
char getRandChar()
{
	char letter;
	srand((unsigned)time(NULL));//根据当前时间产生随机数种子
	letter = (char)((rand() % 26) + 'A');
	return letter;
}

/*sem_id 表示信号量集合的 id*/
/*sem_num 表示要处理的信号量在信号量集合中的索引*/
/*P操作*/
void P(int sem_id, int sem_num)
{
         /*semop()函数:
          * 用来对信号量集表示符为semid中的一个或多个信号量进行P或V操作
          * 函数原型:int semop(int semid, struct sembuf *sops, unsigned nsops)
          * 函数调用失败返回-1
          * 第一个参数semid表示信号量集的标识符;
          * 第三个参数nsops表示进行操作信号量的个数,设置等于1,表示只完成对一个信号量的操作
          * 第二个参数spos是指向进行操作的信号量集结构体数组的首地址,
          * 此结构的具体说明如下:
          * struct sembuf {
          *     short sem_num; //信号量集合中的信号量序号,0代表第1个信号量
          *     short sem_op;//对信号量的操作
          *     //若sem_op>0进行V操作,信号量值加sem_op,表示进程释放控制的资源 
          *     //若sem_op<0进行P操作,信号量值减sem_op,若(semval-val)<0(semval为该信号量值),则调用进程阻塞,直到资源可用
          *     short sem_flg;  //0 设置信号量的默认操作
          *     };
          */
	struct sembuf spos;
	spos.sem_num = sem_num;//信号量编号
	spos.sem_op = -1;/*表示要把信号量减一*/
	spos.sem_flg = SEM_UNDO;
	if (semop(sem_id, &spos, 1) < 0) {
		perror("P failed");
		exit(1);
	}
}

/*V操作*/
void V(int sem_id, int sem_num)
{
	struct sembuf spos;
	spos.sem_num = sem_num;
	spos.sem_op = 1;/*表示要把信号量+1*/
	spos.sem_flg = SEM_UNDO;
	if (semop(sem_id, &spos, 1) < 0) {
		perror("V failed");
		exit(1);
	}
}

void produce(struct my_buffer *buffer,int i){
        
        /*生产产品*/
        printf("我是第 %d 个生产者进程,PID = %d\n", i + 1, getpid());
        char c = getRandChar();/*随机获取字母*/
        buffer->str[buffer->tail] = c;//写入队尾
        buffer->tail = (buffer->tail + 1) % MAX_BUFFER_SIZE;//尾指针+1
        buffer->is_empty = 0;//非空标记设置为0
        buffer->num++;//缓冲区数据+1

        /*打印缓冲区中的数据*/
        printf("缓冲区数据(%d个):", buffer->num);
        /*tail-1>=head表示没有满*/
        int p;//缓冲区的数据的下标,从队尾往对首输出
        p = (buffer->tail - 1 >= buffer->head) ? (buffer->tail - 1) : (buffer->tail - 1 + MAX_BUFFER_SIZE);
        for (p; !(buffer->is_empty) && p >= buffer->head; p--)
        {                    
             printf("%c", buffer->str[p % MAX_BUFFER_SIZE]);
        }
        printf("\t 生产者 %d  放入 '%c'. \n", i + 1, c);
        fflush(stdout);

}
void consume(struct my_buffer *buffer,int i){

        /*消费数据*/
        printf("我是第 %d 个消费者进程,PID = %d\n", i + 1, getpid());                                
        char lt = buffer->str[buffer->head];//取出对首数据
        buffer->head = (buffer->head + 1) % MAX_BUFFER_SIZE;//头指针+1
        buffer->is_empty = (buffer->head == buffer->tail);  //头指针追上尾指针则为空
        buffer->num--;//缓冲区数据-1

        /*打印缓冲区中的数据*/
        printf("缓冲区数据(%d个):", buffer->num);
        int p;
        p = (buffer->tail - 1 >= buffer->head) ? (buffer->tail - 1) : (buffer->tail - 1 + MAX_BUFFER_SIZE);
        for (p; !(buffer->is_empty) && p >= buffer->head; p--)
        {
             printf("%c", buffer->str[p % MAX_BUFFER_SIZE]);
        }
        printf("\t 消费者 %d  取出 '%c'. \n", i + 1, lt);
        fflush(stdout);
}

int main(int argc, char ** argv)
{
        int i,j;
        /*shmget()函数用来创建一个共享内存对象,创建成功则返回共享内存标识符,失败返回-1
         * 原型:int shmget(key_t key, size_t size, int shmflg)
         * size表示新建的共享内存大小,以字节为单位。
         * shmflg参数为模式标志参数,使用时需要与IPC对象存取权限
         *(如0600表示用户可以读写改内存)进行|运算来确定信号量集的存取权限 */
        int shm_id;
	shm_id = shmget(IPC_PRIVATE, MAX_BUFFER_SIZE, 0600 | IPC_CREAT);   
	if (shm_id < 0)
	{
		perror("create shared memory failed");
		exit(1);
	}
        /*shmat()函数:连接共享内存标识符为shmid的共享内存,连接成功后把共享内存区对象映射到调用进程的地址空间,随后可像本地空间一样访问。
         * 原型:void *shmat(int shmid, const void *shmaddr, int shmflg)
         * shmaddr:指定共享内存出现在进程内存地址的什么位置
         * shmflg:SHM_RDONLY:为只读模式,其他为读写模式
         * 调用成功则返回:附加好的共享内存地址,失败返回-1*/

	struct my_buffer *buffer;//实例化一个环形队列的指针
        /*用shmat()函数连接共享内存,返回共享内存的起始地址*/
	buffer = shmat(shm_id, 0, 0); 
	if (buffer == (void*)-1)
	{
		perror("add buffer to using process space failed!\n");
		exit(1);
	}

        /*semget()函数创建一个新的信号量集,原型:
         * int semget(key_t key, int nsems, int semflg);
         * 返回值:如果成功则返回信号量集的IPC标识符。如果失败则返回-1。
         * nsems表示一个新的信号量集中应该创建新的信号量的个数*/
        int sem_id;
	if ((sem_id = semget(IPC_PRIVATE, 3,0600 | IPC_CREAT)) < 0)
	{      //用semget函数创建3个信号量SEM_EMPTY,SEM_FULL,MUTEX 
		perror("create semaphore failed! \n");
		exit(1);
	}

        /*用semctl函数为信号量赋初值*/
        /*semctl()函数:
         * 原型:int semctl(int semid, int semnum, int cmd, union semun arg);
         * semnum:信号量集数组上的下标,表示某一个信号量
         * cmd:操作
         * 操作需要的值来自共用体union semun中的参数*/
        /*SETVAL操作初始化信号量的值*/
	if (semctl(sem_id, SEM_FULL, SETVAL, 0) == -1)
	{	/*将索引为0的信号量SEM_FULL赋初值为0*/
		perror("sem set value error! \n");
		exit(1);
	}

	if (semctl(sem_id, SEM_EMPTY, SETVAL, 3) == -1)
	{	/*将索引为1的信号量SEM_EMPTY赋初值为3*/
		perror("sem set value error! \n");
		exit(1);
	}
	if (semctl(sem_id, MUTEX, SETVAL, 1) == -1)
	{      /*将索引为3的信号量MUTEX赋初值为1*/
		perror("sem set value error! \n");
		exit(1);
	}
        /*缓冲区设置为空*/
	buffer->head = 0;
	buffer->tail = 0;
	buffer->is_empty = 1;
	buffer->num = 0;

        /*创建生产者进程*/
	for (i = 0; i < N_PRODUCER; i++)
	{
		pid_p = fork();
		if (pid_p < 0)/*进程创建失败*/
		{
			perror("the fork failed");
			exit(1);
		}
		else if (pid_p == 0)
		{
                        /*用shmat()函数连接共享内存,返回共享内存的起始地址*/
			buffer = shmat(shm_id, 0, 0);
			if (buffer == (void*)-1)
			{
				perror("add buffer to using process space failed!\n");
				exit(1);
			}
		
			for (j = 0; j < N_WORKTIME; j++)
			{
				P(sem_id, SEM_EMPTY);//空缓冲区-1
				P(sem_id, MUTEX);//占用缓冲区
				
                                sleep(2); 
                                produce(buffer,i);//生产内容
                                printf("生产者%d工作了%d次\n",i+1,j+1);

	      
		           	V(sem_id, MUTEX);//释放缓冲区
                                sleep(5);          
			        V(sem_id, SEM_FULL);//满缓冲区+1
			}
                        /*shmdt()函数说明:与shmat函数相反,是用来断开与共享内存附加点的地址,禁止本进程访问此片共享内存
                           函数原型:int shmdt(const void *shmaddr)*/
			/*将共享内存与进程之间解除连接  */
			shmdt(buffer);
			exit(0);
		}
	}

	for (i = 0; i < N_CONSUMER; i++)
	{
		pid_c = fork();
		if (pid_c < 0)/*调用fork失败*/
		{
			perror("the fork failed");
			exit(1);
		}
		else if (pid_c == 0)//创建子进程成功
		{
	
                        /*用shmat()函数连接共享内存,返回共享内存的起始地址*/
			buffer = shmat(shm_id, 0, 0);  
			if (buffer == (void*)-1)
			{
				perror("add buffer to using process space failed!\n");
				exit(1);
			}

			for (j = 0; j < N_WORKTIME; j++)
			{
                    
				P(sem_id, SEM_FULL);//满缓冲区-1
				P(sem_id, MUTEX);//占用缓冲区
	                        
			        sleep(2);

                                consume(buffer,i);//消费者消费
	                        printf("消费者%d工作了%d次\n",i+1,j+1);
	        
				V(sem_id, MUTEX);//释放缓冲区
				V(sem_id, SEM_EMPTY);//空缓冲区+1
                

			}
			/*将共享内存与进程之间解除连接  */
			shmdt(buffer);
			exit(0);
		}
	}

	/*主进程最后退出  */
	while (wait(0) != -1);//子进程调用结束
	shmdt(buffer);  /*将共享内存与进程之间解除连接*/
	shmctl(shm_id, IPC_RMID, 0);/*当cmd为IPC_RMID时,删除该共享段*/  
	semctl(sem_id, IPC_RMID, 0);/*删除信号量集*/
	printf("主进程运行结束!\n");
	fflush(stdout);
	exit(0);
	return 0;
}

四、运行结果

在这里插入图片描述

在这里插入图片描述

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐