Linux 内核与 Zero-Copy 零拷贝

文章目录

  • 前言
  • [传统 Linux IO:read() + wirte()](#传统 Linux IO:read() + wirte())
  • [零拷贝 Zero Copy](#零拷贝 Zero Copy)
    • [mmap() + write()](#mmap() + write())
    • sendfile()
  • [扩展:Page Cache 和 Buffer Pool Page Cache](#扩展:Page Cache 和 Buffer Pool Page Cache)
  • 总结对比

前言

Linux 系统安全性更高的原因之一就是系统是区分用户态、内核态。如果想要进行硬件调用操作,必须要切换到内核态空间。对于用户进程来说,是没办法操作任何操作系统硬件的。

如果当前应用程序需要进行一个文件读写操作(例如 MySQL 写数据到磁盘),该操作需要将数据写入到磁盘,其实就需要转换成内核态,首先将数据写入到 Kernel Buffer CachePage Cache

Linux 2.4 之前,Kernel Buffer Cache区域属于 Page Cache 和 Buffer Cache 组合;Linux 2.4 之后 Buffer Cache 已经和 Page Cache 统一了,Buffer Cache 现在只是 Page Cache 的一个视图(用于块设备元数据),不再是两块独立的内存

磁盘文件读写操作单位是单页 Page Cache(4KB大小),数据按照 Page 页写入到 Page Cache 之后就是 Dirty Page,然后通过 flush 方式写入到磁盘 Disk。

如果是读磁盘操作,会先看 Page Cache 中是否有对应数据,有直接返回;没有就从磁盘加载到 Page Cache,然后从 Page Cache 进行命中返回,Page Cache 会根据 LRU 算法来进行 Page 的定期淘汰(跟 Redis 内存淘汰策略类似)。如下图:用户数据从用户态到内核态到磁盘的大致流程。

传统 Linux IO:read() + wirte()

2次 CPU Copy + 2次 DMA Copy

传统 Linux 系统的 IO 读写主要就是两个系统调用函数read() + write(),其实可以这么理解,只要是涉及到磁盘写入、读取操作,本质背后就是调用这两个系统函数罢了,不管是MySQL、Redis、Kafka 等其他程序写磁盘操作。

如下图:一次IO读写操作操作是需要 4 次用户态、内核态上下文切换,就是发生在系统调用 read()、write() 调用时候进行切换,经历2次CPU Copy + 2次 DMA Copy,DMA 控制器是不需要占用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/write mmap + write sendfile
核心思路 先读到用户态,再写回内核 把内核文件页映射给用户访问,再写 socket 内核直接把文件发到 socket
是否经过用户缓冲区 不走传统用户缓冲区拷贝,但用户可通过映射访问 不会
典型路径 内核 -> 用户 -> 内核 内核页缓存映射给用户,再进入网络发送路径 内核 -> 内核
CPU copy 数量 通常 2 次明显 CPU copy read/write 少 1 次 最少
系统调用 read + write mmap + write sendfile
应用是否直接拿到数据 会看到映射数据 不会
是否属于零拷贝 部分算,常叫半零拷贝 是,最典型
适合场景 通用业务处理 文件映射、随机访问 文件直接发网络、纯转发
优点 通用、简单、灵活 少一次复制,访问文件高效 CPU 占用更低,吞吐更高
缺点 拷贝多,CPU 开销更大 语义更复杂,不是最彻底 适用场景没那么通用
相关推荐
腾渊信息科技公司1 天前
工业数据运维痛点根治方案:基于AI Agent的产线自动化台账系统落地
运维·人工智能·自动化·个人开发·ai编程
Dxy12393102161 天前
Proxy Protocol v2 详解
运维
踏月的造梦星球1 天前
DMDPC 学习:架构、部署、运维与调优
运维·数据库·学习·架构
潘正翔1 天前
docker核心概念
linux·运维·服务器·docker·容器·centos·运维开发
weigangwin1 天前
agent-workspace-linux 不是远程桌面:Linux AI 工作区的隔离边界验收
linux·xvfb·mcp·桌面隔离·权限边界·bubblewrap·agent-workspace
renhongxia11 天前
世界模型,是“空中楼阁”还是AGI的“最后一块拼图”?
运维·服务器·数据库·人工智能·算法·agi
Dovis(誓平步青云)1 天前
远程办公软件文件传输实测:6 款工具的速度、稳定性和办公体验对比
linux·运维·服务器·后端·生成对抗网络
Java小白笔记1 天前
Docker 安装配置完全指南:MacOS 、Windows、Linux环境下的安装、配置与验证
linux·macos·docker
csdn_aspnet1 天前
GitHub Actions自动化运维实战,用CI/CD流水线实现测试、部署、安全扫描一体化
运维·安全·ci/cd·自动化·github
Inhand陈工1 天前
数据中心UPS无功补偿与智能化监控方案:基于IG502的Modbus RTU转IEC61850实战
运维·人工智能·物联网·信息与通信