linux之进程间通信(管道)

11 篇文章 0 订阅

今天来谈谈进程间通信:

进程间通信简称也叫ipc!

基本介绍:

操作系统提供给用户的几种进程间通信方式。
操作系统为什么要给用户提供进程间通信方式:进程的独立性(虚拟地址空间加页表)----提供一个公共的媒介。
进程间通信是干什么的:进程间数据传输,数据共享,进程控制,事件通知,也正因为有不同的应用场景(不同的需求)因此操作系统也提供了多种不同的进程间通信方式继承于unix而来的管道(匿名管道/命名管道)。

进程间通信分类

管道

  • 匿名管道pipe
  • 命名管道

System V IPC

  • System V 消息队列
  • System V 共享内存
  • System V 信号量

POSIX IPC

  • 消息队列
  • 共享内存
  • 信号量
  • 互斥量
  • 条件变量
  • 读写锁

下面主要讲下管道:
管道:(匿名管道/命名管道)
管道是一个半双工通信:提供双向选择,但是只能单向传输。
管道创建成功后,提供io操作—返回文件描述符作为句柄,句柄(文件描述符)有两个:一个用于读取数据,一个用于写入数据。
有名字则可以通过名字打开相同的管道进行通信。
没有名字,因此只能通过子进程复制父进程的方式(复制了文件描述符)实现通信。
匿名管道只能用于具有亲缘关系的进程间通信。
命名管道可以用于任意的进程间通信。
管道原理:操作系统在内核提供一块缓冲区(只要进程能够访问到这块缓冲区就可以实现通信)。

匿名管道:
int pipe(int pipefd[2]);
pipefd:输出型参数—用于获取管道操作句柄
pipefd[0] 管道的读取端
pipefd[1] 管道的写入端
返回值:0 失败:-1

从本质上来说,pipe()函数的功能就是创建一个内存文件,但与创建普通文件的函数不同,函数pipe()将在参数fildes中为进程返回这个文件的两个文件描述fildes[0]和fildes[1]。其中,fildes[0]是一个具有“只读”属性的文件描述符,fildes[1]是一个具有“只写”属性的文件描述符,即进程通过fildes[0]只能进行文件的读操作,而通过fildes[1]只能进行文件的写操作。

读写特性:
管道中若没有数据,则read会阻塞,直到读取到数据(有数据写入管道了)
管道中若数据满了,则write会阻塞,直到有空闲空间(有数据被读出了)
若所有读端被关闭,则write会触发异常—SIGPIPE(导致进程退出)
若所有写端被关闭,则read读完数据之后不会阻塞,而是返回0
进程在操作管道的时候,如果没有用到某一端,则把这一端关闭掉!

实例代码如下:实现一个父写子读的过程

  1 #include<stdio.h>
  2 #include<stdlib.h>
  3 #include<unistd.h>
  4 #include<errno.h>
  5 #include<string.h>
  6 
  7 int main()
  8 {
  9     int pipefd[2];
 10     int ret = pipe(pipefd);      //创建一个匿名管道
 11     if(ret<0){
 12         perror("pipe error!");
 13         return -1;
 14     }
 15     int pid = fork();
 16     if(pid<0){
 17         perror("fork error!");
 18         return -1;
 19     }
 20     //实现父子之间的通信 父写 子读
 21     else if(pid==0){
 22         while(1){
 23             close(pipefd[1]);  //子进程只读的话就把写端关闭
 24             sleep(1);
 25             char buf[1024]={0};
 26             ret  = read(pipefd[0],buf,1023);
 27             if(ret<0){
 28                 perror("read error!");
 29             }
 30             else{
 31                 printf("buf:[%s]\n",buf);
 32             }
 33         }
 34     }
 35     else{
 36         close(pipefd[0]);             //父进程只写就关掉读端
 37         int i = 0;
 38         while(1){
 39             sleep(1);
 40             char* ptr = "hello my child !";
 41             i += write(pipefd[1],ptr,strlen(ptr));
 42             printf("ret:%d\n",i);
 43         }
 44     }
 45
 46     return 0;
 47 }

这样就实现了基本的父写子读功能!

命名管道:
管道可以见于文件系统,会创建一个管道文件(文件只是名字)
管道通信的本质还是内核的那块缓冲区
int mkfifo(const char *pathname, mode_t mode);
pathname: 管道文件的路径名
mode: 创建文件的权限
返回值:0 失败:-1

