当前位置: 首页 > news >正文

Linux基础 -- 零拷贝之 splice

背景:

**`file_operations` 中的 `.splice_read` / `.splice_write`** 讲透是什么?
VFS 怎么调用?
内部“零拷贝”是如何通过 **pipe buffer** 完成的?
实现者应如何选用通用 helper,以及典型易错点与性能要点。

一、它们到底干什么(用户态视角 → 内核回调)

用户态有三兄弟系统调用:splice(2) / vmsplice(2) / tee(2)。核心目标:在内核里搬运数据,不经用户态缓冲复制。规则是:

  • splice(fd_in, …, fd_out, …, len, flags)至少一端必须是管道

    • 非管道→管道:VFS 走 in->f_op->splice_read()(把“文件/套接字等”的数据送进管道)。
    • 管道→非管道:VFS 走 out->f_op->splice_write()(把管道里的数据写到“文件/套接字等”)。
    • 管道→管道:走内核的 pipe-to-pipe 快速路径(克隆/转移 pipe buffer 引用)。
  • vmsplice:把用户页直接挂到管道(把用户页 pin 住,形成 pipe buffer)。

  • tee:在不复制底层页的情况下,把一端管道的 buffer 克隆到另一端管道(增加引用计数)。

因此 .splice_read / .splice_write 就是 “非管道端” 参与 splice 的两个钩子:

ssize_t (*splice_read)(struct file *in,  loff_t *ppos,struct pipe_inode_info *pipe, size_t len,unsigned int flags);ssize_t (*splice_write)(struct pipe_inode_info *pipe, struct file *out,loff_t *ppos, size_t len, unsigned int flags);
  • 返回实际搬运的字节数;推进 *ppos;遵循 SPLICE_F_* 标志(如 NONBLOCK/MOVE/MORE/GIFT)。

二、设计思想:用“页引用的搬运”替代“数据复制”

核心是 pipe buffer 机制。管道里不是“字节流数组”,而是若干 struct pipe_buffer 单元,每个单元引用一块底层存储(页、页片段、skb 片段、设备私有描述等),并配有一套 ops 回调 指明如何 confirm / release / steal 这块数据。

零拷贝的达成方式:

  • 从“非管道的输入对象”读取时(.splice_read):
    尽量直接把其底层页“借”给管道(把页或片段塞进管道 buffer),而不是复制到临时内核缓冲再写入。
  • 向“非管道的输出对象”写入时(.splice_write):
    由输出对象尽量直接消费管道里的页引用(如 TCP 发送路径直接把页挂进 send queue),避免再次 copy。

若无法纯零拷贝(例如文件必须经页缓存压缩/解密等),仍可通过 页缓存页 → 作为 pipe buffer 的方式把“复制次数”最小化。

三、VFS 调用流程(高层)

  • 用户调 splice() → 内核 do_splice()

    • fd_in 是管道:走 do_splice_from(pipe → out) → 调 out->f_op->splice_write()
    • fd_out 是管道:走 do_splice_to(in → pipe) → 调 in->f_op->splice_read()
    • 两端都是管道:用 pipe-to-pipe 专用路径克隆 buffer。
  • 阻塞/非阻塞:看 SPLICE_F_NONBLOCK;管道满/空时可能 -EAGAIN

  • SPLICE_F_MOVE:尽量“移动”页所有权,不再保留原副本(能不能做到取决于两端实现)。

  • SPLICE_F_MOREhint,后续还有数据(例如 TCP 可据此优化聚包/拥塞)。

  • SPLICE_F_GIFT:把页“赠与”对端,由对端负责释放(常见于 vmsplice)。

四、文件系统/设备如何实现(选通用 helper)

很多对象不需要自己从零写 .splice_*,而是复用内核 helper

  • 普通文件(页缓存)读取 → 管道
    generic_file_splice_read()(老接口)或后继等价实现(有些版本在 mm/filemap.c/fs/splice.c 内)。
    它会从 page cache 拉页(必要时触发 read-in),把这些页作为 pipe buffer 塞进管道。
  • 管道 → 普通文件写入
    iter_file_splice_write()(内部走 vfs_iter_write()),把 ITER_PIPE 迭代器里的数据喂给底层 ->write_iter,常能零拷贝消费。
  • 套接字(TCP/UDP)
    网络栈自己实现 .splice_read/.splice_write,常走“零拷贝接收/发送”路径(如直接把 pipe buffer 变成内核 skb payload 或反之),最大限度地避免 copy。
  • 字符/块设备
    如果驱动能导出页引用(DMA-able 页、bio 片段等),可定制自己的 pipe buffer ops 达到零拷贝;否则可退化为把数据 copy 进/出管道。

注意:若未实现 .splice_*,VFS 可能回退到基于 read_iter/write_iter 的通用 splice 实现(开销更大,但语义不变)。

