文章目录前言传统 Linux IOread() wirte()零拷贝 Zero Copymmap() write()sendfile()扩展Page Cache 和 Buffer Pool Page Cache总结对比前言Linux 系统安全性更高的原因之一就是系统是区分用户态、内核态。如果想要进行硬件调用操作必须要切换到内核态空间。对于用户进程来说是没办法操作任何操作系统硬件的。如果当前应用程序需要进行一个文件读写操作例如 MySQL 写数据到磁盘该操作需要将数据写入到磁盘其实就需要转换成内核态首先将数据写入到Kernel Buffer Cache的Page CacheLinux 2.4 之前Kernel Buffer Cache区域属于 Page Cache 和 Buffer Cache 组合Linux 2.4 之后 Buffer Cache 已经和 Page Cache 统一了Buffer Cache 现在只是 Page Cache 的一个视图(用于块设备元数据)不再是两块独立的内存磁盘文件读写操作单位是单页Page Cache4KB大小数据按照 Page 页写入到 Page Cache 之后就是 Dirty Page然后通过 flush 方式写入到磁盘 Disk。如果是读磁盘操作会先看 Page Cache 中是否有对应数据有直接返回没有就从磁盘加载到 Page Cache然后从 Page Cache 进行命中返回Page Cache 会根据 LRU 算法来进行 Page 的定期淘汰跟 Redis 内存淘汰策略类似。如下图用户数据从用户态到内核态到磁盘的大致流程。传统 Linux IOread() wirte()2次 CPU Copy 2次 DMA Copy传统 Linux 系统的 IO 读写主要就是两个系统调用函数read() write()其实可以这么理解只要是涉及到磁盘写入、读取操作本质背后就是调用这两个系统函数罢了不管是MySQL、Redis、Kafka 等其他程序写磁盘操作。如下图一次IO读写操作操作是需要 4 次用户态、内核态上下文切换就是发生在系统调用read()、write()调用时候进行切换经历2次CPU Copy 2次 DMA CopyDMA 控制器是不需要占用CPU资源的也就是说从磁盘中读写数据操作是不需要CPU占用等待的所以就相当于2次CPU Copy 需要额外占用资源尤其是IO密集型应用场景频繁地 CPU Copy 操作会损失大量的很多性能。所以接下来考虑到如何避免掉 CPU Copy 操作Zero-Copy 零拷贝就是可以做到。零拷贝 Zero Copy零拷贝大白话来说就是不需要数据从内核态进行 CPU Copy 到用户态更不需要从用户态 CPU Copy 到内核态直接就从内核态 Kernel Buffer Cache 同步到 Socket Buffer 网卡缓存然后基于 DMA Copy 进行网卡数据发送这样甚至连 CPU Copy 的操作都不需要了尽量避免 CPU 参与的数据拷贝尤其避免数据在“内核态 ↔ 用户态”之间来回复制。mmap() write()1次 CPU Copy 2次 DMA Copy一种简单的 Zero-Copy 实现方案就是通过系统调用mmap()替换原本的read()mmap 是内存映射相当于把用户态 User Buffer 中的内存缓冲区映射到内核态 Kernel Buffer 缓冲区这样就减少了一次 CPU Copy整体流程大致如下用户进程系统调用mmap进入内核态内核缓冲区映射用户缓冲区。将硬盘数据基于 DMA Copy 把数据复制到内核缓冲区然后系统调用 write 开始进行 CPU Copy到套接字缓冲区Socket Buffer然后在基于 DMA Copy 到网卡进行数据传输。对比传统 Linux IO不仅少了一次CPU Copy而且还节省了一半的内存区域只需要从 Kernel Buffer Cache 进行 CPU Copy 到 Socket Buffer 就行了用户态内核态切换次数还是4次。sendfile()Linux 2.1 版本引入系统调用sendfile()sendfile 的本质是 read write 合并成一次系统调用上下文切换减半它相当于是mmap wirte二合一。扩展Page Cache 和 Buffer Pool Page Cache有一个容易混淆的概念前面一直在说 OS 的 Page Cache(4KB)那 MySQL InnoDB 的 Buffer Pool Page(16KB) 两者什么关系OS Page Cache(4KB) 是通用的无论什么文件都按 4KB 切块存InnoDB Buffer Pool(16KB) 只缓存数据库页。一个 InnoDB 的 16KB Page,在 OS 层对应 4 个连续的 4KB Page Cache 页MySQL 读一条数据的完整链路是这样的SQL 查询↓ Buffer Pool 未命中(16KB 的数据页)↓ InnoDB 调用 read() 向 OS 要数据↓ OS 检查 Page Cache(对应 4 个 4KB 页)↓ 未命中 DMA Copy:磁盘 → Page Cache↓ CPU Copy:Page Cache → Buffer Pool↓ InnoDB 在 16KB 页里定位到具体行,返回给 SQL 执行器总结对比对比项read/writemmap writesendfile核心思路先读到用户态再写回内核把内核文件页映射给用户访问再写 socket内核直接把文件发到 socket是否经过用户缓冲区会不走传统用户缓冲区拷贝但用户可通过映射访问不会典型路径内核 - 用户 - 内核内核页缓存映射给用户再进入网络发送路径内核 - 内核CPU copy 数量通常 2 次明显 CPU copy比read/write少 1 次最少系统调用readwritemmapwritesendfile应用是否直接拿到数据会会看到映射数据不会是否属于零拷贝否部分算常叫半零拷贝是最典型适合场景通用业务处理文件映射、随机访问文件直接发网络、纯转发优点通用、简单、灵活少一次复制访问文件高效CPU 占用更低吞吐更高缺点拷贝多CPU 开销更大语义更复杂不是最彻底适用场景没那么通用