当前位置:首页 > 嵌入式培训 > 嵌入式学习 > 讲师博文 > 经典进程间通信之无名管道和有名管道

经典进程间通信之无名管道和有名管道 时间:2018-09-26      来源:未知

众所周知作为UNIX IPC中老的一种形式管道,是所有UNIX系统都提供的一种通信机制,因而它应用的范围非常广泛。例如我们可以使用管道符“|”来连接进程。在Linux系统中,由管道连接起来的进程可以自动运行,就如同在他们有一个数据流一样。根据管道的适用范围将其分为:无名管道(pipe)和有名管道(fifo)。本文主要围绕二者出发,讨论管道通信的机制。

一、无名管道(pipe)

1.什么是管道

一个管道实际上就是个只存在于内存中的文件,对这个文件的操作要通过两个已经打开文件进行,它们分别代表管道的两端。管道是一种特殊的文件,它不属于某一种文件系统,而是一种独立的文件系统,有其自己的数据结构。类似时空隧道的概念,建立两个进程之间的通讯桥梁。数据的读出和写入:一个进程向管道中写的内容被管道另一端的进程读出。写入的内容每次都添加在管道缓冲区的末尾,并且每次都是从缓冲区的头部读出数据。

2.无名管道的特性

(1)只能用于具有亲缘关系的进程之间的通信,通常一个管道由一个进程创建,然后该进程调用fork,此后父子进程之间就可以通过管道通信。

(2)半双工的通信模式,具有固定的读端和写端:传输方向同时只能是一个方向,。

(3)管道可以看成是一种特殊的文件,对于它的读写可以使用文件IO如read、write函数:但是在文件系统里并不存在pipe对应的文件而且不支持如lseek() 操作。

3.无名管道的创建

无名管道可以由pipe()函数创建

#include

int pipe(int pipefd[2]);

pipe函数调用成功返回0,调用失败返回-1。

调用pipe函数时在内核中开辟一块缓冲区(称为管道)用于通信,它有一个读端一个写端,然后通过pipefd参数传出给用户程序两个文件描述符,pipefd[0]指向管道的读端,pipefd[1]指向管道的写端(很好记,就像0是标准输入1是标准输出一样)。所以管道在用户程序看起来就像一个打开的文件,通过read(pipefd[0]);或者write(pipefd[1]);向这个文件读写数据其实是在读写内核缓冲区。

详细的创建流程:

step1: 父进程创建一个pipe,其中fd[0]固定用于读管道,而fd[1]固定用于写管道。

Step2:父进程fork,子进程继承了父进程的管道

Step3:之后取决于我们想要的数据流方向来关闭相应的端。

4.无名管道读写注意事项

当管道的一端被关闭后:

(1)当读一个写端已被关闭的管道时,在所有数据都被读取后, read返回0,以指示达到了文件结束处(从技术方面考虑,管道的写端还有进程时,就不会产生文件的结束。可以复制一个管道的描述符,使得有多个进程具有写打开文件描述符。但是,通常一个管道只有一个读进程,一个写进程)。

(2)如果写一个读端已被关闭的管道,则产生信号SIGPIPE。如果忽略该信号或者捕捉该信号并从其处理程序返回,则write出错返回,errn设置为EPIPE。在写管道时,常数PIPE_BUF规定了内核中管道缓存器的大小。如果对管道进行write调用,而且要求写的字节数小于等于PIPE_BUF,则此操作不会与其他进程对同一管道(或FIFO)的write相交错。但是,若有多个进程同时写一个管道(或FIFO),而且某个或某些进程要求写的字节数超过PIPE_BUF字节数,则数据可能会与其他写操作的数据相交错。

5.popen和pclose函数

因为常见的操作是创建一个连接到另一个进程的管道,然后读其输出或向其发送输入,所以标准I / O库为实现这些操作提供了两个函数popen和pclose。在这边就不详细说明了,大家感兴趣的化可以查看Man手册中的描述。

6.示例代码

#include

#include

#include

int pid1, pid2;

int main( )

{

int fd[2];

char outpipe[100], inpipe[100];

pipe(fd); /*创建一个管道*/

while ((pid1 = fork( )) == -1);

if (pid1 == 0)

{

sprintf(outpipe, "child 1 process is sending message!");

/*把串放入数组outpipe中*/

write(fd[1], outpipe, 50); /*向管道写长为50字节的串*/

exit(0);

}

else

{

while((pid2 = fork( )) == -1);

if (pid2 == 0)

{

sprintf(outpipe, "child 2 process is sending message!");

write(fd[1], outpipe, 50);

exit(0);

}

else

{

//wait(NULL); /*同步*/

read(fd[0], inpipe, 50); /*从管道中读长为50字节的串*/

printf("%s\n", inpipe);

//wait(NULL);

read(fd[0], inpipe, 50);

printf("%s\n",inpipe);

exit(0);

}

}

return 0;

}

二、有名管道(fifo)

1.有名管道的概念

为何要提出有名管道的说法,目的是为了克服无名管道的不足之处:

(1)无名管道只能用于具有亲缘关系的进程之间,这就限制了无名管道的使用范围

(2)有名管道可以使互不相关的两个进程互相通信。有名管道可以通过路径名来指出,并且在文件系统中可见

