C++ 零拷贝使用
实现文件的拷贝
传统IO
c
#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>
#include <iostream>
void file_copy_read_write(const char *src_file, const char *dest_file)
{
int src_fd = open(src_file, O_RDONLY);
if (src_fd == -1)
{
perror("open src file");
return;
}
// 获取源文件大小
struct stat sb;
if (fstat(src_fd, &sb) == -1)
{
perror("fstat");
close(src_fd);
return;
}
// 打开目标文件(无需预先分配空间)
int dest_fd = open(dest_file, O_WRONLY | O_CREAT | O_TRUNC, 0666);
if (dest_fd == -1)
{
perror("open dest file");
close(src_fd);
return;
}
// 缓冲区(建议大小与文件系统块对齐,通常4KB-64KB)
const size_t BUF_SIZE = 4096;
char buf[BUF_SIZE];
ssize_t total_written = 0;
while (total_written < sb.st_size)
{
// 读取数据到缓冲区
ssize_t n_read = read(src_fd, buf, BUF_SIZE);
if (n_read == -1)
{
perror("read");
break;
}
// 写入目标文件
ssize_t n_written = write(dest_fd, buf, n_read);
if (n_written != n_read)
{
perror("write");
break;
}
total_written += n_written;
}
// 确保数据落盘
fsync(dest_fd);
// 关闭文件
close(src_fd);
close(dest_fd);
}
int main()
{
file_copy_read_write("/zwh/C++/Test/file/init.txt", "/zwh/C++/Test/file/out.txt");
return 0;
}
mmap
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
映射的起始地址(通常传
NULL
,由内核自动选择)映射的字节大小(通常为文件大小)
内存保护模式(
PROT_READ
/PROT_WRITE
/PROT_EXEC
/PROT_NONE
)
PROT_READ
:只读映射(适用于只读文件)。PROT_READ | PROT_WRITE
:可读可写(适用于读写文件或共享内存)。PROT_READ | PROT_EXEC
:可读可执行(适用于加载动态库)。文件描述符
MAP_SHARED
: 共享映射,修改会同步到文件,其他进程可见(适用于进程间通信 IPC),多个进程共享同一文件(如数据库、日志文件)
MAP_PRIVATE
: 私有映射,修改不会写入文件(写时复制,进程独享),进程需要独立修改文件内容的场景
MAP_ANONYMOUS
:匿名映射,不用关联文件
- 可代替
malloc
分配大块内存(如glibc
的malloc
对大内存使用mmap
)。- 进程间共享内存(需配合
MAP_SHARED
)c// 分配 1MB 匿名共享内存 void *addr = mmap(NULL, 1024*1024, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);
文件偏移量(通常为
0
,表示从文件开头映射)
ftruncate
:为目标文件预先分配磁盘空间
mmap
要求目标文件的大小必须足够,否则映射会失败(例如,如果目标文件是空文件,mmap
会返回MAP_FAILED
)ftruncate(fd, size)
的作用是:
- 如果文件 小于
size
,则 扩展文件 并填充\0
(空洞文件,实际不占用磁盘块)。- 如果文件 大于
size
,则 截断文件
c
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <iostream>
void mmap_write_example(const char *src_file, const char *dest_file)
{
int src_fd = open(src_file, O_RDONLY);
if (src_fd == -1)
{
perror("open src file");
return;
}
// 获取文件大小
struct stat sb;
if (fstat(src_fd, &sb) == -1)
{
perror("fstat");
close(src_fd);
return;
}
// 映射文件到内存
void *src_map = mmap(NULL, sb.st_size, PROT_READ, MAP_PRIVATE, src_fd, 0);
if (src_map == MAP_FAILED)
{
perror("mmap");
close(src_fd);
return;
}
// 打开目标文件
int dest_fd = open(dest_file, O_RDWR | O_CREAT | O_TRUNC, 0666);
if (dest_fd == -1)
{
perror("open dest file");
munmap(src_map, sb.st_size);
close(src_fd);
return;
}
// 调整目标文件大小,为目标文件预先分配磁盘空间大小是sb.st_size
if (ftruncate(dest_fd, sb.st_size) == -1)
{
perror("ftruncate");
close(dest_fd);
munmap(src_map, sb.st_size);
close(src_fd);
return;
}
// 写入目标文件
ssize_t n = write(dest_fd, src_map, sb.st_size);
if (n != sb.st_size)
{
perror("write");
}
// 清理资源
close(dest_fd);
munmap(src_map, sb.st_size);
close(src_fd);
}
int main()
{
mmap_write_example("/zwh/C++/Test/file/init.txt", "/zwh/C++/Test/file/out.txt");
return 0;
}
sendfile
ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count);
- 目标文件描述符
- 源文件描述符
- 源文件的起始偏移量为NULL则从文件当前开始
- 要传输的字节数
- 返回实际传输的字节数(可能小于
count
)
c
#include <fcntl.h>
#include <unistd.h>
#include <sys/sendfile.h>
#include <sys/stat.h>
#include <iostream>
void file_copy_sendfile(const char *src_file, const char *dest_file) {
int src_fd = open(src_file, O_RDONLY);
if (src_fd == -1) {
perror("open src file");
return;
}
// 获取源文件大小
struct stat sb;
if (fstat(src_fd, &sb) == -1) {
perror("fstat");
close(src_fd);
return;
}
// 打开目标文件(必须可写)
int dest_fd = open(dest_file, O_WRONLY | O_CREAT | O_TRUNC, 0666);
if (dest_fd == -1) {
perror("open dest file");
close(src_fd);
return;
}
// 使用sendfile拷贝数据
ssize_t total_sent = 0;
while (total_sent < sb.st_size) {
ssize_t n_sent = sendfile(dest_fd, src_fd, NULL, sb.st_size - total_sent);
if (n_sent == -1) {
perror("sendfile");
break;
}
total_sent += n_sent;
}
// 确保数据落盘
fsync(dest_fd);
// 关闭文件
close(src_fd);
close(dest_fd);
}
int main() {
file_copy_sendfile("/zwh/C++/Test/file/init.txt", "/zwh/C++/Test/file/out.txt");
return 0;
}
测试(仅供参考)
本地
1.5GB
文件测试,瓶颈来自磁盘,因此时间基本一致,但是零拷贝的CPU时间和占有率大大降低
传统IO
mmap
sendfile
网络
Socket
,传统IO
和sendfile
发送文件测试- 综合多次测试,从时间、吞吐量、
CPU
使用率、系统调用次数上看,sendfile
效果更好