linux下用memfd_create+splice或sendfile+af_unix套接字才能真正绕过用户态内存拷贝;mmap共享内存本身不等于zero-copy,若后续调用read()/write()仍触发page fault和memcpy,关键在数据流全程不经过用户缓冲区。

Zero-Copy 在 C++ 跨进程通信中到底省了哪几份拷贝?
直接说结论:Linux 下用 memfd_create + splice 或 sendfile + AF_UNIX 套接字,才能真正绕过用户态内存拷贝;用 mmap 共享内存本身不等于 zero-copy —— 如果后续还要 read()/write(),内核仍会触发 page fault 和 memcpy。关键在数据流路径是否全程不经过用户缓冲区。
常见误判场景:
- 以为
mmap一段共享内存后调用memcpy就是 zero-copy —— 实际仍是 CPU 拷贝,且可能触发缺页中断 - 用
shm_open+mmap但配对进程仍用read()从 fd 读 —— 内核会把共享页内容再 copy 到 socket 发送队列 - 在容器或 PID namespace 中未正确传递 memfd 文件描述符,导致
splice失败并静默回退到 copy
如何用 memfd_create + splice 实现跨进程零拷贝传输?
memfd_create 创建的匿名内存文件可被 SCM_RIGHTS 通过 Unix 域套接字安全传递,接收方拿到 fd 后直接 splice 到目标 socket,全程无用户态内存参与。
核心约束与实操要点:
- 发送方必须用
memfd_create("data", MFD_CLOEXEC)创建 fd,并写入数据(write或pwrite) - 接收方不能
mmap这个 fd —— 一旦 mmap,splice可能因 page lock 冲突失败,返回EAGAIN - 两端 socket 必须是
AF_UNIX类型,且目标 socket 需启用SOCK_STREAM并关闭TCP_NODELAY(避免 Nagle 算法引入延迟) - 调用
splice(fd_in, nullptr, fd_out, nullptr, len, SPLICE_F_MOVE | SPLICE_F_NONBLOCK),注意SPLICE_F_MOVE仅在内核支持且源/目标均为 pipe 或 socket 时生效
示例片段(发送端):
int memfd = memfd_create("payload", MFD_CLOEXEC);
ftruncate(memfd, payload_size);
write(memfd, data_ptr, payload_size);
// 通过已建立的 unix socket 发送 fd
struct msghdr msg = {0};
struct cmsghdr *cmsg;
char cmsg_buf[CMSG_SPACE(sizeof(int))];
msg.msg_control = cmsg_buf;
msg.msg_controllen = sizeof(cmsg_buf);
cmsg = CMSG_FIRSTHDR(&msg);
cmsg->cmsg_level = SOL_SOCKET;
cmsg->cmsg_type = SCM_RIGHTS;
cmsg->cmsg_len = CMSG_LEN(sizeof(int));
*((int*)CMSG_DATA(cmsg)) = memfd;
sendmsg(unix_sock_fd, &msg, 0);
为什么 sendfile 不适用于跨进程大数据交换?
sendfile 本质是“文件到 socket”搬运工,要求源 fd 是普通文件或管道 —— 但跨进程共享内存无法直接作为 sendfile 的 src_fd(memfd 虽可,但 sendfile 不支持 offset > 0 的非 seekable fd;而 splice 支持 off_in/off_out 参数)。
更实际的问题:
-
sendfile无法跨进程传递内存所有权,只能复制内容;而splice在同属一个用户命名空间时可实现 page reference transfer - 当 payload 超过 2GB,
sendfile的off_t参数在部分旧内核上存在截断风险;splice使用size_t且分段调用更可控 -
sendfile对AF_UNIXsocket 支持不稳定,glibc 2.27+ 才修复部分 errno 返回异常问题
实际部署时最常被忽略的三个细节
协议能跑通不等于高效,这三个点几乎每次压测都会暴露:
-
/proc/sys/vm/max_map_count必须足够大 —— 每个memfd占用一个 vma 区域,短连接高频创建会快速耗尽,默认 65530 往往不够 - 接收方需调用
fcntl(fd, F_SETFL, O_NONBLOCK)设置 memfd 为非阻塞,否则大块splice可能因 socket 缓冲区满而阻塞整个线程 - 务必检查内核版本:
splice对memfd的 full zero-copy 支持始于 Linux 4.5,4.12 加入SPLICE_F_MOVE优化;低于此版本会 fallback 到内核内部 copy,且不报错
没有共享页表、没有 fd 传递、没有内核版本兜底,所谓 zero-copy 就只是个名词。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











