Post

传统IO优化方案

传统IO优化方案

传统IO

背景:传统IO场景下,服务器把磁盘文件网络IO发送给客户端Socket

1
2
read(file_fd, user_buf, ...);   // 读文件到用户缓冲区
write(sock_fd, user_buf, ...);  // 从用户缓冲区写到网络

这个过程中会发生很多次内核用户态切换:

read()

  • 磁盘→内核缓存(DMA读入)

DMA:Direct Memory Access 直接内存访问,让硬件自己把数据搬进/搬出内存,CPU 不用一字节一字节地搬。将CPU从繁琐的搬运工作中解放出来,主动承揽内存搬运这种苦力活。

  • 内核缓冲→用户缓存(一次拷贝

write()

  • 用户缓冲→内核socket缓冲(再次拷贝)

  • 内核 socket 缓冲区 → 网卡(DMA 发送)

上述read()和write()都是系统调用,每次都会触发至少一次用户态↔内核态的上下文切换(进入内核/返回用户态),切换多了也耗 CPU。

所以上述传统IO最核心的开销有两种:

  • 多次数据拷贝(尤其是“内核↔用户”这两次)
  • 多次上下文切换(系统调用 + 内核处理)

针对传统IO的优化

针对传统IO场景,存在以下优化方式。

mmap

mmap:内存映射,让用户态进程除了堆、栈、共享内存区这些可以直接操作的内存区域外,多出了mmap 出来的用户映射区

mmap是系统调用,用户态申请内核,内核在进程页表中建立映射,让一段用户虚拟地址 指向某些 物理页,并且通过页表权限位保证用户态只能按规则访问被指向的物理页。读文件时可能少一次显式 read 拷贝(不用 read() 把数据再拷到用户 buffer)

sendfile

sendfile : 典型的下载加速。是内核提供的“把一个文件送到另一个socket”的系统调用。用户应用发系统调用请求:“把这个文件发到这个 socket”,涉及到的数据流程在内核:页缓存 → socket → 网卡,典型用于:静态文件下载、CDN/对象存储的下行、Nginx 这类场景

方案对比

方案 用户态是否拿到文件内容 内核↔用户拷贝次数 适合场景
read + write 通用、实现简单
mmap + write (像访问内存) 较少/取决于用法 需要在用户态处理数据
sendfile 最少 纯转发下载(不改内容)

千万注意:sendfile 的零拷贝是“文件→socket”路径的优化,不是“内存→socket”的通用优化。mmap 映射出来的是给用户态读写的内存视图,不是 sendfile 的输入类型,所以“先 mmap 写、再 sendfile 发”本质是在绕路把内存伪装成文件,通常不划算;更现实的方案是 mmap + write(或直接 write)。

This post is licensed under CC BY 4.0 by the author.