【linux】【进程】进程间通信(IPC)方式 效率排序

【linux】【进程】进程间通信(IPC)方式 效率排序(自己理解)

按照效率从高到低排序:

共享内存 > 信号 > 管道 > 消息队列 > 套接字

1. 共享内存 (最高效)

  • 现代操作系统,对于内存管理,采用的是虚拟内存技术,也就是每个进程都有自己独立的虚拟内存空间
  • 不同进程的虚拟内存映射到不同的物理内存中。所以,即使进程 A和 进程B的虚拟地址是一样的,其实访问的是不同的物理内存地址,对于数据的增删查改互不影响。
  • 共享内存的机制,就是拿出一块虚拟地址空间来,映射到相同的物理内存中。这样这个进程写入的东西另外一个进程马上就能看到了,都不需要拷贝来拷贝去,传来传去,大大提高了进程间通信的速度。

访问机制

  • 共享内存允许多个进程将自己的虚拟地址空间映射到同一块物理内存上。
  • 进程可以直接在这块内存上进行读写操作,而无需通过内核进行拷贝或切换。
  • 只需建立一次映射,之后的数据访问直接在用户态进行,效率最高。
    【linux】【进程】进程间通信(IPC)方式 效率排序_第1张图片
  • 共享内存避免了进程间的数据拷贝

优点
✅ 不需要跨进程的“数据复制”
✅ 减少用户态 → 内核态切换
✅ 在读写过程中,CPU 直接操作数据,避免上下文切换
✅ 传输路径最短、内存访问速度最快

缺点
❌ 需要处理同步问题(需要加锁)
❌ 容易出现数据竞争或脏数据
❌ 需要手动管理内存生命周期

传输效率总结

直接在用户态操作 → 省去了内核态和用户态之间的拷贝 → 高效


2. 信号(次高效)

对于异常情况下的工作模式,就需要用 信号 的方式来通知进程。
在 Linux 操作系统中, 为了响应各种各样的事件,提供了几十种信号,分别代表不同的意义。我们可以通过 kill -l 命令,查看所有的信号:
【linux】【进程】进程间通信(IPC)方式 效率排序_第2张图片
运行在 shel 终端的进程,我们可以通过键盘输入某些组合键的时候,给进程发送信号。例如

  • Ctrl+C产生 SIGINT 信号,表示终止该进程。
  • Ctrl+Z产生 SIGTSTP 信号,表示停止该进程,但还未结束。

如果进程在后台运行,可以通过 kill命令的方式给进程发送信号,但前提需要知道运行中的进程 PID号,例如:

  • kill -9 1050 表示给 PID 为 1050 的进程发送 SIGKILL 信号,用来立即结束该进程

所以,信号事件的来源主要有硬件来源(如键盘 Cltr+c)和软件来源(如 ki 命令)。

信号是进程间通信机制中唯一的异步通信机制,因为可以在任何时候发送信号给某一进程,一旦有信号产生,我们就有下面这几种,用户进程对信号的处理方式:

  1. 执行默认操作。Linux 对每种信号都规定了默认操作,例如,上面列表中的 SIGTERM 信号,就是终止进程的意思。
  2. 捕捉信号。我们可以为信号定义一个信号处理函数。当信号发生时,我们就执行相应的信号处理函数。
  3. 忽略信号。当我们不希望处理某些信号的时候,就可以忽略该信号,不做任何处理。有两个信号是应用进程无法捕捉和忽略的,即 SIGKILLSEGSTOP,它们用于在任何时候中断或结束某一进程。

访问机制

  • 信号是一种异步通知机制,内核直接为目标进程设置一个中断标志。
  • 由于信号涉及的是内核级别的中断触发,不涉及大量数据传输,所以操作极快。
  • 信号传递的内容是一个整数(信号号)或者携带一个小量数据。

数据传输路径

  • 内核直接修改进程状态 → 触发中断 → 执行信号处理函数

优点
✅ 操作极快,传输路径短
✅ 实现简单,直接通过系统调用完成
✅ 适合事件通知(如终止、挂起、唤醒)

