Linux进程间通信

成功完成上一次平时作业后,成就感还是蛮大滴,兴趣也更浓厚了。收到了新一次的作业,便迫不及待地尝试、研究了起来。
本次作业,针对Linux进程间通信。操作系统提供的进程间的通信主要有两种方式:一种是较低级的、间接的通信方式,它是利用同步机构(如锁和信号量),通过共享的存储器来实现的,因装置常常限制为一个字或几个字的信息存储,因而进程间传递的只能是单一的信号;另一种是进程通信(IPC)的机制,它能传递大量的信息,是实现进程间更有效同步的直接的进程通信方式。
下面通过具体的题目来深入探讨吧。
1、利用管道实现多进程间通信
利用管道实现进程间的通信,顾名思义是在进程之间搭建一个通道。然后从通道的一段放入数据,另一端取出数据,从而实现一方发出信息、另一方接收信息的功能,即进程间的通信。
管道通信具有单向,无结构,先进先出的字节流特点。根据管道提供的应用接口的不同,可将管道分为无名管道和命名管道。
(1)无名管道
无名管道是一种特殊类型的文件,完全由操作系统管理和维护。因为其存储位置只有亲缘关系的进程知道,所以只能用于亲缘关系的进程之间的通信。
无名函数的创建函数为int pipe(int fd[2]),该函数定义于头文件unistd.h中。创建一个无名管道fd包含2个文件描述符的数组,fd[0]用于读,fd[1]用于写若成功返回0,否则返回-1。
一般某个进程用于读,另一个进程用于写,用于读的进程需要close(fd[1]),用于写的进程需要close(fd[0])。
在这里插入图片描述在这里插入图片描述在这里插入图片描述
若只想读取某个进程的结果,或写入某个进程的输入可以使用popen函数。FILE *popen(const char *command,const char *type),定义于头文件stdio.h中,command执行的shell命令,type命令的输入输出类型(r:打开命令执行的标准输出w:打开命令执行的标准输入),成功返回文件I/O流否则返回NULL。
在这里插入图片描述在这里插入图片描述在这里插入图片描述
这里,正序输出了当前目录下所有文件和子目录。
(2)命名管道
命名管道作为一个特殊的文件保存在文件系统中,任意一个进程都可以对命名管道进行读写,只要进程具有相应的读写权限。
命名管道通过int mkfifo(const char *pathname,mode_t mode)函数创建,pathname为文件路径名,mode为存取权限,若成功返回0,否则返回-1。;
在这里插入图片描述在这里插入图片描述
以上就是利用管道实现多进程间通信的基本内容及实例程序啦,感觉这一部分要多接触,尝试不同的情况。比如,command执行的shell命令、存取权限mode,每一种对应有什么效果,都是要亲自尝试,才能理解、掌握的呀。
2、利用信号量机制实现生产者消费者问题(用进程通信进行模拟)
为了实现进程间正确的协作,操作系统提供了实现进程协作的措施和方法,称为同步机构。同步机构有两种,一种有锁和上锁、开锁操作,另一种是称为信号灯(信号量)的同步机构,包括信号灯和P、V操作。使用信号灯能方便地解决临界区问题。
信号灯是一个确定的二元组(s,q),s是一个具有非负初值的整型变 量,q是一个初始状态为空的队列。操作系统利用信号灯的状态对并发进程和共享资源进行控制和管理。
信号灯是整型变量:
变量值>=0 时,表示绿灯,进程执行;
变量值< 0 时,表示红灯,进程停止执行。
P操作:
对信号灯s的 p操作记为 p(s),p(s)是一个不可分割的原语操作,即取信号灯值减1,若相减结果为负,则调用p(s)的进程被阻,并插入到该信号灯的等待队列中,否则可以继续执行。
V操作:
对信号灯s的 v操作记为 v(s),v(s)是一个不可分割的原语操作,即取信号灯值加1,若相加结果大于零,进程继续执行,否则,要帮助唤醒在信号灯等待队列上的一个进程。
生产者-消费者问题是一种典型的同步问题。计算机系统中的每个进程都可以消费(使用)或生产(释放)某类资源,这些资源可以是硬资源(如主存缓冲区、外设或处理机),也可以是软资源(如临界区、消息等)。当系统中进程使用某一资源时.可以看做是消耗,且将该进程称为消费者。而当某个进程释放资源时,则它就相当于一个生产者。当两个进程之间互通信件时,也可抽象为一一个发信件进程产生消息,然后把它放置到缓冲存储器中去,与此平行地,一个读消息进程从缓冲存储器中移走信息并处理(消费)它。
同步关系:
生产者:当有界缓冲区中无空位置时,要等待;
向有界缓冲区放入物品后,要发消息。
消费者:当有界缓冲区中无物品时,要等待;
从有界缓冲区取出物品后,要发消息。
问题图示:
在这里插入图片描述
信号灯设置:
两个同步信号灯:
sb :表示空缓冲区的数目,初值 = n
sa : 表示满缓冲区(即信息)的数目,初值 = 0
一个互斥信号灯:
mutex:表示有界缓冲区是否被占用,初值 = 1
同步描述:
在这里插入图片描述
伪代码描述:
在这里插入图片描述在这里插入图片描述

