由浅入深探讨Linux进程间通信(上篇)

由浅入深探讨Linux进程间通信(上篇)

目录

前言:

由浅入深探讨Linux进程间通信(上篇) 图1

最近Linux进程间通信视频课程很火爆,很多同学通过这个课程开始对Linux进程间通信原理有所了解,本文继续围绕Linux进程间通信这个主题,由浅入深讲解Linux进程间通信,让你彻底学懂Linux进程间通信。

1.进程间通信介绍

进程间通信(简称IPC) 是指在多个进程之间传输数据或共享信息的机制。在操作系统中,每个进程都具有独立的地址空间和资源,为了实现进程之间的数据交换和协作,需要使用IPC机制。

  • 数据交换:进程间通信可以实现在不同进程之间传递数据。
  • 共享资源:进程间通信可以实现多个进程之间共享资源,如共享内存、文件、设备等。
  • 进程同步:进程间通信可以用于实现进程之间的同步操作。
  • 消息传递:进程间通信可以通过消息传递的方式实现进程之间的通信和协作。(理论知识,了解一下就行了)。

2.由浅入深探索进程间通信

2.1 从最简单开始

由浅入深探讨Linux进程间通信(上篇) 图2

( 本文以32位CPU来讲解进程间通信)。32位CPU能访问的地址空间范围为0x00000000-0xFFFFFFFF共4GB。4GB分为两部分:

  • 用户空间:0x00000000-0xC0000000,共3GB,用户态和内核态都能访问。
  • 内核空间:0xC0000000-0XFFFFFFFF,共1GB,内核态访问。

CPU从用户态切换到内核态的方法:

  • 系统调用
  • 异常
  • 外部设备中断

2.2 难度升级

由浅入深探讨Linux进程间通信(上篇) 图3

我们在前面的基础上加入新的概念 地址映射 。 地址映射就是把虚拟地址转换为物理地址,通过物理地址去访问物理内存。

  • CPU寻址是使用虚拟地址,必须通过地址映射将虚拟地址转换成物理地址,才能正确读写物理内存。
  • 每个进程都需要维护一个虚拟地址空间表,进程被CPU选中后,CPU会去读取进程地址空间的代码段和数据段,然后执行对应的指令。
  • 注意内核空间是直接映射,直接映射是指虚拟地址等同于物理地址。

直接映射区域的物理内存可以通过虚拟地址直接访问(记住这个结论)。

2.3 难度再次升级

由浅入深探讨Linux进程间通信(上篇) 图4

我们以进程A和进程B为例来讲解。 首先我们思考一个问题,进程A和进程B都处于用户态能相互通信吗? 答案: 不能 ,因为进程A和进程B的虚拟地址空间(0-3GB)需要进行地址映射,映射后进程A和进程B访问的物理内存不是同一块内存,所以进程A和进程B无法在用户态通信。 既然用户态无法通信,所以我们只能想另外的办法,我们把进程切换到内核态,看一下进程A和进程B是否能通信。 内核地址空间是直接映射,所以进程A和进程B能通过相同的虚拟地址空间访问物理内存,进程A和进程B实现通信就有了理论依据。因为直接映射的这个机制,所有的进程的内核空间都指向同一块物理内存,所有进程共享内核代码段和数据段。进程间通信的本质:进程之间能够访问内核空间相同的物理内存。

2.4 揭开进程间通信神秘面纱

由浅入深探讨Linux进程间通信(上篇) 图5

从内核的角度来看,进程其实就是一个tast_struct对象。task_struct对象有三个成员:

  • mm_struct *mm:用来存储进程地址空间表。
  • files_struct *files:用来存储进程已打开文件表,可用于管道和Posix进程间通信。
  • nsproxy*nsproxy:用来存储进程命名空间信息,可用于System v进程间通信。

两个进程(task_struct对象)能够通过对象成员访问相同的物理内存区域,那么两个进程就能实现进程间通信。进程间通信的步骤如下:

  1. 通过系统调用,进程A从用户态切换到内核态。
  2. 进程A切换到内核态后,进程A获取到task_struct对象成员,通过对象成员找到物理内存区域,读写物理内存区域。
  3. 进程B执行步骤1,2就能和进程A通信。

3.进程间通信方法之管道

管道 是Linux进程通信的一种方式,管道的内核实现是pipe inode文件节点,pipe inode文件节点基于pipefs文件系统(伪文件系统)实现,为了方便理解,我们把pipe inode节点分为三个部分:

  • 读端:对应读file。
  • 写端:对应写file。
  • 管道:内核空间一块内存区域,用于读写数据。

读端,写端,管道三个部分的不同组合也就产生了无名管道(pipe)和命名管道(fifo)。无名管道和命名管道底层实现原理相同,都是基于pipe inode实现。

由浅入深探讨Linux进程间通信(上篇) 图6

3.1 无名管道

无名管道不能通过文件名创建和打开pipe inode文件节点。无名管道读端和写端都属于同一个进程已打开文件表,非亲缘关系的其他进程无法获取到读端和写端,所以无名管道不能用于非亲缘关系进程间通信。 为什么亲缘关系进程能使用无名管道通信呢? 答案:父进程调用fork函数时,把文件表复制给了子进程,子进程通过继承方式获取到了无名管道的读端和写端,所以父子进程都能往同一pipe inode读写数据,也就能完成通信。

由浅入深探讨Linux进程间通信(上篇) 图7

3.2 命名管道

命名管道能通过指定的文件名来创建和打开pipe inode节点。命名管道的读端和写端可以属于不同的进程,当然也可以属于同一进程或者父子进程。因为命名管道是通过文件名创建和打开的,进程只要有文件名,就有了打开命名管道的钥匙,就能对管道进程操作。使用命名管道的时候,不需要去关注进程之间的关系,只需要关注进程是否知道文件名。 由浅入深探讨Linux进程间通信(上篇) 图8

最后思考一下:管道为什么满足进程间通信要求? 管道(pipe inode)属于内核空间的一块内存区域,进程(task_struct对象)能够通过文件表访问这块内存区域,所以管道满足进程间通信要求。

← 返回文章列表