前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Linux进程间通信——匿名管道

Linux进程间通信——匿名管道

作者头像
zy010101
发布2019-05-25 19:56:39
1.4K0
发布2019-05-25 19:56:39
举报
文章被收录于专栏:程序员程序员

版权声明:本文为博主原创文章,转载请注明博客地址: https://cloud.tencent.com/developer/article/1433351

进程如果不是独立进程,那么它就需要和别的进程进行通信。在进程协作时可以采用共享一个缓冲区的方式来实现。当然,OS的IPC提供了一种机制,以允许不必通过共享地址空间来通信和同步其动作。这就不得不提Linux的的前身Unix。因为Linux一开始就是从这儿借鉴的。加上Linux从一开始就遵守POSIX标准。

Unix最早是由AT&T的贝尔实验室开发的,值得一提的是,在Unix操作系统发展的过程中,产生了许多副产物(POSIX标准也是副产物之一),其中最著名的应当是C语言。是的,它仅仅是个副产物。那个时候Ken Thompson 与Dennis Ritchie感到用汇编语言做移植太过于头痛,他们想用高级语言来完成第三版。后来他们改造了B语言,就形成了今天大名鼎鼎的C语言。这个自发明到现在这个物联网时代仍占据编程语言榜前10的稳固位置。不得不感叹其生命力的强大以及适应性的强大。当然,Ken Thompson 与Dennis Ritchie也是图灵奖得主。

到了1980年,有两个最主要的Unix的版本线,一个是UC Berkeley的BSD UNIX,另一个是AT&T的Unix。至今为止UC Berkeley仍在维护Unix(这学校真牛逼)。

最初的Unix的IPC包括,管道,FIFO,信号。贝尔实验室对Unix早期的进程通信进行了改进,形成了system V这个操作系统的IPC。它包括:system V消息队列,system V信号灯,system V共享内存。当然POSIX IPC也有相应的一套。BSD Unix设计了socket(套接字)通信。这样将进程之间的通信不仅仅限制在单机内。Linux继承了这些。

进程间通信的目的:

  1. 数据传输:一个进程将数据发送给另一个进程
  2. 共享数据:多个进程操作共享数据(比如:售票系统),一个进程对共享数据进行了修改,另外一个进程应该立即看到,(否则票买完了,但是另一边不知道,还在卖)
  3. 通知:一个进程告诉另外一个进程发生了某些事件。
  4. 资源共享
  5. 进程控制:一个进程控制另外一个进程的执行(例如debug程序)。它希望知道另一个进程的实时状态。

Linux进程通信方式:

管道:管道(pipe)分为无名管道和有名管道。无名管道用于具有亲缘关系进程间的通信,有名管道则可以在任意的进程中间进行通信。

管道通信具有以下的特点:

  1. 管道是半双工的。(双向通信的,但是不能同时向双方传输)
  2. 只能用于父子进程或者是兄弟进程之间(就是要具有亲缘关系)
  3. 管道是一种文件(能读写),它只存在于内存之中。他是具有亲缘关系的进程共享的。
  4. 写入的内容每次都添加到管道缓冲区的末尾,并且每次都是从缓冲区的头部读取数据。

Linux建立无名管道函数是pipe函数。它需要的头文件是#include<unistd.h>.

函数原型:int pipe(int filedes2);

函数功能:pipe建立一个无名管道文件,若成功返回0,否则返回-1.错误原因由errno给出。管道文件的描述符由filedes数组返回。其中filedes0为管道的读取端,filedes1为写入端。

代码测试如下:

代码语言:javascript
复制
#include<unistd.h>
#include<errno.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<wait.h>
#include<linux/limits.h>        //这个头文件中有PIPE_BUF


int main()
{
    int filedes[2];         //保存管道文件的文件描述符
    char str[30] = {"Hello World!"};
    char temp[30] = {0};

    if(0 != pipe(filedes))      //创建管道失败
    {
        printf("errno=%d\n",errno);
        return 0;
    }
    if(0 < fork())          //父进程
    {
        close(filedes[0]);      //为避免不必要的错误,关闭读端
        write(filedes[1],str,strlen(str));
        close(filedes[1]);
        wait(NULL);         //回收子进程
        exit(0);
    }
    else
    {
        sleep(3);      //让父进程先执行
        close(filedes[1]);      //为避免不必要的错误,关闭写端
        read(filedes[0],temp,strlen(str));
        close(filedes[0]);
        printf("%s\n",temp);
        exit(0);
    }
    
    return 0;
}

在读写管道文件的时候,最好是严格遵守文件的读写规则,在使用完毕后一定要关闭文件。为了避免不必要的一些错误,在使用管道的文件的要先创建管道文件,然后创建新进程,这样所有的进程才能共享这个管道文件。代码中为了避免向读取端写入和从写入端读取而引发的错误,在读的时候关闭写端,在写的时候关闭读端。

代码中先让父进程向管道文件中写入了字符串“Hello World!”。然后子进程读取管道文件中的字符串,并向屏幕打印。程序执行结果如下:

如果子进程读取到的管道文件为空,那么read()函数将会使得进程阻塞,这时候父进程将会执行,然后完成对管道文件的写入。之后wait()将父进程挂起,子进程完成读取。同样,管道已经满时,进程再试图写管道,在其它进程从管道中移走数据之前,写进程将一直阻塞。(典型的生产者——消费者模型)管道是存在于内存中的文件(实际上内核实现的一个队列),他是进程的资源,会随着进程的销毁而销毁。还有一点是管道中的东西在读取后就会被删除。管道文件有大小限制的,在我现在的内核版本下他是4KB。管道文件的大小由PIPE_BUF描述。它在#include<linux/limits.h>这个头文件中给出。

代码语言:javascript
复制
#define PIPE_BUF        4096	/* # bytes in atomic write to a pipe */

向管道写入数据的时候Linux不保证写入的原子性,管道缓冲区一有空闲,写进程就会去写入。所以要及时读取管道文件

同时管道还要求写端对读端的依赖性,示例代码如下:

代码语言:javascript
复制
#include<unistd.h>
#include<errno.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<wait.h>
#include<linux/limits.h>        //这个头文件中有PIPE_BUF


int main()
{
    int filedes[2];         //保存管道文件的文件描述符
    char str[30] = {"Hello World!"};
    char temp[30] = {0};
    
    int num;
    if (0 < fork()) 
    {
        sleep(1);
        close(filedes[0]);      //关闭读端
       num =  write(filedes[1],str,strlen(str));
       if(-1 == num)
       {
           printf("error!\n");
       }
       else
       {
           printf("write to pipe is %d\n",num);
       }
        close(filedes[1]);
        wait(NULL);
        //exit(0);
    }
    else
    {
        //子进程不读,不写,直接将管道文件两端都关闭
        close(filedes[0]);
        close(filedes[1]);
        exit(0);
    }
    
    return 0;
}

输出结果如下:

这个时候,在父进程中将无法写入。所以管道这个描述还是很形象的,当你向一段水管里面装水的时候,需要将另一端堵上,否则装入的水全都流走了。因此在父进程写的时候,需要先关闭读;在子进程读的时候需要先关闭写。同时,不能在没有读的情况下将管子两头堵上。

当子进程结束的时候,父进程关闭读,调用write写数据,这时候父进程将会收到子进程SIGPIPE信号,当前进程将会中断,而不是阻塞。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2018年11月06日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档