缺点
❌ 传输的数据量非常有限(通常只传整数或少量数据)
❌ 不适合大规模数据传输
❌ 信号量溢出或覆盖可能导致丢失

示例

  • kill(pid, SIGINT) 向某个进程发送信号
  • SIGCHLD 通知父进程子进程结束

传输效率总结

直接触发中断 → 轻量级操作 → 延迟极低


3. 管道

3.1 匿名管道
  • 通过 pipe() 系统调用创建
 int pipe(int fd[2])
  • 生成的文件描述符(fd)会指向内核中的内存缓冲区(即环形缓冲区)
  • 在父进程 fork() 出子进程后,父子进程可以通过这些文件描述符共享这个缓冲区

这里表示创建一个匿名管道,并返回了两个描述符,一个是管道的读取端描述符 fd[0],另一个是管道的写入端描述符 fd[1]。注意,这个匿名管道是特殊的文件,只存在于内存,不存于文件系统中。

【linux】【进程】进程间通信(IPC)方式 效率排序_第3张图片

  • 匿名管道没有在文件系统中创建实际的文件,数据存储在内核的缓冲区中。
  • 父进程写入的数据通过内核缓冲区传递给子进程。

其实,所谓的管道,就是内核里面的一串缓存 从管道的一段写入的数据,实际上是缓存在内核中的,另一端读取,也就是从内核中读取这段数据。另外,管道传输的数据是无格式的流且大小受限。

我们可以使用 fork() 创建子进程,创建的子进程会复制父进程的文件描述符,这样就做到了两个进程各有两个fd[0]fd[1],两个进程就可以通过各自的 fd 写入和读取同一个管道文件实现跨进程通信了。

【linux】【进程】进程间通信(IPC)方式 效率排序_第4张图片

  • 我们可以得知,对于匿名管道,它的通信范围是存在父子关系的进程。因为管道没有实体,也就是没有管道文件,只能通过 fork() 来复制父进程 fd 文件描述符,来达到通信的目的。
3.2 命名管道
  • 命名管道(FIFO)创建在文件系统中,表现为一个特殊的设备文件
  • 通过 mkfifo()创建,使用open()、write() 和 read() 进行通信。
  • 由于它存在于文件系统中,因此不相关的进程也能通过访问这个文件进行通信。

创建命名管道示例:

mkfifo my_pipe
  • my_pipe就是一个实际存在于文件系统中设备文件
  • 进程 A 和进程 B 只要都能访问 my_pipe 文件,就可以通过它进行通信。

  • 对于命名管道,它可以在不相关的进程间也能相互通信。因为命令管道,提前创建了一个类型为管道的设备文件,在进程里只要使用这个设备文件,就可以相互通信。
  • 不管是匿名管道还是命名管道,进程写入的数据都是缓存在内核中,另一个进程读取数据时候自然也是从内核中获取,同时通信数据都遵循先进先出原则,不支持lseek 之类的文件定位操作。

匿名管道 vs 命名管道

特性 匿名管道 命名管道
是否有文件实体 ❌ 无 ✅ 有(设备文件)
创建方式 pipe() mkfifo()
文件路径 ❌ 无 ✅ 有
通信范围 父子进程 任何进程(通过文件路径)
数据位置 内核缓冲区 内核缓冲区
支持 lseek ❌ 不支持 ❌ 不支持

优点
✅ 直接操作文件描述符,涉及的数据路径短
✅ 匿名管道创建快,适用于父子进程间通信
✅ 适合顺序传输、单向通信

缺点
❌ 需要在内核中创建缓存区
❌ 容量受限,存在阻塞或丢弃风险
❌ 管道不适合进程间频繁地交换数据

示例

  • pipe() 系统调用
  • mkfifo() 创建有名管道
  • dup2() 重定向标准输入输出

为什么比消息队列快?
✅ 传输路径更短 → 不涉及复杂的消息格式和管理
✅ 直接在内核态缓存区中操作 → 无需消息调度

传输效率总结

直接文件描述符操作 → 简单数据结构 → 更高效


4.消息队列

