进程间通信IPC(一)pipe fifo mmap

本文深入探讨了Linux进程间通信的几种方式,包括pipe管道、FIFO(有名管道)和内存映射(mmap)。详细介绍了它们的工作原理、使用示例以及优缺点。通过实例代码展示了如何实现简单的命令如ps-grep,并解释了读写行为、阻塞特性和大小限制。此外,还讨论了匿名映射和有名管道在无血缘关系进程间通信的应用。
摘要由CSDN通过智能技术生成

目录

 

1 概念

2 pipe管道通信

3 pipe管道使用例子

4 pipe实现ps-grep命令

第一版

第二版

5 管道的读写行为

6 管道大小和优劣

7 FIFO命名管道进程间通信

阻塞探究 fifo与open

8 mmap文件映射共享IO

基本思想

mmap函数定义

munmap函数定义

使用例子

10 mmap实现父子进程之间的通信

结果分析:

11 匿名映射

12 mmap无血缘关系进程通信

结果分析:


1 概念

虚拟内存技术实现了进程间的隔离,但是有时候需要进行进程间的通信,因为不同进程的虚拟地址空间中内核态的部分是所有进程共享的,因此可以通过内核提供的缓冲区进行数据交换。

注意这里的pipe管道与linux的管道命令 | 不一样, | 是将前边命令的标准输出作为后边命令的标准输入。但本质上,管道命令 | 可以由管道通信实现,前边命令对应的进程将内容写到缓冲区,后边命令对应的进程将内容读出来再写到终端就实现了管道命令

2 pipe管道通信

pipe系统api定义,unistd.h是unix系统内置头文件,包含系统调用等

输入是一个int类型数组,数组长度为2,对应两个文件描述符,返回值也是int类型,成功返回0,失败返回-1并设置errno 这两个文件描述符指向同一个文件,这个文件是内核区创建的管道伪文件,为什么不用普通文件进行通信? 第一文件IO速度慢,第二一边读一边写实现有点麻烦。 如下图所示

管道只能用于有亲缘关系的进程之间的通信,且管道创建在子进程之前,这样当父进程创建管道伪文件的时候,两个文件描述符一个用来写信息,一个用来读信息,如下图中的w和r,当创建子进程的时候,子进程会继承父进程的文件描述符,因此此时子进程也有w和r对应的两个文件描述符。 从图上看,可以让父进程写,子进程读,也可以让子进程写,父进程读,但是同一时刻只能是一个写一个读,即管道通信是一种半双工通信。

3 pipe管道使用例子

运行结果

不出所料,pipe函数给管道伪文件的两个文件描述符是当前最小可用文件描述符3,4 因为read在读设备、管道、网络的时候,默认是阻塞的,因此就算我们在子进程逻辑中加了sleep也会阻塞等待直到读到数据

4 pipe实现ps-grep命令

第一版

ps命令:将所有进程信息输出到STDOUT_FILENO文件描述符对应的文件,默认是终端,毕竟系统调用一般都是操作文件描述符,而不是直接操作文件,linux命令应该也一样吧。 grep:检索命令, grep pattern file,意思是从文件file中用正则表达式匹配pattern grep pattern ,当不指定file文件的时候,默认从终端输入读取内容来匹配,即输入grep pattern则此时会堵塞,直到终端输入pattern则匹配到了,然后把pattern输出到终端之后继续堵塞,等待下一次输入,除非关掉终端

第二版

第一版代码有两个问题 1 子进程死后成为僵尸进程 2 父进程一直没死,相当于处于阻塞状态 第一个问题好解决,只要父进程按时死亡就行了 第二个问题,为什么子进程已经死了,成为僵尸进程了,父进程还没有死?理论上对于grep命令来说,如果输入端文件关闭,那么就应该结束,所以导致这个问题的原因是,输入端没有关闭! 如下图,因为父进程和子进程同时都打开了写端和读端,所以当子进程死亡后,父进程自己还在打开着写端,所以父进程会检测到写端还开着,就会阻塞等待写端的输入。解决方法就是可以把管道的半双工通信特性直接改成单工,单向流动即可

5 管道的读写行为

以下讨论默认没有设置非阻塞

读管道的时候:

      写端全部关闭:read会返回0,相当于读到文件末尾

      写端没有全部关闭:

             有数据:read正常读取

             没有数据:read阻塞(可以通过fcntl设置非阻塞,阻塞与非阻塞是与文件描述符关联的)

                       

写管道的时候:

        读端全部关闭:产生一个信号,SIGPIPE,程序异常终止

        读端未全部关闭:

                管道已满:write阻塞

                管道未满:write正常写入

                管道的本质就是内核区划出来的一段缓冲区,所以有满和不满

