IPC优化与历史总结
本文最后更新于 2026年6月14日 凌晨
功能实现阶段
最早期的 IPC 更多是为了解决功能问题,也就是让不同进程之间可以交换数据或信号。
经典结构包括:
- Pipe
- Signal
- Message Queue
- Shared Memory
这些模型现在依然被使用,只是不同模型适合不同场景。
最基础的通信路径可以抽象成:
1 | |
这一阶段的重点还不是极致性能,而是让隔离的进程能够安全通信。
MicroKernel 与 IPC
后来,Mach 提出:
Everything is Message
根据这个理念,MicroKernel 开始把很多传统内核服务拆到用户态。
原来在 Monolithic Kernel 中,路径通常是:
1 | |
而在 MicroKernel 中,很多服务变成:
1 | |
也就是说,原来一次内核内部函数调用,可能变成了跨地址空间的消息传递。
这个设计让系统结构更清晰,也更容易隔离不同服务。但早期测试时性能并不理想,核心原因是 IPC 太贵。
主要开销包括:
- 上下文和地址空间切换
- 数据复制
- 调度器参与
- Kernel Entry / Exit
因此,MicroKernel 的性能问题逐渐集中到了 IPC 性能上。
L4 的优化
后来 L4 的作者 Jochen Liedtke 发现,微内核的主要性能瓶颈就是 IPC,于是开始系统性优化 IPC。
优化目标主要是减少 Copy、减少 Scheduler 参与、减少不必要的 Queue 操作,并让常见 IPC 走 Fast Path。
区分 Short /Long Path
L4 发现,大部分 IPC 消息都很短,并且 Receiver 往往已经在等待。
如果这种场景仍然走完整路径,就会浪费很多调度和队列维护成本。
因此,L4 将 IPC 区分为:
1 | |
Short IPC 用于处理最常见的小消息场景,尽可能跳过复杂逻辑;Long IPC 则处理更复杂、更少见的情况。
值的传递
对于小消息,L4 直接使用寄存器传递:
1 | |
这样可以避免把消息先复制到内核缓冲区,再从内核缓冲区复制到接收方。
对于大消息,则可以通过页表映射减少复制:
1 | |
核心就是让数据尽量不要经过额外的内核缓冲区复制。
Scheduler 相关
如果内核已经知道 Receiver 正在等待,那么 IPC 时可以直接切到对应的 Server。
Fast Path 可以变成:
1 | |
这样可以减少调度器参与。
此外,L4 还使用 Lazy Scheduling 减少等待队列修改。
Lazy Scheduling 的思路是:
1 | |
核心是把线程状态变化和调度队列维护解耦,让短时间重复进出的操作不必立刻反映到队列上。
但以上的优化还没有解决 Context Switch 的问题。
Shared Memory 与 Lock-Free Queue
即使单次 IPC 已经很快,频繁通信仍然会产生:
- Kernel Entry
- Context Switch
- Scheduler 开销
- Message Copy
因此,后续高性能 IPC 开始采用 Shared Memory + Notification。
数据不再通过每次 IPC 复制,而是放在共享内存中:
1 | |
发送方和接收方通常只需要维护 head 和 tail。在 SPSC 等简单场景中,可以使用原子变量实现 Lock-Free Queue,从而减少锁竞争。
其核心变化是:
1 | |
Event-driven:从阻塞等待变成事件通知
传统阻塞式模型中,每个线程会等待自己的 I/O:
1 | |
大量连接通常意味着大量线程以及大量 Context Switch。
epoll 将其改成事件驱动模型:
1 | |
应用不需要为每个连接创建一个阻塞线程,而是可以使用少量线程处理大量事件。
这一阶段主要减少了:
- 阻塞线程数量
- 无效轮询
- 线程上下文切换
- 调度器压力
不过,epoll 只表示某个 I/O 已经 Ready。应用被唤醒后,通常仍然需要调用:
1 | |
来真正执行 I/O。
io_uring:提交与完成解耦
io_uring 进一步把 I/O 的提交与完成拆开:
1 | |
其中,SQ 是 Submission Queue,CQ 是 Completion Queue。用户态和内核共享这些 Ring Buffer。
应用可以批量提交多个请求:
1 | |
内核完成后,再批量写入 CQ。
其主要优化包括:
1 | |
这与 Shared Memory + Notification 的模式非常相似:SQ / CQ 负责传递数据,系统调用或内核线程负责通知和处理。
Async Runtime:从线程调度转向任务调度
Tokio 等异步运行时将操作系统提供的 I/O 事件转化为 Future 和 Waker。
当 I/O 尚未完成时:
1 | |
当 epoll 或 io_uring 返回事件时:
1 | |
因此,一个等待 I/O 的任务不需要一直占用一个操作系统线程。
原来的模型是:
1 | |
异步运行时中则变成:
1 | |
优化重点也从 Process / Thread Context Switch 转向 Future 状态切换和用户态任务调度。
不过,异步运行时并没有完全消灭调度,而是把一部分调度从内核移动到了用户态。
Embassy:中断直接唤醒 Future
在 MCU 或裸机环境中,通常没有:
- epoll
- io_uring
- 完整的操作系统线程
Embassy 直接使用硬件中断作为异步事件源。
Linux 异步运行时中的路径通常是:
1 | |
Embassy 中则可以缩短成硬件中断直接唤醒 Future:
1 | |
因此,Embassy 减少了操作系统层次的参与,也适合静态内存和资源有限的嵌入式环境。
异步 IPC 优化主线
整体演进可以总结为:
1 | |
这些技术处于不同的系统层次,但优化目标基本一致:
1 | |
从 L4 到现代异步运行时,优化重点逐渐发生了变化:
1 | |