消息队列的通信模式就可以解决 管道通信效率低,管道不适合进程间频繁地交换数据的问题

  • A 进程要给 B进程发送消息,A 进程把数据放在对应的消息队列后就可以正常返回了,B进程需要的时候再去读取数据就可以了。同理,B进程要给 A进程发送消息也是如此。

  • 再来,消息队列是保存在内核中的消息链表,在发送数据时,会分成一个一个独立的数据单元,也就是消息体(数据块),消息体是用户自定义的数据类型,消息的发送方和接收方要约定好消息体的数据类型所以每个消息体都是固定大小的存储块,不像管道是无格式的字节流数据。如果进程从消息队列中读取了消息体,内核就会把这个消息体删除。

  • 消息队列生命周期内核,如果没有释放消息队列或者没有关闭操作系统,消息队列会一直存在,而前面提到的匿名管道生命周期,是随进程的创建而建立,随进程的结束而销毁

  • 消息这种模型,两个进程之间的通信就像平时发邮件一样,你来一封,我回一封,可以频繁沟通了。

  • 但邮件的通信方式存在不足的地方有两点,一是通信不及时,二是附件也有大小限制,这同样也是消息队列通信不足的点。

  • 消息队列不适合比较大数据的传输,因为在内核中每个消息体都有一个最大长度的限制,同时所有队列所包含的全部消息体的总长度也是有上限。在 Linux 内核中,会有两个宏定义MSGMAX和 MSGMNB它们以字节为单位,分别定义了一条消息的最大长度和一个队列的最大长度。

  • 消息队列通信过程中,存在用户态与内核态之间的数据拷贝开销,因为进程写入数据到内核中的消息队列时,会发生从用户态拷贝数据到内核态的过程,同理另一进程读取内核中的消息数据时,会发生从内核态拷贝数据到用户态的过程。

访问机制

  • 消息队列在内核中维护一个链表,每个消息都带有元信息(如类型、长度、优先级)。
  • 消息通过系统调用写入或读取,涉及格式化、调度和管理

数据传输路径

  • 用户态 → 内核态(写入) → 排队 → 内核态(读取) → 用户态

优点
✅ 支持结构化数据传输(有消息头、消息体)
✅ 支持优先级和分类操作
✅ 支持任意进程之间的通信

缺点
❌ 需要格式化 → 处理开销更高
❌ 排队和管理增加延迟
❌ 需要进行两次数据拷贝(用户态到内核态 → 内核态到用户态)

示例

  • msgget() 创建消息队列
  • msgsnd() 发送消息
  • msgrcv() 接收消息

为什么比管道慢?
✅ 传输路径更复杂(涉及链表管理和调度)
✅ 数据需要解析 → 额外开销

传输效率总结

涉及格式化和调度 → 传输路径更长 → 效率降低


5. 套接字(最低效)

前面提到的管道、消息队列、共享内存、信号量和信号都是在同一台主机上进行进程间通信,那要想跨网络与不同主机上的进程之间通信,就需要 Socket 通信了。
实际上,Socket 通信不仅可以跨网络与不同主机的进程间通信,还可以在同主机上进程间通信。

针对 TCP 协议通信的 socket 编程模型:

【linux】【进程】进程间通信(IPC)方式 效率排序_第5张图片
访问机制

  • 套接字是跨主机或本地的通用通信接口,涉及完整的协议栈操作。
  • 数据需要经过封装、解析、校验和重传机制。

数据传输路径

  • 用户态 → 协议封装(TCP/UDP) → 发送 → 网络栈 → 目标接收 → 解析 → 用户态

优点
✅ 兼容性强,支持跨机器通信
✅ 支持流式通信和分组通信
✅ 本地通信可以用 UNIX 域套接字

缺点
❌ 传输路径最复杂,涉及完整协议栈
❌ 系统调用和协议栈解析增加延迟
❌ 可靠性要求(TCP)会导致额外重传和确认

示例

  • socket() 创建套接字
  • bind() 绑定地址
  • connect() 建立连接
  • send()/recv() 发送和接收

传输效率总结

完整协议栈 → 最长路径 → 最低效率


