首页 > linux进程间通信:无名管道 pipe

linux进程间通信:无名管道 pipe

文章目录

      • 内核层实现
        • 结构
        • 通信原理
        • 特点
      • 使用
        • 函数声明
        • 使用实例
          • 单向通信
          • 双向通信
        • 编程注意事项
          • 管道中无数据时读操作会阻塞
          • 将管道的写端句柄关闭,不会影响读端数据读取
          • 管道中没有数据,写操作关闭则读操作会立即返回
          • 管道大小测试 64K
          • 管道发生写满阻塞,一旦有4k空间,写继续
      • 总结

内核层实现

结构

Linux操作系统中的无名管道结构如下图:

在这里插入图片描述

管道在内核中的实现即如一个缓冲区,内核提供将该缓冲区以一个文件句柄的形式提供给用户态供其调用,进程1使用文件句柄f[0]读,进程2使用文件句柄f[1]写。

无名管道的数据结构声明文件pipe_fs_i.h

执行命令locate pipe_fs_i.h即可找到该文件路径,查看管道文件描述符如下,由于我的内核版本较新,可能该声明和其他版本差异较大:

/***      struct pipe_inode_info - a linux kernel pipe*      @mutex: mutex protecting the whole thing*      @wait: reader/writer wait point in case of empty/full pipe*      @nrbufs: the number of non-empty pipe buffers in this pipe*      @buffers: total number of buffers (should be a power of 2)*      @curbuf: the current pipe buffer entry*      @tmp_page: cached released page*      @readers: number of current readers of this pipe*      @writers: number of current writers of this pipe*      @files: number of struct file referring this pipe (protected by ->i_lock)*      @waiting_writers: number of writers blocked waiting for room*      @r_counter: reader counter*      @w_counter: writer counter*      @fasync_readers: reader side fasync*      @fasync_writers: writer side fasync*      @bufs: the circular array of pipe buffers*      @user: the user who created this pipe**/struct pipe_inode_info { struct mutex mutex;wait_queue_head_t wait;unsigned int nrbufs, curbuf, buffers;unsigned int readers;unsigned int writers;unsigned int files;unsigned int waiting_writers;unsigned int r_counter;unsigned int w_counter;struct page *tmp_page;struct fasync_struct *fasync_readers;struct fasync_struct *fasync_writers;struct pipe_buffer *bufs;struct user_struct *user;
};

通信原理

  • 管道的内核封装 是一个文件(pipefs):

    a. 内核将一个缓冲区与管道文件进行关联、封装

    b. 用户通过open/read/write/close等IO接口进行读写

    读写过程如下所示,进程运行时使用管道会默认打开一些文件句柄包括标准输入流/输出流/错误 等。

    在这里插入图片描述

特点

  • 像一个管道连接两个进程
  • 一个进程作为输入,一个进程作为输出
  • 用于亲缘关系进程之间的通信:共享资源

    在这里插入图片描述

使用

函数声明

  • int pipe(int pipefd[2]);
  • int pipe(int pipefd[2],int flags);

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

函数空间主要包含两个文件描述符,一个用来读,一个用来写

详细信息可以通过man 2 pipe查看系统调用信息

使用实例

单向通信

实现方式如下

在这里插入图片描述

代码如下:

#include 
#include 
#include 
#include 
#include #define handle_error(msg)
{perror(msg);exit(-1);}int main()
{ int pipe_fd[2];if (pipe(pipe_fd) == -1) //创建管道handle_error("pipe");int f;f = fork();if (f == -1)handle_error("fork");if(f == 0){ char str[100] = { 0};printf("child process input :
");scanf("%s",str);write(pipe_fd[1],str,strlen(str)); //子进程向管道写内容close (pipe_fd[1]);_exit(0);}if (f > 0){ char buf[100] = { 0};read (pipe_fd[0],buf,30);printf("parent process output : %s
",buf); //父进程从管道读内容close(pipe_fd[0]);_exit(0);}return 0;
}

输出如下:

zhang@ubuntu:~/test$ gcc test_pipe.c -o test_pipe
zhang@ubuntu:~/test$ ./test_pipe 
child process input :
aa
parent process output : aa
双向通信

实现方式如下,父进程和子进程之间既可以读也可以写,该实现是通过两个管道进行读写处理。

在这里插入图片描述

代码实现如下:

#include 
#include 
#include 
#include 
#include #define handle_error(msg)
{perror(msg);exit(-1);}int main()
{ int pipe_fd[2];int pipe_fd2[2];if (pipe(pipe_fd) == -1 || pipe(pipe_fd2)) //创建两个管道handle_error("pipe");int f;f = fork();if (f == -1)handle_error("fork");if(f == 0) //子进程处理,子进程先写pipe_fd[1],再读pipe_fd[0]{ char str[100] = { 0};char str2[100] = { 0};printf("child process input :
");scanf("%s",str);write(pipe_fd[1],str,strlen(str));close (pipe_fd[1]);read(pipe_fd2[0],str2,100);printf("in child process read : %s
",str2);close(pipe_fd2[0]);_exit(0);}if (f > 0) //父进程和子进程相反,先读pipe_fd[0],再写pipe_fd2[1]{ char buf[100] = { 0};char buf2[100] = { 0};read (pipe_fd[0],buf,30);printf("parent process output : %s
",buf);close(pipe_fd[0]);printf("in parent process write: 
");scanf("%s",buf2);write(pipe_fd2[1],buf2,strlen(buf2));_exit(0);}return 0;
}

最终输出如下:

zhang@ubuntu:~/test$ ./test_pipe_double 
child process input :
hello
parent process output : hello
in parent process write: 
world
zhang@ubuntu:~/test$ in child process read : world

编程注意事项

管道中无数据时读操作会阻塞

如下代码

#include 
#include 
#include 
int main(int argc, const char *argv[])
{ int fd[2];char buf[50] = { 0};//缓存if(pipe(fd)!=0)// 创建无名管道{ perror("pipe fail: ");exit(1);}printf("%d %d
",fd[0],fd[1]);//打开的文件描述符,此处为3,4 默认打开 0,1,2,标准输入,输出,出错//管道中没有数据的时候读阻塞//  write(fd[1],"hello",10);  //此处不向管道写数据时,读操作会阻塞,管道中有数据时,读操作后结束进程read(fd[0],buf,10);printf("%s",buf);putchar(10); // '
'return 0;
}

输出如下

在这里插入图片描述

将管道的写端句柄关闭,不会影响读端数据读取

代码如下

#include 
#include 
#include 
int main(int argc, const char *argv[])
{ int fd[2];char buf[50] = { 0};//缓存if(pipe(fd)!=0)// 创建无名管道{ perror("pipe fail: ");exit(1);}printf("%d %d
",fd[0],fd[1]);//打开的文件描述符,此处为3,4 默认打开 0,1,2,标准输入,输出,出错//管道中没有数据的时候读阻塞write(fd[1],"hello",10);  //此处不向管道写数据时,读操作会阻塞,管道中有数据时,读操作后结束进程close(fd[1]);read(fd[0],buf,10);close(fd[0]);printf("%s",buf);putchar(10); // '
'return 0;
}

输出正常如下:

zhang@ubuntu:~/test$ ./a.out 
3 4
hello
管道中没有数据,写操作关闭则读操作会立即返回

测试代码如下:

#include 
#include 
#include 
int main(int argc, const char *argv[])
{ int fd[2];char buf[50] = { 0};//缓存if(pipe(fd)!=0)// 创建无名管道{ perror("pipe fail: ");exit(1);}printf("%d %d
",fd[0],fd[1]);//打开的文件描述符,默认打开 0,1,2//写端关闭,管道中无数据,读操作立即返回close(fd[1]);read(fd[0],buf,5);return 0;
}
zhang@ubuntu:~/test$ ./a.out 
3 4
管道大小测试 64K
#include 
#include 
#include 
int main(int argc, const char *argv[])
{ int fd[2];char buf[65536] = { 0};//缓存if(pipe(fd)!=0)// 创建无名管道{ perror("pipe fail: ");exit(1);}int f = fork();int num = 0;if (f == 0) { int ret = write(fd[1],"123",1024);while (1 && ret != 0){ ret = write(fd[1],"123",1024);printf("write size is %d
",ret);num ++;printf("write count is %d
",num);}close(fd[1]);_exit(1);}if (f > 0){ printf("%d %d
",fd[0],fd[1]);//打开的文件描述符,默认打开 0,1,2//写端关闭,管道中无数据,读操作立即返回read(fd[0],buf,65536);printf("write result is %s
",buf);close(fd[0]);_exit(1);}return 0;
}

