本章内容

  • 采用pipe管道如何进行进程之间的通信
  • pipe管道进程通信的规则和限制
  • Linux中pipe管道的实现机制和管理pipe管道的结构体

什么是进程通信

进程通信就是两个进程之间进行数据交换,在Linux中有好几种可以进行进程通信的方式,在这篇文章中我们主要介绍最基本的进程通信方式——pipe管道。


进程通信的途径

进程之间交换信息的唯一途径就是传送打开的文件。


管道(pipe)

管道是一种最古老也是最基本的系统IPC形式,所有的Linux系统都提供此种通信机制。但是管道有以下两个局限性:

  • 它是半双工的,即数据一个管道上的数据只能在一个方向上流动,如果要实现双向通信,就必须在两个进程之间建立两个管道;
  • 管道只能在具有公共祖先的两个进程之间使用;
  • -

pipe的实现机制

管道是由内核管理的一个缓冲区,它的一端连接一个进程的输出,另一端连接一个进程的输入。管道的缓冲区不需要很大,它被设计为环形的数据结构,当两个进程都终止后,管道的生命周期也会被结束。

管道的创建

管道是通过调用pipe函数创建的。


#include <unistd.h>
int     pipe(int fd[2]);

它由输出型参数fd返回两个文件描述符,fd[0]为读而打开,fd[1]为写而打开,fd[1]的输出是fd[0]的输入,当管道创建成功后pipe函数返回0,如果创建失败则返回-1,fd[0]和fd[1]之间的关系如下图:

如何通过pipe进行通信

上面我们在单个进程中建立了管道,但是实际上,单个进程中的管道是没有什么用的,通常,进程会先调用pipe函数产生管道,接着调用fork()函数,fork函数会将父进程的相关数据结构继承到子进程中,这样就使子进程中的文件描述符表中的fd[0]和fd[1]指向父进程所指向的管道文件,这样就能实现两个进程之间的通信了。上面的过程如下图:

利用pipe通信的相关规则

对于一个从子进程到父进程的管道(子进程写,父进程读),父进程关闭fd[1],子进程关闭fd[0],当管道的一段被关闭后(在上面的基础上关闭管道的一端)下列两条规则起作用:

  1. 当读一个写段已经被关闭的管道时,在所有的数据都被读取后,read返回0(read返回0表示已经读到文件结束符);
    下面我们进行验证:
#include<stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <stdlib.h>int main()
{//create pipeint fd[2]={0,0};if(pipe(fd)!=0){//create falseperror("pipe");exit(1);}// pipe create successpid_t id=fork();if(id==0){//child -->write fd[1]printf("Child\n");sleep(2);const char* msg="Hello,leap\n";close(fd[0]);int count=3;while(count--){ssize_t size=write(fd[1],msg,strlen(msg));printf("size:%d\n",size);//if(count--){//  sleep(1);//}sleep(1);printf("child is writing...\n");}close(fd[1]);exit(0);}else{//father -->read fd[0]printf("Father\n");sleep(2);close(fd[1]);char buf[1024];int count=3;while(1){ssize_t Len=read(fd[0],buf,1024);//printf("Len::%d\n",Len);printf("Father is reading...\n");if(Len>0){//read successbuf[Len]='\0';printf("child say:%s",buf);}else if(Len==0){//read end of fileprintf("Read the end of pipe\n");break;}else{perror("read");exit(1);}}close(fd[0]);int status=0;pid_t _pid=waitpid(id,&status,0);if(_pid==id){printf("Wait success for child\n");printf("Exit code:%d,Exit signal:%d\n",(status>>8)&0xff,status&0xff);}else{perror("wait");}exit(0);}return 0;
}

程序让子进程写三次字符串然后关闭子进程fd[1],即关闭管道的写端,不关闭父进程的fd[0],即管道的读端。

  1. 如果写一个读端已经被关闭的管道,则会产生相关信号对写段的进程进行终止,如果忽略该信号或捕捉该信号并从处理程序返回,则write会返回-1,errno会设置为EPIPE;
    下面我们进行验证:
