进程间通信——管道(无名管道)

Linux下进程间通信的手段基本是从Unix平台继承而来。管道是第一个广泛应用的进程间通信手段。日常在终端执行shell命令时,经常会将上一个命令的输出,作为下一个命令的输入,有多个命令配合完成一件事情,会大量用到管道。
管道是一种文件,可以调用read,write,close等操作文件的接口来操作管道。它属于一种特殊的文件系统:Pipe File System。管道的本质是内核维护了一块缓冲区与管道文件相关联,对管道文件的操作,被内核转化成这块缓冲区的操作。
管道中的内容是阅后即焚的,即读取管道里的内容是消耗行为,一个进程读取了管道的一些内容之后,这些内容既不会继续存在。
一般来讲管道是单向的,一个进程负责往管道里面写内容,另一个进程读取管道里面的内容。若两个进程都往管道中写,或都从里面读,则管道里面的内容会变得混乱,无法完成进程间通信的任务。如果两个进程想进行双向通信,可以建立两个管道。如图所示:
在这里插入图片描述管道可分为无名管道和有名管道。

无名管道

无名管道的作用是在有亲缘关系的进程之间传递消息,即进程之间用于共同的祖先,可以用于父子进程,兄弟进程,祖孙进程,叔侄进程。只要有共同的祖先曾经调用了pipe函数,打开的管道文件就会在fork之后被各个后代进程所共享。父子进程共享fork之前打开的文件描述符。
管道实质是一个字节流,若多个进程发送的数据混合在一起,则无法识别出各自的内容。
所以一般是两个有亲缘关系的进程用一条管道来进行通信。
在Linux下,可用如下接口创建管道:
#include<unistd.h>
int pipe(int pipefd[2]);
如果成功,返回值为0,失败返回值为-1,并且设置error。有一下三种出错情况:
EMFILE
该进程使用的文件描述符已经多于MAX_OPEN-2
ENFILE
系统中同时打开的文件已经超过了系统的限制
EFAULT
pipefd参数不合法

无名管道没有文件名与之关联,只能通过文件描述符来访问管道,只有能看到这两个文件描述符的进程和其子孙进程才能够使用管道。成功调用pipe函数之后,会返回两个打开的文件描述符,一个是管道的读取端文件描述符pipefd[0],另一个是管道的写入端描述符pipefd[1]。可以对写入端描述符调用pipefd[1]调用write,向管道里面写入数据。
write(pipefd[1],buff,count);
写入成功之后,就可以对读取端描述符pipefd[0]调用read,读出管道里面的内容。
read(pipefd[0],buff,count);
返回的字节数等于请求字节数count和当前管道存在的字节数的最小值。如果管道为空,read调用阻塞(若没有设置O_NONBLOCK标志)。

思考:若对读取端进行写入操作,对写入端进行读取操作,会如何???

进程调用pipe函数之后如下如所示。但是一个进程管道,只是自言自语,不是通信。
在这里插入图片描述
调用pipe函数的进程随后调用fork函数,创建了子进程,子进程复制父进程打开的文件描述符,建立起两条通信管道。此时,父进程可以往管道里面写(读),子进程可以从里面读(写)。父子进程也可以关闭各自不用的文件描述符,管道成为一个通信的通道。如下图:
在这里插入图片描述

实现方式为父进程关闭pipefd[0]端口,子进程关闭pipefd[1]端口。代码示例如下

#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<unistd.h>
#include<string.h>
#include<fcntl.h>
int main()
{
         int fd[2] = {-1,-1};
         int ret = pipe(fd);
        assert(ret != -1); 
        
        pid_t n = fork();
        assert(n != -1);

        if(n == 0)//子进程
        {
                close(fd[0]);//关闭读端
                char q[127];
                printf("Please Input:");
               scanf("%s",q); 
                ret = write(fd[1],q,127);
                printf("Write Successfully!\n");
               close(fd[1]);//关闭写端
                exit(0);
        }
        else
        {
                int fp = open("./c.txt",O_WRONLY);
                close(fd[1]);//关闭写端
                char s[127] = {0};
                ret = read(fd[0],s,127);
                int i = 0;
                printf("%s\n",s);
                for(i = 0;i < strlen(s);i++)
                {
                        printf("%c",s[i]);
                }
                printf("\nread successfully!\n");
                int a = write(fp,s,strlen(s));
                if(a > 0)
                {
                        printf("Write in file Successfully!\n");
                }
               close(fd[0]);
        }
        return 0;
}

所以,任何两个有亲缘关系的进程,只要共同的祖先打开了一个管道,总能够通过关闭不相关进程的某些管道文件描述符,来建立起两者之间单向通信的管道。
关闭未使用的管道文件描述符的必要性:
Ø 让数据流向更为清晰
Ø 节省文件描述符。
Ø 关闭未使用的管道文件描述符对管道的正确使用影响重大。
管道有如下三条性质:
1 只有当所有的写入端描述符都已关闭,且管道中的数据都被读出,对读取端描述符调用read函数才会返回0(即读到EOF标志)
2 如果所有读取端描述符都已关闭,此时进程再次往管道里面写入数据,写操作会失败,errno被设置为EPIPE,同时内核会向写入进程发送一个SIGPIPE的信号。
3 当所有的读取端和写入端都关闭后,管道才能被销毁。
从管道读取数据的进程,须要关闭其持有的管道写入端描述符。不参与通信的其他有亲缘关系的进程也应该关闭管道写入端描述符。当read函数返回0时,则表明写入过程已经运行完毕,读取进程就不需要再等待。

如果负责读取的进程或者与通信无关的进程,不关闭管道的写入端描述符,就会有管道写入端描述符泄露。当所有负责写入的进程都关闭了写入端描述符之后,负责读的进程调用read时,(若没有设置O_NONBLOCK标志)会阻塞于此,且永不返回。因为内核维护的引用计数发现还有进程可以写入管道,因此read函数依旧会阻塞。

如果写入管道的进程不关闭管道的读取文件描述符,哪怕其他进程都已经关闭了读取端,该进程仍然可以向管道中写入数据,但是管道最终会被写满,后续的写入请求会被阻塞。

当所有的管道读取端都不复存在时,写入操作会失败。即没有了消费者,生产者也就没有生产的必要。

管道对应的内存区大小

管道本质是一片内存区域,自然有大小。自Linux 2.6.11版本起,管道的默认大小是65536字节,可以调用fcntl来获取和修改这个值地大小。

pipe_capacity = fcntl(fd,?F_GETPIPE_SZ);//获取管道大小

ret = fcntl(fd,?F_SETPIPE_SZ,size);//设置管道大小

管道内存区域的大小必须在页面大小和上限值之间,其上限记录在

/proc/sys/fs/pipe-max-size里(我的在file-max),特权用户还可以修改该上限值。

缩小管道容量时,如果当前管道中已存在的内容大于fcntl函数调用中指定的size,此时返回失败,错误码为EBUSY。

管道有大小,写入须谨慎。管道写满,写入进程被阻塞。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值