为了这种有名管道,Linux中专门设立了一个专门的特殊文件系统--管道文件,以FIFO的文件形式存在于文件系统中,这样,即使与FIFO的创建进程不存在亲缘关系的进程,只要可以访问该路径,就能够彼此通过FIFO相互通信,因此,通过FIFO不相关的进程也能交换数据。但在磁盘上只是一个节点,而文件的数据则只存在于内存缓冲页面中,与普通管道一样。

2.有名管道的创建

(1)有名管道可以从命令行上创建,命令行方法是使用下面这个命令:

$ mkfifo myfifo

(2)有名管道也可以从程序里创建,相关API有:

#include

#include

int mkfifo(const char *filename,mode_t mode);

mkfifo函数成功返回0,失败返回-1并且设置errno。

该函数的第一个参数是一个普通的路径名,也就是创建后FIFO的名字。第二个参数与打开普通文件的open()函数中的mode参数相同。如果mkfifo的一个参数是一个已经存在路径名时,会返回EEXIST错误,所以一般典型的调用代码首先会检查是否返回该错误,如果确实返回该错误,那么只要调用打开FIFO的函数open就可以了。

3.FIFO的open打开规则

O_RDONLY、O_WRONLY和O_NONBLOCK标志共有四种合法的组合方式:

flags=O_RDONLY:open将会调用阻塞,除非有另外一个进程以写的方式打开同一个FIFO,否则一直等待。

flags=O_WRONLY:open将会调用阻塞,除非有另外一个进程以读的方式打开同一个FIFO,否则一直等待。

flags=O_RDONLY|O_NONBLOCK:如果此时没有其他进程以写的方式打开FIFO,此时open也会成功返回,此时FIFO被读打开,而不会返回错误。

flags=O_WRONLY|O_NONBLOCK:立即返回,如果此时没有其他进程以读的方式打开,open会失败打开,此时FIFO没有被打开,返回-1。

总而言之:

● 在一个FIFO上打开一个读端

● 在一个FIFO上打开一个写端

4.有名管道的读写规则

有名管道的读写原则和无名管道的读写原则基本一致,主要参考无名管道的读写原则即可。

5.示例代码

使用完成拷贝文件的功能:

#include

#include

#include

#include

#include

#include

#include

#include

#include

#define ERR_EXIT(m)

do {

perror(m);

exit(EXIT_FAILURE);

} while(0)

int main(int argc, char *argv[])

{

mkfifo("tp", 0644);

int infd = open("Makefile", O_RDONLY);

if (infd == -1)

ERR_EXIT("open error");

int outfd;

outfd = open("tp", O_WRONLY);

if (outfd == -1)

ERR_EXIT("open error");

char buf[1024];

int n;

while ((n = read(infd, buf, 1024)) > 0)

write(outfd, buf, n);

close(infd);

close(outfd);

return 0;

}

三、无名管道与有名管道的区别与联系

1.PIPE和FIFO的区别:

对于FIFO和无名管道的编码区别:

(1)创建并打开一个管道只需调用pipe。创建并打开一个FIFO则需在调用mkfifo后再调用open。

(2)管道在所有进程终都关闭它之后自动消失。FIFO的名字则只有通过调用unlink才文件系统删除。

FIFO需要额外调用的好处是:FIFO在文件系统中有一个名字,该名字允许某个进程创建个FIFO,与它无亲缘关系的另一个进程来打开这个FIFO。对于管道来说,这是不可能的。

系统规定 :如果写入的数据长度小于等于PIPE_BUF字节,那么或者写入全部字节,要么一个字节都不写入。

在非阻塞的write调用情况下,如果FIFO 不能接收所有写入的数据,将按照下面的规则进行:

(1)请求写入的数据的长度大于PIPE_BUF字节,调用失败,数据不能被写入。

(2)请求写入的数据的长度小于PIPE_BUF字节,将写入部分数据,返回实际写入的字节数,返回值也可能是0。

其中。PIPE_BUF是FIFO的长度,它在头文件limits.h中被定义。在linux或其他类UNIX系统中,它的值通常是4096字节。注意:PIPE_BUF与FIFO容量是有区别的,PIPE_BUF表示可原子的写往一个管道或FIFO的大数据量。PIPE_BUF为4096,但是FIFO的容量为65536.

2.PIPE和FIFO的相同点:

(1)虽然管道,特别是有名管道可以很方便地在双向上打开读写,但其内核实现依然是单向的。严格遵循先进先出(first in first out),对管道及FIFO的读总是从开始处返回数据,对它们的写则把数据添加到末尾。

(2)pipe, fifo都不支持诸如lseek()等文件定位操作。

(3)对于pipe或者fifo,如果在读端或者写端打开了多个读写端(进程),之间的读写是不确定的,需要通过其他的同步机制实现多进程通讯的同步。

上一篇:Linux下动态库和静态库的制作及使用

下一篇:Android导入第三方静态库.a编译成动态库.so

热点文章推荐
华清学员就业榜单
高薪学员经验分享
热点新闻推荐
前台专线:010-82525158 企业培训洽谈专线:010-82525379 院校合作洽谈专线:010-82525379 Copyright © 2004-2022 北京华清远见科技集团有限公司 版权所有 ,京ICP备16055225号-5京公海网安备11010802025203号

回到顶部