#include<stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <stdlib.h>int main()
{//create pipeint fd[2]={0,0};if(pipe(fd)!=0){//create falseperror("pipe");exit(1);}// pipe create successpid_t id=fork();if(id==0){//child -->write fd[1]printf("Child\n");sleep(2);const char* msg="Hello,leap\n";close(fd[0]);int count=5;while(1){ssize_t size=write(fd[1],msg,strlen(msg));printf("size:%d\n",size);if(count--){sleep(1);}printf("child is writing...\n");}close(fd[1]);}else{//father -->read fd[0]printf("Father\n");sleep(2);close(fd[1]);char buf[1024];int count=3;while(count--){ssize_t Len=read(fd[0],buf,1024);//printf("Len::%d\n",Len);printf("Father is reading...\n");if(Len>0){//read successbuf[Len]='\0';printf("child say:%s",buf);}else if(Len==0){//read end of fileprintf("Read the end of pipe\n");}else{perror("read");exit(1);}}close(fd[0]);int status=0;pid_t _pid=waitpid(id,&status,0);if(_pid==id){printf("Wait success for child\n");printf("Exit code:%d,Exit signal:%d\n",(status>>8)&0xff,status&0xff);}else{perror("wait");}exit(0);}return 0;
}

代码的意图是这样:我们让write端(子进程)一直写字符串msg,而read端(父进程)先读三次然后在关闭掉父进程的fd[0],这样就形成了子进程一直写,而父进程没有在读的情况。结果如下:

我们发现父进程关闭掉fd[0]后子进程被异常终止了,我们从子进程的退出码和退出信号码发现它是被13号信号(SIGPIPE)所终止的,所以写一个读端关闭的管道这对PIPE来说并不成立,操作系统会在读端关闭后向写端的进程发送SIGPIPE使进程被终止。

  1. 如果管道的读端和写端都没有关闭,但是管道的写端没有再向管道写数据了。这时如果管道中没有数据了,那么在此read进程会产生阻塞,直到管道中有数据了才读取数据并返回。
  2. 如果有指向管道读端的文件描述符没有关闭,而持有管道读端的没有从管道中读数据,这时有进程向管道中写数据,如果管道被写满再向管道写数据是,再次write会导致进程阻塞,直到管道中有空间了才会继续向管道中写数据并返回。

pipe管道容量

我们可以通过* man 7 pipe*;来查询管道的容量pipe_capacity

## Linux的管道实现机制
从本质上说,管道也是一种文件,但它又和一般的文件有所不同,管道可以克服使用文件进行通信的两个问题,具体表现为:
管道是一个固定大小的缓冲区,在Linux中,该缓冲区的大小为一页,即4kb,使它的大小不会像普通文件那样不加检验的增长。在Linux中,内核使用struct pipe_inode_info结构体来描述一个管道,这个结构体定义在pipe_fs_i.h中。

struct pipe_inode_info结构体

struct pipe_inode_info {
//管道等待队列,当pipe为空/满时指向等待的读者和写者wait_queue_head_t wait;
//pipe中非空缓冲区的数量和当前pipe的入口unsigned int nrbufs, curbuf;
//临时释放的页也叫高速缓存区页框指针struct page *tmp_page;
//读进程的标志或ID号unsigned int readers;
//写进程的标志或ID号unsigned int writers;
//在等待队列中睡眠的写进程的个数unsigned int waiting_writers;
//reader的总数unsigned int r_counter;
//writer的总数unsigned int w_counter;
//用于通过信号进行异步I/O通知struct fasync_struct *fasync_readers;struct fasync_struct *fasync_writers;
//pipe对应的inodestruct inode *inode;
//pipe的环形缓冲区struct pipe_buffer bufs[PIPE_BUFFERS];
};

缓冲区的个数

#define PIPE_BUFFERS (16)

管理缓冲区的结构

struct pipe_buffer {
//包含当前pipe_buffer数据的页struct page *page;
//页中所包含的数据的偏移量,长度unsigned int offset, len;
//与buffer相关的操作const struct pipe_buf_operations *ops;
//pipe_buffer标志unsigned int flags;unsigned long private;
};

