1、管道(pipe)

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

●    无名管道

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

●    命名管道

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

2、环形缓冲区

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

2.1环形缓冲区实现原理

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

2.2读写操作

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

2.3判断“满”和“空”

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

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

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

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

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

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

●Pipe_read(fs/pipe.c)

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

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

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

▲    完成数据的读出;

▲    Pipe没有writer进程

▲    进程设置了O_NONBLOCK标志

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

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

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

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

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

●    pipe_write(fs/pipe.c)

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

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

FIFO文件的操作方法只有open方法(具体实现在fs/fifo.c)。但是,这并不是fifo文件真正的操作方法,其真正的读写方法是根据不同的打开方式而决定的。

FIFO文件的打开操作

第一次打开fifo文件的进程调用fifo_open时,该命名管道的缓冲页面还没有分配,

因此43行中alloc_pipe_info()函数会被执行。

分配所需要的pipe_inode_info数据结构和缓冲页面。以后打开该文件的进程会跳过该部分。

Fifo可以以“只读”、“只写”、“读写”三种方式打开。另外,open系统调用中有flag参数,如果调用fifo_open的进程开始时设置了flag中的O_NONBLOCK参数为1,则在打开的过程中无论是否可以正常打开,进程都不能进入睡眠,必须立即返回。下面具体分析每个打开方式的不同操作。

◆    以“只读”方式打开。即命名管道的读端的几种情况:

a)      如果命名管道的写端已经打开,那么管道的创建就完成了。这时,一般写端(生产者)一般都在睡眠,因此要调用wake_up_partner()将其唤醒。

b)     如果写端没有打开,而且设置了O_NONBLOCK标志,此时尽管读端已经打开,但是没有完成管道的打开,由于进程要求不能等待,因此必须立即返回。

c)      写读没有打开,但是没有设置O_NONBLOCK标志,读进程调用wait_for_partner()函数,进入睡眠状态等待写读打开后将其唤醒。

◆    以“只写”方式打开。即打开命名管道写端的几种情况:

a)      如果命名管道的读端没有打开,并且设置了O_NONBLOCK标志,写端进程就要直接跳转到err处(判断是否有读进程或写进程在睡眠,如果没有就释放pipe_inode_info)执行。否则,让filp的f_op指向write_pipefifo_fops方法。然后管道的写进程计数加1

b)     如果命名管道的读端已经打开,那么写端就完成了命名管道的打开。此时,读端一般都在睡眠等待,应该调用wake_up_partner()将其唤醒。

c)      如果命名管道读端没有打开,那么写端就要调用wait_for_partner()进入睡眠等待,直到读端打开,将其唤醒之后才能返回。

◆    以“读写”方式打开

读写的方式打开命名管道,相当于同一个进程打开了命名管道的两端,因此不需要等待。但是,也有可能已经有进程已经打开了某一端正在睡眠等待,因此,任意一端第一次打开,就唤醒了正在睡眠的进程。这种打开方式下,真正的操作方法是rdwr_pipefifo_fops。

命名管道一旦建立,以后的读写以及关闭都与普通管道相同。尽管FIFO文件的inode节点是在磁盘上,但是数据只是存在于内存缓存中,与普通管道相同。

分析完FIFO文件的不同打开方式之后,接下来分析各自对应的操作方法。具体实现在

Fs/pipe.c文件中。

首先,我们可以看到在三个操作方法中llseek都调用的是no_llseek。

从代码中,可以发现no_llseek并没有做任何事情,只是在被调用的时候返回错误代码。也就是在FIFO文件中是不能使用seek方法的,对文件的读写只能根据先进先出的顺序进行访问。

接下来,在read_pipefifo_fops和write_pipefifo_fops中分别调用了bad_pipe_w和bad_pipe_r函数:

从代码中,可以看到它们也只是返回错误码,也就是说在pipe中读端写操作是禁止的,在写端读操作同样也是禁止的。

