`
xidajiancun
  • 浏览: 457744 次
文章分类
社区版块
存档分类
最新评论

Linux 管道(pipe)原理及使用

 
阅读更多

简介:本文主要介绍了管道(pipe)的基本概念和用途;分析了环形缓冲区的存储、访问及其实现方法;分析并发访问可能引发的问题,并给出解决方法;分析了linux2.6.29内核中pipe的读写函数。

1、管道(pipe

管道是进程间通信的主要手段之一。一个管道实际上就是个只存在于内存中的文件,对这个文件的操作要通过两个已经打开文件进行,它们分别代表管道的两端。管道是一种特殊的文件,它不属于某一种文件系统,而是一种独立的文件系统,有其自己的数据结构。根据管道的适用范围将其分为:无名管道和命名管道。

<wbr><wbr><wbr><wbr><wbr></wbr></wbr></wbr></wbr></wbr>无名管道

主要用于父进程与子进程之间,或者两个兄弟进程之间。在linux系统中可以通过系统调用建立起一个单向的通信管道,且这种关系只能由父进程来建立。因此,每个管道都是单向的,当需要双向通信时就需要建立起两个管道。管道两端的进程均将该管道看做一个文件,一个进程负责往管道中写内容,而另一个从管道中读取。这种传输遵循“先入先出”(FIFO)的规则。

<wbr><wbr><wbr><wbr><wbr></wbr></wbr></wbr></wbr></wbr>命名管道

命名管道是为了解决无名管道只能用于近亲进程之间通信的缺陷而设计的。命名管道是建立在实际的磁盘介质或文件系统(而不是只存在于内存中)上有自己名字的文件,任何进程可以在任何时间通过文件名或路径名与该文件建立联系。为了实现命名管道,引入了一种新的文件类型——FIFO文件(遵循先进先出的原则)。实现一个命名管道实际上就是实现一个FIFO文件。命名管道一旦建立,之后它的读、写以及关闭操作都与普通管道完全相同。虽然FIFO文件的inode节点在磁盘上,但是仅是一个节点而已,文件的数据还是存在于内存缓冲页面中,和普通管道相同。

2、环形缓冲区

每个管道只有一个页面作为缓冲区,该页面是按照环形缓冲区的方式来使用的。这种访问方式是典型的“生产者——消费者”模型。当“生产者”进程有大量的数据需要写时,而且每当写满一个页面就需要进行睡眠等待,等待“消费者”从管道中读走一些数据,为其腾出一些空间。相应的,如果管道中没有可读数据,“消费者”进程就要睡眠等待,具体过程如下图所示。

linux<wbr>pipe

<wbr></wbr>

1<wbr></wbr>生产者——消费者关系图

2.1环形缓冲区实现原理

环形缓冲区是嵌入式系统中一个常用的重要数据结构。一般采用数组形式进行存储,即在内存中申请一块连续的线性空间,可以在初始化的时候把存储空间一次性分配好。只是要模拟环形,必须在逻辑上把数组的头尾相连接。只要对数组最后一个元素进行特殊的处理——访问尾部元素的下一元素时,重新回到头部元素。对于从尾部回到头部只需模缓冲长度即可(假设maxlen为环形缓冲的长度,当读指针read指向尾部元素时,只需执行read=read%maxlen即可使read回到头部元素)。

linux<wbr>pipe

2<wbr></wbr>环形缓冲区图

2.2读写操作

环形缓冲区要维护写端(write)和读端(read)两个索引。写入数据时,必须先确保缓冲区没有满,然后才能将数据写入,最后将write指针指向下一个元素;读取数据时,首先要确保缓冲区不为空,然后返回read指针对应得元素,最后使read指向下一个元素的位置。读写操作伪代码:linux<wbr>pipe

2.3判断“满”和“空”

readwrite指向同一个位置时环形缓冲区为空或满。为了区别环满和空,当readwrite重叠的时候环空;而当writeread快,追到距离read还有一个元素间隔的时候,就认为环已经满了。环形缓冲区原理图如图3所示。

linux<wbr>pipe

3<wbr></wbr>环形缓冲区实现原理图

3<wbr></wbr>并发访问

考虑到在不同环境下,任务可能对环形缓冲区的访问情况不同,需要对并发访问的情况进行分析。

在单任务环境下,只存在一个读任务和一个写任务,只要保证写任务可以顺利的完成将数据写入,而读任务可以及时的将数据读出即可。如果有竞争发生,可能会出现如下情况:

Case1:假如写任务在“写指针加1,指向下一个可写空位置”执行完成时被打断,如图3所示,此时写指针write指向非法位置。当系统调度读任务执行时,如果读任务需要读多个数据,那么不但应该读出的数据被读出,而且当读指针被调整为0是,会将以前已经读出的数据重复读出。

linux<wbr>pipe

4<wbr></wbr>写指针非法

Case2:假设读任务进行读操作,在“读指针加1”执行完时被打断,如图4所示,此时read所处的位置是非法的。当系统调度写任务执行时,如果写任务要写多个数据,那么当写指针指到尾部时,本来缓冲区应该为满状态,不能再写,但是由于读指针处于非法位置,在读任务执行前,写任务会任务缓冲区为空,继续进行写操作,将覆盖还没有来的及读出的数据。

linux<wbr>pipe

5<wbr></wbr>读指针非法

为了避免上述错误的发生,必须保证读写指针操作是原子性的,读写指针的值要么是没有修改的,要么是修改正确的。可以引入信号量,有效的保护临界区代码,就可以避免这些问题。在单任务环境下,也可以通过采取适当的措施来避免信号量的使用,从而提高程序的执行效率。

4.linux内核中pipe的读写实现

Linux内核中采用struct pipe_inode_info结构体来描述一个管道。

linux<wbr>pipe

其中,当pipe为空/满时,采用等待队列,该队列使用自旋锁进行保护。

linux<wbr>pipe

struct Pipe_buffer数据结构描述pipe的缓冲(buffer

linux<wbr>pipe

本文重点针对pipe实现中对环形缓冲区的操作方法,目的是借鉴学习其互斥访问方法。因此,着重分析pipe_readpipe_write方法。

Pipe_read(fs/pipe.c)

访问pipe对应的inode必须获得相应的互斥锁,防止并发访问。

linux<wbr>pipe

数据的读出放在一个死循环中,整个for循环中的代码均属于临界区,需要互斥锁进行保护。

linux<wbr>pipe

有以下几种情况才会退出:

<wbr><wbr><wbr><wbr><wbr></wbr></wbr></wbr></wbr></wbr>完成数据的读出;

<wbr><wbr><wbr><wbr><wbr></wbr></wbr></wbr></wbr></wbr>Pipe没有writer进程

<wbr><wbr><wbr><wbr><wbr></wbr></wbr></wbr></wbr></wbr>进程设置了O_NONBLOCK标志

linux<wbr>pipe

325行将buffer中的数据读出。完成后,紧接着调整buffer中指针的位置

linux<wbr>pipe

其中,348行设置标志,do_wakeup1,说明buffer中已经有空位置可以写入数据,这时,可以唤醒等待队列中的睡眠的写进程。

linux<wbr>pipe

如果没有退出,或者成功读取数据,读进程会主动调用pipe_wait函数进行睡眠等待,直到有writer进程写入数据并将其唤醒。

linux<wbr>pipe<wbr><span lang="EN-US" style="margin:0px; padding:0px; word-wrap:break-word"><wbr></wbr></span></wbr>

当进程从临界区中退出后会释放互斥锁。

<wbr></wbr>

linux<wbr>pipe

<wbr></wbr>

最后,为了防止reader进程是因为收到信号量而退出,再给睡眠的writer进程一次机会,检查do_wakeup,如果为1就唤醒睡眠的writer进程。

<wbr><wbr><wbr><wbr><wbr></wbr></wbr></wbr></wbr></wbr>pipe_write(fs/pipe.c)

首先,与pipe_read相同,pipe_write采用互斥锁对临界区进行保护。写操作也放在死循环中,退出条件也与read相同。

linux<wbr>pipe

<wbr></wbr>

pipe_read不同,writer进程不总是睡眠等待,在调用pipe_wait进行睡眠后,如果有read进程读走某些数据,write进程会随时进行写操作。


<wbr></wbr>

下面再来看看,系统调用pipe的例子。在下面的例子中,父进程通过管道向子进程发送了一个字符串。

子进程将它显示出来:

1.创建pipe文件

<wbr><wbr>#inlcude<wbr></wbr></wbr></wbr>

<wbr><wbr> int fd[2];</wbr></wbr>

<wbr><wbr> if ( pipe(fd) != 0 )</wbr></wbr>

2.读fd[0]

<wbr><wbr> close(fd[1]);</wbr></wbr>

<wbr><wbr> read(fd[0], buffer,BUF_SIZ);</wbr></wbr>

3.写fd[1]

<wbr><wbr> close(fd[0]);</wbr></wbr>

<wbr><wbr> write(fd[1], buffer,strlen(buffer));</wbr></wbr>

4.给个可以运行的代码

<wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr>

<wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr>#include&lt;stdio.h&gt;</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr>

<wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr> #include&lt;unistd.h&gt;</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr>

int main(void){

<wbr><wbr> int fd[2];</wbr></wbr>

<wbr><wbr> char buf[256];</wbr></wbr>

<wbr><wbr> pid_t pid;</wbr></wbr>

<wbr><wbr> if(pipe(fd) != 0){</wbr></wbr>

<wbr><wbr><wbr><wbr> puts("Pipe No!");</wbr></wbr></wbr></wbr>

<wbr><wbr> }</wbr></wbr>

<wbr><wbr> else{</wbr></wbr>

<wbr><wbr><wbr><wbr> puts("Pipe ok!");</wbr></wbr></wbr></wbr>

<wbr><wbr> }</wbr></wbr>

<wbr><wbr> if((pid=fork()) == 0){</wbr></wbr>

<wbr><wbr><wbr><wbr> puts("Sun");</wbr></wbr></wbr></wbr>

<wbr><wbr><wbr><wbr> close(fd[0]);</wbr></wbr></wbr></wbr>

<wbr><wbr><wbr><wbr> char str[]="Jack";</wbr></wbr></wbr></wbr>

<wbr><wbr><wbr><wbr> write(fd[1], str, sizeof(str));</wbr></wbr></wbr></wbr>

<wbr><wbr> }</wbr></wbr>

<wbr><wbr> else{</wbr></wbr>

<wbr><wbr><wbr><wbr> puts("Father");</wbr></wbr></wbr></wbr>

<wbr><wbr><wbr><wbr> close(fd[1]);</wbr></wbr></wbr></wbr>

<wbr><wbr><wbr><wbr> read(fd[0], buf, sizeof(buf));</wbr></wbr></wbr></wbr>

<wbr><wbr><wbr><wbr> puts(buf);</wbr></wbr></wbr></wbr>

<wbr><wbr> }</wbr></wbr>

<wbr><wbr> return 0;</wbr></wbr>

}


转自:http://blog.sina.com.cn/s/blog_67b7d7e401018dvz.html

分享到:
评论

相关推荐

    Python中使用PIPE操作Linux管道

    Linux上,创建管道使用pipe函数,当它执行后,会产生两个文件描述符,分别为读端和写端。单个进程中的管道几乎没有任何作用,通常会先调用pipe,然后调用fork,从而创建从父进程到子进程的IPC通道。 Linu

    Linux管道详解-管道通信、无名管道、有名管道、具体应用示例

    无名管道(PIPE)是一种亲缘进程间的通信方法2.1 无名管道的通信原理无名管道存在于kernel中,A,B必须具有亲缘关系进程。同一时刻,只能有一个写端或一个读端。父子进程间,只要是fork()出来的,就会完美复制父...

    windows进程间管道通信

    windows每个内核对象都可以用一个HANDLE 来进行索引,本实例利用管道实现Console输出重定向的功能,简单易学,解释了父子进程间内核对象句柄继承的原理。 代码编译运行后,ping的输出将保存到"C:\test_console_...

    操作系统上机实验报告-进程的管道通信

     使用系统调用pipe()建立一条管道,两个子进程分别向管道写一句话:  Child process1 is sending a message!  Child process2 is sending a message!  父进程从管道读出来自两个子进程的信息,显示在屏幕上。  ...

    边干边学Linux__第二版_doc格式

    6.10 管道(pipe) 第7章 C语言开发工具 7.1 编写程序的工具 7.2 编C语言程序 7.3 make工具 7.4 gdb调试工具 第8章 Bourn Again Shell编程 8.1 bash脚本的建立和运行 8.2 shell的变量 8.3 shell脚本位置参数的传递 ...

    Linux高性能服务器编程

    1.5.2 ARP高速缓存的查看和修改 1.5.3 使用tcpdump观察ARP通信过程 1.6 DNS工作原理 1.6.1 DNS查询和应答报文详解 1.6.2 Linux下访问DNS服务 1.6.3 使用tcpdump观察DNS通信过程 1.7 socket和TCPIP协议族的...

    城院操作系统-实验报告-实验八.doc

    掌握Linux支持管道的系统调用和管道的使用; 4. 巩固进程同步概念。 二、实验内容 用系统调用pipe( )创建管道,实现父子进程间的通信。 三、实验步骤 1、并发进程的无管道通信 1) 编译运行给出的pipe1.c,观察运行...

    通过实例解析python subprocess模块原理及用法

    一、subprocess以及常用的封装函数 运行python的时候,我们都是在创建并运行一...另外subprocess还提供了一些管理标准流(standard stream)和管道(pipe)的工具,从而在进程间使用文本通信。 subprocess.call() 父进程

    C语言实现进程间通信原理解析

    最近学习了操作系统的并发;...pipe(int fd[] ), 其参数为长度为2的int数组,分别代表读端fd[0], 写端fd[1], 在创建管道后,f d[0],fd[1]成为文件描述符; 写入(write)管道一端fd[1]的数据,在管道的另一端fd

    操作系统实验

    使用系统调用pipe()建立一条管道线;创建子进程P1、P2、…。子进程Pi分别向管道各写信息,而父进程则从管道中读出来自于各子进程的信息,实现进程家族间无名管道通讯。 扩展之,使之成为客户/服务器模式,并完成一定...

    吉林大学2022年操作系统实验报告(仅供参考)

    用pipe()创建一个管道文件,然后用fork()创建两个生产进程和两个消费进程,它们之间通过pipe()传递信息。 用clone()创建四个轻进程(线程),用参数指明共享内存等资源,通过共享内存模拟生产消费问题,利用pthread_...

    操作系统实验报告

    使用系统调用pipe()建立一条管道线;两个子进程P1和P2分别向管道中写一句话: Child 1 is sending a message! Child 2 is sending a message! 而父进程则从管道中读出来自于两个子进程的信息,显示在屏幕上。 要求父...

    ganda:快速的cmd-line应用程序,可快速请求数百万个URL并保存结果

    它以 的unix原理设计,并希望在命令行管道链中使用以并行发出请求。 默认情况下,它将回显所有响应主体以符合标准输出,但可以选择将每个请求的结果保存在目录中以供以后分析。 给定一个带有ID列表的文件,您可以...

Global site tag (gtag.js) - Google Analytics