Linux 零拷贝演进史:从传统 read/write 到 sendfile 与 splice 的演进对比
在分布式消息队列如 Kafka、Pulsar、高性能网络网关如 Netty、Nginx以及现代底层存储架构中吞吐量动辄突破每秒数十万甚至数百万 QPS。每当被问及这些中间件为何能将千兆/万兆网卡带宽打满时几乎所有工程师都会脱口而出“因为它们使用了零拷贝Zero-Copy技术”。但如果面试官继续追问底层“数据到底在物理内存的哪块区域DMA 是怎么介入的为什么早期sendfile依然需要 1 次 CPU 拷贝直到引入 SG-DMA 才彻底清零为什么在两个任意 socket 之间转发时sendfile会失效必须依赖splice的管道缓冲区”很多人的理解瞬间就模糊了。零拷贝绝不是单指某一个具体的 API而是 Linux 内核在长达数十年的演进过程中为了极致消除 CPU 参与的数据搬运与用户态/内核态上下文切换而构筑的一整套体系化演进路线。阶段一传统 I/O 的“四次切换与四次拷贝”考虑一个最朴素的场景Web 服务器从磁盘读取一个 100MB 的静态视频文件并通过 TCP 套接字发送给远端客户端。传统的写法是循环调用read()和write()char buf[4096]; while ((n read(file_fd, buf, sizeof(buf))) 0) { write(socket_fd, buf, n); }在这段看似人畜无害的代码底层发生了一场昂贵的数据大搬家第 1 次上下文切换调用read()进程从用户态陷入内核态第 1 次数据拷贝DMA 拷贝DMA直接内存访问控制器将磁盘数据搬运到内核的PageCache页缓存第 2 次数据拷贝CPU 拷贝CPU 介入将内核页缓存的数据复制到进程在用户空间分配的堆内存buf中第 2 次上下文切换read()返回进程由内核态切回用户态第 3 次上下文切换调用write()进程再次从用户态陷入内核态第 3 次数据拷贝CPU 拷贝CPU 再次介入将用户空间的buf数据复制到内核的Socket Buffer套接字发送缓冲区第 4 次上下文切换write()系统调用返回用户态第 4 次数据拷贝DMA 拷贝DMA 控制器将 Socket Buffer 中的数据异步搬运到网卡硬件缓冲区通过网线发送出去。代价汇总总共发生了4 次上下文切换以及4 次数据拷贝2 次 DMA 硬件拷贝2 次极为昂贵的 CPU 拷贝。在这期间CPU 被充当了毫无技术含量的“内存搬运工”大量的 CPU 时钟周期被浪费在内存总线带宽的来回穿梭中L1/L2 缓存被严重污染。阶段二mmap write 的初级减负与 SIGBUS 陷阱为了消灭用户态与内核态之间的冗余搬运Linux 提供了内存映射系统调用mmap()buf mmap(NULL, file_size, PROT_READ, MAP_SHARED, file_fd, 0); write(socket_fd, buf, file_size);mmap()利用操作系统的虚拟内存管理机制将内核 PageCache 的物理内存页直接映射到进程的虚拟地址空间。这样原本由内核向用户空间复制的那一次 CPU 拷贝被彻底省去。代价减少了 1 次 CPU 拷贝但依然存在4 次上下文切换与3 次数据拷贝2 次 DMA1 次由 PageCache 到 Socket Buffer 的 CPU 拷贝。隐藏的 SIGBUS 崩溃陷阱mmap()在生产环境中有一个极具隐蔽性的坑当你映射了一个文件在调用write()尚未完成时另一个进程将该文件强行截断truncate变小。此时你的进程尝试访问这段原本合法的虚拟地址内核无法从磁盘补齐对应页会直接向你的进程抛出一个无法捕获的SIGBUS总线错误信号导致进程瞬间闪退。阶段三sendfile 时代的质变与 SG-DMA 终极清零在 Linux 2.1 内核中引入了划时代的系统调用sendfile()ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count);数据直接在内核空间完成从文件到网络套接字的流转用户态不再参与中转上下文切换直接从 4 次骤降到2 次调用一次sendfile陷入内核发送完毕返回用户态。彻底消除 CPU 拷贝的关键SG-DMAScatter-Gather DMA在 Linux 2.4 之前sendfile依然需要内核 CPU 将 PageCache 的数据复制一份到 Socket Buffer 中此时仍有 1 次 CPU 拷贝。而在 Linux 2.4 及更新版本中伴随着网卡硬件对Scatter-Gather DMA分散/聚集 DMA的支持最后一次 CPU 拷贝被彻底消灭sendfile()被调用DMA 将磁盘数据读入内核 PageCache内核不再把整个数据块拷贝到 Socket Buffer而是仅仅把数据的**内存地址指针、长度File Descriptor 描述符**等极轻量的元数据写入 Socket Buffer网卡的 DMA 控制器直接根据 Socket Buffer 中的指针从 PageCache 物理页中聚合读取真实数据直接推入网卡发送队列此时系统达成了真正意义上的**“零 CPU 拷贝”**2 次上下文切换 2 次 DMA 硬件拷贝 0 次 CPU 拷贝。Kafka 消费端向消费者投递消息的高吞吐神话底座正是建立在sendfile与 SG-DMA 的基石之上。阶段四splice——任意描述符之间的零拷贝拓扑尽管sendfile性能强悍但它在设计之初存在一个巨大的局限out_fd必须是一个 Socket 套接字直到后期内核才略微放宽但仍不支持任意文件之间或两个套接字之间的全双工零拷贝。为了彻底打破这个枷锁Linux 2.6.17 正式引入了splice()系统调用long splice(int fd_in, loff_t *off_in, int fd_out, loff_t *off_out, size_t len, unsigned int flags);splice()能够在任意两个文件描述符之间以完全零拷贝的方式移动数据其内部核心依赖于 Linux 的管道缓冲区机制Pipe Buffer。内核源码级解密struct pipe_buffer的页指针流转在内核源码fs/splice.c与include/linux/pipe_fs_i.h中管道并不是一块连续分配的物理数据内存而是一个环形数组里面存储的是一系列页面引用结构体struct pipe_buffer { struct page *page; // 指向真实物理页的指针 unsigned int offset; // 页内偏移 unsigned int len; // 数据长度 const struct pipe_buf_operations *ops; // 页面引用计数与释放钩子 unsigned int flags; unsigned long private; };当使用splice()在两个套接字之间转发流量时数据从网卡经 DMA 写入内核接收缓冲物理页splice()仅仅是将该物理页的指针struct page *和偏移量填入中间管道的pipe_buffer环形队列中并增加该物理页的引用计数get_page随后目标套接字直接从该管道中消费物理页引用DMA 控制器直接抓取该页送入发送网卡。全过程没有任何一次内存数据的复制甚至不需要像sendfile那样受限于文件与套接字的硬性绑定。现代高性能反向代理如 HAProxy在内核态转发流量时广泛采用的就是基于splice的超低开销拓扑。演进对比全景与 Java NIO 的工程落地各阶段零拷贝方案在开销上的核心对比总结如下方案系统调用上下文切换次数CPU 拷贝次数DMA 硬件拷贝次数适用场景与局限传统 I/Oread()write()4 次2 次2 次通用可在用户空间随意修改加工数据内存映射mmap()write()4 次1 次2 次适合大文件随机读取注意防范SIGBUSsendfile (带 SG-DMA)sendfile()2 次0 次2 次文件直接流向网络Kafka 核心底座但早期不能处理任意 fdsplicesplice()2 次0 次2 次依托管道页指针流转支持任意 fd如 socket 到 socket转发在 Java 中的工程映射FileChannel.transferTo()在 Java NIO 中零拷贝的经典入口是FileChannel sourceChannel FileChannel.open(path, StandardOpenOption.READ); SocketChannel socketChannel ...; // 将数据从文件通道直接传输到网络通道 long transferred sourceChannel.transferTo(position, count, socketChannel);在底层 HotSpot JVM 的实现中以 Linux x86_64 为例FileChannelImpl.transferToDirectly()会首先尝试调用系统调用sendfile64。避坑提示transferTo()在不同的操作系统上有单次传输大小上限例如老版本 Linux 内核针对单次sendfile限制最大单次传输不能超过 2GB 减 1 字节Java 底层会在循环中分片处理若目标 Channel 不是一个底层基于操作系统 socket 的通道例如一个堆内自定义 ChannelJVM 会优雅降级回堆外内存缓冲的传统拷贝方式。深入理解从物理 DMA、内核 PageCache、pipe_buffer页指针引用到硬件网卡发送队列的流转本质你才能在面对高并发数据洪峰时真正选出最优的 I/O 架构解法。

相关新闻

llm-wiki-compiler可配置生命周期Profile(CLP)完全指南:一个profile.json搭建领域知识系统

llm-wiki-compiler可配置生命周期Profile(CLP)完全指南:一个profile.json搭建领域知识系统

llm-wiki-compiler可配置生命周期Profile(CLP)完全指南:一个profile.json搭建领域知识系统 【免费下载链接】llm-wiki-compiler The knowledge compiler. Raw sources in, interlinked wiki out. Inspired by Karpathys LLM Wiki pattern. …

2026/10/13 12:22:29 阅读更多 →
为什么LoxodonFramework能0GC更新UI?数据绑定性能优化原理深度剖析

为什么LoxodonFramework能0GC更新UI?数据绑定性能优化原理深度剖析

为什么LoxodonFramework能0GC更新UI?数据绑定性能优化原理深度剖析 【免费下载链接】loxodon-framework LoxodonFramework是一个轻量级的MVVM(Model-View-ViewModel)框架,它是专门为Unity3D游戏开发设计的,参考了WPF和Android的MVVM设计&…

2026/10/13 5:42:15 阅读更多 →
动态目标三维重构支撑无人平台目标运动预测技术方案

动态目标三维重构支撑无人平台目标运动预测技术方案

一、方案概述本方案依托镜像视界空间实景重构模型(MV-SRR Model)国产化自研动静一体化三维感知核心技术,由镜像视界浙江普陀时空大数据研究院耿文海团队主导无人平台动态感知、三维态势推演、目标运动预测算法体系原创研发与无人平台运动预判…

2026/10/15 0:40:15 阅读更多 →

最新新闻

AI整合安全实战:从提示词注入防御到系统级硬约束

AI整合安全实战:从提示词注入防御到系统级硬约束

直接说结论:AI整合项目里,提示词工程环节的安全策略,决定了整个系统的下限。我在多个接入大型语言模型的项目里见过同一类事故——提示注入、系统提示词被撬、训练数据被套话、输出未经校验就进了业务流程。这些问题单看都不严重,…

2026/10/17 4:43:14 阅读更多 →
多智能体编排六种模式详解:从顺序链到反思循环

多智能体编排六种模式详解:从顺序链到反思循环

做多智能体应用时间久了,你会发现真正难的从来不是单个智能体怎么写,而是多个智能体怎么协作。MAF系列写到这里是第13篇,前几篇陆续讲过工具调用、记忆管理、上下文控制,这篇集中把“智能体编排”这件事拆开聊。所谓编排&#xff…

2026/10/17 4:43:14 阅读更多 →
纯JavaScript实现RGB、HSV与十六进制颜色互转及工程实践

纯JavaScript实现RGB、HSV与十六进制颜色互转及工程实践

1. 颜色换算在真实项目里到底解决什么问题前端开发做到一定年头,你一定会碰到颜色格式打架的情况。设计师在 Figma 里给你一个#3B82F6,后端接口返回的却是rgb(59, 130, 246),而某个取色器插件复制出来的是hsv(217, 76%, 96%)。三种写法描述的…

2026/10/17 4:43:14 阅读更多 →
Android端KIMI API流式输出实战:OkHttp+Kotlin Flow实现SSE响应

Android端KIMI API流式输出实战:OkHttp+Kotlin Flow实现SSE响应

简介:本资源是一套面向Android开发者的KIMI大模型API流式响应集成实践工程,适用于需要在移动端实现低延迟、渐进式AI内容输出的中高级开发者。项目完整封装了KIMI API的异步流式调用逻辑,涵盖请求构建、SSE解析、UI线程安全更新及错误重试机制…

2026/10/17 4:43:14 阅读更多 →
malloc底层原理与动态内存管理实战指南

malloc底层原理与动态内存管理实战指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/17 4:43:14 阅读更多 →
金融数据服务中台架构实战:从多源聚合到实时推送全解析

金融数据服务中台架构实战:从多源聚合到实时推送全解析

先交代下背景。我最近在负责一个代号叫financial-services的内部项目,说白了就是一套面向金融场景的数据服务中台:上游接行情源、资讯源、账户流水,下游对接移动端、Web 和量化脚本。这个项目名字看起来很简单,但真正落地的时候牵…

2026/10/17 4:42:13 阅读更多 →

日新闻

DeepSeek选型与部署:V3/R1、Ollama、API全攻略

DeepSeek选型与部署:V3/R1、Ollama、API全攻略

简介:《DeepSeek使用方法.pdf》是一份面向DeepSeek入门及进阶用户的完整使用指南,重点围绕V3与R1两款模型的能力差异与适用场景展开,帮助读者根据任务类型与成本选择合适的模型。资源共1个PDF文件,压缩包大小约311KB,篇…

2026/10/17 0:00:25 阅读更多 →
200+Deepseek润色指令拆解:从底层原理到场景落地的完整指南

200+Deepseek润色指令拆解:从底层原理到场景落地的完整指南

简介:这是一份聚焦DeepSeek提示词优化的《200Deepseek润色指令》PDF文档,共收录两百余条可直接套用的润色指令,涵盖语法修正、风格统一、逻辑梳理、表达精炼等维度,并针对商务写作、学术论文、广告文案等场景给出差异化建议。文档…

2026/10/17 0:00:25 阅读更多 →
OpenCV轮廓法物体计数系统:轻量、可控、可落地的工业级实现

OpenCV轮廓法物体计数系统:轻量、可控、可落地的工业级实现

简介:本资源是一份基于OpenCV的本科课程设计完整报告,面向计算机视觉初学者、图像处理学习者及高校通信/信息类专业学生,解决物体智能计数与检测结果结构化记录的实际问题。项目覆盖图像预处理、轮廓检测、边界框绘制、轨迹栏交互式参数调节、…

2026/10/17 0:00:25 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/16 21:35:51 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/17 1:44:59 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/16 15:04:24 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/16 21:54:38 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/16 3:49:43 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/16 14:23:36 阅读更多 →