1.进程间通信介绍

1.1 进程通信的基本概念

在之前我们已经学习过进程地址空间。Linux 环境下,进程地址空间相互独立,每个进程各自有不同的用户地址空间。任何一个进程的全局变量在另一个进程中都看不到,所以进程和进程之间不能相互访问,要交换数据必须通过内核,在内核中开辟一块缓冲区,进程1把数据从用户空间拷到内核缓冲区,进程2再从内核缓冲区把数据读走,内核提供的这种机制称为进程间通信(IPC,Inter Process Communication)。

1.2 为什么要进程间通信

进程通信主要有以下目的:

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

1.3 常见的进程通信方式

在进程间完成数据传递需要借助操作系统提供特殊的方法,如今常见的进程间通信方式有:

① 管道 (分为匿名管道与命名管道)

​ ② 信号 (开销最小)

​ ③ 共享内存

2.管道

2.1管道简介

管道是Unix中最古老的进程间通信方式,我们把从一个进程连接到另一个进程的数据流叫做管道。

在Linux中,| 符号被用来代表管道。因为在Linux中,不同的命令,如ps,ls,grep等命令的本质都是可执行程序,| 前面的命令前面的命令通常会输出大量的结果,这些结果将会交由 | 后面的命令继续处理。

如下面这个命令就是将ps axj中含有PID的结果输出:

2.2 管道的创建和应用

管道的本质是内核中一块供不同进程进行读写的缓冲区,而外在的操作形式是通过文件读写的方式进行。

#include <unistd.h>
功能:创建一无名管道
原型
int pipe(int fd[2]);
参数
fd:文件描述符数组,这是一个输出型参数,调用该接口后,将会给fd[2]数组分配两个文件描述符,两个文件描述符分别对应管道的读写两端。其中fd[0]表示读端, fd[1]表示写端
返回值:成功返回0,失败返回错误代码

我们先用一个简单的例子来看一下管道的创建:

#include<iostream>
#include<unistd.h>
int main()
{
int fd[2];
int ret=pipe(fd);
if(-1==ret)
{
std::cout<<"管道创建失败!"<<std::endl;
}
std::cout<<"fd[0]:"<<fd[0]<<std::endl<<"fd[1]:"<<fd[1]<<std::endl;
return 0;
}

运行后:

可以看到,此时fd[0]和fd[1]返回了两个文件描述符。这两个文件描述符分别分别对应管道的读写两端。

#include<string.h>
#include<unistd.h>
#include<sys/wait.h>
#include<sys/stat.h>
#include<stdlib.h>
#include <sys/types.h>
#include <fcntl.h>
int main()
{
int fd[2];
pipe(fd);
pid_t pid = fork();
if(pid < 0)
{
printf("fork error!");
}else if(pid == 0)
{
//child
close(fd[0]);
char str[100];
while(1)
{
printf("child:");
fgets(str, 100, stdin);
ssize_t len = strlen(str);
if(write(fd[1], str, len) != len)
{
perror("write to pipe");
exit(1);
}
memset(str, 0, len);
sleep(1);
}
}
//father
int count = 0;
close(fd[1]);
while(count < 10)
{
char str[100];
ssize_t s = read(fd[0], str, 100);
if(s < 0){
perror("read from pipe");
break;
}else{
printf("father:%s", str);
}
memset(str, 0, strlen(str));
}
return 0;
}

上面这段代码实现了子进程写入管道,父进程读出的过程。

2.3 管道的底层机制

管道是在有血缘关系的进程之间来通信的,如父子进程,兄弟进程等。因此,应用匿名管道时一定会有fork函数的参与。

如下面这个简化图可以看到,

  1. 父进程先使用pipe函数创建管道,得到两个文件描述符 fd[0]、fd[1]指向管道的读端和写端。

  2. 父进程调用fork创建子进程,此时父子进程有相同的struct files_struct,父子进程指向的struct file又指向了同一片文件缓冲区。(注意:这个表述并不严谨,我们下面马上就会讲到)

  3. 接下来父进程关闭写端,子进程关闭读端,就可以实现子进程向管道中写,父进程读。注意:管道的通信是单向的!!!!