具体实现代码:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/sem.h>
#include <sys/types.h>

#define SHM_MODE 0600
#define SEM_MODE 0600
#define SEM_FULL 0
#define SEM_EMPTY 1
#define MUTEX 2
#define MAX_BUFFER_SIZE 10
const int N_CONSUMER = 2;  //消费者数量
const int N_PRODUCER = 2;  //生产者数量
const int N_BUFFER = 10;  //缓冲区容量
const int N_WORKTIME = 6;  //工作次数
int shm_id = -1;
int sem_id = -1;
pid_t child;
pid_t parent;

struct my_buffer
{
	int head;
	int tail;
	char str[MAX_BUFFER_SIZE];
	int is_empty;
}*buf;

char getRandChar()  //得到A~Z的一个随机字母  
{
	char letter; 
	srand((unsigned)(getpid())); 
	letter = (char)((rand() % 26) + 'A'); 
	return letter; 
}

//semSetId 表示信号量集合的 id
//semNum 表示要处理的信号量在信号量集合中的索引
void waitSem(int semSetId,int semNum)  //P操作
{
	struct sembuf sb;
	sb.sem_num = semNum;
	sb.sem_op = -1;//表示要把信号量减一
	sb.sem_flg = SEM_UNDO;//
	//第二个参数是 sembuf [] 类型的,表示数组
	//第三个参数表示 第二个参数代表的数组的大小
	if(semop(semSetId,&sb,1) < 0){
		perror("waitSem failed");
		exit(1);
	}
}

void sigSem(int semSetId,int semNum)  //V操作
{
	struct sembuf sb;
	sb.sem_num = semNum;
	sb.sem_op = 1;
	sb.sem_flg = SEM_UNDO;
	//第二个参数是 sembuf [] 类型的,表示数组
	//第三个参数表示 第二个参数代表的数组的大小
	if(semop(semSetId,&sb,1) < 0){
		perror("waitSem failed");
		exit(1);
	}
}

void init()
{
	if((shm_id = shmget(IPC_PRIVATE,MAX_BUFFER_SIZE,SHM_MODE)) < 0)  //缓冲区分配以及初始化
	{
		perror("create shared memory failed");
		exit(1);
	}
	buf = shmat(shm_id, 0, 0);   //将申请的共享内存附加到申请通信的进程空间
	if (buf == (void*)-1)
	{  
        perror("add buffer to using process space failed!\n");  
        exit(1);  
    	}  
	if((sem_id = semget(IPC_PRIVATE,3,SEM_MODE)) < 0)  //创建信号量
	{
		perror("create semaphore failed");
		exit(1);
	}
	
	if(semctl(sem_id,SEM_FULL,SETVAL,0) == -1)  //将索引为0的信号量设置为0
	{						
		perror("sem set value error! \n");		
		exit(1);
	}
	if(semctl(sem_id,SEM_EMPTY,SETVAL,10) == -1)  //将索引为1的信号量设置为10
	{										
	 	perror("sem set value error! \n");
	 	exit(1);
	}
	if(semctl(sem_id,MUTEX,SETVAL,1) == -1)  //将索引为2的信号量设置为1
	{												
	 	perror("sem set value error! \n");
	 	exit(1);
	}
	buf -> head = 0;  
    	buf -> tail = 0;  
	buf -> is_empty = 1;  
}