管道的本质就是内核区的一段缓冲区,用两个文件描述符来分别读和写这段缓冲区,当然一个进程也可以自己读自己写,即与自身通信,但这样是没有意义的。

6 管道大小和优劣

ulimit -a可以看到系统可以使用的资源 其中有pipe size即为管道缓冲区大小,512*8字节

优点:使用简单

缺点:

           只能是有学院关系的进程之间通信,比如父子进程和兄弟进程

           本质上只能单向通信,一旦某个管道确定了进程a写进程b读,就不能反过来,如果要双向通信,可以定义两个管道来实现

7 FIFO命令管道进程间通信

PIPE只能实现有血缘关系的进程通信,是匿名管道。

FIFO是先进先出的意思,实际上是通过先进先出的队列来创建了一个管道伪文件,FIFO虽然是Linux基础文件类型的一种,但并不是磁盘上的真正的文件。FIFO可以实现无血缘关系的进程通信,各进程可以打开FIFO文件进行read/write,实际上是读写一个内核通道。 FIFO是有名管道。

创建FIFO伪文件:

        mkfifo myfifo命令创建

        int mkfifo(const char *pathname, mode_t mode)

内核会针对fifo文件开辟一个缓冲区,不同进程可以通过读写这个fifo文件,即读写这个缓冲区来实现进程间通信,p开头即为管道类型的文件

创建用来写数据的进程:

创建用来读数据的进程:

打开两个终端启动两个进程:

先启动写,再启动读:

当读端关闭的时候,写端也跟着关闭

也可以开多个读对应多个写,即多对多,多对多的时候都是混乱传输的,比如二对二,不存在两两配对的情况

当一个写对应两个读时候,这两个读读出来的内容加一块才是写的,也就是说,read来读fifo命名管道的时候,读的同时就把fifo中的内容取了出来,读出来了以后fifo中就没有了,这与普通文件的读是不一样的。

 

如果启动写之后,没有启动读,则写进程阻塞

 

先启动读,再启动写:

这个时候读会阻塞,等待写入

关闭写不会连带关闭读,读会阻塞

阻塞探究 fifo与open

当先启动写,尚未启动读的时候,写进程阻塞,阻塞的是 open函数,即不是最后写入信息的write函数处阻塞,而是在

这里就阻塞了,根本就没能成功打开fifo伪文件

居然是在open的地方阻塞了!

这里牵涉到open函数读写fifo时候的一个特性

如果要打开的是一个fifo文件的读端和写端,那么必须读端和写端都打开了以后open才是返回对应的文件描述符。即打开fifo文件的时候,读端会阻塞等待写端,写端也会阻塞等待读端。

8 mmap文件映射共享IO

基本思想

在磁盘上创建一个文件,通过mmap函数把该文件的一部分映射到一块内存区域,该文件的一部分由offset和length指定,即距离文件开头的偏移以及字节数,这样我们就可以操作内存区域来影响文件,操作内存要比直接操作文件快多了(用memcpy等函数比read快)。

 

mmap函数定义

void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset)

        void *addr: 直接传NULL或nullptr应该就行

        size_t length: 要映射的区域大小,单位字节

        int prot: 此参数设置映射内存区域的读写特性

               PROT_READ 可读

               PTOR_WRITE 可写

               一般通过或运算设置可读可写

        int flags: 如果进程要通信,必须设置成共享的

              MAP_SHARED 共享的,设置为共享的,则修改内存会影响文件

              MAP_PRIVATE 私有的,修改内存不影响原文件

        int fd: 文件描述符,就是打开的那个磁盘文件的描述符

        offset: 映射开始位置到开头的偏移量,设置成0就行

        返回值:

               成功:返回内存中映射区域的首地址

               失败:返回MAP_FAILED

munmap函数定义

释放内存中的映射区域

int munmap(void *addr, size_t length)

          void *addr: 传mmap的返回值,即开辟的内存映射区域

          size_t length: mmap创建的长度

          返回值:成功返回0,失败返回-1

void *类型指针:可以存放任何变量的地址,从void *的角度看,任何的内存空间仅仅是内存空间,我们是不能对void *类型指针解引用的。但是由于void*可以认为是泛型的,可以转化成任意类型,因此 也可以这样 int *mem = mmap() 即用int*来接收映射区地址

使用例子

第一步,创建一个磁盘上文件,并写入一些数据ggg....,可以看到一共写了19各g

第二步,使用mmap和munmap创建和释放内存映射区域

查看磁盘上的文件发现文件被改动了,虽然是用memcpy改的内存区域,但是磁盘上文件也被同步改动了,如果mmap的第四个参数设置为私有的,则改变内存区域不会改变文件。

