首先说一下为什么进程之间要进行通信?

每个进程都有各自的用户地址空间,互相看不到别的进程的数据,有的时候进程之间要相互交换数据,因此必须在内核中开辟一块缓冲区,进程1把数据写入缓冲区,进程2再从缓冲区中把数据读走,这样就实现了进程间的通信

进程间通信之管道_管道

管道(pipe)是一种最基本的进程间通信(IPC)机制,它由pipe()函数创建

     #include <unistd.h>

原型为:int pipe(int pipefd[2])    


pipe()在内核中开辟一块缓冲区(管道),给数组pipefd[2]返回两个非负小整数,即文件描述符,指向缓冲区的两端,pipefd[0]为读端,pipefd[1]为写端,通过写端向文件里写数据相当于是往缓冲区里写数据,通过读端从文件中读数据就是从缓冲区里往出都数据。

pipe()创建管道成功返回0,失败返回-1.

缓冲区是在内存中。


通信步骤:

  1. 父进程调用pipe()创建管道,如果创建成功则返回两个文件描述符,分别指向读端和写端

  2. 父进程fork出一个子进程,该子进程会复制父进程的全部特征,即子进程的读端和写端也都指向了同一块缓冲区

  3. 父进程关闭写端(close(pipefd[1])),子进程关闭读端(close(pipefd[0])),这样子进程可以往管道里写,父进程可以从管道里读



管道可以克服使用文件进行通信的两个问题,具体表现为:  

1. 限制管道的大小。

2. 读取进程也可能工作得比写进程快。当所有当前进程数据已被读取时,管道变空。当这种情况发生时,一个随后的read()调用将默认地被阻塞,等待某些数据被写入,这解决了read()调用返回文件结束的问题。



管道是用环形队列实现的

从本质上说管道也是一种文件,但它和普通文件有区别,它是一个固定大小的缓冲区,限制了文件的大小,而不像其它文件一样不加检验的无限制增长,Linux下该缓冲区的大小为一页,即4K字节(2^12)


内存中是没有环形结构的,它是用数组的线性空间来实现的,当数据到达这个数组的尾部时,再返回头部进行处理,这个转回是通过取模操作执行的

这个环形队列实际上是把数组q[0]和q[MAXN-1]连起来形成的,这里的MAXN是数组的最大长度

进程间通信之管道_include_02


head指向可以读的位置,tail指向可以写的位置,它们之间空的位置都是可用空间。

从管道读数据是一次性操作,数据一旦被读,它就被抛弃来存放更多的数据,从上图来讲就是读完数据后head指针就向后移动,这样可用空间又会增加。


代码实现管道通信的四种情况:(以下均为子进程写父进程读)

1.所有指向读端的文件描述符都关闭了(读端引用计数为0),还有进程往写端写,该进程将会收到一个信号SIGPIPE,写端将会停止写入。

#include<stdio.h>
#include<unistd.h>
#include<errno.h>
#include<stdlib.h>
#include<string.h>

int main()
{
    int pipefd[2];
    int res=pipe(pipefd);
    if(res==-1){
        perror("pipe");
        return 1;
    }
    else{
        pid_t id=fork();
        if(id<0){
            perror("fork");
            exit(1);
        }
        else if(id==0){//child
            close(pipefd[1]);
            int count=10;
            char str[100];
            while(count){
                memset(str,'\0',sizeof(str));
                int _size=read(pipefd[0],str,sizeof(str));
                printf("size:%d,str:%s\n",_size,str);
                count--;
            }
            close(pipefd[0]);
        }
        else{//father
            close(pipefd[0]);
            int count=15;
            char* str=NULL;
            while(count){
                str="hello world!";
                write(pipefd[1],str,strlen(str)+1);
                sleep(1);
                count--;
            }
        }
    }
    return 0;
}

运行结果如下:

进程间通信之管道_缓冲区_03