void main()
{
	init();
	for(int i = 0; i < N_PRODUCER; i++)
	{
		parent = fork();
		if(parent < 0)
		{
			perror("the fork failed");
			exit(1);
		}
		else if(parent == 0)
		{
			buf = shmat(shm_id, 0, 0);   //将申请的共享内存附加到申请通信的进程空间
			if (buf == (void*)-1)
			{  
        		perror("add buffer to using process space failed!\n");  
        		exit(1);  
    			}  
		int count = 0;
		for(int j = 0; j < N_WORKTIME; j++)
		{
			waitSem(sem_id, SEM_EMPTY);
			waitSem(sem_id, MUTEX);
			sleep(1);
			printf("生产者进程%d,PID = %d\n", i + 1, getpid());
			char c = getRandChar(); 
			buf -> str[buf->tail] = c; 
			buf -> tail = (buf->tail + 1) % MAX_BUFFER_SIZE; 
			buf -> is_empty = 0;
		int p; 
		printf("缓冲区数据:"); 
		p = (buf->tail-1 >= buf->head) ? (buf->tail-1) : (buf->tail-1 + MAX_BUFFER_SIZE); 
		for (p; !(buf -> is_empty) && p >= buf -> head; p--) 
		{ 
			printf("%c", buf -> str[p % MAX_BUFFER_SIZE]);
		} 
		printf("\n生产者 %d  放入 '%c'. \n\n", i + 1, c); 
		fflush(stdout);
		sigSem(sem_id, MUTEX);
		sigSem(sem_id, SEM_FULL);
		}
		shmdt(buf);  
            	exit(0); 
		}
	}
	for(int i = 0; i < N_CONSUMER;i++)
	{
		child = fork();
		if(child < 0)
		{
			perror("the fork failed");
			exit(1);
		}
		else if(child == 0) 
		{ 
			int count = 0; 
			buf = shmat(shm_id, 0, 0); 
			if (buf == (void*)-1) 
			{
				 perror("add buffer to using process space failed!\n");
				 exit(1);
			 } 
		for(int j = 0; j < N_WORKTIME; j++)
			{
				waitSem(sem_id, SEM_FULL);
				waitSem(sem_id, MUTEX);
				sleep(1);
				printf("消费者进程%d,PID = %d\n", i + 1, getpid());
				char lt = buf -> str[buf -> head]; 
				buf -> head = (buf -> head + 1) % MAX_BUFFER_SIZE;
 				buf -> is_empty = (buf->head == buf->tail);
				int p;
				printf("缓冲区数据:"); 
				p = (buf -> tail - 1 >= buf -> head) ? (buf -> tail-1) : (buf -> tail - 1 + MAX_BUFFER_SIZE); 
				for (p; !(buf -> is_empty) && p >= buf -> head; p--) 
				{
					 printf("%c", buf -> str[p % MAX_BUFFER_SIZE]);
				 } 
				 printf("\n消费者 %d  取出 '%c'. \n\n", i + 1, lt);	
				fflush(stdout);
				sigSem(sem_id,MUTEX);
				sigSem(sem_id,SEM_EMPTY);
			}
		shmdt(buf);  //将共享段与进程之间解除连接 
        	exit(0);
		}
	}  
	//主进程最后退出  
	while (wait(0) != -1);   //将共享段与进程之间解除连接
	shmdt(buf);   //对共享内存区执行控制操作  
	shmctl(shm_id,IPC_RMID,0);  //当cmd为IPC_RMID时,删除该共享段  
	shmctl(sem_id,IPC_RMID,0); 
	printf("主进程运行结束!\n"); 
	fflush(stdout); 
	exit(0); 
}

程序运行截图:
在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述
·遇到的一个小问题
在这里插入图片描述
查阅资料得知,C语言中使用const不能直接当成常量,const修饰的变量一般只当成“只读的”,而不是将其作为一个常量。所以,在全局声明一个数组,不能使用const变量做下标。将const int MAX_BUFFER_SIZE=10;改为#define MAX_BUFFER_SIZE 10 问题解决。
终于成功地利用信号量机制实现了生产者消费者问题,超开心!不得不承认,这个过程还是很考验人的。遇到了一些大大小小的问题,一直弄不好感到有些烦躁,甚至萌生了放弃的念头。不过,好在自己还是坚持了下来,最终取得胜利。分析一下,感到实现过程有些困难,主要是因为这个问题本身稍显复杂,再加上在理论知识学习过程中,写的是伪代码,而非实际的代码。很多函数需要通过查阅资料,一点点理解、应用,才能将伪代码转化为实际的代码。这也说明,平时自己动手操作是十分重要的,只有这样才能真正做到把所学知识融会贯通。未来也要继续努力,攻克一个个难题,不断进步呀。

参考资料
【1】https://www.cnblogs.com/CodingUniversal/p/7400219.html
【2】https://blog.csdn.net/qq_31490151/article/details/78984593
【3】https://blog.csdn.net/zhaohaibo_/article/details/89322902

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值