进程间通信之--匿名管道

一、为什么进程间通信需要管道

两个进程之间的通信,每个进程各有不同的地址空间,每个地址空间的数据信息是独立的,任何一个进程的全局变量在另一个进程中都看不到。例如:父进程中有一个变量 a = 0;在子进程中改变 a 的值是不会影响在父进程中的 a 的值,因为虽然子进程所有的数据信息都是拷贝(写时拷贝)自父进程,两个进程有各自不同的地址空间。
eg:代码验证:

 //mypipe.c

  1 #include<sys/types.h>                                                       
  2 #include<sys/wait.h>
  3 #include<stdlib.h>
  4 #include<unistd.h>
  5 #include<stdio.h>
  6 int a=0;
  7 
  8 int main()
  9 {
 10    pid_t pid=fork();
 11    if(pid < 0)
 12    {
 13        perror("fork");
 14        exit(1);
 15    }
 16    else if(pid  == 0)
 17    {
 18        //child
 19        a = 110;
 20        printf("child is a= %d\n", a);
 21    }
 22    else
 23    {
  24        //father
 25        sleep(1);
 26        printf(" father is a= %d\n", a);
 27        waitpid(pid , NULL , 0);
 28    }
 29    return 0;
 30 }  
//Makefile 文件
  1 mypipe:mypipe.c
  2     gcc -o $@ $^
  3 .PHONY:clean
  4 clean:
  5     rm -f mypipe

代码运行结果:
这里写图片描述
由上面代码和运行截图可清晰的看出,父子进程之间没有共享数据,所以两个进程是不能直接通信的。要通信就必须要通过内核,在内核中开辟一块缓冲区(第三方资源),让不同的进程看到一份公共的资源。具体实现是进程 1 把数据从用户空间拷贝到内核缓存区,进程2 再从内核缓冲区把数据读走,这就实现了进程之间的通信。

二、进程通信–管道

管道的创建:pipe
这里写图片描述
返回值:
调用成功返回 0,调用失败返回 -1;
作用:
调用pipe函数时在内核中开辟一块缓冲区(又称管道)用于通信,有一个读端和一个写端,pipe[1] 指向的管道的写端,向管道中写的话就直接调用 write(pipefd[1] ,msg ,strlen(msg)); 从管道中读直接调用read (pipefd[0] ,buf ,sizeof(buf)-1);所以管道看起来就像一个打开的文件,两个进程分别向管道(资源共享区(临界资源)(访问该资源的代码叫临界区))读写,就完成了进程之间的通信。

例一、

1 #include<unistd.h>
  2 #include<stdio.h>
  3 #include<stdlib.h>
  4 
  5 int main()
  6 {
  7     int fd[2];
  8     int pi = pipe(fd);
  9     if(pi < 0)
 10     {
 11         perror("pipe");
 12         exit(1);
 13     }
 14     printf("fd[0]=%d ,fd[1]=%d\n",fd[0],fd[1]);                             
 15   return 0;
 16 }

这里写图片描述
从上面这个例子中我们可以看到:
读端:fd[0] = 3; 写端:fd[1] = 4;

这里写图片描述

例二、

因为子进程数据资源都继承自父进程,所以子进程的文件描述符表和他父进程的文件描述表一样,以下是两个进程同时指向一个管道的例图:
这里写图片描述

代码实现如下:

 1 #include<sys/types.h>
  2 #include<sys/wait.h>
  3 #include<stdlib.h>
  4 #include<unistd.h>
  5 #include<stdio.h>
  6 #include<string.h>
  7 
  8 int main()
  9 {
 10    int fd[2];
 11    pid_t pi = pipe(fd);
 12    if(pi < 0)
 13    {
 14        perror("pipe");
 15        exit(1);
 16    }
 17    pid_t pid=fork();
 18    if(pid < 0)
 19    {
 20        perror("fork");                                                      
 21        exit(2);
 22    }
 23    else if(pid  == 0)
 24    {
 25        //child
 26        char *msg="I am child";
 27        close(fd[0]);
 28        while(1)
 29        {
 30 
 31             sleep(1);
 32             write(fd[1] , msg , strlen(msg));
 33             printf("child write# %s\n",msg);
 34        }
 35    }
 36    else
 37    {
 38        //father
 39        char buf[1024];
 40        close(fd[1]);
 41        while(1)
 42        {
 43        int ret=read(fd[0] , buf ,sizeof(buf)-1);
 44        if(ret > 0)
 45        {
 46            buf[ret] = 0;     
 47            printf("father read#  %s\n", buf);
 48        }
 49        }
 50        waitpid(pid , NULL , 0);
 51    }
 52    return 0;
 53 }   

这里写图片描述
在上面的显示结果中“I am child”这句话是由子进程写在管道中的,父进程从管道中读出来数据,这就完成了父子进程之间的单向通信。
先创建管道再创建子进程
从上面代码中,看到子进程在写之前关闭了读的文件描述符fd[0],父进程在读之前关闭了写文件描述符fd[1];这是为了预防发生错误,这就要涉及到管道的使用限制了,两个进程通过一个管道只能实现单向通信,父进程读,子进程写。要完成父进程写子进程读的话就需要重新开辟一个管道了。若在父进程读的情况下,没有关闭父进程的写fd[1],此时在父进程中去写,就会导致某些未知的错误(在父进程中不去写就不会出错,关闭是为了防止错误的出现);关闭fd[1],在父进程想写进管道时就不会成功。管道是一种半双工方式,即对于进程来说,要么只能读管道,要么只能写管道。不允许对管道又读又写。

以上的管道为匿名管道,它的特点是:

1. 单向通信;
2. 通信依赖于文件系统,其生命周期是:进程退出,管道也退出(称为:随进程);
3. 数据读写时是按照数据流来读写的,(流式服务)
4. 匿名管道只适应于有血缘关系的进程(eg:父子进程,兄弟进程)通信。
5. 自带同步机制;(若写的慢,则读的一方的速度也会慢(随着写的速度来读))

使用管道需要注意以下4种情况:(读情况都是父进程,写情况都是子进程)
1、子进程写的慢,父进程读的快;父进程从管道中读数据,管道中数据全部被读完后,父进程再次去read会阻塞,等待子进程写,直到管道中有数据可读了才读取数据并返回,例子就是上面实现的代码。
2、 子进程写的快,父进程读得慢;导致管道中数据全被写满,再次write会被阻塞,直到管道中有空位置了才写入数据并返回。
第二种情况的代码部分

  1 #include<sys/types.h>                                                       
  2 #include<sys/wait.h>
  3 #include<stdlib.h>
  4 #include<unistd.h>
  5 #include<stdio.h>
  6 #include<string.h>
  7 
  8 int main()
  9 {
 10    int fd[2];
 11    pid_t pi = pipe(fd);
 12    if(pi < 0)
 13    {
 14        perror("pipe");
 15        exit(1);
 16    }
 17    pid_t pid=fork();
 18    if(pid < 0)
 19    {
 20        perror("fork");
 21        exit(2);
 22    }
 23    else if(pid  == 0)
 24    {
 25        //child
 26        char *msg="I am child";
 27        close(fd[0]);
 28        int count = 0;
 29        while(1)
 30        {
 31 
 32             write(fd[1] , msg , strlen(msg));
 33             printf("count = %d\n",++count);
 34             //printf("child write# %s\n",msg);
 35        }
 36    }
 37    else
 38    {
 39        //father
 40        char buf[1024];
 41        close(fd[1]);
 42        while(1)
 43        {
 44          sleep(10);
 45          int ret=read(fd[0] , buf ,sizeof(buf)-1);
 46          if(ret > 0)  
 47          {
 48            buf[ret] = 0;
 49            printf("father read#  %s\n", buf);
 50          }
 51        }
 52        waitpid(pid , NULL , 0);
 53    }
 54    return 0;
 55 }     

这里写图片描述
写到管道的最大容量,等待读取之后在进行写入
这里写图片描述
这里写图片描述
等再次write,管道中有空余,则继续写入,知道写满,下次写入阻塞,直到管道中有空位置了才写入数据并返回。

3、父进程中读fd[0] 关闭,子进程写;子进程向管道中一写,操作系统会终止该进程,进程异常终止。

#include<stdio.h>  
#include<stdlib.h>  
#include<unistd.h>  
#include<string.h>  
int main()  
{  
    int fd[2] = {0,0};  
    if(pipe(fd) < 0){  
        perror("pipe error\n");  
        return 1;  
    }  

    pid_t id = fork();  
    if(id == 0){  
            //child --->write  
            close(fd[0]);  
            int count = 0;  
            const char *msg = "I am child,hello father\n";  
            while(1){  
                write(fd[1],msg,strlen(msg));  
            //  printf("child\n");  
                sleep(1);  
                //printf("count:%d\n",++count);  
            }  
        }else{  
            //father--->read  
            char buf[1024];  
            close(fd[1]);  
            int count = 0;  
            while(1){  
                ssize_t s = read(fd[0],buf,sizeof(buf) - 1);  
            //  printf("father\n");  
                printf("count:%d\n",count);  
                if(s > 0){  
                    buf[s] = 0;  
                    printf("%s",buf);  
                }  

                if(count++ > 5)  
                {  
                    close(fd[0]);  
                    break;  
                }  

            }  
            int status = 0;  
            pid_t ret = waitpid(id,&status,0);  
            printf("ret:%d,sig:%d,exitCode:%d\n",ret,status&0xff,(status>>8)&0xff);  

        }  

    return 0;  
}  

这里写图片描述
4、进程关闭写,父进程读;子进程没有向管道中写数据,父进程从管道中读数据,管道中所以数据都被读取后,在次read会阻塞,直到管道中有数据可读了才读取数据并返回。

#include<stdio.h>  
#include<stdlib.h>  
#include<unistd.h>  
#include<string.h>  
int main()  
{  
    int fd[2] = {0,0};  
    if(pipe(fd) < 0){  
        perror("pipe error\n");  
        return 1;  
    }  

    pid_t id = fork();  
    if(id == 0){  
            //child --->write  
            close(fd[0]);  
            int count = 0;  
            const char *msg = "I am child,hello father\n";  
            while(1){  
                write(fd[1],msg,strlen(msg));  
                if(count++ > 5)  
                {  
                    close(fd[1]);  
                    break;  
                }  
                sleep(1);  
                printf("count:%d\n",count);  
            }  
        }else{  
            //father--->read  
            char buf[1024];  
            close(fd[1]);  
            int count = 0;  
            while(1){  
                ssize_t s = read(fd[0],buf,sizeof(buf) - 1);  
                if(s > 0){  
                    buf[s] = 0;  
                    printf("%s",buf);  
                }  

            }  
            int status = 0;  
            pid_t ret = waitpid(id,&status,0);  
            printf("ret:%d,sig:%d,exitCode:%d\n",ret,status&0xff,(status>>8)&0xff);  

        }  

    return 0;  
}  

这里写图片描述
由上面代码可以证实,子进程关闭写,父进程读完管道的数据,会一直阻塞等待直到有数据写入,才读取数据并返回;
进程间通信并没有结束,这只是个开始;

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值