零拷贝是什么?把数据搬运次数从 4 次砍到 1~2 次
答案:零拷贝是一类减少数据在用户态与内核态之间重复拷贝的技术,让数据尽量只在内核里流动(DMA 直达网卡/磁盘),典型实现:sendfile、mmap、splice、io_uring。
传统方式的浪费(为什么慢)
磁盘 → 页缓存 →(copy_to_user)→ 用户态 →(copy_from_user)→ socket 缓冲 → 网卡传统 read() + write() 传一个大文件:
- 4 次拷贝(2 次经过用户态中转)
- 4 次上下文切换(内核态/用户态来回)
用户态那两次中转,对"磁盘→网卡"这种纯搬运毫无意义。
三种主流方案对比
| 方案 | 拷贝次数 | 上下文切换 | 适用 |
|---|---|---|---|
| 传统 read+write | 4 | 4 | 通用 |
mmap + write | 3(省 1 次内核→用户) | 4 | 读多改少、零成本访问文件 |
sendfile | 2(或 1 次,SG 网卡) | 2 | 文件→socket 高速转发 |
splice | 2 | 2 | 任意 fd 之间(管道/socket)搬运 |
io_uring | 2 且批量化 | 大幅减少 | 高并发 IO 场景 |
什么时候用
- Web 静态文件 / CDN 回源:
sendfile是首选(Nginx 默认开启); - 大文件批量复制、日志转发:
splice或sendfile; - 需要就地读改数据:
mmap省掉 read 拷贝,但要小心 page fault 与写回时机; - 高并发小 IO:
io_uring用共享 ring 批量提交,减少 syscall 与切换。
深度入口
- 零拷贝原理与实验:零拷贝详解
- IO 子系统与 DMA:IO 子系统
- io_uring 全解:io_uring 指南
FAQ
Q: 零拷贝是什么? A: 零拷贝是一类减少数据在用户态和内核态之间重复拷贝的技术,目标是让数据尽量只在内核里流动(DMA 直达),典型实现有 sendfile、mmap、splice。
Q: 传统方式为什么慢? A: 传统 read + write 一次传输要 4 次拷贝 + 4 次上下文切换:磁盘→内核页缓存→用户态→内核 socket 缓冲→网卡,用户态中转两次纯属浪费。
Q: sendfile 怎么做到零拷贝? A: sendfile 让数据从页缓存直接经 DMA 到网卡,只需 2 次拷贝(或 1 次 + SG 网卡),省掉用户态中转,且只需 2 次上下文切换。
Q: mmap 是零拷贝吗? A: 是"减少拷贝":mmap 让用户态直接映射页缓存,省去 read 那一次内核→用户拷贝;但写回仍需 copy_from_user,且 CPU 模式切换还在。
Q: splice 解决什么问题? A: splice 在两个文件描述符之间搬数据,全程内核态、不经过用户态缓冲,适合管道、socket 之间的数据搬运。
Q: 零拷贝一定更快吗? A: 不一定。零拷贝省的是大块数据传输的拷贝开销;小数据、需频繁加工/改写的场景,普通读写可能更简单划算。
一句话总结:零拷贝 = 用 sendfile/mmap/splice/io_uring 砍掉用户态中转拷贝,把文件→网络这类"纯搬运"从 4 次拷贝降到 1~2 次,静态文件与大数据量转发场景收益最明显。