[Linux]进程间通信

1 进程间通信

进程间通信的本质:让进程看到同一块空间,俩进程对这一块内存进行读写。其通信的作用就是传输数据,资源共享

1.1 通信方式

  1. 管道(系统自带)
  2. System V (主机内)
  3. POSIX 进程间通信(常用于网络传输)

1.1.1 管道

管道是如何传输的呢?其实很好理解,在现实生活中管一般都是**单向传输**(一头传输,一头接收),其操作系统中管道也是如此。在这里插入图片描述

在操作系统中有俩种管道

  1. 匿名管道
  2. 命名管道
1.1.1.1 匿名管道

常用于有亲属关系的进程(也就是进程自己fork出来的),也就是”父子“进程间通信,利用的其实就是子进程所有的数据其实都是从父进程那拷贝的。那么父进程可以提前开打开一个文件,在创建子进程,那么父子进程就可以依靠这个文件进行通信。(涉及到了文件的知识

如图所示: >
在这里插入图片描述

那向文件写入不会写实拷贝吗?

其实我们并不是写入到文件中,而是写入到改文件的缓冲区。我们也没有修改数据和代码呀


实现父子进程间通信

  1. 父进程读写方式分别打开(毕竟如果子进程要读或者写总不可能自己打开吧)
  2. 各自关闭一个接口(管道是单向传输的,其实也可以不关)
  3. 传输数据与接收数据

C语言有个接口可以直接以读写方法打开,pipe很好就是管道
在这里插入图片描述

代码

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include<string.h>
 
int main()
{
	int pipefd[2];
	pipe(pipefd);//读写方式打开了同文件
	
	pid_t pid= fork();
	if(pid<0)//创建失败
	{
		perror("fork");
		return 1;
	}
	else if(pid>0)//子进程
	{
		close(pipefd[0]);
		const char *str="Hello i am child course";
		int count =3;
		while(count>0)
		{
			count--;
			write(pipefd[1],str,strlen(str));//写入
			sleep(1);
		}
		
		//写完后子进程退出
		printf("child course just writing done ,now eixt\n");
		exit(1);//
	}
	else //父进程
	{
		close(pipefd[1]);
		char buffer[1024];
		while(1)
		{	buffer[0]=0;//初始化
			//从缓冲区中读取
			ssize_t ret=read(pipefd[0],buffer,sizeof(buffer)-1);	
			buffer[ret]=0;
			if(ret==0)
			{
				break;
			}
			else
			{
				printf("the massage form child course $$%s\n",buffer);
			}
		}
		
	}
	
	//回收资源
	int status;
	wait(&status);	
	return 0;
}

运行结果:
在这里插入图片描述

底层运行原理
在这里插入图片描述

匿名管道的特性

  1. 只读不写 (读阻塞等待)
  2. 只写不读 (写阻塞等待,前提是IO流没有满)
  3. 读关闭,一直写入 (os会发信号杀死进程提高效率与节省资源)
  4. 一直读,写关闭 (read函数读取数为0,退出)
1.1.1.2 命名管道

匿名管道这种方式显然只适用于有血缘关系的进程,那么俩个毫无关系的进程如何进行通信呢?依旧坚持一个理念让不同的进程看到同一块空间,而命名管道使用的是管道文件(普通文件但或许会出错)

不同进程间通信

  1. 创建一块空间
  2. 一个写入一个接收

如何创建空间

C语言提供了一个接口mkfifo创建一个管道文件在这里插入图片描述
管道文件
在这里插入图片描述

代码:

service

#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include <fcntl.h>
#include<string.h>
#include<stdlib.h>

int main()
{
	if(mkfifo("fpp",0666)==-1)//创建管道文件
	{
		perror("mkfifo");
		return 1;
	}
	
	int fd=open("fpp",O_RDONLY);//打开文件

	//读取另一个进程的写入的数据
	char *buffer[1024];
	while(1)
	{
		buffer[0]=0;
		ssize_t ret=read(fd,buffer,sizeof(buffer)-1);
		buffer[ret]=0;
		printf("massage## %s",buffer);
	}
	close(fd);
	return 0;
}

client

#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include <fcntl.h>

int main()
{
	int fd=open("fpp",O_O_WRONLY);
	while(1)
	{
	  char str[64];
      str[0]=0;
      printf("请输入##### ");
      fflush(stdout);
      ssize_t ret=read(0,str,sizeof(str)-1);//从标准输入中获取
      str[ret]=0;
      
      write(fd,str,strlen(str));//将数据写入文件中
	}
}

运行效果
在这里插入图片描述


管道的特性
1.
结论

从上述俩个管道其实可以得出一个结论,管道这种通行方式所看到的同一块空间其实就是文件


1.1.2 System V

相比上面管道这种方式通信显然无法满足,假如我要多个进程读写呢?管道只适用于俩个进程间通信(毕竟只有俩个端口),那么就有一个新的通信方式System V

System V通信方式

在内存中开辟一块空间,再把这块空间通过页表 映射到共享区中,因为内存是 在内存中的。那么别的进程也可以映射到自己的共享区中,这就是所谓的System V的通信方式,前提是可以找到这块内存。

在这里插入图片描述

如何寻找

如果可以申请一块空间,那我如果没有限制的话我就可以一直申请,那么假如没有内存的限制,空间一多是否需要管理,且OS的工作是啥,进程、内存、驱动、文件管理。先描述在组织,在用一个数据结构维护起来。

如何创建共享内存呢?

C语言中有个接口shmge可以获取一块内存
在这里插入图片描述

如何获取唯一的key值呢???

C语言也提供了一个接口ftok
在这里插入图片描述

那那问题又来了,pathname和proj_id如何别的进程如何知道呢?不是需要唯一的key值吗

这个解决方法有很多,可以包同一个头文件,或者把他们写在同一个文件,然后读文件获取也可以

shared.h 头文件让不同进进程可以生成同一个pathname和proj_id

#pragma once 
#include<stdio.h>

#define PNAME "/home/wxb/linux/day3"
#define PID 0x6666
#define SIZE 4096 //内存块大小

service.c 服务端接收数据

  1. 开辟共享内存与释放共享内存

如何查看,释放共享内存呢?

查看共享内存
在这里插入图片描述
有俩种方法 1: shmctrl()C语言接口 2:ipcrm -shmid(标识符)指令
在这里插入图片描述

代码

#include "shared.h"
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#include<unistd.h>

int main()
{
	key_t key=ftok(PNAME,PID);//创建唯一的key值
	int shmid=shmget(key,SIZE,IPC_CREAT|IPC_EXCL|0666);
	if(shmid < 0)//生气共享内存
	{
		perror("shmget");//申请失败
		return 1;
	}
	
	sleep(5);
	shmctl(shmid,0,NULL);	
	return 0;
}

client 写入数据

#include "shared.h"
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#include<unistd.h>

int main()
{
	key_t key=ftok(PNAME,PID);//创建唯一的key值
	int shmid=shmget(key,SIZE,IPC_CREAT);//存在获取之
	if(shmid < 0)//生气共享内存
	{
		perror("shmget");//申请失败
		return 1;
	}
	
	return 0;
}

运行结果
在这里插入图片描述

使用共享内存
既然共享内存已经开辟那么,如何使用呢?shmat() 将进程挂接到开辟的共享内存中,既然可以挂接那么自然也要去挂接,shmdt()
在这里插入图片描述

那么shmat操作怎么和语言层面上的new malloc 一样 ,在堆上申请空间不也是如此吗?在内存申请空间在进行映射
在这里插入图片描述

service.c

	char *str=(char*)shmat(shmid,NULL,0);
	//to do
	shmdt(str);

client.c

	char *str=(char*)shmat(shmid,NULL,0);
	//to do
	shmdt(str);

如何查看一个共享内存的挂接数呢?

ipcs -m 面板中有一个 nattch这一列代表该共享内存的挂接数
在这里插入图片描述


共享内存双进程通信完整代码

service.c

#include"shared.h"
#include<sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include<unistd.h>
int main()
{
  //创建
  key_t key=ftok(PNAME,PID);
  int shmid=shmget(key,SIZE,IPC_CREAT|IPC_EXCL|0666);//不存在创建,存在报错
  if(shmid<0)
  {
    perror("shmget\n");
    return 1;
  }

  //挂接共享内存
  char * sh_mem= (char*)shmat(shmid,NULL,0);


  //从共享内存中读取
    while(1)
    {
      printf("%s\n",sh_mem);

      sleep(1);
    }


  //使用完毕取消(服务端可取可不取)
  shmdt(sh_mem);

  //释放共享内存
  shmctl(shmid,0,NULL);
}

client.h

#include"shared.h"
#include<sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include<unistd.h>

int main()
{
  //创建
  key_t key=ftok(PNAME,PID);
  int shmid=shmget(key,SIZE,IPC_CREAT);
  if(shmid<0)
  {
    perror("shmget\n");
    return 1;
  }

  //挂接共享内存
  char *sh_mem=(char*)shmat(shmid,NULL,0);


  //传输数据到共享内存
  char ch='A';
  while(1)
  {
     sh_mem[ch-'A']=ch;
      if(sh_mem[ch-'A'] > 'Z')
      {
        break;

      }
      ch++;
     sleep(2);
  }

  //取消挂接
   shmdt(sh_mem);
}

运行结果:
在这里插入图片描述

从上述的运行结果可以看出service端是不会只阻塞式等待client端发送的数据,而是一直在获取共享内存中的数据,那么就说明共享内存不提供同步与互斥的操作,双方独立

浅谈同步

多进程间互相通信,互相等待,内容一致性,的一种约束关系

浅谈互斥

当一个进程访问临界资源的时候,别的进程就要等待(阻塞式等待)之前进程访问完毕才可重新访问(解除阻塞

浅谈临界资源

某种资源在同一时间,只可以被一个人访问,这种资源就是临界资源,后面进程在访问资源中的代码叫做临界区


那么如何才可以让共享内存具备同步与互斥

只需要访问共享资源前加个锁、二元信号,下文介绍

是管道效率高还是system V的方式效率高?

system V 的效率的高,管道这个操作需要进行多次拷贝(io流),而system V(类似堆) 只需直接读取


1.2 信号量

信号量到底是啥????其实信号量是一个计数器,对临界资源描述个数的计数器,但是他其实也是临界资源

原子性

当一个进程向共享内存写入数据的时候无非俩中情况 1. 写了 2. 没写(这就是原子性)。而在多进程下无法保证。

如何让共享内存写入数据具有原子性呢?保证数据的不会被破坏

加锁也就是加限制,在访问这个共享内存期间只允许x个,其余的等待.

伪代码:

lock()
	…………写或者读数据
unlock();

当一个进程访问共享内存的时。他是否会使用全部的共享内存资源?? 当然不会,所以就可以把共享资源分成多份,用一个计数器(信号量)来维护进程使用资源

伪代码:

int count =3;
begin:
lock()
	if(count==0)
	{
		go to beign;
	}
	else
	{
		count—-;
	}
unlock();	

执行流程
在这里插入图片描述

上述的操作时申请信号量,访问共享内存,那进程退出也要让count(信号量)++,当信号量其实也是临界资源所也要要加锁,原子性

伪代码:

lock()
	count++;
unlock()

上述只能干的申请信号量与归还信号量,在操作系统学科中叫做pv操作

如果把信号量设置为1呢?

那其实即使所谓的互斥,只允许一个进程访问或者写入,这不就是管道吗

结尾

上述其实就是进程间通信的全部内容了,希望对你会有所帮助
在这里插入图片描述

  • 5
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值