Linux内核中无名管道pipe和有名管道fifo的分析相关推荐

  1. linux系统调用创建无名管道,linux 无名管道pipe和有名管道FIFO

    1.管道(pipe) 管道是进程间通信的主要手段之一.一个管道实际上就是个只存在于内存中的文件,对这个文件的操作要通过两个已经打开文件进行,它们分别代表管道的两端.管道是一种特殊的文件,它不属于某一种 ...

  2. 【Linux系统编程学习】匿名管道pipe与有名管道fifo

    此为牛客Linux C++和黑马Linux系统编程课程笔记. 0. 关于进程通信 Linux环境下,进程地址空间相互独立,每个进程各自有不同的用户地址空间.任何一个进程的全局变量在另一个进程中都看不到 ...

  3. 【编撰】linux IPC 002 - 匿名管道PIPE和有名管道FIFO的概念和实例,以及应用比较

    前言:上一节提到IPC的概述,本一节,原文作者比较详细的讨论了,管道的概念和实例和使用场景: 原文作者:郑彦兴 (mlinux@163.com)国防科大计算机学院 http://www.ibm.com ...

  4. linux 有名管道pipe,linux 用無名管道pipe和有名管道fifo實現線程間通信

    1.pipe 用與實現同一個進程下不同線程間的通信(跟IPC進程間通信中的具有血緣關系的進程通信實現方式一樣) #include #include #include #include #include ...

  5. 什么是Linux系统调用system call?(Linux内核中设置的一组用于实现各种系统功能的子程序)(区别于标准C库函数调用)核心态和用户态的概念、中断的概念、系统调用号、系统调用表

    文章目录 什么是系统调用? 为什么要用系统调用? 系统调用是怎么工作的? 如何使用系统调用? _syscall*()是什么? errno是什么? 调用性能问题 Linux系统调用列表 进程控制 文件系 ...

  6. Linux进程间通信之管道(pipe)、命名管道(FIFO)与信号(Signal)

    整理自网络 Unix IPC包括:管道(pipe).命名管道(FIFO)与信号(Signal) 管道(pipe) 管道可用于具有亲缘关系进程间的通信,有名管道克服了管道没有名字的限制,因此,除具有管道 ...

  7. 你真的懂Linux内核中的阻塞和异步通知机制吗?(花了五天整理,墙裂推荐!)

    工科生一枚,热衷于底层技术开发,有强烈的好奇心,感兴趣内容包括单片机,嵌入式Linux,Uboot等,欢迎学习交流! 爱好跑步,打篮球,睡觉. 欢迎加我QQ1500836631(备注CSDN),一起学 ...

  8. Linux内核中锁机制之完成量、互斥量

    在上一篇博文中笔者分析了关于信号量.读写信号量的使用及源码实现,接下来本篇博文将讨论有关完成量和互斥量的使用和一些经典问题. 八.完成量 下面讨论完成量的内容,首先需明确完成量表示为一个执行单元需要等 ...

  9. 简单谈一点linux内核中套接字的bind机制--数据结构以及端口确定

    众所周知,创建一个套接字可以bind到一个特定的ip地址和端口,实际上套接字这一概念代表了TCP/IP协议栈的应用层标识,协议栈中的应用层就是通过一个ip地址和一个端口号标识的,当然这仅仅是对于TCP ...

最新文章

  1. php析构函数的用法
  2. C# 温故而知新:Stream篇(二)
  3. 实验五 oracle高级数据查询技术
  4. 登上更高峰!颜水成、程明明团队开源ViP,引入三维信息编码机制,无需卷积与注意力...
  5. 状态压缩 + 暴力 HDOJ 4770 Lights Against Dudely
  6. 计算机应用技术工作项目是,论计算机应用技术在工程项目管理中的应用(原稿)...
  7. IOS UISwitch 组件的使用
  8. 当贝显示服务器生病,智能电视一直显示“缓冲中”是什么问题?当贝市场为你解答...
  9. 指针java_浅谈Java与指针 - 穿梭于偶然
  10. matlab重复线性回归,(MATLAB)一元线性回归和多元线性回归
  11. caffe(CPU版本)配置 及MNIST调用
  12. w ndows平板,Win8.1千元芯平板 昂达V975w四核评测
  13. MyCat之全局表和ER表
  14. 贪心算法数塔问题c语言,c语言背包问题_c语言背包问题几种解法_背包问题贪心算法(2)...
  15. 如何发表SCI论文?写SCI文章的心得
  16. 《Effective Modern C++》翻译--条款2: 理解auto自动类型推导
  17. 汉语拼音的5个声调该怎么学?
  18. 在安卓项目中使用gifsicle编辑GIF动图-Android NDK 编译 gifsicle 为可执行文件记录
  19. 数据库05子查询,union
  20. 别再眼红别人家的年终奖了,这才是大多数人的现状!

热门文章

  1. 利用系统滴答时间计算实际程序运行时间
  2. 科大星云诗社动态20211108
  3. [云炬Python学习笔记] Python读取指定文件夹下的文件
  4. 一步一步教你如何将 yolov3/yolov4 转为 caffe 模型
  5. 第04课:深度学习框架 PyTorch
  6. 【远程操控】Pycharm远程连接服务器之本地显示独立的plot窗口
  7. matlab中大括号的使用 { },可以装4维的数据
  8. 类对象和类指针深入分析与对比
  9. code ro rw zi 编译的一个ARM的程序,编译结果中的一句话
  10. shell--变量的替换