【Linux】进程间通信(匿名管道、命名管道、共享内存)

引言

通信的本质是传递数据,是互相的。
进程间不能直接相互传递数据,进程具有独立性,所有的数据操作,都会发生写时拷贝,一定要通过中间媒介的方式来进行通信。
进程间通信的本质:让不同的进程先看到同一份资源(内存空间)。

进程通信

目的:
数据传输:一个进程需要将它的数据发送给另一个进程。
资源共享:多个进程之间共享同样的资源。
通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程)。
进程控制:有些进程希望完全控制另一个进程的执行,此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变。

进程间通信发展

1.管道
2.System V进程间通信
3.POSIX进程间通信

进程间通信分类

管道:
1.匿名管道pipe
2.命名管道

System V IPC
1.System V消息内存
2.System V共享内存
3.System V信号量

POSIX IPC
1.消息队列
2.共享内存
3.信号量
4.互斥量
5.条件变量
6.读写锁

管道

在这里插入图片描述

匿名管道

供具有血缘关系的进程,进行进程间通信。(常见于父子)
管道只能进行单向数据通信。
因此,父子进程关闭不需要的文件描述符,来达到构建单向通信的信道的目的。
在这里插入图片描述

为什么曾经要打开?
不打开读写,子进程拿到的文件打开方式必定和父进程一样,无法通信。

建立管道代码:

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>
#include <stdlib.h>

int main()
{
    
    
	int pipe_fd[2] = {
    
    0};
	if(pipe(pipe_fd) < 0)
	{
    
    
		perror("pipe");
		return 1;
	}
	printf("%d", %d\n",pipe_fd[0],pipe_fd[1]);
	pid_t id = fork();
	if(id<0)
	{
    
    
		perror("fork");
		return 2;
	}
	else if(id == 0)
	{
    
    
	//关闭读
		close(pipe_fd[0]);

		const char *msg = "hello parent ,i am child";
        int count =5;
		while(count)
		{
    
    
			write(pipe_fd[1],msg,strlen(msg));
			sleep(1);
			count--;
		}
		close(pipe_fd[1]);
		exit(0);
	}
	else
	{
    
    
	//关闭写
		close(pipe_fd[1]);
        
        char buffer[64];
        while(1)
        {
    
    
        	buffer[0] = 0;
        	ssize_t size = read(pipe_fd[0],buffer,sizeof(buffer) - 1);
        	if(size>0)
        	{
    
    
        		buffer[size] = 0;
        		printf("parent get message from child# %s\n",buffer);
        	}
        	else if(size == 0)
        	{
    
    
        		printf("pipe file close,child quit!\n");
        		break;
        	}
        	else
        	{
    
    
        		 //...........
        	}
        }  
        int status = 0;
        if(waitpid(id,&status,0)>0)
        {
    
    
        	printf("child quit,wait success!\n");
        }
        close(pipe_fd[0]);
	}
	return 0;
}

进程间同步:
如果管道里面没有消息,父进程(读端)在等待,等管道内部有数据就绪(子进程写入)。
如果管道里面写端已经写满了。继续写入是不能写的,它在等待,等待管道内部有空闲空间(父进程读走)。
管道本身就是一个文件。

管道的特性:
1.管道自带同步机制!
2.管道是单向通信的!
3.管道是面向字节流的!
4.管道只能保证是具有血缘关系的进程进行通信,常用于父子进程。
5.管可以保证一定程度的数据读取的原子性!

进程退出,曾经打开的文件也会被关掉。管道也是文件,管道的生命周期随进程!

读取关闭,一直写,毫无意义。本质就是在浪费系统资源,写进程会立马被OS终止掉!
![在这里插入图片描述](https://img-blog.csdnimg.cn/24f1b7304f6c418aa3445425893aae67.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBATGF5bWFu5YWJ772e,size_20,color_FFFFFF,t_70,g_se,x_16

命名管道

不相关的进程之间进行进程间通信叫做命名管道。
匿名管道:父子共享文件的特征
命名管道:文件路径具有唯一性,让进程看到同一个文件

共享内存

进程间通信的本质:要先让不同的进程看到同一份资源!
1.OS申请一块物理内存空间
2.OS将该内存映射进对应进程的共享区中(堆栈之间)
3.OS可以将映射之后的虚拟地址返回给用户

1.申请共享内存
2.进程1和进程2分别挂接对应的共享内存到自己的地址空间(共享区)
3.双方就看到了同一份资源!即可以进行正常通信了!

操作系统内部提供了通信机制的(IPC)ipc模块

查看共享内存:ipcs -m
所有的ipc资源都是随内核的,不随进程。
删除共享内存:ipcrm -m shmid号

//创建key
key_t k = ftok(PATH_NAME,PROJ_ID);
//申请共享内存
int shmid = shmget(k,SIZE,IPC_CREAT|IPC_EXCL);//共享内存如果不存在,创建;如果存在,出错返回。
//释放共享内存
shmctl(shmid,IPC_RMID,NULL);
//将当前进程和共享内存进行关联
char* start = (char*)shmat(shmid,NULL,0);
//将当前进程和共享内存去关联
shmdt(start);

1.共享内存的生命周期随OS
2.共享内存不提供任何同步与互斥的操作,双方彼此独立
3.共享内存是所有的进程间通信中,速度最快的

共享内存的大小:系统在分配shm的时候,是按照4kb为基本单位的。

key:是一个用户层生成的唯一键值,核心作用是为了区分“唯一性”,不能用来进行IPC资源的操作!
shmid:是一个系统给我们返回的IPC资源标识符,用来进行操作IPC资源。

类比:
key,文件的inode号
shmid,文件的fd

信号量

1.让进程看到同一份资源(内存空间),这份资源叫做临界资源。
2.进程内的所有代码,不是全部的代码都在访问临界资源,而是只有一部分在访问。可能造成数据不一致问题的是这部分少量的代码。(临界区代码)
3.为了避免数据不一致,保护临界资源,需要对临界区代码进行某种保护。(方法叫做互斥)
4.互斥:一部分空间任何时候,有且只能有一个进程在进行访问。串行化的执行(锁,二元信号量)
5.加锁和解锁是有对应的代码的。本质:对临界区进行加锁和解锁,完成互斥操作。

信号量:本质是一个计数器。(用来描述临界资源中,资源数目的计数器)
申请信号量:P操作(计算器减减)
释放信号量:V操作 (计数器加加)

PV操作的伪代码:

P:
begin:
Lock();
	if(count<=0)
	{
    
    
		goto begin;
	}
	else
	{
    
    
		count--;
	}
	Unlock();

//内部访问临界资源。

V:
Lock();
count++;
Unlock();

1.多个进程不能操作同一个count值
2.信号量是保护临界资源的安全性

信号量本身就是一个临界资源。

1.信号量,多进程环境下,如何保证信号量被多个进程看到?
semget,semctl,ftok() ---->Key ----->IPC
2.如果信号量的计数器的值是:1
则为1,0两种结果,二元信号量---->就是一种互斥语义。

猜你喜欢

转载自blog.csdn.net/qq_46994783/article/details/123373079