进程间的通信

 进程间通信的目的

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

一、管道

作用:实现资源数据的传输

特性:半双工通信(同一时间内不能同时发送又接受,一条管道只能一个进程写,一个进程读,一个进程写完后,另一个进程开始读),可以选择方向的单向通信

 本质:管道的本质是内核中的一块缓冲区(内存,由系统进行管理)

1.匿名管道​​​​​​

匿名管道:管道没有标识符,不能被其他进程所找到,适用于具有亲缘关系的进程间通信(例如褥子进程)

实现原理:父子进程对文件进行读写操作,父子进程在文件内核缓冲区中写入或读出数据,从而实现通信。

 

 接口:

int pipe(int pipefd[2]);

功能:创建一个管道,通过参数返回管道的两个操作句柄(一个是写入端的描述符,一个是输出端的描述符,可以通过这两个描述符往管道写入或者读取数据。)

参数:

pipefd[2]---具有两个整形元素的数组

pipefd[0]---向管道读取数据

pipefd[1]---向管道写入数据

返回值:

成功返回0,失败返回-1

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

我们知道在Linux下,一切皆为文件,所以也可以将管道当做文件来处理

 

代码实现:

父进程写入数据,子进程读出

    1 #include<stdio.h>
    2 #include<unistd.h>
    3 #include<stdlib.h>
    4 #include<string.h>
    5 #include<fcntl.h>
    6 #include<sys/wait.h>
    7 
    8 int main()
    9 {
   10   int pipefd[2];
   11   //父进程创建管道
   12   int ret=pipe(pipefd);
   13   //创建失败,返回-1
   14   if(ret<0)
   15   {
   16     perror("pipe error");
   17     return -1;
   18   }
   19   char*data="I am father";
   20   //父进程向管道的写端写入数据
   21   write(pipefd[1],data,strlen(data));
   22   //创建子进程
   23   pid_t pid1=fork();
   24   if(pid1==0)
   25   {
   26     char buf[1024]={0};                                                                                                                           
   27     //子进程从管道的读端读取strlen(data)个字节的数据到buf中
   28     read(pipefd[0],buf,strlen(data));
   29     printf("子进程接收到父进程的数据:%s\n",buf);
   30     exit(0);
   31   }
   32   //等待子进程退出
   33   wait(NULL);
   34   return 0;
   35 }

匿名管道特点:

只能用于具有共同祖先的进程(具有亲缘关系的进程)之间进行通信;通常,一个管道由一个进程创建,然后该进程调用fork,此后父、子进程之间就可应用该管道。
一般而言,进程退出,管道释放,所以管道的生命周期随进程
一般而言,内核会对管道操作进行同步与互斥
管道是半双工的,数据只能向一个方向流动;需要双方通信时,需要建立起两个管道

管道读写原则:

1.管道中如果没有数据,则read从管道中读取数据会被阻塞,直到有数据了,才会读取数据返回

2.如果管道中数据满了,则write继续向管道中写入数据则会被阻塞,直到管道中有空间才可以继续写入

3.管道的所有读端被关闭,则再向管道中写入数据,会导致程序崩溃退出(所有读端被关闭,已经没有进程再去从管道读端读取数据,此时在进行写入已经没有意义)

4.管道的所有写端被关闭,则read从管道中读取完所有数据后,将不会被阻塞,直接返回0

2.命名管道

本质:内核中的一块缓冲区,但是有名字,能够被其他进程找到,因此适用于同一主机上的任意进程间通信(多个进程用同一个标识符访问同一个管道)

标识符:是一个可见于文件系统的管道文件

接口:int mkfifo(char*pathname,mode_t mode);

pathname: 管道文件名称

node: 管道文件的访问权限

返回值:成功返回0,失败返回-1

原理:

一个进程创建了一个命名管道的名字,多个进程通过相同的管道名字,打开同一个管道,访问同一块内核缓冲区

