A11 —— 用户和内核的内存边界:零拷贝的技术
金句:数据从磁盘到网络,最快的路径是根本不复制。
用户态和内核态的内存边界
用户程序和内核有各自的地址空间,交互需要通过系统调用或特定的机制。
用户态和内核态的隔离:
用户地址空间(0x00000000 ~ 0x7FFFFFFFFFFF):
- 每个进程有独立的页表
- 只能访问自己的虚拟地址
- 访问内核地址 → 崩溃
内核地址空间(0xFFFF800000000000 ~):
- 所有进程共享
- 内核可以访问任何物理内存
- 用户态不能直接访问
数据交互的方式:
1. 系统调用参数传递(少量数据):
read(fd, buf, 1024); // buf 在用户态,内核写进去
2. mmap(大量数据):
mmap(NULL, size, PROT, MAP_SHARED, fd, 0);
// 用户态直接访问文件内容,内核参与 page fault
3. 共享内存(进程间):
shm_open() / mmap(MAP_SHARED)
// 用户态和内核共享同一块物理页传统 I/O 的数据拷贝路径
传统 read/write 的数据路径:
读取文件到 socket(传统方式):
[磁盘] [Page Cache] [用户 Buffer] [Socket Buffer] [网络]
│ │ │ │ │
▼ ▼ ▼ ▼ ▼
DMA ──────────────→ 物理页(Page Cache) ◄──── 内核读文件 │ │ │
│ │ │
copy ──────────────────────────► │ │
│ │
copy ◄──────────────────────────── │ │
copy ────────────► │
│ │
▼ ▼
socket buffer DMA
to network
复制次数:
1. 磁盘 → Page Cache(DMA)
2. Page Cache → 用户 Buffer(memcpy)
3. 用户 Buffer → Socket Buffer(memcpy)
→ 4 次 DMA/memcpy
用户态参与:
→ CPU 执行 memcpy
→ 用户态和内核态切换(syscall)
→ 高延迟,高 CPU 占用零拷贝(Zero-Copy)技术
零拷贝的目标是减少甚至消除用户态和内核态之间的数据拷贝。
sendfile() 系统调用:
#include <sys/sendfile.h>
ssize_t sendfile(out_fd, in_fd, &offset, count);
数据路径:
[磁盘] → [Page Cache] → [Socket Buffer] → [网络]
│ │
│ │
DMA ──────────► │
│
copy(Page Cache → Socket Buffer)
// 仍然有 1 次 copy
适合场景:
- 文件到 socket 的传输(静态文件服务)
- 不需要处理数据,直接转发
splice() 系统调用:
splice(fd_in, &off_in, fd_out, &off_out, len, flags);
- 在两个文件描述符之间移动数据
- 不经过用户态
- 内部使用管道缓冲区作为中转
数据路径:
[磁盘] → [Page Cache] → [pipe] → [Socket Buffer] → [网络]
│ │ │
│ │ │
DMA zero-copy copy(pipe → socket)
// 内核内部移动,不需要用户态mmap + write(内存映射文件):
1. mmap 把文件映射到用户地址空间
2. write 把数据写入 socket
3. 内核自动把 Page Cache 的数据发送到网络
改进点:
- 减少一次 User Buffer 的 memcpy
- 但仍然需要 Page Cache → Socket Buffer copy
data sync:
msync() / fsync() 确保数据写回磁盘(不用于零拷贝)
零拷贝的完整实现(真正的零 copy):
- 需要硬件支持:RDMA(Remote Direct Memory Access)
- 或者:网卡支持 scatter-gather DMA
- 数据直接从 Page Cache → 网卡,不需要中间 copyDMA 和零拷贝
传统 DMA vs RDMA:
传统 DMA:
- 数据必须经过物理内存
- CPU 参与设置 DMA 描述符
- 系统调用有开销
RDMA(Remote Direct Memory Access):
- 网卡直接读写远程内存
- 零拷贝:网卡 → 远程内存
- 无 CPU 参与,无系统调用开销
- 适用:高性能计算、机器学习、存储
InfiniBand / RoCE / iWARP:
- RDMA 的网络实现
- 数据传输绕过内核
- 延迟 < 1μs(vs TCP 10~100μs)网卡 scatter-gather:
支持 SG 的网卡可以直接从多个内存区域读取数据:
skb(socket buffer)包含多个物理地址:
- iovec[0]: page 0, offset 0, len 1500
- iovec[1]: page 1, offset 0, len 1500
→ 网卡直接从这两个页 DMA 数据
Linux 的实现:
- skb(socket buffer)管理数据
- page pool:零拷贝的页面管理
- XDP(Express Data Path):跳过协议栈,直接网卡处理总结
- 用户/内核隔离:用户态独立页表,内核全局访问,需要系统调用交互
- 传统 I/O:磁盘→Page Cache→用户 Buffer→Socket Buffer,4次复制,CPU 参与
- sendfile():Page Cache→Socket Buffer,2次复制(适合文件到 socket)
- splice():通过 pipe 中转,内核内部零拷贝,不需要用户态
- RDMA:网卡直接读写远程内存,完全零拷贝,需要特殊硬件
下篇预告(A12):A 层最后一篇——所有层之间的内存流动:物理内存→页表→buddy→SLAB→用户malloc→mmap→应用→内存泄漏检测,完整总结整个内存系统。
关注公众号「AI不着急」,回复”资料”获取内存学习路线图。
评论