在 Linux 中,管道的实现并没有使用专门的数据结构,而是借助了文件系统的file结构和VFS的索引节点inode。通过将两个 file struct指向同一个临时的 inode,而这个 VFS 索引节点又指向一个物理页面而实现的。

如上图所示,有两个 file 数据结构,但它们定义文件操作例程地址是不同的,其中一个是向管道中写入数据的例程地址,而另一个是从管道中读出数据的例程地址。

这样,用户程序的系统调用仍然是通常的文件操作,而内核却利用这种抽象机制实现了管道这一特殊操作。看待管道,就如同看待文件一样!管道的使用和文件一致,迎合了“Linux一切皆文件思想”。

2.4 管道读写规则

用阻塞的方式打开管道(即默认情况下)

  1. 如果所有管道写端对应的文件描述符被关闭(管道写端引用计数为 0),读端在将管道中剩余数据读取后,再次read会返回0。(写端关闭)

  2. 如果有指向管道写端的文件描述符没关闭,且持有管道写端的进程也没有向管道中写数据,这时有进程从管道读端读数据,那么管道中剩余的数据都被读取后,再次 read 会阻塞。(读完不写)

  3. 如果所有指向管道读端的文件描述符都关闭了(管道读端引用计数为 0),进行write操作会产生信号SIGPIPE,进而可能导致write进程退出。(读端关闭)

  4. 如果有指向管道读端的文件描述符没关闭(管道读端引用计数大于 0),且读端进程并没有向管道中读进程,则当写端进程写满后,会进入阻塞。(写满不读)

2.5 管道的特点

  • 只能用于具有共同祖先的进程(具有亲缘关系的进程)之间进行通信。
  • 管道提供流式服务。
  • 管道的生命周期随进程,进程退出,管道释放。
  • 内核会对管道操作进行同步与互斥。
  • 管道是半双工的,数据只能向一个方向流动;需要双方通信时,需要建立起两个管道
  • 管道大小为65536 byte