总结

1. 共享内存

  • 通过 shmget()shmat() 分配一段共享的物理内存,不同进程通过映射直接访问。
  • 数据直接在用户态访问,不需要系统调用或拷贝。
  • 通信速度最快。

同步问题:
需要加锁(如互斥锁、信号量)来解决数据竞争和同步问题。
数据直接在用户态共享,可能因进程冲突导致数据损坏。

总结:
速度最快(直接内存访问)
支持复杂、大块数据通信
需要解决同步和竞争问题
可能导致脏数据或数据一致性问题

2. 信号

  • 信号是一种异步通信机制,用于通知进程特定的事件发生。
  • 常见的信号包括:
    • SIGINT(Ctrl + C 触发)
    • SIGKILL(强制终止)
    • SIGSTOP(暂停进程)
  • 信号可以由内核用户空间进程触发。
  • 信号事件的来源主要有**硬件来源:**如键盘 Cltr+C 和 软件来源:(如 kill 命令)
  • 一旦有信号发生,进程有三种方式响应信号 1.执行默认操作 2.捕捉信号 3.忽略信号

限制:

  • 信号信息量有限(仅传递信号编号)。
  • 无法传输复杂数据,仅能用于事件通知。

总结:
✅ 通信轻量、速度快
✅ 适用于通知类任务
❌ 信息量有限,不适用于数据传输

3. 匿名管道

  • 匿名管道只存在于内核中,不存在于文件系统中。
  • 通过 | 命令或 pipe() 系统调用创建。
  • 数据是无格式的流,大小受限,单向通信。
  • 通信基于**先进先出(FIFO)**原则,不支持 lseek

限制:

  • 只能在具有父子关系的进程间通信,因为管道通过 fork() 复制文件描述符实现通信。
  • 由于匿名管道不具备路径名,不能用于不相关的进程间通信。

4. 命名管道

  • 命名管道存在于文件系统中,作为一个特殊的设备文件。
  • 通过 mkfifo() 创建,通信双方通过访问相同的管道文件进行通信。
  • 支持不具备父子关系的进程间通信。
  • 数据同样遵循**先进先出(FIFO)**原则,不支持 lseek

限制:

  • 需要访问权限和文件路径。
  • 通信仍然是基于半双工(单向通信)。

5. 消息队列

  • 基于内核的通信机制,数据以消息的形式存储在内核的消息链表中。
  • 通过 msgsnd()msgrcv() 系统调用进行通信。
  • 消息具有特定的数据结构(消息类型、内容等)。
  • 需要在进程间保持消息格式一致,才能保证数据完整性。

限制:

  • 受限于内核的消息队列大小和管理机制。
  • 数据在用户态和内核态之间需要拷贝,通信速度受限。

总结:
✅ 数据格式化,通信更有结构
✅ 适合短小的、多类型消息传递
❌ 用户态和内核态之间存在数据拷贝,通信效率受限

6. 套接字(Socket)

  • Socket 是基于TCP/UDP 协议的通信方式。
  • 适用于不同主机间的网络通信。
  • 支持双向通信(全双工)。

限制:

  • 需要协议封装、网络传输,通信开销较大。
  • 通信效率受网络状况影响。

总结:
✅ 支持本机和跨主机通信
✅ 支持面向连接(TCP)或无连接(UDP)
❌ 传输效率相对较低(受协议和网络状况限制)


效率排序(从高到低):

排名 通信方式 原因 适用场景
共享内存 直接内存访问,用户态直接操作 大量数据、高速通信
信号 异步触发,传输少量信息,速度快 事件通知
匿名管道 内存中传输,无文件系统开销 父子进程间通信
命名管道 通过文件系统传输,有系统调用开销 不相关进程间通信
消息队列 内核管理,存在数据拷贝 结构化数据传输
套接字 涉及协议和网络传输,开销最大 远程通信

ps:自己的理解 不保证完全正确
参考文章:https://xiaolincoding.com/os/4_process/process_commu.html#%E6%80%BB%E7%BB%93

你可能感兴趣的:(Linux,linux,服务器,java)