2.如果有指向管道读端的文件描述符没关闭(管道读端的引用计数大于0),而持有管道读端的进程也没有从管道中读数据,这时有进程向管道写端写数据,那么在管道被写满时write会阻塞,直到管道中有空位置了才写入数据

#include<stdio.h>
#include<unistd.h>
#include<errno.h>
#include<stdlib.h>
#include<string.h>

int main()
{
    int pipefd[2];
    int res=pipe(pipefd);
    if(res==-1){
        perror("pipe");
        return 1;
    }
    else{
        pid_t id=fork();
        if(id<0){
            perror("fork");
            exit(1);
        }
        else if(id==0){//child
            close(pipefd[1]);
            int count=1000;
            sleep(5);
            char str[100];
            while(1){
                if(count){
                    memset(str,'\0',sizeof(str));
                    int _size=read(pipefd[0],str,13);
                    printf("size:%d,str:%s\n",_size,str);
                    count--;
                }
            }
        }
        else{//father
            close(pipefd[0]);
            char* str=NULL;
            int i=0;
            while(1){
                str="hello world!";
                write(pipefd[1],str,strlen(str)+1);
                //sleep(1);
                i++;
                 printf("i=%d  ",i);
            }
        }
    }
    return 0;
}

运行结果如下:

进程间通信之管道_include_04


进程间通信之管道_缓冲区_05

当父进程写入数据时,子进程没有在运行,管道写满后write阻塞,当子进程读走数据后父进程又开始写入数据


3.如果所有指向写端的文件描述符全部被关闭了,但仍然有进程从读端读数据,当管道中的数据全部被读完时,read会返回0,就像读到文件末尾一样

#include<stdio.h>
#include<unistd.h>
#include<errno.h>
#include<stdlib.h>
#include<string.h>

int main()
{
    int pipefd[2];
    int res=pipe(pipefd);
    if(res==-1){
        perror("pipe");
        return 1;
    }   
    else{
        pid_t id=fork();
        if(id<0){
            perror("fork");
            return 1;
        }
        else if(id==0){//child
            close(pipefd[1]);
            int count=10;
            char str[100];
            while(count){
                memset(str,'\0',sizeof(str));
                int _size=read(pipefd[0],str,sizeof(str));
                printf("size:%d,str:%s\n",_size,str);
                count--;
            }
        }
        else{//father
            close(pipefd[0]);
            int count=5;
            char* str=NULL;
            while(count){
                str="hello world!";
                write(pipefd[1],str,strlen(str)+1);
                sleep(1);
                count--;
            }
            close(pipefd[1]);
        }
    }   
    return 0;
}

运行结果:

进程间通信之管道_include_06


4.如果指向写端的文件描述符没有关闭,但也没有在写,此时从读端读数据的进程读完管道中的数据后会被阻塞直到写端再次写入数据

#include<stdio.h>
#include<unistd.h>
#include<errno.h>
#include<stdlib.h>
#include<string.h>
    
int main()
{
    int pipefd[2];
    int res=pipe(pipefd);
    if(res==-1){
        perror("pipe");
        return 1;
    }   
    else{
        pid_t id=fork();
        if(id<0){
            perror("fork");
            exit(1);
        }
        else if(id==0){//child
            close(pipefd[1]);
            int count=10;
            char str[100];
            while(count){
                memset(str,'\0',sizeof(str));
                int _size=read(pipefd[0],str,sizeof(str));
                printf("size:%d,str:%s\n",_size,str);
                count--;
            }
        }
        else{//father
            close(pipefd[0]);
            char* str=NULL;
            int count=10;
            while(count){
                if(count==5){
                    printf("I just want to sleep!\n");
                    sleep(5);
                }
                str="hello world!";
                write(pipefd[1],str,strlen(str)+1);
                sleep(1);
                count--;
            }
        }
    }
    return 0;
}

运行结果:

进程间通信之管道_管道_07

进程间通信之管道_缓冲区_08


当管道中没有数据可读时(count==5时sleep),read阻塞,直到写端再次写入数据