进程间通信原理解答

进程间通信原理解答

进程间通信

目的

进程间通信目的
1、数据传输:一个进程需要将它的数据发送给另一个进程

2、资源共享:多个进程之间共享同样的资源。

3、通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程)。

4、进程控制:有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变。

如何做到通信

1、进程运行的时候是具有独立性的(数据层面)

2、进程间通信,一般一定要借助第三方(OS)资源

3、通信的本质就是"数据的拷贝" ,进程A将数据"拷贝"给OS,OS将数据"拷贝"给进程B,OS一定要提供一段内存区域,且这段内存区域能够被双方进程看到.

进程间通信:本质是让不同的进程,先看到同一份资源(内存,文件内核缓冲内),资源由OS系统中的模块提供,就有了不同的进程间通信方式。

资源就是一块空间,以及维护这块空间的相关数据结构,以及相关方法。

匿名管道pipe

什么是管道

管道是Unix中最古老的进程间通信的形式。我们把从一个进程连接到另一个进程的一个数据流称为一个“管道”。

在这里插入图片描述
在这里插入图片描述

站在文件描述符角度-深度理解管道

在这里插入图片描述

代码实现通信

代码实现,子进程写数据,父进程读取数据

1 #include<stdio.h>                                                                                                   
  2 #include<unistd.h>
  3 #include<sys/types.h>
  4 #include<sys/wait.h>
  5 #include<stdlib.h>
  6 #include<string.h>
  7 
  8 int main()
  9 {
 10   //子进程写,父进程读
 11   int fd[2]={0};
 12   if(pipe(fd)<0)  //pipe返回值0,表示创建成功,返回值-1,表示创建失败
 13   {
 14     perror("pipe!");
 15     return 1;
 16   }
 17 
 18   //printf("fd[0]:%d\n",fd[0]);  //f[0]默认是3号文件描述符,f[0]默认读文件
 19   //printf("fd[1]:%d\n",fd[1]);  //f[1]默认为 4号文件描述符,f[1]默认写文件
 20 
 21   pid_t id=fork();    //创建子进程
 22   if(id==0)
 23   {
         //子进程
 25     //想让子进程写,就要关闭读
 26     close(fd[0]); //关闭读
 27     
 28     const char *msg="hello father ,I am child";
 29     int count=10;
 30     while(count)
 31     {
 32       write(fd[1],msg,strlen(msg));
 33       count--;
 34       sleep(1);
 35     }
 36     close(fd[1]);
 37     exit(0);
 38 
 39   }                                                                                                                 
 40   //父进程
 41   //让父进程只读不写,关闭写通道
 42   close(fd[1]);
 43   char buff[64];
 44   while(1)
 	 {
 46    ssize_t s = read(fd[0],buff,sizeof(buff)); //返回值0表示读到文件结尾,读到buff里,返回值大于0,表示读到多少数据
 47    if(s>0)
 48    {
 49      buff[s]='\0';  //结尾保存\0
 50      printf("child send to father# %s\n",buff);
 51    }
 52    else if(s==0)   //s==0,说明读到结尾了
 53    {
 54      printf("read file end!\n");
 55      break;
 56    }
 57    else 
 58    {
 59      printf("read error!\n");  //返回值小于0,读取失败
 60      break;                                                                                                         
 61    }
 62    //close(fd[0]);
 63    //break;
 64   }
	//sleep(3);
 66   //int status=0;
 67   waitpid(id,NULL,0);                                                                                               
 68   //printf("child quit!: sig:%d\n",status&0x7F);
 69   return 0;
 70 }



在这里插入图片描述
结果: 实现通信,父进程读取了子进程的信息。
在这里插入图片描述
注意
1、管道内部提供了同步与互斥功能。就是一个进程在读/写时,另一个只能在等待状态。不允许两个进程同时进行读写操作。

如果管道里没有数据,读进程会被挂起
如果管道里写满了数据,写进程会被挂起

挂起:就是R状态,变为非R状态,当前进程的PCB挂到等待队列。

当管道里有数据,或有空间,对应进程就被被唤醒,就是将进程的非R状态变为R状态,当前进程的PCB放入运行队列中。

2、如果写段关闭,读端就会读到 '0’值,代表文件结尾。

3、如果打开文件的进程退出了,文件也就会被释放掉。文件的生命周期随进程

4、管道是提供流式服务的。就是写端想写多少就写多少,读端想怎么读就怎么读。

5、管道属于半双工通信。

6、匿名管道适合具有血缘关系的的进程进行进程间通信,例如父子进程。、

7、不写,一直读,会读阻塞
不读,一只写,会写阻塞
write写完,关闭,read返回值为0
read关闭,一直写,写方会被操作系统杀掉,写入无意义。