Linux进程通信(一)——pipe管道相关推荐

  1. Linux进程通信:无名管道

    进程通信目的: (1)数据传输:进程间数据传输: (2)通知事件:一个进程向另一个或一组进程发送消息,通知某个事件的发生(如子进程终止时需通知父进程): (3)资源共享:多个进程共享资源,需要内核提供 ...

  2. Linux 进程通信之:管道 (Pipe)

    一.简介 管道(pipe) 是一种最基本的 IPC(Inter Process Communication) 机制,优点是简单. 二.特点: 管道只适用于 存在血缘关系 的两个进程之间通信,因为只有存 ...

  3. linux进程通信:pipe实现进程同步

    文章目录 通过管道同步进程 实现代码 管道缓冲区 设置缓冲区大小 总结 :pipe的特点 通过管道同步进程 管道自带同步互斥机制: 管道的内核实现:fs/pipe.c ,主要通过内核的锁以及等待队列等 ...

  4. linux进程通信中有名管道的特点,linux进程通信之(四):有名管道的读与写

    前面我们说了无名管道,下面我们来说说有名管道,请看下面一段代码: fifo_write.c: #include#include#include#include#include#include#incl ...

  5. Linux进程通信的四种方式——共享内存、信号量、无名管道、消息队列|实验、代码、分析、总结

    Linux进程通信的四种方式--共享内存.信号量.无名管道.消息队列|实验.代码.分析.总结 每个进程各自有不同的用户地址空间,任何一个进程的全局变量在另一个进程中都看不到,所以进程之间要交换数据必须 ...

  6. linux进程管道通信缺点,Linux进程通信(IPC)的方式详解

    前言:Linux进程通信的方式 什么是进程通信?进程通信是指进程之间交换信息 进程通信方式共有6种: 管道(pipe),包括流管道(s_pipe)和有名管道(named pipe) 信号(signal ...

  7. Linux进程通信——匿名管道、命名管道、管道的特性和共享内存

    Linux进程通信--匿名管道.命名管道.管道的特性和共享内存 一.管道 1.1 什么是管道? 1.2 匿名管道 <1> 匿名管道参数说明 <2> fork共享管道原理 < ...

  8. 【操作系统实验】Linux进程通信—共享内存通信、管道通信

    Linux进程通信-共享内存通信.管道通信 一.实验目的: 二.实验题目: 1. 试设计程序利用共享内存完成如下进程通信 1.shmget函数 2.shmat函数 3.shmdt函数 4.shmctl ...

  9. linux进程通信1:进程通信概述,管道通信原理(无名管道,有名管道),管道编程实战

    进程通信概述,管道通信原理(无名管道,有名管道),管道编程实战 1.进程间通信概述: 举例1: 你手机微信和别人手机微信通信 举例2: 如:父子进程wait 和 exit之间的通信 进程间通信(IPC ...

  10. linux进程通信的异同,进程间通信方式的比较

    进程间通信就是在不同进程之间传播或交换信息,那么不同进程之间存在着什么双方都可以访问的介质呢?进程的用户空间是互相独立的,一般而言是不能互相访问的,唯一的例外是共享内存区.但是,系统空间却是" ...

最新文章

  1. python主要就业方向-四种Python高薪就业方向
  2. 重装系统后需要安装的软件
  3. 多元分布和狄利克雷分布
  4. 迁移 Spring Boot 到函数计算
  5. 关于字节序(大端法、小端法)的定义
  6. 操作系统以什么方式组织用户使用计算机,操作系统习题
  7. java windows so文件_windows下编译使用NDK,调用SO文件 | 学步园
  8. datatable使用groupby进行分组统计 .
  9. 【高校宿舍管理系统】第十一章 学生系统
  10. Entity Framework 5自动生成ObjectContext或者DbContext的设置
  11. fckeditor for java_基于java使用FCKeditor
  12. Android CPU 双核,为何安卓八核CPU不如苹果双核?
  13. Spring Boot中的配置文件使用以及重新加载
  14. Python-基于OpenCV的轮廓填充 泛洪算法 孔洞填充
  15. 【图解】九张图带你读懂大数据医疗
  16. 鱼c笔记——Python字典(二)
  17. 【C语言经典100题】(古典问题)兔子问题
  18. Win7 蓝屏代码 全攻略
  19. 拓嘉辰丰:多多买菜提货点怎样获益?为什么很便宜?
  20. 夺命雷公狗---javascript NO:18 BOM模型

热门文章

  1. Presto Connector 实现原理
  2. 直播推流拉流概念介绍
  3. python调用java之Jpype实现java接口
  4. c语言中源文件未编译是什么,源文件未编译什么意思
  5. android AP热点(wifi热点)开发
  6. 无锡:车联网先导区“排头兵”,编织的自动驾驶产业雄心!
  7. granger Z-score问题
  8. MySQL的错误1264, 1406, 1329 ,1101 的修改方法
  9. 五年以太扩容梦 破壁原是枕边人——记2016年G神与V神的ETH 2.0路线之争
  10. Android中如何计算图片占用的实际内存大小?