9 mmap八问

        1. 如果改变一下mmap返回的地址mem,再传入munmem函数还能成功释放吗?

         答:不能

        2. 如果对mem越界操作,比如映射区域是6字节,结果传入了11个字符会怎么样?

         答:只要原文件装的下,都能成功写入原文件,但是如果原文件装不下,则会把传入的“hello world”截断写入原文件。但是如果原文件只有6字节,传入11个字符,只要我们把映射区域设置成11个字节,用这11个字节通信完全没问题,只是没办法写入原文件。即通信只与映射区域有关,与原文件无关。

  

        3. 最后一个参数文件偏移量能不能随便填个数?

        答:不能,首先这个偏移量不能超过文件大小,其次,有一个很重要的限制,文件偏移量offset必须是4k的整数倍,即0,4096,8192等,这是操作系统的限制

        4. 如果用mmap创建映射内存区域以后,把原文件的文件描述符关闭,还能不能通过    修改内存来影响文件?

        答:没有影响,因为用mmap函数前边已经把内存区域和原文件之间的通道打通了,因此用完mmap之后在哪里关闭文件描述符完全没影响。

        5. open的时候,可以创建一个新的文件来创建映射区域吗?

        答:可以创建一个新文件来做映射的原文件,但是这个文件不能是空文件,应该在创建之后扩展一下大小,至少让大小足够用才行。

          扩展大小可以用下边这两个函数

 

        6. open的时候选择O_WRONLY可以吗?

        答:不可以,因为在创建映射区域的时候隐含了一次读操作

        在调用mmap的时候要先读原文件的内容,然后才能映射到内存缓冲区

        7. 选择MAP_SHARED的时候,open选择O_RDONLY,prot可以选择    PROT_READ|PROT_WRITE吗?

        答:不可以,当选择共享的时候,对内存区域的修改会影响原文件,prot设置的是内存区域的权限,如果内存区域是读写的,那么也就意味着可以向内存区域写入,这个时候由于内存区域与原文件共享,所以也要修改原文件,但是原文件只有读权限,因此出错。 也就是说,当选择共享的时候,原文件的权限一定是包含内存区域的权限的。

        8. 如果mmap报错不判断返回值会怎么样?

        答:一定要判断返回值!mmap一定要判断返回值!!!

10 mmap实现父子进程之间的通信

应该先创建内存映射,再fork生成子进程,这样两个进程才可以共享内存映射区域

由结果可看出,子进程和父进程修改都生效了

但是要注意共享的不能改成私有的,改成私有的则无法通信

 

再做一个实现,原文件只有5个字节大,而映射区的大小为40个字节,远大于原文件

 

结果分析:

原文件5个字节,映射区域40个字节,但是由输出结果可知,父子进程通信是通过内核去内存映射区域进行的,也就是说原文件只要大小不等于0就行了,我们需要多大的内存映射区域可以按需要设定,无需考虑原文件大小,即原文件只是创建内存映射区域的一个媒介,一旦内存映射区域创建完成,原文件就没啥用了。

每次都要创建一个新文件太麻烦了,因此有两个解决方法:

一:用匿名映射,仅限于linux系统

二:用现成的文件/dev/zero

11 匿名映射

前面方法创建映射区必须依赖一个文件,使用匿名映射的方法可以不依赖文件直接创建映射区域

使用匿名映射的话,就不需要再mmap中指定文件描述符了,因为根本不需要打开文件,因此fd参数的位置直接放-1即可

但是MAP_ANONYMOUS 和MAP_ANON是linux系统特有的,有些unix系统没有这个东西

这个时候就需要借助unix系统中一个叫 /dev/zero的文件,还有一个 /dev/null文件也很有趣,但是我们只用zero就行了,如果我们不想自己创建文件去映射,那就直接打开zero这个文件。

12 mmap无血缘关系进程通信

无学院关系进程通信就没办法用匿名映射了,还是需要借助一个文件来创建内存映射区域

写进程:

读进程:

结果分析:

先启动写进程,再启动读进程,则读进程可能不是从写进程的第一条信息开始读的,可能写进程已经改过好几次了,这点与管道和有名管道是不一样的,pipe和fife写东西如果写满了就阻塞,mmap中内存映射区域就是一块进程的共享内存

fifo中如果一个进程用read把数据读走了,则另一个进程就读不到了

mmap中多个读进程可以同时读内存映射区域的内存而读到相同的内容

fifo中读就像水杯接水,接走了那杯水不在管子里了

mmap中读就像相机照相,多个进程可以在共享内存区域照到相同的照片

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值