命名管道

1、管道应用的一个限制就是只能在具有共同祖先(具有亲缘关系)的进程间通信。

2、如果我们想在不相关的进程之间交换数据,可以使用FIFO文件来做这项工作,它经常被称为命名管道。

3、命名管道是一种特殊类型的文件

在这里插入图片描述

代码实现通信

通信实例1: 用户端和服务端通信,client从键盘读取数据发送给server.
在这里插入图片描述

comm.h

 #pragma once                                                                                                        
  2 #include<stdio.h>
  3 #include<sys/types.h>
  4 #include<sys/stat.h>
  5 #include<fcntl.h>
  6 
  7 #include<unistd.h>
  8 #include<string.h>
  9 
 10 #define FILE_NAME  "myfifo"
 11 

在这里插入图片描述

server.c

#include "comm.h"                                                                                                   
  2 #include<sys/wait.h>
  3 #include<stdlib.h>
  4 
  5 int main()
  6 {
  7   if(mkfifo(FILE_NAME,0644)<0)  //返回值小于0创建文件失败
  8   {
  9     perror("mkfifo");
 10     return 1;
 11   }
 12 
 13   int fd=open(FILE_NAME,O_RDONLY);   //只读打开文件
 14   if(fd<0)  //打开失败
 15   {
 16     perror("open error!\n");
 17     return 2;
 18   }
 19 
 20   char msg[128];
 21 
 22   //读文件
 23   while(1)
 	  {
 25     msg[0]=0;  //清空数组,相当于第一个字符为'\0'
 26 
 27     //从fd文件里读,读到msg中,期望读sizeof(msg)-1个字节,返回值是实际读多少,返回值若<0,读取失败
 28     ssize_t s=read(fd,msg,sizeof(msg)-1);  
 29 
 30     if(s>0)  //读取成功
 31     {
 32        msg[s]=0;
 33        printf("client# %s\n",msg);
 34     }
 35     else if(s==0)  //对端关闭
 36     {
 37       printf("client quit!\n");
 38       break;
 39     }
 40     else           //读取失败                                                                                       
 41     {
 42       printf("read error!\n");
 43       break;
 44     }
 45   }
 		close(fd);
 47     return 0;                                                                                                         
 48 }


在这里插入图片描述

client.c

1 #include "comm.h"                                                                                                   
  2 
  3 int main()
  4 {
  5   int fd=open(FILE_NAME,O_WRONLY);  //打开server.c创建的文件  只写
  6   if(fd<0)//打开失败
  7   {
  8     printf("open error!\n");
  9     return 1;
 10   }
 11 
 12   //int in=open("file.txt",O_RDONLY);
 13   char msg[128];
 14   //读
 15   while(1)
 16   {
 17     msg[0]=0;
 18     printf("Please Enter#\n");
 19     fflush(stdout);   //刷新显示器
 20 
 21     //*****  先从键盘里读取数据,然后将数据写到msg里,然后将msg里的内容,写道fd中   *****
 22     
 23     ssize_t s=read(0,msg,sizeof(msg));   //从键盘读,读到msg里,期望读sizeof(mag)的大小
 if(s>0)
 25     {
 26       msg[s]=0;
 27       write(fd,msg,strlen(msg));
 28     }
 29   }
 30   close(fd);
 31   return 0;
 32 }              

在这里插入图片描述
实现结果
在这里插入图片描述
匿名管道与命名管道的区别

匿名管道由pipe函数创建并打开。

命名管道由mkfifo函数创建,打开用open

FIFO(命名管道)与pipe(匿名管道)之间唯一的区别在它们创建与打开的方式不同,一但这些工作完成之后,它们具有相同的语义。

共享内存

共享内存的概念

共享内存区是最快的IPC形式。一旦这样的内存映射到共享它的进程的地址空间,这些进程间数据传递不再涉及到内核,换句话说是进程不再通过执行进入内核的系统调用来传递彼此的数据。

内存共享:要让不同的进程看到同一份空间,这份空间由物理内存通过页表映射到进程地址空间中
在这里插入图片描述
想要达到内存共享,就要将物理内存通过页表映射到进程地址空间中。

映射的本质:修改页表,虚拟地址空间中开辟空间

如何映射:要用系统接口,完成所谓的开辟空间,建立映射,开辟虚拟地址,将虚拟地址返还给用户,这些都是操作系统做的

共享内存的建立

共享内存建立的过程:

1、申请共享内存
在这里插入图片描述

2、共享内存挂接到地址空间
在这里插入图片描述

3、取消关联共享内存
在这里插入图片描述

4、释放共享内存,内存归还给系统
在这里插入图片描述

共享内存的实现