注意:管道文件的创建,并不涉及缓冲区的创建,管道文件时标识符,是一直存在的,但这个管道有没有被其他进程用来通信是不一定的,如果管道一直没有进程通信,一直维护缓冲区就很不划算,所以,只有当进行进程通信的时候,才会开辟缓冲区

 代码实现:

  read_fifo.c 
  1 #include<stdio.h>
  2 #include<string.h>
  3 #include<stdlib.h>
  4 #include<fcntl.h>
  5 #include<unistd.h>
  6 #include<sys/stat.h>
  7 #include<errno.h>
  8 
  9 //命名管道
 10 int main()
 11 {
 12   umask(0);
 13   //mkfifo函数创建命名管道
 14   int ret=mkfifo("./test.fifo",0664);
 15 
 16   if(ret<0&&errno!=EEXIST)
 17   {
 18     perror("mkfifo error");
 19     return -1;
 20   }
 21   //以只读的方式打开管道文件./test.fifo
 22   int fd=open("./test.fifo",O_RDONLY);
 23   if(fd<0)
 24   {
 25     perror("open error");
 26     return -1;                                                                                                                                      
 27   }
 28 
 29   while(1)
 30   {                                                                                                                                                 
 31     char buf[1024]={0};
 32     //将管道文件./test.fifo中的数据读入到buf中
 33     int ret=read(fd,buf,1023);
 34     if(ret<0)
 35     {
 36       //如果读入数据失败,则关闭文件并返回
 37       perror("read error");
 38       close(fd);
 39       return -1;
 40     }
 41     //read函数返回实际读出的字节数
 42     //如果管道文件中没有数据,则说明read函数读出字节数为0,也就是返回值ret等于0
 43     else if(ret==0)
 44     {
 45       printf("所有写端已经关闭\n");
 46       close(fd);
 47       return -1;
 48     }
 49     //读取到数据,打印数据
 50     printf("%s\n",buf);
 51   }
 52   close(fd);
 53   return 0;
 54 }
  write_fifo.c
  1 #include<stdio.h>
  2 #include<string.h>
  3 #include<stdlib.h>
  4 #include<fcntl.h>
  5 #include<unistd.h>
  6 #include<sys/stat.h>
  7 #include<errno.h>
  8 
  9 //命名管道
 10 int main()
 11 {
 12   umask(0);
 13   //mkfifo函数创建命名管道
 14   int ret=mkfifo("./test.fifo",0664);
 15 
 16   if(ret<0&&errno!=EEXIST)
 17   {
 18     perror("mkfifo error");
 19     return -1;
 20   }
 21   //以只写的方式打开管道文件./test.fifo
 22   int fd=open("./test.fifo",O_WRONLY);
 23   if(fd<0)
 24   {
 25     perror("open error");
 26     return -1;                                                                                                                                      
 27   }
 28
 29   while(1)
 30   {
 31     char buf[1024]={0};
 32     scanf("%s",buf);
 33     //将buf中的数据写入文件./test.fifo中
 34     int ret=write(fd,buf,strlen(buf));
 35     if(ret<0)
 36     {
 37       //如果写入数据失败,则关闭文件并返回
 38       perror("write error");
 39       close(fd);
 40       return -1;
 41     }
 42   }
 43   close(fd);
 44   return 0;
 45 }

 

此处,输出没有空格原因是用scanf输入的字符串,遇到空格结束

命名管道特性:

具备上述匿名管道的特性

如果以只读的方式打开管道文件,则会阻塞,直到管道被任意进程以写的方式打开

如果以只写的方式打开管道文件,则会阻塞,直到管道被任意进程以读端方式打开

因为一个管道如果不具备同时读写,就没必要开辟缓冲区

 

二、共享内存

作用:用于多个进程间的数据共享

原理:开辟出一块物理内存,然后多个进程将这块物理内存映射到自己的虚拟地址空间中,这些进程通过虚拟地址直接访问这块物理内存中的数据

 