Linux系统编程之匿名管道的更多相关文章

  1. Linux系统编程之命名管道与共享内存

    在上一篇博客中,我们已经熟悉并使用了匿名管道,这篇博客我们将讲述进程间通信另外两种常见方式--命名管道与共享内存. 1.命名管道 管道是使用文件的方式,进行进程之间的通信.因此对于管道的操作,实际上还 ...

  2. Linux系统编程——进程间通信:管道(pipe)

    管道的概述 管道也叫无名管道,它是是 UNIX 系统 IPC(进程间通信) 的最古老形式,全部的 UNIX 系统都支持这样的通信机制. 无名管道有例如以下特点: 1.半双工,数据在同一时刻仅仅能在一个 ...

  3. linux系统编程之管道(三)

    今天继续研究管道的内容,这次主要是研究一下命名管道,以及与之前学过的匿名管道的区别,话不多说,进入正题: 所以说,我们要知道命名管道的作用,可以进行毫无关系的两个进程间进行通讯,这是匿名管道所无法实现 ...

  4. Linux 系统编程 学习:02-进程间通信1:Unix IPC(1)管道

    Linux 系统编程 学习:02-进程间通信1:Unix IPC(1)管道 背景 上一讲我们介绍了创建子进程的方式.我们都知道,创建子进程是为了与父进程协作(或者是为了执行新的程序,参考 Linux ...

  5. Linux 系统编程

    简介和主要概念 Linux 系统编程最突出的特点是要求系统程序员对它们工作的的系统的硬件和操作系统有深入和全面的了解,当然它们还有库和系统调用上的区别. 系统编程分为:驱动编程.用户空间编程和网络编程 ...

  6. 读书笔记之Linux系统编程与深入理解Linux内核

    前言 本人再看深入理解Linux内核的时候发现比较难懂,看了Linux系统编程一说后,觉得Linux系统编程还是简单易懂些,并且两本书都是讲Linux比较底层的东西,只不过侧重点不同,本文就以Linu ...

  7. Linux系统编程@进程通信(一)

    进程间通信概述 需要进程通信的原因: 数据传输 资源共享 通知事件 进程控制 Linux进程间通信(IPC)发展由来 Unix进程间通信 基于System V进程间通信(System V:UNIX系统 ...

  8. Linux系统编程【转】

    转自:https://blog.csdn.net/majiakun1/article/details/8558308 一.Linux系统编程概论 1.1 系统编程基石 syscall: libc:标准 ...

  9. 《Linux系统编程(第2版)》

    <Linux系统编程(第2版)> 基本信息 作者: (美)Robert Love 译者: 祝洪凯 李妹芳 付途 出版社:人民邮电出版社 ISBN:9787115346353 上架时间:20 ...

随机推荐

  1. Python 3.10 正式发布,新增模式匹配,同事用了直呼真香!

    关注微信公众号:K哥爬虫,QQ交流群:808574309,持续分享爬虫进阶.JS/安卓逆向等技术干货! 前几天,也就是 10 月 4 日,Python 发布了 3.10.0 版本,什么?3.9 之后居 ...

  2. 第十一章 Dockerfile安装Jenkins-2.249.3-1.1

    一.安装Docker Docker部署Jenkins前提已经安装Docker,这边脚本安装Docker. #1.编写Docker安装脚本 [root@ip-10-0-12-212 ~]# vim In ...

  3. C#特性知识图谱-二、事件

    C#特性知识图谱-二.事件 二.事件 在事件驱动的软件系统中,符合某种预设条件的情形出现是,一个事件就会被触发. 2.1 事件三要素 事件源:激发事件的对象 事件信息:事件本身说携带的信息 事件响应者 ...

  4. netty系列之:使用netty实现支持http2的服务器

    目录 简介 基本流程 CleartextHttp2ServerUpgradeHandler Http2ConnectionHandler 总结 简介 上一篇文章中,我们提到了如何在netty中配置TL ...

  5. javascript-vue介绍

    vue.js是一个用于创建web交互页面的库 从技术角度讲,vue专注于MVVM模型的viewModel层,它通过双向数据绑定把view层和model层连接起来,实际DOM封装和输出格式都被抽象为Di ...

  6. 【数据结构与算法Python版学习笔记】图——拓扑排序 Topological Sort

    概念 很多问题都可转化为图, 利用图算法解决 例如早餐吃薄煎饼的过程 制作松饼的难点在于知道先做哪一步.从图7-18可知,可以首先加热平底锅或者混合原材料.我们借助拓扑排序这种图算法来确定制作松饼的步 ...

  7. [软工顶级理解组] Beta阶段团队贡献分评分

    贡献分评分依据 下述表格适用于前端.后端.爬虫开发者的评分,在此基础上进行增减. 类别 程度 加减分 准时性 提前完成 +0 按时完成 +0 延后完成,迟交时间一天内或未延误进度 -2 延后完成,迟交 ...

  8. 热身训练4 Article

    Article 在这个学期即将结束时,DRD开始写他的最后一篇文章. DRD使用著名的Macrohard的软件World来写他的文章. 不幸的是,这个软件相当不稳定,它总是崩溃. DRD需要在他的文章 ...

  9. CSP-S 2021 遗言

    感谢€€£,谢谢宁嘞! 第一题,€€£给了很多限制条件,什么"先到先得"."只有一个跑道",让它看起来很好做,然后来骗,来偷袭,广大"消费者" ...

  10. 设计的MOS管三极管简单开关电路驱动能力不够1

    您需要 登录 才可以下载或查看,没有帐号?注册 x . ?& P' U5 r/ ~& `: B 用AOD409设计的开关电路为什么驱动能力不够,请大家帮忙分析一下原因啊.这个电路作用就 ...