按照效率从高到低排序:
共享内存 > 信号 > 管道 > 消息队列 > 套接字
访问机制
优点
✅ 不需要跨进程的“数据复制”
✅ 减少用户态 → 内核态切换
✅ 在读写过程中,CPU 直接操作数据,避免上下文切换
✅ 传输路径最短、内存访问速度最快
缺点
❌ 需要处理同步问题(需要加锁)
❌ 容易出现数据竞争或脏数据
❌ 需要手动管理内存生命周期
传输效率总结
直接在用户态操作 → 省去了内核态和用户态之间的拷贝 → 高效
对于异常情况下的工作模式,就需要用 信号 的方式来通知进程。
在 Linux 操作系统中, 为了响应各种各样的事件,提供了几十种信号,分别代表不同的意义。我们可以通过 kill -l 命令,查看所有的信号:
运行在 shel 终端的进程,我们可以通过键盘输入某些组合键的时候,给进程发送信号。例如
如果进程在后台运行,可以通过 kill命令的方式给进程发送信号,但前提需要知道运行中的进程 PID号,例如:
所以,信号事件的来源主要有硬件来源(如键盘 Cltr+c)和软件来源(如 ki 命令)。
信号是进程间通信机制中唯一的异步通信机制,因为可以在任何时候发送信号给某一进程,一旦有信号产生,我们就有下面这几种,用户进程对信号的处理方式:
访问机制
数据传输路径
优点
✅ 操作极快,传输路径短
✅ 实现简单,直接通过系统调用完成
✅ 适合事件通知(如终止、挂起、唤醒)
缺点
❌ 传输的数据量非常有限(通常只传整数或少量数据)
❌ 不适合大规模数据传输
❌ 信号量溢出或覆盖可能导致丢失
示例
kill(pid, SIGINT)
向某个进程发送信号SIGCHLD
通知父进程子进程结束传输效率总结
直接触发中断 → 轻量级操作 → 延迟极低
pipe()
系统调用创建 int pipe(int fd[2])
fork()
出子进程后,父子进程可以通过这些文件描述符共享这个缓冲区这里表示创建一个匿名管道,并返回了两个描述符,一个是管道的读取端描述符 fd[0],另一个是管道的写入端描述符 fd[1]。注意,这个匿名管道是特殊的文件,只存在于内存,不存于文件系统中。
其实,所谓的管道,就是内核里面的一串缓存 从管道的一段写入的数据,实际上是缓存在内核中的,另一端读取,也就是从内核中读取这段数据。另外,管道传输的数据是无格式的流且大小受限。
我们可以使用 fork() 创建子进程,创建的子进程会复制父进程的文件描述符,这样就做到了两个进程各有两个fd[0] 与 fd[1],两个进程就可以通过各自的 fd 写入和读取同一个管道文件实现跨进程通信了。
没有实体
,也就是没有管道文件,只能通过 fork() 来复制父进程 fd 文件描述符,来达到通信的目的。文件系统
中,表现为一个特殊的设备文件。创建命名管道示例:
mkfifo my_pipe
匿名管道 vs 命名管道
特性 | 匿名管道 | 命名管道 |
---|---|---|
是否有文件实体 | ❌ 无 | ✅ 有(设备文件) |
创建方式 | pipe() |
mkfifo() |
文件路径 | ❌ 无 | ✅ 有 |
通信范围 | 父子进程 | 任何进程(通过文件路径) |
数据位置 | 内核缓冲区 | 内核缓冲区 |
支持 lseek |
❌ 不支持 | ❌ 不支持 |
优点
✅ 直接操作文件描述符,涉及的数据路径短
✅ 匿名管道创建快,适用于父子进程间通信
✅ 适合顺序传输、单向通信
缺点
❌ 需要在内核中创建缓存区
❌ 容量受限,存在阻塞或丢弃风险
❌ 管道不适合进程间频繁地交换数据
示例
pipe()
系统调用mkfifo()
创建有名管道dup2()
重定向标准输入输出为什么比消息队列快?
✅ 传输路径更短 → 不涉及复杂的消息格式和管理
✅ 直接在内核态缓存区中操作 → 无需消息调度
传输效率总结
直接文件描述符操作 → 简单数据结构 → 更高效
消息队列的通信模式就可以解决 管道通信效率低,管道不适合进程间频繁地交换数据的问题
A 进程要给 B进程发送消息,A 进程把数据放在对应的消息队列后就可以正常返回了,B进程需要的时候再去读取数据就可以了。同理,B进程要给 A进程发送消息也是如此。
再来,消息队列是保存在内核中的消息链表
,在发送数据时,会分成一个一个独立的数据单元,也就是消息体(数据块),消息体是用户自定义的数据类型,消息的发送方和接收方要约定好消息体的数据类型所以每个消息体都是固定大小的存储块,不像管道是无格式的字节流数据。如果进程从消息队列中读取了消息体,内核就会把这个消息体删除。
消息队列生命周期随内核,如果没有释放消息队列或者没有关闭操作系统,消息队列会一直存在,而前面提到的匿名管道的生命周期,是随进程的创建而建立,随进程的结束而销毁。
消息这种模型,两个进程之间的通信就像平时发邮件一样,你来一封,我回一封,可以频繁沟通了。
但邮件的通信方式存在不足的地方有两点,一是通信不及时,二是附件也有大小限制,这同样也是消息队列通信不足的点。
消息队列不适合比较大数据的传输,因为在内核中每个消息体都有一个最大长度的限制,同时所有队列所包含的全部消息体的总长度也是有上限。在 Linux 内核中,会有两个宏定义MSGMAX和 MSGMNB它们以字节为单位,分别定义了一条消息的最大长度和一个队列的最大长度。
消息队列通信过程中,存在用户态与内核态之间的数据拷贝开销,因为进程写入数据到内核中的消息队列时,会发生从用户态拷贝数据到内核态的过程,同理另一进程读取内核中的消息数据时,会发生从内核态拷贝数据到用户态的过程。
访问机制
数据传输路径
优点
✅ 支持结构化数据传输(有消息头、消息体)
✅ 支持优先级和分类操作
✅ 支持任意进程之间的通信
缺点
❌ 需要格式化 → 处理开销更高
❌ 排队和管理增加延迟
❌ 需要进行两次数据拷贝(用户态到内核态 → 内核态到用户态)
示例
为什么比管道慢?
✅ 传输路径更复杂(涉及链表管理和调度)
✅ 数据需要解析 → 额外开销
传输效率总结
涉及格式化和调度 → 传输路径更长 → 效率降低
前面提到的管道、消息队列、共享内存、信号量和信号都是在同一台主机上进行进程间通信,那要想跨网络与不同主机上的进程之间通信,就需要 Socket 通信了。
实际上,Socket 通信不仅可以跨网络与不同主机的进程间通信,还可以在同主机上进程间通信。
针对 TCP 协议通信的 socket 编程模型:
数据传输路径
优点
✅ 兼容性强,支持跨机器通信
✅ 支持流式通信和分组通信
✅ 本地通信可以用 UNIX 域套接字
缺点
❌ 传输路径最复杂,涉及完整协议栈
❌ 系统调用和协议栈解析增加延迟
❌ 可靠性要求(TCP)会导致额外重传和确认
示例
传输效率总结
完整协议栈 → 最长路径 → 最低效率
1. 共享内存
shmget()
和 shmat()
分配一段共享的物理内存,不同进程通过映射直接访问。同步问题:
需要加锁(如互斥锁、信号量)来解决数据竞争和同步问题。
数据直接在用户态共享,可能因进程冲突导致数据损坏。
总结:
速度最快(直接内存访问)
支持复杂、大块数据通信
需要解决同步和竞争问题
可能导致脏数据或数据一致性问题
2. 信号
SIGINT
(Ctrl + C 触发)SIGKILL
(强制终止)SIGSTOP
(暂停进程)限制:
总结:
✅ 通信轻量、速度快
✅ 适用于通知类任务
❌ 信息量有限,不适用于数据传输
3. 匿名管道
|
命令或 pipe()
系统调用创建。lseek
。限制:
fork()
复制文件描述符实现通信。4. 命名管道
mkfifo()
创建,通信双方通过访问相同的管道文件进行通信。lseek
。限制:
5. 消息队列
msgsnd()
和 msgrcv()
系统调用进行通信。限制:
总结:
✅ 数据格式化,通信更有结构
✅ 适合短小的、多类型消息传递
❌ 用户态和内核态之间存在数据拷贝,通信效率受限
6. 套接字(Socket)
限制:
总结:
✅ 支持本机和跨主机通信
✅ 支持面向连接(TCP)或无连接(UDP)
❌ 传输效率相对较低(受协议和网络状况限制)
排名 | 通信方式 | 原因 | 适用场景 |
---|---|---|---|
① | 共享内存 | 直接内存访问,用户态直接操作 | 大量数据、高速通信 |
② | 信号 | 异步触发,传输少量信息,速度快 | 事件通知 |
③ | 匿名管道 | 内存中传输,无文件系统开销 | 父子进程间通信 |
④ | 命名管道 | 通过文件系统传输,有系统调用开销 | 不相关进程间通信 |
⑤ | 消息队列 | 内核管理,存在数据拷贝 | 结构化数据传输 |
⑥ | 套接字 | 涉及协议和网络传输,开销最大 | 远程通信 |
ps:自己的理解 不保证完全正确
参考文章:https://xiaolincoding.com/os/4_process/process_commu.html#%E6%80%BB%E7%BB%93