P76 什么是零拷贝?零拷贝如何减少 CPU 拷贝与上下文切换?
面试题:什么是零拷贝?传统 IO 和零拷贝(sendfile/mmap)的区别?
1. 传统 IO 的数据流程(一次文件发送给网络)
text
read():磁盘 → 内核缓冲区 → 用户缓冲区 (2 次拷贝 + 2 次上下文切换)
write():用户缓冲区 → 内核 Socket 缓冲区 → 网卡 (2 次拷贝 + 2 次上下文切换)合计:4 次用户态/内核态上下文切换 + 4 次数据拷贝(其中 2 次是 CPU 拷贝)。
2. 零拷贝的核心
减少/消除 CPU 参与的数据拷贝和上下文切换,让数据尽量在内核里流动。
① mmap(内存映射)
text
磁盘 → 内核缓冲区(mmap 映射到用户空间,无用户态拷贝)→ Socket 缓冲区 → 网卡省掉一次"内核 → 用户"的 CPU 拷贝(读路径),上下文切换减少。
② sendfile(Linux 2.1+)
text
磁盘 → 内核缓冲区 → Socket 缓冲区 → 网卡- 全程不经过用户态:1 次系统调用,2 次上下文切换;
- 数据拷贝从 4 次降到 2 次(仍是 CPU 拷贝,但内核里);
③ sendfile + DMA 收集(Linux 2.4+,真正接近"零")
text
磁盘 → 内核缓冲区(DMA 拷贝)
内核缓冲区 → Socket(只传描述信息,无数据拷贝)
Socket → 网卡(DMA 拷贝)CPU 不搬数据,由 DMA(直接内存访问) 完成,只剩 2 次 DMA 拷贝 + 少量元数据传递——这就是"零拷贝"。
3. Java 里的零拷贝
java
// NIO:sendfile
FileChannel in = FileChannel.open(path);
in.transferTo(0, in.size(), socketChannel);- Netty 的
FileRegion底层就是 transferTo; - RocketMQ/Kafka 消息存储用 mmap 读写文件(MappedByteBuffer)。
4. 对比总结
| 方案 | CPU 拷贝 | 上下文切换 |
|---|---|---|
| 传统 read+write | 2 次(+2 次 DMA) | 4 次 |
| mmap+write | 1 次 | 4 次 |
| sendfile | 1 次(+2 次 DMA) | 2 次 |
| sendfile+DMA gather | 0 次(纯 DMA) | 2 次 |
5. 加分点
- 说清"零拷贝不是 0 拷贝,而是减少 CPU 参与的拷贝";
- 提到 PageCache 的作用:磁盘读缓存,热点数据零磁盘 IO;
- 追问"什么时候不能用零拷贝":数据需要跨进程/修改、需要落用户空间处理时不行。
一句话总结
零拷贝通过 mmap/sendfile 让数据在内核态流转、由 DMA 搬运,把传统 4 次上下文切换 + 多次 CPU 拷贝降到 2 次切换 + 0 次 CPU 拷贝(sendfile+DMA gather),Netty/Kafka 都用它提升 IO 吞吐。