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()系统调用对管道不起作用。

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

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main(void)
{
int     fd[2], nbytes;
pid_t   childpid;
char    string[] = "Hello, world!\n";
char    readbuffer[80];
pipe(fd);
if((childpid = fork()) == -1)
{
printf("Error:fork");
exit(1);
}
if(childpid == 0)        /* 子进程是管道的写进程 */
{
close(fd[0]);      /*关闭管道的读端 */
write(fd[1], string, strlen(string));
exit(0);
}
else                           /* 父进程是管道的读进程 */
{
close(fd[1]);    /*关闭管道的写端 */
nbytes = read(fd[0], readbuffer, sizeof(readbuffer));
printf("Received string: %s", readbuffer);
}
return(0);
}

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

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

#include <fcntl.h>

fcntl(fd,F_SETFL,O_NONBlOCK);

管道的应用(pipe)《深入分析Linux内核源码》相关推荐

  1. 深入分析Linux内核源码oss.org.cn/kernel-book/

    本html页面地址:http://oss.org.cn/kernel-book/ 深入分析Linux内核源码 前言         第一章 走进linux 1.1 GNU与Linux的成长 1.2 L ...

  2. 深入分析linux内核源码

    http://oss.org.cn/kernel-book/index.htm 转载于:https://www.cnblogs.com/licb/p/3912543.html

  3. Linux内核源码分析方法

    说明:这是一个刚接触内核三个月的同学,通过对一个内核特性的分析,总结出一套分析内核的方法. 一.内核源码之我见 Linux内核代码的庞大令不少人"望而生畏",也正因为如此,使得人们 ...

  4. linux内核开发基础(linux内核源码、树莓派源码编译、SD卡挂载)

    首先下载树莓派linux内核源码: 下载网址:https://github.com/raspberrypi/linux 在树莓派使用指令:uname -r查看当前树莓派的版本号,然后选择对应的linu ...

  5. linux的进程/线程/协程系列3:查看linux内核源码——vim+ctags/find+grep

    linux的进程/线程/协程系列3:查看linux内核源码--vim+ctags/find+grep 前言 摘要: 1. 下载linux内核源码 2. 打标签方法:vim+ctags 2.1 安装vi ...

  6. Linux内核源码分析方法—程序员进阶必备

    一.内核源码之我见 Linux内核代码的庞大令不少人"望而生畏",也正因为如此,使得人们对Linux的了解仅处于泛泛的层次.如果想透析Linux,深入操作系统的本质,阅读内核源码是 ...

  7. 【Linux内核源码剖析】内核源码的组织结构

    文章目录 前言 一.Linux内核的特征 二.为什么要学Linux内核 三.Linux操作系统结构 1. Linux内核在系统中的位置 2. Linux内核子系统之间的关系 3. 系统主要的数据结构 ...

  8. 内核大佬讲述,Linux内核源码分析方法(建议收藏)

    一.内核源码之我见 Linux内核代码的庞大令不少人"望而生畏",也正因为如此,使得人们对Linux的了解仅处于泛泛的层次.如果想透析Linux,深入操作系统的本质,阅读内核源码是 ...

  9. Linux内核源码中使用宏定义的若干技巧

    在C中,宏定义的概念虽然简单,但是真要用好却并不那么容易,下面从Linux源码中抽取一些宏定义的使用方法,希望能从中得到点启发: 1. 类型检查 比如module_init的宏定义: 点击(此处)折叠 ...

最新文章

  1. c++11 auto 类型说明符详解
  2. 96. (GO)不同的二叉搜索树
  3. android BluetoothAdapter无法搜索到蓝牙问题
  4. 解决 Microsoft Excel has stopped working
  5. JavaWeb基础—JS学习小结
  6. 会议交流 | IJCKG 2021:Keynotes released!欢迎注册参会
  7. 1603. 设计停车系统
  8. Qt 学习之路 2(2):Qt 简介 笔记
  9. lisp语言画地物符号_地形图中的地物符号说明
  10. css如何将图片调成合适大小,如何利用CSS自动调整图片的大小
  11. PD-GAN: Probabilistic Diverse GAN for Image Inpainting
  12. SaaSBase:什么是企业微信?
  13. FT60F011A/FT60F010A为辉芒微电子基本I/O型系列Flash单片机SOT23-6/SOP8
  14. 打开运行PS、AI等软件时卡在启动窗口的解决办法
  15. 代谢组数据:QC归一化(R语言:MetNormalizer包)
  16. 首席新媒体黎想教程:SEO中的反向链接是什么意思?
  17. php 匹配图片加上域名,20170321_正则表达式:匹配图片地址,添加域名
  18. 我本人一直以来犯的错误,在看了《Think In Java》后才抓了出来
  19. STM32的升级--ICP/ISP/IAP以及Ymodem协议分析
  20. 对于整数取反(取负NEG)运算的理解

热门文章

  1. pandas滑动窗口防止nan出现
  2. php 购物车案例教程,php初步实现购物车功能的实例分析
  3. ddr4服务器内存和普通内存_买主板送DDR4内存!微星日联合大促开幕
  4. 中科大计算机考研科学岛,科学岛研究生_请问谁知道研究生去合肥科学岛与在学校的利与弊啊着急!!!!_淘题吧...
  5. eazyui ajax传值,jquery easyui ajax data属性传值方式
  6. SQL*Plus 系统变量之53 - TERM[OUT]
  7. 收集、分析线上日志数据实战——ELK
  8. iOS开发之Xcode常用调试技巧总结
  9. ntpd时钟同步服务
  10. 《构建之法》第八章自习感想与知识点