Linux 作为一款开源的操作系统,其文件操作是系统管理与应用开发中的核心环节,在众多文件操作中,复制文件是最基础也最频繁的任务之一,本文将深入探讨 Linux 系统中实现文件复制的多种函数,涵盖从系统调用到标准库函数的不同层次,分析其原理、使用场景及注意事项,帮助开发者根据实际需求选择最合适的复制方法。
系统调用级别的文件复制:copy_file_range
copy_file_range 是 Linux 内核提供的一个相对较新的系统调用(自 Linux 4.5 版本引入),专门用于高效地在文件描述符之间复制数据,其核心优势在于,如果源文件和目标文件位于同一个文件系统,内核可以通过“复制粘贴”文件数据块的方式,避免用户空间和内核空间之间的数据拷贝,从而实现零拷贝(zero-copy)操作,显著提升大文件复制的性能。
该函数的原型如下:
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);
fd_in和fd_out:分别表示源文件和目标文件描述符。off_in和off_out:分别表示源文件和目标文件的起始偏移量,若为 NULL,则从当前文件偏移量开始复制,并更新偏移量。len:要复制的字节数。flags:保留参数,目前必须为 0。
使用 copy_file_range 时,需要注意以下几点:
- 原子性:该调用是原子的,确保在复制过程中数据不会被部分写入。
- 跨文件系统:如果源文件和目标文件位于不同文件系统,内核会回退到传统的 read-write 方式,此时性能优势不明显。
- 错误处理:需要检查返回值,若返回 -1 则表示出错,可通过
errno获取具体错误原因。
以下是一个简单的使用示例:
#include <sys/syscall.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <errno.h>
#ifndef __NR_copy_file_range
#define __NR_copy_file_range 326
#endif
ssize_t my_copy_file_range(int fd_in, loff_t *off_in, int fd_out, loff_t *off_out, size_t len) {
return syscall(__NR_copy_file_range, fd_in, off_in, fd_out, off_out, len, 0);
}
int main() {
int fd_in = open("source.txt", O_RDONLY);
int fd_out = open("destination.txt", O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (fd_in == -1 || fd_out == -1) {
perror("open");
return 1;
}
loff_t src_off = 0, dst_off = 0;
ssize_t copied;
while ((copied = my_copy_file_range(fd_in, &src_off, fd_out, &dst_off, 4096)) > 0) {
// 循环复制,直到复制完毕或出错
}
if (copied == -1) {
perror("copy_file_range");
}
close(fd_in);
close(fd_out);
return 0;
}
标准库函数级别的文件复制
相较于系统调用的底层和复杂性,标准 C 库(如 glibc)提供了更易用的高级函数,广泛应用于日常编程中,主要包括 fopen、fread、fwrite 以及 copyfile(非标准,但部分系统支持)等。
基于 fopen、fread、fwrite 的传统方式
这是最通用、最跨平台的文件复制方法,其原理是打开源文件和目标文件,以二进制模式读取源文件数据块,然后写入目标文件,直到文件结束。
#include <stdio.h>
#include <stdlib.h>
void copy_file_fread_fwrite(const char *src_path, const char *dst_path) {
FILE *src_file = fopen(src_path, "rb");
FILE *dst_file = fopen(dst_path, "wb");
if (!src_file || !dst_file) {
perror("fopen");
if (src_file) fclose(src_file);
if (dst_file) fclose(dst_file);
exit(EXIT_FAILURE);
}
char buffer[4096];
size_t bytes_read;
while ((bytes_read = fread(buffer, 1, sizeof(buffer), src_file)) > 0) {
if (fwrite(buffer, 1, bytes_read, dst_file) != bytes_read) {
perror("fwrite");
break;
}
}
if (ferror(src_file)) {
perror("fread");
}
fclose(src_file);
fclose(dst_file);
}
优点:
- 代码简洁易懂,可移植性好。
- 适用于各种平台和文件系统。
缺点:
- 需要从内核空间到用户空间的数据拷贝(read),再从用户空间到内核空间(write),对于大文件,性能不如
copy_file_range。 - 需要管理缓冲区大小,4KB 或 8KB 是一个较好的平衡点。
mmap 内存映射方式
mmap (Memory Mapped Files) 是一种将文件或设备直接映射到进程虚拟地址空间的技术,通过 mmap,进程可以像访问内存一样访问文件数据,避免了显式的 read/write 系统调用,从而提高 I/O 效率。
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
void copy_file_mmap(const char *src_path, const char *dst_path) {
int src_fd = open(src_path, O_RDONLY);
int dst_fd = open(dst_path, O_RDWR | O_CREAT | O_TRUNC, 0644);
if (src_fd == -1 || dst_fd == -1) {
perror("open");
exit(EXIT_FAILURE);
}
struct stat st;
if (fstat(src_fd, &st) == -1) {
perror("fstat");
exit(EXIT_FAILURE);
}
off_t file_size = st.st_size;
if (ftruncate(dst_fd, file_size) == -1) {
perror("ftruncate");
exit(EXIT_FAILURE);
}
char *src_map = mmap(NULL, file_size, PROT_READ, MAP_PRIVATE, src_fd, 0);
char *dst_map = mmap(NULL, file_size, PROT_WRITE, MAP_SHARED, dst_fd, 0);
if (src_map == MAP_FAILED || dst_map == MAP_FAILED) {
perror("mmap");
exit(EXIT_FAILURE);
}
memcpy(dst_map, src_map, file_size);
if (munmap(src_map, file_size) == -1 || munmap(dst_map, file_size) == -1) {
perror("munmap");
}
close(src_fd);
close(dst_fd);
}
优点:
- 对于大文件,性能通常优于传统的 fread/fwrite,因为减少了数据拷贝次数。
- 适合随机访问文件内容。
缺点:
- 消耗虚拟内存空间,每个映射区域都会占用进程的地址空间。
- 如果文件非常大,可能导致内存不足。
- 需要处理
mmap可能失败的情况。
不同复制方式的性能对比
为了更直观地理解不同方法的性能差异,我们可以通过一个表格进行对比,假设场景为在同一个文件系统上复制一个 1GB 的文件。
| 复制方式 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
copy_file_range |
零拷贝,性能最高,原子操作 | 内核版本要求高,跨文件系统性能下降 | 高性能需求,大文件复制,同文件系统 |
mmap + memcpy |
性能较好,适合大文件和随机访问 | 消耗虚拟内存,大文件可能内存不足 | 大文件复制,需要频繁访问文件内容 |
fread/fwrite |
简单易用,可移植性强 | 多次数据拷贝,性能相对较低 | 小文件复制,通用场景,跨平台开发 |
cp 命令 (用户空间) |
方便快捷,由 Shell 调用 | 底层可能组合多种系统调用,性能视实现而定 | 命令行操作,日常文件管理 |
总结与选择建议
在 Linux 系统中,实现文件复制有多种函数和方法,选择哪种取决于具体的应用场景和需求:
- 追求极致性能且满足内核版本要求:优先考虑
copy_file_range,这是目前 Linux 下最高效的文件复制方式,尤其适用于大文件在同文件系统间的复制。 - 需要灵活访问文件内容或处理较大文件:
mmap是一个不错的选择,它提供了内存般的访问方式,性能也较好,但要注意内存消耗。 - 编写通用、可移植的代码或处理小文件:传统的
fopen、fread、fwrite组合是最佳实践,其简单性和跨平台特性使其成为大多数应用的首选。 - 命令行操作:直接使用
cp命令即可,其内部实现通常会根据系统环境选择最优的复制策略。
理解这些函数的底层原理和性能特点,有助于开发者在实际项目中做出更合理的技术选型,从而编写出高效、可靠的文件操作代码,无论是系统级编程还是应用软件开发,掌握 Linux 文件复制的精髓都是提升程序性能的重要一环。



