五、内部关键细节(实现视角)

  1. 管道容量与反压:管道有固定 buffer 槽位数与总字节限制。写满则生产者睡眠;读空则消费者睡眠;NONBLOCK 则立刻 -EAGAIN
  2. pipe_buffer ops:每个 buffer 带有一组操作(如 pipe_buf_ops),定义了如何 pin/unpin 页、能否“偷页”(move/steal)、如何确认有效性(confirm)。零拷贝能否达成、能到什么程度,取决于这些 ops。
  3. 位置推进:VFS 负责维护并传递 *ppos;实现者务必在成功返回的字节数上推进它(或遵循 DIO 的位置语义)。
  4. 页生命周期:谁持有引用、何时 put,严格由 pipe buffer 的 ops/引用计数管理,避免过早释放或泄漏。
  5. 安全与一致性:若数据需要解密/校验/解压,通常发生在“源端→管道”这一边(.splice_read);“管道→落地”的 .splice_write 便可零拷贝直落,提高吞吐。
  6. 大页/Folio:新内核以 folio 为单位组合页;对 splice 的抽象不变,只是底层更高效地管理页组。

六、典型路径对比(例)

  • 文件 → 管道(splice_read

    1. 计算文件偏移对应的缓存页;2) 缺页读入;3) 将该页封装成 pipe_buffer(引用页,不复制数据);4) 更新 *ppos 与返回字节数。
  • 管道 → TCP 套接字(splice_write

    1. 从管道取出若干 pipe_buffer;2) 直接把其底层页挂进 skb(或通过 sendpage/sendmsg 零拷贝路径);3) 根据 MORE/拥塞窗口决定批量提交;4) 发送并释放引用。

七、常见坑位与建议

  • 必须处理 NONBLOCK:管道没空间/对端没就绪时要返回 -EAGAIN,不能睡。
  • 返回值语义:允许短写/短读;推进 *ppos 与返回字节严格一致。
  • 对齐/边界:块设备/直写路径可能要求对齐;不满足时要么退化 copy,要么报错。
  • 页“移动”不是强制SPLICE_F_MOVE 只是尽力尝试;做不到要优雅退化。
  • 内存压力:长链路零拷贝也会消耗大量页引用;注意 backpressure,避免 OOM/泄漏。
  • 加密/压缩顺序:通常 读端解密/解压 → 进管道 → 写端零拷贝落地;相反次序会导致不可零拷贝或安全风险。

八、实现者的“开箱即用”清单

  • 普通文件:

    • 读端:直接用 generic_file_splice_read()
    • 写端:用 iter_file_splice_write()(要求实现好 ->write_iter)。
  • 设备/协议端:

    • 能导出/接收页引用→定制 pipe_buf_operations 以实现真零拷贝;
    • 否则先接入通用 helper,待性能稳定后再优化。

总结

`.splice_read` / `.splice_write` 是 VFS 为 `splice(2)` 提供的“**非管道端零拷贝**”钩子。
设计思想是**以管道为中心**,通过 **pipe buffer 对页的“引用转移/克隆”** 来搬运数据,避免用户态往返复制;
两侧对象只需实现“如何把自己的数据变成/消费成 pipe buffer”,其余由 VFS/pipe 框架负责队列、背压与生命周期管理。
http://www.dtcms.com/a/554190.html

相关文章:

  • Go 协程
  • 做网站时怎样图片上传怎么才能让图片不变形有什么插件吗淄博住房和城乡建设局网站
  • leetcode1312.让字符串成为回文串的最少插入次数
  • 宜春做网站 黑酷seo快递网站建站需要什么
  • org.apache.commons.lang3都有什么常用的类
  • edas会议投稿显示格式错误+消除浮动块下面的空白
  • 宁波建设网站公司北京seo案例
  • 虚拟网站仿制教程河南国控建设集团招标网站
  • viewerjs+vue3 using typescript
  • U81904 【模板】树的直径
  • 如何将React自定义语法转化为标准JavaScript语法?
  • 自己做网站主机wordpress 引号被转义
  • 做营销网站推广快速开发安卓app
  • 文件基础操作详解
  • 【22】C语言 - 二维数组详解
  • 嵌入式项目代码架构与分层笔记
  • 自己房子做民宿挂什么网站数字今天科技 网站
  • 建设ca网站aws wordpress 集群
  • Rust数据类型(上):标量类型全解析
  • BPC EPM表单常规设置
  • 关于C++递归函数和指针部分
  • 基于STM32的智能天气时钟
  • 传奇网站建设网站开发公用头部
  • 安徽省建设厅官方网站黄世山电商办公室
  • 做的网站上更改内容改怎么办科技公司logo设计图片
  • 飞腾D2000/8在Ubuntu20.04下压力测试
  • 深度学习模型部署:将 TensorFlow 模型转为 TFLite 适配移动端
  • 新版ubuntu中sac安装问题(缺少libncurses5)
  • 使用Docker搭建YApi接口管理平台
  • 建立网站的成本林州网站建设服务