ILD

Linux zero copy API: copy_file_range()
作者:Yuan Jianpeng 邮箱:yuanjp89@163.com
发布时间:2025-12-24 站点:Inside Linux Development

1 历史

copy_file_range()的函数原型如下:

    ssize_t copy_file_range(int fd_in, loff_t *off_in,
                            int fd_out, loff_t *off_out,
                            size_t len, unsigned int flags);


copy_file_range()出现的时间比较晚,在4.5内核(2016/3)才引入。参考[2],提到了4.5内核引入了一个新的系统调用copy_file_range。


copy_file_range()最开始要求fd_in和fd_out在同一个文件系统。5.3的一个patch放开了限制,允许跨文件系统。但是Go语言的开发者在其io.Copy()接口中使用copy_file_range()来实现拷贝的时候,引入了一个bug。当其中一个fd是procfs的文件时,没有拷贝任何内容,且报告了成功,因为procfs的文件,在stat的时候,返回的大小是0,但是read()却可以读到内容。这引发了内核开发社区激烈的讨论。最终5.19内核又将限制修改为,不需要是同一个挂载点,但是两个文件系统需要是相同类型,或者文件系统显式的支持copy_file_range()。


2 机理

参考[3]详细讲述了copy_file_range()的实现机理。

首先,在filesystem层级,添加了一个新的同名file_operations接口,也就是说文件系统可以自己去实现copy_file_range()。


在kernel内部,有3个flag。

COPY_FR_COPY ,正常的拷贝操作。将kernel page cache直接写入底层存储设备,等价于splice()系统调用。

COPY_FR_REFLINK,引用的方式执行拷贝,实现真正的0拷贝,有些文件系统如btrfs,可以支持。

COPY_FR_DEDUP,用来将目标的内容替换为引用,可以用来实现去除文件系统中,重复的内容。


当系统调用的flag为0时,内部使用 flags = COPY_FR_COPY | COPY_FR_REFLINK;


3 Copy offloading

由于文件系统可以自己实现copy_file_range(),所以其真正的杀手锏,是可以实现copy offloading。


对于本地文件系统可能节省的开销不大,毕竟节省的用户态内存拷贝的开销远小于磁盘IO开销。

但是对于NFS,好处就很明显了,不用将数据从nfs server传输到nfs client,在传输回去,可以直接在server实现copy动作。


在没有copy_file_range()的时候,尽管coW文件系统,如btrfs可以通过ioctl来实现copy offloading,现在cp使用--reflink实现。

$ strace -e ioctl cp --reflink a b

ioctl(4, BTRFS_IOC_CLONE or FICLONE, 3) = 0


4 和sendfile的差异

sendfile()出现的时间非常早,2.6内核就已经有了。


差异1:

sendfile()的一个fd可以是socket,用来实现文件到网络的传输。但是copy_file_range()不行,它的fd只能是文件。


差异2:

当两个fd都是文件的时候,sendfile()是vfs级别的,而copy_file_range()是可以是filesystem级别的。


参考:

【1】How useful should copy_file_range() be? https://lwn.net/Articles/846403/

【2】https://kernelnewbies.org/Linux_4.5

【3】copy_file_range() https://lwn.net/Articles/659523/

【4】Copy offload https://lwn.net/Articles/637436/

【5】https://man7.org/linux/man-pages/man2/copy_file_range.2.html


Copyright © linuxdev.cc 2017-2024. Some Rights Reserved.