特性:共享内存是最快的进程间通信方式(共享内存的操作相对于其他进程间通信方式,少了俩次数据拷贝过程

接口:

创建共享内存
int shmget(key_t key,size_t size,int shmflg);
key:共享内存的标识符
size:要创建共享内存的大小,最好是页面大小的整数倍
shmflg:IPC_CREAT|IPC_EXCL|0664
IPC|CREAT----如果共享内存不存在,则创建打开,如果已经存在则直接打开
IPC|EXCL-----与IPC|CREAT搭配使用,共享内存不存在则创建打开,若存在则报错返回
mode_flags---共享内存访问权限
返回值:成功返回一个操作句柄(非负数),失败返回-1


映射共享内存
void*shmat(int shmid,void*addr,int shmflag);
shmid:shmget打开共享内存时返回的操作句柄
addr:映射首地址,通常设置为NULL,让操作系统自动分配
shmflag:默认为0,表示可读可写,
返回值:成功返回映射的首地址,失败返回-1


解除映射
int shmdt(const void*shmaddr);
shmaddr:shmat函数的返回值,映射的首地址


删除共享内存
int shmctl(int shmid,int cmd,stuct shmid_ds*buf);
shmid:是shmget()函数返回的操作句柄
cmd:是对共享内存进行的操作
     IPC_RMID:标记一个共享内存段要被删除
buf:用于设置或者获取共享内存信息,当cmd是IPC_RMID时被忽略

代码实现:

  shmread.c
  1 #include<stdio.h>
  2 #include<unistd.h>
  3 #include<stdlib.h>
  4 #include<string.h>
  5 #include<sys/shm.h>
  6 
  7 #define SHM_KEY 0x12345678
  8 int main()
  9 {
 10   //创建一块共享内存,打开成功返回操作句柄,失败返回-1
 11   int shmid=shmget(SHM_KEY,4096,IPC_CREAT|0664);
 12   if(shmid<0)
 13   {
 14     perror("shmget error");
 15     return -1;
 16   }
 17 
 18   //映射共享内存到当前进程的虚拟地址空间
 19   void *start=shmat(shmid,NULL,0);   //0表示可读可写
 20   if(start==(void*)-1)
 21   {
 22     perror("shmat error");
 23     return -1;
 24   }
 25   while(1)
 26   {                                                                                                                                                 
 27     //从共享内存中读出数据,但不会删除共享内存中的数据,共享内存不会随着进程的结束而被删除
 28     printf("%s\n",start);
 29     sleep(1);
 30   }
 31   //解除映射关系
 32   shmdt(start);
 33   //删除共享内存
 34   shmctl(shmid,IPC_RMID,NULL);
 35   return 0;
 36 }
    shmwrite.c
    1 #include<stdio.h>
    2 #include<unistd.h>
    3 #include<stdlib.h>
    4 #include<string.h>
    5 #include<sys/shm.h>
    6 
    7 #define SHM_KEY 0x12345678
    8 int main()
    9 {
   10   //创建一块共享内存,打开成功返回操作句柄,失败返回-1
   11   int shmid=shmget(SHM_KEY,4096,IPC_CREAT|0664);
   12   if(shmid<0)
   13   {
   14     perror("shmget error");
   15     return -1;
   16   }
   17 
   18   //映射共享内存到当前进程的虚拟地址空间
   19   void *start=shmat(shmid,NULL,0);   //0表示可读可写
   20   if(start==(void*)-1)
   21   {
   22     perror("shmat error");
   23     return -1;
   24   }
   25   int count=0;
   26   while(1)                                                                                                                                        
   27   {
   28     //从共享内存中读出数据,但不会删除共享内存中的数据,共享内存不会随着进程的结束而被删除
   29     sprintf(start,"I am process A +%d\n",count++);
   30     sleep(1);
   31   }
   32   //解除映射关系
   33   shmdt(start);
   34   //删除共享内存
   35   shmctl(shmid,IPC_RMID,NULL);
   36  

 

从运行结果我们可以看出,当停止进程shmwrite后,此时共享内存中的数据不会被修改,所以shmread进程一直打印相同的内容,当我们将进程shmread也退出去之后,共享内存也不会被删除,只是映射连接数为0,所以共享内存不会随着进程的退出而退出

 特性:

1.最快的通信方式

2.生命周期随内核(不会随着进程的退出而被释放)

三、消息队列

功能:实现进程间的数据传输(适用于频繁的交换数据)

本质:内核中的一个优先级队列

实现原理:A进程往消息队列写入数据后就可以正常返回,B进程需要时再去读取就可以了,效率比较高。

特性:

生命周期随内核

自带互斥与同步

传输是一种数据块的传输

四、信号量

作用:用于实现进程的同步与互斥

本质:计数器+pcb等待队列

实现原理:

1.信号量实现互斥

进程互斥:多个进程在运行的过程中,都需要某一个资源时,他们便产生的竞争关系,当一个进程获取到资源时,其他进程只能等待,只有当这个进程访问资源结束释放资源后,其他进程才可以访问

初始化信号量为1,表示资源只有1个

P操作:先读计数器值进行-1,如果-1之后的值大于等于0,说明此时有资源,可以访问;如果小于0,说明没有资源访问,被阻塞

V操作:先将计数器的值进行+1,如果+1之后的值小于等于0,说明此时有进程在等待,此时释放资源,唤醒一个正在等待的进程;若+1之后值大于0,则说明此时无人在等待队列,直接释放资源即可

举例:

进程A在访问临界资源之前,首先进行P操作,因为此时计数器值为1,进行-1之后大于等于0,说明此时临界资源无人占用,A进程可以访问

当A进程正在访问临界资源时,B进程此时也想访问,则在访问之前,进行P操作,此时计数器的值为0,进行-1之后值小于0,说明此时有人占用临界资源,则B进程无法访问,被阻塞到队列中等待

当A进程访问完临界资源后,进行V操作,此时计数器的值为-1,进行+1之后值小于等于0,说明此时有进程在等待队列中等待,此时,A进程释放资源,同时唤醒队列中的第一个进程

2.信号量实现同步

进程同步:多个进程中发生的一些事件存在某种时序关系,必须协同动作共同完成一个任务。

当俩个进程A,B同时运行,进程A需要进程B运行到某一步的结果,才能进行自己的下一步运行,这个时候就需要等待B进程与自己进行通信,发送一个消息或者信号,才能继续往下执行,这种进程间的协作称作为进程同步

我们知道,因为进程并发,所以进程的执行顺序可能不一样,而进程同步就是要让各并发进程按要求有序地推进

具体例子:

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值