输出如下,当写入数据达到64K时会阻塞

write count is 63
write size is 1024
write count is 64
...
管道发生写满阻塞,一旦有4k空间,写继续
include <stdio.h>
#include 
#include 
int main(int argc, const char *argv[])
{ int fd[2];char buf[65536] = { 0};//缓存if(pipe(fd)!=0)// 创建无名管道{ perror("pipe fail: ");exit(1);}int f = fork();int num = 0;if (f == 0) { int ret = write(fd[1],"123",1024);while (1 && ret != 0){ ret = write(fd[1],"123",4096);printf("write size is %d
",ret);num ++;printf("write count is %d
",num);}close(fd[1]);_exit(1);}if (f > 0){ sleep(1);printf("get the wirte result is %d %d
",fd[0],fd[1]);//打开的文件描述符,默认打开 0,1,2//写端关闭,管道中无数据,读操作立即返回read(fd[0],buf,4096);printf("write result is %s
",buf);close(fd[0]);_exit(1);}return 0;
}

输出如下

write count is 15
get the wirte result is 3 4 //读出来一次,
write result is 123 
zhanghuigui@ubuntu:~/test$ write size is 4096  子进程继续写入读出的空间
write count is 16 //写满后又发生了阻塞
...  

总结

综上可见,管道是应用在拥有亲缘关系的进程之间的共享资源。

优点也很明显:

管道属于系统调用,且数据存放在内存之中,它的父子进程通信过程效率非常高

缺点同样也很明显:

父子进程通信交互并不友好,阻塞式的通信非常影响用户体验

更多相关:

  • fifo的双向通信的方式如下图: 两个进程间的通信需要两个命名管道,分别处理一个进程的读和写 导致这种通信方式出现的根因还是由于fifo的阻塞读和阻塞写,所以这里需要使用两个管道对读写进行分别处理。 同时因为管道传输的数据为流式数据,则无法对数据进行指定标记(数据的发送者,接受者,大小。。。)。 SERVER端代码如下: /*...

  • 文章目录介绍重定向函数介绍总结 linux terminal输入如下命令,其中"|"符号即为我们上文中所说的无名管道 介绍 正如我们上文中所描述的"|“无名管道提供了具有亲缘关系的进程之间的通信,它由于直接使用系统调用,运行效率较高。则linux系统下可以大批量的使用”|"来提供命令直接输入输出的重定向。 具体sh...

  • 2019独角兽企业重金招聘Python工程师标准>>> fd_set 结构,是一个数字。 文件描述符fd的值 不能超过1024,而不是数量不能超过1024。 在开发打开句柄多的程序时,最好不要使用select。否则就使用多进程开发。 /usr/include/X11 Xpoll.h 文件 #ifndef _XPOLL_H_#...

  •         Apache POI是一个开源的利用Java读写Excel,WORD等微软OLE2组件文档的项目。        我的需求是对Excel的数据进行导入或将数据以Excel的形式导出。先上简单的测试代码:package com.xing.studyTest.poi;import java.io.FileInputSt...

  • 要取得[a,b)的随机整数,使用(rand() % (b-a))+ a; 要取得[a,b]的随机整数,使用(rand() % (b-a+1))+ a; 要取得(a,b]的随机整数,使用(rand() % (b-a))+ a + 1; 通用公式:a + rand() % n;其中的a是起始值,n是整数的范围。 要取得a到b之间的...

  • 利用本征图像分解(Intrinsic Image Decomposition)算法,将图像分解为shading(illumination) image 和 reflectance(albedo) image,计算图像的reflectance image。 Reflectance Image 是指在变化的光照条件下能够维持不变的图像部分...

  • 题目:面试题39. 数组中出现次数超过一半的数字 数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。 你可以假设数组是非空的,并且给定的数组总是存在多数元素。 示例 1: 输入: [1, 2, 3, 2, 2, 2, 5, 4, 2] 输出: 2 限制: 1 <= 数组长度 <= 50000 解题: cl...

  • 题目:二叉搜索树的后序遍历序列 输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历结果。如果是则返回 true,否则返回 false。假设输入的数组的任意两个数字都互不相同。 参考以下这颗二叉搜索树:      5     /    2   6   /  1   3示例 1: 输入: [1,6,3,2,5] 输出...