管道的应用(pipe)《深入分析Linux内核源码》 http://blog.csdn.net/wangpengqi/article/details/7996182

分类: 进程_线程_进程/线程间通信2012-09-19 15:14 921人阅读 评论(0) 收藏 举报


7.1.1 Linux管道的实现机制

在Linux中,管道是一种使用非常频繁的通信机制。从本质上说,管道也是一种文件,但它又和一般的文件有所不同,管道可以克服使用文件进行通信的两个问题,具体表现为:

·      限制管道的大小。实际上,管道是一个固定大小的缓冲区。在Linux中,该缓冲区的大小为1页,即4K字节,使得它的大小不象文件那样不加检验地增长。使用单个固定缓冲区也会带来问题,比如在写管道时可能变满,当这种情况发生时,随后对管道的write()调用将默认地被阻塞,等待某些数据被读取,以便腾出足够的空间供write()调用写。

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

注意:从管道读数据是一次性操作,数据一旦被读,它就从管道中被抛弃,释放空间以便写更多的数据。

1. 管道的结构

     在 Linux中,管道的实现并没有使用专门的数据结构,而是借助了文件系统的file结构和VFS的索引节点inode。通过将两个 file 结构指向同一个临时的 VFS 索引节点,而这个 VFS 索引节点又指向一个物理页面而实现的。如图 7.1所示。

 

图7.1中有两个 file 数据结构,但它们定义文件操作例程地址是不同的,其中一个是向管道中写入数据的例程地址,而另一个是从管道中读出数据的例程地址。这样,用户程序的系统调用仍然是通常的文件操作,而内核却利用这种抽象机制实现了管道这一特殊操作。

2.管道的读写

      管道实现的源代码在fs/pipe.c中,在pipe.c中有很多函数,其中有两个函数比较重要,即管道读函数pipe_read()和管道写函数pipe_wrtie()。管道写函数通过将字节复制到 VFS 索引节点指向的物理内存而写入数据,而管道读函数则通过复制物理内存中的字节而读出数据。当然,内核必须利用一定的机制同步对管道的访问,为此,内核使用了锁、等待队列和信号。

     当写进程向管道中写入时,它利用标准的库函数write(),系统根据库函数传递的文件描述符,可找到该文件的 file 结构。file 结构中指定了用来进行写操作的函数(即写入函数)地址,于是,内核调用该函数完成写操作。写入函数在向内存中写入数据之前,必须首先检查 VFS 索引节点中的信息,同时满足如下条件时,才能进行实际的内存复制工作:

 

       ·内存中有足够的空间可容纳所有要写入的数据;

       ·内存没有被读程序锁定。

 

如果同时满足上述条件,写入函数首先锁定内存,然后从写进程的地址空间中复制数据到内存。否则,写入进程就休眠在 VFS 索引节点的等待队列中,接下来,内核将调用调度程序,而调度程序会选择其他进程运行。写入进程实际处于可中断的等待状态,当内存中有足够的空间可以容纳写入数据,或内存被解锁时,读取进程会唤醒写入进程,这时,写入进程将接收到信号。当数据写入内存之后,内存被解锁,而所有休眠在索引节点的读取进程会被唤醒。

     管道的读取过程和写入过程类似。但是,进程可以在没有数据或内存被锁定时立即返回错误信息,而不是阻塞该进程,这依赖于文件或管道的打开模式。反之,进程可以休眠在索引节点的等待队列中等待写入进程写入数据。当所有的进程完成了管道操作之后,管道的索引节点被丢弃,而共享数据页也被释放。

   因为管道的实现涉及很多文件的操作,因此,当读者学完有关文件系统的内容后来读pipe.c中的代码,你会觉得并不难理解。

 

 

7.1.2管道的应用

      管道是利用pipe()系统调用而不是利用open()系统调用建立的。pipe()调用的原型是:

 

       int pipe(int fd[2])

 

    我们看到,有两个文件描述符与管道结合在一起,一个文件描述符用于管道的read()端,一个文件描述符用于管道的write()端。由于一个函数调用不能返回两个值,pipe()的参数是指向两个元素的整型数组的指针,它将由调用两个所要求的文件描述符填入。

fd[0]元素将含有管道read()端的文件描述符,而fd[1]含有管道write()端的文件描述符。系统可根据fd[0]和fd[1]分别找到对应的file结构。在第8章我们会描述pipe()系统调用的实现机制。

 注意,在pipe的参数中,没有路径名,这表明,创建管道并不象创建文件一样,要为它创建一个目录连接。这样做的好处是,其它现存的进程无法得到该管道的文件描述符,从而不能访问它。那么,两个进程如何使用一个管道来通信呢?

我们知道,fork()和exec()系统调用可以保证文件描述符的复制品既可供双亲进程使用,也可供它的子女进程使用。也就是说,一个进程用pipe()系统调用创建管道,然后用fork()调用创建一个或多个进程,那么,管道的文件描述符将可供所有这些进程使用。pipe()系统调用的具体实现将在下一章介绍。

这里更明确的含义是:一个普通的管道仅可供具有共同祖先的两个进程之间共享,并且这个祖先必须已经建立了供它们使用的管道。

注意:在管道中的数据始终以和写数据相同的次序来进行读,这表示lseek()系统调用对管道不起作用。

下面给出在两个进程之间设置和使用管道的简单程序:

 

  1. #include <stdio.h>  
  2.   
  3. #include <unistd.h>  
  4.   
  5. #include <sys/types.h>  
  6.   
  7. int main(void)  
  8.   
  9. {  
  10.   
  11.        int     fd[2], nbytes;   
  12.   
  13.        pid_t   childpid;  
  14.   
  15.        char    string[] = "Hello, world!\n";  
  16.   
  17.       char    readbuffer[80];  
  18.   
  19.    
  20.   
  21.       pipe(fd);  
  22.   
  23.    
  24.   
  25.        if((childpid = fork()) == -1)  
  26.   
  27.        {  
  28.   
  29.                printf("Error:fork");  
  30.   
  31.                exit(1);  
  32.   
  33.        }  
  34.   
  35.    
  36.   
  37.        if(childpid == 0)        /* 子进程是管道的写进程 */  
  38.   
  39.       {  
  40.   
  41.               close(fd[0]);      /*关闭管道的读端 */  
  42.   
  43.               write(fd[1], string, strlen(string));   
  44.   
  45.               exit(0);  
  46.   
  47.        }  
  48.   
  49.        else                           /* 父进程是管道的读进程 */  
  50.   
  51.        {  
  52.   
  53.                   close(fd[1]);    /*关闭管道的写端 */  
  54.   
  55.               nbytes = read(fd[0], readbuffer, sizeof(readbuffer));  
  56.   
  57.                printf("Received string: %s", readbuffer);  
  58.   
  59.        }          
  60.   
  61.        return(0);  
  62.   
  63. }  


 

注意,在这个例子中,为什么这两个进程都关闭它所不需的管道端呢?这是因为写进程完全关闭管道端时,文件结束的条件被正确地传递给读进程。而读进程完全关闭管道端时,写进程无须等待继续写数据。

阻塞读和写分别成为对空和满管道的默认操作,这些默认操作也可以改变,这就需要调用fcntl()系统调用,对管道文件描述符设置O_NONBLOCK标志可以忽略默认操作:

 

#include <fcntl.h>

 

fcntl(fd,F_SETFL,O_NONBlOCK);

发布了8 篇原创文章 · 获赞 25 · 访问量 88万+
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 大白 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览