serverclient之间实现通信,server接收数据,client发数据

server.c

#include"comm.h"
    2 #include<unistd.h>
    3 
    4 int main()
    5 {
    6    //**************  获取key值  ***********************
    7 
    8    key_t k=ftok(PATHNAME,PROJ_ID); //生成k值,PATHNAME 项目路径名,PROJ_ID 项目id 
    9    if(k<0)  //获取key值失败
   10    {
   11      printf("ftok error\n");
   12      return 1;
   13    }
   14    //printf("k: %x\n",k);
   15 
   16    //**************  创建共享内存  ******************
   17 
   18    int shmid=shmget(k,SIZE,IPC_CREAT | IPC_EXCL |0644);  //0644是共享内存的权限                                   
   19    if(shmid<0)  //创建共享内存失败
   20    {
   21      perror("shmget");
   22      return 2;
   23    }
   //printf("shmid: %d\n",shmid);                                                                                 
   26    
   27    //***********  共享内存挂接到地址空间  ***************
   28    
E> 29    char *mem=shmat(shmid,NULL,0);  //挂接,返回物理地址映射到虚拟内存的首地址
   30    
   31 
   32    //************   进程间通信  *************
   33    
   34    while(1)
   35    {
   36      printf("client message :%s\n",mem);
   37      sleep(1);
   38    }
   39    //************ 取消关联共享内存  *************
   40    
   41    shmdt(mem);

//***********  删除 ****************
   46    shmctl(shmid,IPC_RMID,NULL);//删除共享内存,shmid表示移除哪一个,IPC_RMIDRMID表示删除
   47   return 0; 
   48 }


在这里插入图片描述

client.c

#include<stdio.h>                                                                                                 
    2 #include "comm.h"
    3 #include<unistd.h>
    4 
    5 int main()
    6 {
    7   //***************  获得key值  *****************
    8   
    9   key_t k=ftok(PATHNAME,PROJ_ID);
   10   if(k<0)
   11   {
   12     perror("ftok");
   13     return 1;
   14   }
   15   printf("k:%x\n",k);
   16 
   17   //*************  获取共享内存 *****************
   18   
   19   int shmid=shmget(k,SIZE,IPC_CREAT);  //server.c中已经创建了共享内存,这里只要获取共享内存就行了
   20   if(shmid<0)
   21   {
   22     perror("shmget");
   23     return 2;
      }
   25 
   26   //*************  共享内存挂接到地址空间  *************
   27   
E> 28   char *mem=shmat(shmid,NULL,0);
   29   
   30   //**************  通过共享内存通信 *******************
   31   
   32   int i=0;
   33   while(1)
   34   {
   35     mem[i]='A'+i;
   36     sleep(1);
   37     i++;
   38     mem[i]='\0';
   39   }
   40 
   41   //************* 取消关联共享内存 ******************                                                             
   42   
   43   shmdt(mem);
   44 
   45   return 0;
   46 }
  

在这里插入图片描述

comm.h

1 #ifndef _COMM_H_
  2 #define _COMM_H_
  3 
  4 #include<stdio.h>
  5 #include<sys/types.h>
  6 #include<sys/ipc.h>
  7 #include<sys/shm.h>
  8 #define PATHNAME "/home/ly/code/lesson23/shm/shm"
  9 
 10 #define PROJ_ID 0x66                                                                                                
 11 #define SIZE 4096
 12 #endif


在这里插入图片描述
成果
server和client通过共享内存,实现通信
在这里插入图片描述
结论:
1、共享内存是所有进程间通信最快的,因为它的拷贝次数是最少的。
在这里插入图片描述

2、不提供任何保护机制,没有同步和互斥。

消息队列

消息队列提供了从一个进程向另外一个进程发送一块数据的方法,每个数据块都被认为是有一个类型,接收者进程接收的数据块可以有不同的类型值。
在这里插入图片描述

IPC资源必须删除,否则不会自动清除,除非重启,所以system V IPC资源的生命周期随内核

信号量

信号量主要用于同步和互斥的,下面先来看看什么是同步和互斥。

由于各进程要求共享资源,而且有些资源需要互斥使用,因此各进程间竞争使用这些资源,进程的这种关系为进程的互斥

系统中某些资源一次只允许一个进程使用,称这样的资源为临界资源或互斥资源。

在进程中涉及到互斥资源的程序段叫临界区

IPC资源必须删除,否则不会自动清除,除非重启,所以system V IPC资源的生命周期随内核

保护:用信号量来保护,信号量的本质是一个计数器,用来描述临界资源中资源数目。

二元信号量:sem=1;只有一份临界资源,实现的是互斥的功能。在这里插入图片描述

三级目录

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值