由于需要由管道自身来保证通信进程间的同步,命名管道也是一个只能单方向访问的文件,并且数据传输方式为FIFO方式。
也就是说,命名管道提供了一个路径名与之关联,以FIFO的文件形式存在于文件系统中,在文件系统中产生一个物理文件,其他进程只要访问该文件路径,就能彼此通过管道通信。在读数据端以只读方式打开管道文件,在写数据端以只写方式打开管道文件。

打开特性:
若管道文件没有被写的方式打开,则以只读打开会阻塞
若管道文件没有被读的方式打开,则以只写打开会阻塞
读写特性雷同于匿名管道

实例代码如下:创建命名管道,通过一个文件,实现一端写,一端读并显示到终端上。

  //fifo_read.c
  1 #include<stdio.h>
  2 #include<stdlib.h>
  3 #include<unistd.h>
  4 #include<fcntl.h>
  5 #include<errno.h>
  6 
  7 int main()
  8 {
  9     char* ptr = "./a.fifo";                 //这就是提供的公共访问路径名
 10     int ret = mkfifo(ptr,0664);              //0664是权限,可以自由设置
 11     if(ret<0){
 12         if(errno!=EEXIST){                   //如果不是早已存在的话,就说明创建失败
 13          perror("mkfifo error");
 14          return -1;
 15         }
 16     }
 17     printf("mkfifo sucess!\n");
 18     int fd = open(ptr,O_RDONLY);             //以只读方式打开
 19     if(fd<0){
 20         perror("open error!\n");
 21         return -1;
 22     }
 23     printf("open fifo sucess!\n");
 24     while(1){
 25         sleep(10);
 26         char buf[1024]={0};
 27         int ret = read(fd,buf,1023);     
 28         if(ret<0){
 29             perror("read error!\n");
 30             return -1;
 31         }
 32         else if(ret==0){
 33             printf("write closed!\n");
 34             return -1;
 35         }
 36         printf("buf:[%s]\n",buf);
 37     }
 38     close(ret);
 39     return 0;
 40 }
//fifo_write.c
  1 #include<stdio.h>
  2 #include<stdlib.h>
  3 #include<fcntl.h>
  4 #include<unistd.h>
  5 #include<string.h>
  6 #include<errno.h>
  7 
  8 int main()
  9 {
 10     char* ptr = "./a.fifo";
 11     int ret = mkfifo(ptr,0664);
 12     if(ret<0){
 13         if(errno!=EEXIST){
 14             perror("mkfifo error!\n");
 15             return -1;
 16         }
 17     }
 18     printf("mkfifo success!\n");
 19     int fd = open(ptr,O_WRONLY);           //以只写方式打开
 20     if(fd<0){
 21         perror("open error!\n");
 22         return -1;
 23     }
 24     printf("open fifo success!\n");
 25     while(1){
 26         char buf[1024]={0};
 27         scanf("%s",buf);
 28         write(fd,buf,strlen(buf));        //将指定内容写到fd所指区域
 29     }
 30     close(ret);
 31     return 0;
 32 }

运行结果如下:我xshell6开了两个窗口
这是写端的情况:
在这里插入图片描述
下面是读端的情况:
在这里插入图片描述
可以看到写端关闭后,读端没有阻塞,而是正常退出了!

小结

管道:本质是内核的一块缓冲区
匿名管道只能用于具有亲缘关系的进程间通信/命名管道可以用于任意的进程间通信
管道的读写特性+命名管道的打开特性
管道是半双工通信—可选方向的单向通信
管道的生命周期随进程
管道提供流式服务—字节流传输(传输灵活/数据粘连)
管道自带同步与互斥功能(读写操作数据大小不超过PIPE_BUF大小,读写操作受保护)
互斥:对临界资源(公共资源)的唯一访问性(我操作的时候别人不能操作)
同步:对临界资源访问的时序可控性(我操作完了别人才能操作)
管道的同步与互斥体现就是:管道没有放数据则读阻塞,放了数据之后唤醒对方阻塞,
对方读数据(我放了,你才能读–同步),我数据往管道中没放完别人不能读也不能写(互斥)。
管道提供字节流服务

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值