【Linux】进程间通信1-匿名管道1

2023-11-04


前言

为什么需要进程间通信呢?

每一个进程的数据都是存储在物理内存当中的,进程通过各自的进程虚拟地址空间进行访问,访问的时候,通过各自的页表映射关系,访问到物理内存。
从进程的角度看,每个进程都认为自己有4G的空间,物理内存当中怎么存储,进程并不清楚,这也保证了进程的独立性。
因为进程的独立性,所以进程之间不能进行数据交换,进程间通信就是为了让进程和进程之间交换数据的。
进程间通信方式:管道、共享内存、消息队列、网络。

管道符[|]

ps aux | grep test

我们通过上面这个命令,来理解一下管道符,管道符左侧的命令是列举当前Linux操作系统当中的进程信息,右侧命令是过滤存在test字符串的项,ps aux 的返回结果,通过管道,传输给grep进程,grep进程在ps aux的结果当中过滤存在test的字符串,这样,独立的ps aux进程就和独立的grep进程通过管道进行了通信。

管道的本质:管道在内核当中就是一块缓冲区,供进程进行读写,交换数据。

在这里插入图片描述

创建匿名管道的pipe函数

pipe函数原型:

int pipe(int pipefd[2];

调用该函数创建一个匿名管道

参数:是一个数组,有两个元素。

当我们调用pipe函数的时候,我们就在内核当中创建了一块缓冲区(管道),程序员需要往这个缓冲区中读写,于是pipe函数就给我们提供了读写两端,pipefd[0]是管道的读端,pipefd[1]是管道的写端,pipe函数通过参数告诉我们它创建的管道的读写两端是什么,所以函数参数为出参,程序员在创建管道之前,肯定是不知道所创建的管道的读写两端的。

参数为出参,也就是pipefd的值是由pipe函数进行填充的,调用者进行使用。

pipefd[0]是管道的读端,pipefd[1]是管道的写端,这两个元素保存的内容是文件描述符,也就是我们调用了pipe函数,该函数给我们创建了一个匿名管道,pipe函数给我们返回一个数组,该数组有两个元素,这两个元素的类型就是文件描述符,程序员通过操作这两个文件描述符,对管道进行读写。

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

从内核角度深入理解管道

在这里插入图片描述

代码验证pipe函数

我们要通过代码验证以下三个方面的内容:

  • 验证pipe函数的出参
  • 验证fd[0]、fd[1]是文件描述符
  • 通过fd[0]、fd[1]是不是可以进行读写
 1 #include <stdio.h>
  2 #include <unistd.h>
  3 #include <string.h>
  4 int main(){
  5   int fd[2];
  6   printf("fd[0] = %d, fd[1] = %d.\n",fd[0],fd[1]);
  7   int ret = pipe(fd);
  8   if(ret == -1){
  9     perror("pipe");
 10     return 0;
 11   }
 12   printf("fd[0] = %d, fd[1] = %d.\n",fd[0],fd[1]);
 13 
 14   const char* lp = "hello world";
 15   write(fd[1],lp,strlen(lp));
 16 
 17   char buf[1024] = {0};
 18   read(fd[0],buf,sizeof(buf)-1);
 19 
 20   printf("read buf : %s\n",buf);
 21   return 0;                                                                    
 22 }          


执行结果:

[jxy@VM-4-2-centos pipe_test]$ ./pipe_test 
fd[0] = 4196224, fd[1] = 0.
fd[0] = 3, fd[1] = 4.
read buf : hello world

我们给他加上一个睡眠,去 /proc/[pid]/fd 下面看看是不是多了两个描述符,并且该文件描述符是不是和fd[0]、fd[1]对应。

在这里插入图片描述

管道和子进程的先后创建顺序

父子进程要通过匿名管道进行通信,核心点在于父子进程都要能够读写管道,也就意味着父子进程都有管道两端的文件描述符。

那如果要实现父子进程匿名管道的通信,到底应该先创建管道还是应该先创建子进程呢?

我们假设先创建子进程再创建匿名管道:

子进程拷贝父进程的PCB,那子进程的文件描述符表和父进程相同,如果先创建子进程再创建管道,那子进程就得不到父进程创建的匿名管道的读写两端,最后就是这样的结果:
在这里插入图片描述
子进程的文件描述符表里面没有匿名管道的读写两端的文件描述符,所以父子进程此时就无法正常通过匿名管道进行通信。

我们再假设先创建匿名管道再创建子进程:

最后子进程拷贝父进程的PCB,得到的就是这样一个结果:
在这里插入图片描述

在先创建管道再创建子进程的情况下,子进程拷贝得到的文件描述符表就包含了父进程创建的读写两端的文件描述符。这种情况下,父子进程就可以通过匿名管道进行通信了。
结论:一定要先创建匿名管道,再创建子进程,否则,子进程当中就没有匿名管道读写两端的文件描述符。

代码实现父子进程的通信

我们写一段代码,目标即使实现父子进程的通信,代码逻辑就是父进程先创建匿名管道,父进程再创建子进程,父进程写,子进程读,代码如下:

#include <string.h>
  4 int main(){
  5   int fd[2];
  6   int ret = pipe(fd);
  7   //创建管道
  8   if(ret == -1){
  9     perror("pipe");
 10     return 0;
 11   }
 12 
 13   ret = fork();
 14   //创建子进程
 15   if(ret < 0){
 16     perror("fork");
 17     return 0;
 18   }else if(ret == 0){
 19     //child
 20     char buf[1024] = {0};
 21     read(fd[0],buf,sizeof(buf)-1);
 22     //子进程读
 23     printf("I am child,read buf :%s.\n",buf);
 24   }else{
 25     //father
 26     const char* lp = "I am father,hello child";                                
 27     write(fd[1],lp,strlen(lp));                
 28     //父进程写                 
 29   }           
 30   return 0;
 31 } 

执行结果:

[jxy@VM-4-2-centos pipe_comm]$ ./pipe_comm 
I am child,read buf :I am father,hello child.

通过这个执行结果,不难看出来,我们的父子进程通过匿名管道进行了数据交换,进行了通信。
针对上面的代码,我们可能会有以下疑问:父进程创建出来子进程,父子进程是抢占式执行的,如果是父进程先执行,子进程再执行,那得出上述执行结果顺理成章,父进程往匿名管道写入数据,子进程再从匿名管道读出数据,没有任何问题。但是另一种情况呢?子进程先执行,父进程再执行,子进程去匿名管道读出数据的时候,父进程还没有写入数据,但是依然得出上述执行结果了。那为什么不管怎么运行程序,输出的结果都一致呢?

因为当管道中没有数据的时候,子进程调用的read函数从管道当中读的时候,会阻塞,知道管道当中有内容,read函数读到内容之后,read才会返回。

我们用代码验证一下,在上面代码的基础上,让父进程休眠100秒。

sleep(100);

查看堆栈信息如下:

[jxy@VM-4-2-centos fd]$ ps aux | grep pipe_comm
jxy      11266  0.0  0.0   4212   360 pts/0    S+   11:57   0:00 ./pipe_comm
jxy      11267  0.0  0.0   4212    96 pts/0    S+   11:57   0:00 ./pipe_comm
jxy      11524  0.0  0.0 112816   980 pts/2    S+   11:58   0:00 grep --color=autopipe_comm
[jxy@VM-4-2-centos fd]$ pstack 11266
#0  0x00007f9a950e99e0 in __nanosleep_nocancel () from /lib64/libc.so.6
#1  0x00007f9a950e9894 in sleep () from /lib64/libc.so.6
#2  0x00000000004007e1 in main ()
[jxy@VM-4-2-centos fd]$ pstack 11267
#0  0x00007f9a95113b40 in __read_nocancel () from /lib64/libc.so.6
#1  0x00000000004007bc in main ()

管道的特性

  • 1、半双工·:数据只能从写端流向读端,单向通信
  • 2、匿名管道没有标识符,只能具有亲缘性关系的进程进行进程间通信
  • 3、管道的生命周期是跟随进程的,进程退出了,管道在内核当中就销毁了。
  • 4、管道的大小为64k

我们写段代码验证一下管道的大小:

代码实现思想,只写不读,直到将管道写满。

在这里插入图片描述

总共读入了65536个字节,也就是64k,但是程序并没有结束,我们查看一下堆栈信息:

[jxy@VM-4-2-centos fd]$ ps aux | grep pipe_size
jxy      31612  0.6  0.0   4216   356 pts/0    S+   14:19   0:00 ./pipe_size
jxy      31654  0.0  0.0 112816   984 pts/2    R+   14:19   0:00 grep --color=autopipe_size
[jxy@VM-4-2-centos fd]$ pstack 31612
#0  0x00007f495283eba0 in __write_nocancel () from /lib64/libc.so.6
#1  0x0000000000400656 in main ()

为什么程序没有结束呢?因为write往管道当中写的时候阻塞了。

当管道写满之后,再调用write之后,write就会阻塞。

  • 5、管道提供字节流服务

先后两次写入管道的数据之间没有间隔

我们用代码验证一下:

#include <stdio.h>
  2 #include <unistd.h>
  3 #include <string.h>
  4 int main(){
  5   int fd[2];
  6   int ret = pipe(fd);//创建管道
  7   if(ret<0){
  8     perror("pipe");
  9     return 0;
 10   }
 11 
 12   write(fd[1],"hello",5);
 13   write(fd[1],"world",5);//调用两次write
 14 
 15   char buf[1024];
 16   read(fd[0],buf,sizeof(buf)-1);//读
 17   printf("read buf : %s\n",buf);                                               
 18   return 0;
 19 }

执行结果:

[jxy@VM-4-2-centos pipe_stream]$ ./pipe_stream 
read buf : helloworld

数据是被读走,而不是被拷贝走,并且读端在进行读的时候,可以按照任意大小去读。

我们再来验证一下:

代码1:

#include <stdio.h>
  2 #include <unistd.h>
  3 #include <string.h>
  4 int main(){
  5   int fd[2];
  6   int ret = pipe(fd);
  7   if(ret<0){
  8     perror("pipe");
  9     return 0;
 10   }
 11   write(fd[1],"abc",3);                                                        
 12   char buf[1024]={0};
 13   read(fd[0],buf,sizeof(buf)-1);
 14   printf("read buf :%s.\n",buf);
 15 
 16   memset(buf,'\0',sizeof(buf));
 17 
 18   read(fd[0],buf,sizeof(buf)-1);
 19   printf("read buf :%s.\n",buf);
 20   return 0;
 21 }

执行结果:

在这里插入图片描述

程序并没有结束,这是为什么呢?因为read阻塞了,我们查看一下进程的堆栈信息:

[jxy@VM-4-2-centos fd]$ ps aux | grep pipe_stream
jxy       6670  0.0  0.0   4216   356 pts/0    S+   15:04   0:00 ./pipe_stream
jxy       6718  0.0  0.0 112816   988 pts/2    S+   15:05   0:00 grep --color=autopipe_stream
[jxy@VM-4-2-centos fd]$ pstack 6670
#0  0x00007fa1d81a7b40 in __read_nocancel () from /lib64/libc.so.6
#1  0x0000000000400763 in main ()

此时管道中没有任何数据可供第二个read函数读,就造成了阻塞。

代码2:

1 #include <stdio.h>
  2 #include <unistd.h>
  3 #include <string.h>
  4 int main(){
  5   int fd[2];
  6   int ret = pipe(fd);
  7   if(ret<0){
  8     perror("pipe");
  9     return 0;
 10   }
 11   write(fd[1],"abcdef",6);
 12   char buf[4]={0};                                                             
 13   read(fd[0],buf,sizeof(buf)-1);
 14   printf("read buf :%s.\n",buf);
 15 
 16   memset(buf,'\0',sizeof(buf));
 17 
 18   read(fd[0],buf,sizeof(buf)-1);
 19   printf("read buf :%s.\n",buf);
 20   return 0;
 21 }

执行结果:

[jxy@VM-4-2-centos pipe_stream]$ ./pipe_stream 
read buf :abc.
read buf :def.

这两段代码就验证了我们的数据是被读走,而不是被拷贝走。

  • 6、pipe_size:4096字节

当写入或者是读取的字节数量小于pipe_size的时候,管道保证读写的原子性。

在这里插入图片描述

那什么是原子性呢?

一个操作要么不间断的全部被执行,要不一个也没有执行,非黑即白,没有中间状态。对于管道来说,就是读或者写操作在同一时刻只有一个进程在操作,保证进程在操作的时候,读写操作不会被其他进程打扰。

  • 7、阻塞属性

读写两端的文件描述符初始的属性为阻塞属性。当write一直写,读却不去读,则写满之后write会阻塞;当read一直读,当管道内部的数据被读完之后,则read会阻塞,也就是说,当管道为空的时候,调用read,则read函数就会阻塞。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

【Linux】进程间通信1-匿名管道1 的相关文章

  • 如何在 Vim 中突出显示 Bash 脚本?

    我的 Vim 编辑器自动突出显示 PHP 文件 vim file php HTML 文件 vim file html 等等 但是当我输入 vim file在里面写一个Bash脚本 它不会突出显示它 我如何告诉 Vim 将其突出显示为 Bas
  • 如何通过 makefile 在 Linux 上安装程序? [复制]

    这个问题在这里已经有答案了 可能的重复 Linux Unix make install 应该包含什么 https stackoverflow com questions 528399 what should linux unix make
  • PIL 的 Image.show() 带来*两个*不同的查看器

    在 python shell 中处理图像时 我使用 image show 其中 image 是 Image 的实例 很久以前什么也没发生 但在定义了一个名为 xv 的 Mirage 符号链接后 我很高兴 最近几天 show 将显示 Imag
  • 应用程序中两个不同版本的库

    考虑一个场景 其中有两个不同版本的共享库 考虑 A 1 so 链接到 B so A 2 so 链接到 C so 现在 B so 和 C so 都链接到 d exe 当 B so 想要调用 A 1 so 中的函数时 它最终会调用 A 2 so
  • 如何将一个文本文件拆分为多个 *.txt 文件?

    我有一个文本文件file txt 12 MB 包含 something1 something2 something3 something4 有没有办法分开file txt分成 12 个 txt 文件 比方说file2 txt file3 t
  • 使用脚本检查 git 分支是否领先于另一个分支

    I have branch1 and branch2我想要某种 git branch1 isahead branch2 这将显示如果branch1已承诺branch2没有 也可能指定这些提交 我无法检查差异原因branch2 is在之前br
  • bash 将输出重定向到文件,但结果不完整

    重定向命令输出的问题已经被问过很多次了 但是我有一个奇怪的行为 我使用的是 bash shell debian 版本 4 3 30 1 release 并尝试将输出重定向到文件 但并非所有内容都记录在文件中 我尝试运行的 bin 文件是 l
  • 如何从 Bash 命令行在后台 Vim 打开另一个文件?

    我正在从使用 Gvim 过渡到使用控制台 Vim 我在 Vim 中打开一个文件 然后暂停 Vim 在命令行上运行一些命令 然后想返回到 Vim Ctrl Z 在正常模式下 暂停 Vim 并返回到控制台 fg可用于将焦点返回到 Vim job
  • MySQL 与 PHP 的连接无法正常工作

    这是我的情况 我正在尝试使用 Apache 服务器上的 PHP 文件连接到 MySQL 数据库 现在 当我从终端运行 PHP 时 我的 PHP 可以连接到 MySQL 数据库 使用 php f file php 但是当我从网页执行它时 它只
  • BASH:输入期间按 Ctrl+C 会中断当前终端

    我的 Bash 版本是 GNU bash version 4 3 11 1 release x86 64 pc linux gnu 我有一段这样的代码 while true do echo n Set password read s pas
  • Crontab 每 5 分钟一次 [关闭]

    Closed 这个问题是无关 help closed questions 目前不接受答案 我如何告诉 crontab 每 5 分钟运行一次 但从每小时的第二分钟开始 换句话说 我想在以下时间执行我的脚本minute 5 2 例如 我的脚本应
  • 如何并行执行4个shell脚本,我不能使用GNU并行?

    我有4个shell脚本dog sh bird sh cow sh和fox sh 每个文件使用 xargs 并行执行 4 个 wget 来派生一个单独的进程 现在我希望这些脚本本身能够并行执行 由于某些我不知道的可移植性原因 我无法使用 GN
  • 正则表达式删除块注释也删除 * 选择器

    我正在尝试使用 bash 从 css 文件中删除所有块注释 我有以下 sed 命令的正则表达式 sed r s w s w d 这可以很好地去除块注释 例如 This is a comment this is another comment
  • 为 Linux 编译 Objective-C 应用程序(API 覆盖范围)

    我可能在这里问一些奇怪的问题 但我不确定从哪里开始 问题是我正在考虑使用 Obj C 和 Foundation 类在 Mac 上编写一个命令行工具 但存在一个非常大的风险 那就是我希望能够为不同的 Linux 发行版编译它 以便将来作为服务
  • 如何在 Linux 上通过 FTP 递归下载文件夹 [关闭]

    Closed 这个问题不符合堆栈溢出指南 help closed questions 目前不接受答案 Locked 这个问题及其答案是locked help locked posts因为这个问题是题外话 但却具有历史意义 目前不接受新的答案
  • ssh 连接超时

    我无法在 git 中 ssh 到 github bitbucket 或 gitlab 我通常会收到以下错误消息 如何避免它 输出 ssh T email protected cdn cgi l email protection i ssh
  • Tomcat Intellij Idea:远程部署

    RackSpace 云服务器 Ubuntu 12 04 Intellij Idea 11 1 2 Windows 8 Tomcat 7 0 26 JDK 6 在 Intellij Idea 上 当我尝试在远程 Tomcat 7 服务器上运行
  • vmsplice() 和 TCP

    在原来的vmsplice 执行 有人建议 http lwn net Articles 181169 如果您的用户态缓冲区是管道中可容纳的最大页面数的 2 倍 则缓冲区后半部分成功的 vmsplice 将保证内核使用缓冲区的前半部分完成 但事
  • 找不到包“gdk-pixbuf-2.0”

    我正在尝试在 Amazon Linux 发行版实例上构建 librsvg 我已经通过 yum 安装了大部分依赖项 其中一些在实例上启用的默认 yum 存储库中不可用 因此必须从头开始构建它们 我已经走了很远 但还停留在最后一点 跑步时sud
  • 为什么 Linux 没有 DirectX API?

    在考虑现代显卡的 Windows 系统上 DirectX API 的驱动程序端实现时 我想知道为什么此实现在非 Windows 系统 尤其是 Linux 上不可用 由于明显缺乏此功能 我只能假设有一个我无视的充分理由 但在我的原始理解中 我

随机推荐

  • python拯救爱情

    题目描述 小艺酱走到一个花之占卜店中 店员小Q看到小艺酱可怜的样子 允许小艺酱免费占卜一次 花瓣占卜 1 一瓣 在一起 一瓣 不在一起 开始的花瓣表示 在一起 2 直到最后一个花瓣落地游戏结束 3 可以选择多朵花 选择撕一朵花就必须撕完 输
  • 使用JQuery快速高效制作网页交互特效 第十一章 蔚蓝网项目

    样式 global css charset utf 8 CSS Document margin 0px padding 0px font size 12px line height 20px color 333 ul li ol h1 dl
  • contenteditable属性将标签变为可编辑状态,可用于在线编辑修改文本

    1 概述 html 中大部分标签都是不可以编辑的 但是添加了contenteditable属性之后 标签会变成可编辑状态 同时可以触发 input事件 输入内容变化时触发 focus事件 获取焦点时触发 blur事件 失去焦点时触发 2 示
  • WebVirtMgr新建KVM虚拟机

    WebVirtMgr新建KVM虚拟机 一 登录WebVirtMgr 二 KVM节点配置 1 链接qemu相关命令 2 删除宿主机容器的默认网络 三 创建网络 1 关闭NetworkManager 2 创建网桥 3 使用命令行创建网桥 4 创
  • 浮动的特性与清除

    浮动的特性与清除 浮动 float 可以改变元素标签默认的排列方式 最典型的是可以让多个块级元素一行内排列显示 在css中 任何元素都可以浮动 在布局的时候是非常有用的 浮动特性 浮动元素会脱离标准流 脱标 浮动元素会一行内显示并且元素顶部
  • jpg和png通道数上的区别

    JPEG jpg 和PNG png 是两种常见的图像格式 它们在通道数上有一些区别 1 JPEG图像的通道数 JPEG图像通常用于存储彩色图像 具有3个通道 红色 绿色和蓝色 表示RGB颜色模式 每个通道存储相应颜色的强度值 以生成彩色图像
  • C# WinForm 设置DataGridView选中指定行

    int rowIndex 3 指定行号 this dgvInGoodsInfo Rows rowIndex Selected true this dgvInGoodsInfo CurrentCell this dgvInGoodsInfo
  • 阿里云OSS 图片处理api(custom)

    首先放个阿里云OSS图片处理接口文档 阿里云官方地址 OSS 图片处理接口文档 我们有时会抱怨用户上传的图文中图片文件大了 假如说3M的图片 导致生成的网页打开速度慢 怎么办呢 问题分析 网页打开慢是因为网页资源下载的那张图片下载慢 只要能
  • LoadRunner中的WebTours单独配置,及页面空白展示解决方案

    1 从loadrunner12安装包中提取文件 提取strawberry perl文件整个文件夹 webtours文件整个文件 或者去官网下载 我觉得下载太慢就到安装包中直接提取 2 拷贝strawberry perl webtours到一
  • golang中关于channel中的for range遍历操作

    note channel中若要使用for range进行遍历操作 channel必须首先要进行关闭操作的 例一 func main wg Add 2 write make chan int 5 read make chan int 5 go
  • canvas 动态背景粒子特效 直接拿走

  • 五子棋c++代码_C++大作业赏析

    清小C C 大作业赏析 如何完成一篇优秀的C 大作业 苦恼C 大作业的同学快点进来看看 小声 五子棋设计 五子棋是大家最为熟悉双人对弈的棋类游戏之一 只要任意行 列 斜线连成五子即可获胜 上个学期中 陈昊柯同学的大作业实现了五子棋 不但能够
  • 循环进度可视化tqdm

    Python的tqdm库是循环进度条可视化 可以在 Python 循环中添加一个进度提示信息 用户只需要封装任意的迭代器 tqdm iterator 这里我们分3个部分展示 介绍 使用 实例 安装用pip即可 1 介绍 首先先来看一下如何创
  • express中间件详解

    Express中间件是一个函数 它可以访问请求对象 req 响应对象 res 和next函数 中间件的主要作用是在处理请求之前或之后执行一些操作 例如验证用户身份 记录日志 解析请求体等 使用Express中间件有两种方式 应用级别和路由级
  • STM32CUBEMX_日志系统_RTT和串口(重映射浮点数打印)

    STM32CUBEMX 日志系统 RTT和串口 重映射浮点数打印 前言 嵌入式系统开发过程中日志的打印至关重要 他在系统架构搭建和开发过程中包括后期调试定位问题都非常重要 所以在此记录两种日志打印的方式 第一种是移植J link中自带的RT
  • 生成数字与字母组合随机的字符串

    package kmt test cn import java util Random public class StringRandom 生成随机数字和字母 public String getStringRandom int length
  • Springboot中Eureka的使用方法

    在微服务架构中 注册中心是核心的基础服务 它主要记录各个微服务和微服务地址的映射关系 各个微服务都将自己注册到这个注册中心上面 当微服务之间需要互相调用时 就可以从注册中心上面去发现微服务和进行调用 Spring Cloud是一个开箱即用的
  • Linux基础及Linux环境搭建(保姆级别)

    详细 第一章 Linux基础及Linux环境搭建 保姆级别 Kali BugChen的博客 CSDN博客 第一章 Linux基础及Linux环境的搭建 保姆级别 一 Linux简介 1 什么是Linux 一款免费开源流行的操作系统 2 Li
  • 被车撞了!

    买了新车之后 因为是新手司机上路 开了不到4个月 车身已经被划伤好多处了 有的是我自己不小心划的 有的是车在停车场 不知道被谁划伤的 找不到肇事者了 不过好在都是小小的划伤 不影响车的颜值 也不影响驾驶体验 一开始我还很心疼 后来也释然了
  • 【Linux】进程间通信1-匿名管道1

    文章目录 管道符 创建匿名管道的pipe函数 从内核角度深入理解管道 代码验证pipe函数 管道和子进程的先后创建顺序 代码实现父子进程的通信 管道的特性 前言 为什么需要进程间通信呢 每一个进程的数据都是存储在物理内存当中的 进程通过各自