C++_面试15_零拷贝

📌 面试题 1:什么是零拷贝?

标准答案:

零拷贝(Zero-Copy)是一种减少或消除 CPU 内存复制的技术,使数据在 用户态与内核态之间不发生拷贝
数据只在内核内部移动,或者直接经 DMA 发送到 NIC,大幅减少 CPU 开销与上下文切换。

📌 面试题 2:Linux 上 Zero-Copy 是哪些函数实现的?

标准答案:

Linux 的 Zero-Copy 由系统调用提供:

  • sendfile() —— 文件直接发送到 socket

  • splice() —— 内核 fd → fd 之间传输

  • tee() —— 管道复制但不拷贝内存

  • mmap() —— 文件映射到用户空间(读零拷贝,但写/发送不完全 zero)

这些函数避免了传统 read/write 产生的两次拷贝和两次切换。

Linux 下 Zero-Copy 的核心函数(系统调用)

#include <sys/sendfile.h>

sendfile(out_fd, in_fd, offset, count);

用途:
从文件直接发送到 socket,不经过用户态缓冲区。

数据流程(零拷贝):

Disk → Kernel Buffer → Socket Buffer → NIC
            (无用户态拷贝)
2. mmap() + write() —— 让文件直接映射到用户态

void* p = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0);
用途:
让用户空间访问文件内容,不需要 read() 拷贝。

流程:

Disk → Kernel Page Cache
User read buffer 只是指针访问内核同一块内存
注意:
mmap + write 并不是严格零拷贝,还涉及一次 NIC 发送拷贝。

2. mmap() + write() —— 让文件直接映射到用户态

void* p = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0);
用途:
让用户空间访问文件内容,不需要 read() 拷贝。

Disk → Kernel Page Cache
User read buffer 只是指针访问内核同一块内存
注意:
mmap + write 并不是严格零拷贝,还涉及一次 NIC 发送拷贝。

3. splice() —— 内核缓冲区之间管道传输(零拷贝)

splice(fd_in, NULL, fd_out, NULL, len, SPLICE_F_MORE | SPLICE_F_MOVE);
用途:
内核文件描述符之间 直接搬运数据,不经过用户态。

文件 → 管道 → socket
全过程不进入用户态。

4. tee() —— 拷贝管道数据但不复制内容(零拷贝)

tee(fd_in, fd_out, len, SPLICE_F_NONBLOCK);

用途:
复制管道数据流,但不拷贝实际内容(引用计数 + page 共享)
🔥 Zero-Copy 函数组合(Nginx/Tengine 用法)

例如:
sendfile + splice + tee 常被用来实现高性能 Web 服务器。

📌 面试题 3:sendfile() 与传统 read/write 的区别?

传统方式:
文件 → 内核 → 用户 → 内核 → NIC(4 次拷贝)

sendfile:
文件 → 内核 → NIC(1 次 DMA,0 次用户拷贝)

优势:

tee

📌 面试题 6:std::move 是 Zero-copy 吗?

  • CPU 使用率更低

  • 内存带宽占用更少

  • 更少的系统调用和上下文切换

  • 大幅提升网络 IO 性能

  • 📌 面试题 4:mmap 是不是零拷贝?

    标准答案(面试官爱听):

    mmap 是 “内存映射零拷贝”,只减少了文件读的拷贝(无需 read() 将数据拷贝到用户态)。
    但如果数据需要发送到 socket,仍然需要经过:

  • Kernel Buffer → Socket Buffer → NIC

  • 因此:
    ✔ mmap 是用于 “文件读取” 的零拷贝
    ✘ 不是网络发送端的完全零拷贝

  • 📌 面试题 5:splice / tee 适合什么场景?

  • splice

  • 管道模式

  • TCP 代理、反向代理

  • 如多播、日志复制

  • 复制管道数据但不复制内存内容

  • 共享相同的 page cache

  • 文件 → socket,不经过用户态

标准答案:

不是。

std::move 是编译器语义,用来把对象转为右值引用。
它减少用户态深拷贝,但与 OS 零拷贝完全无关。

Zero-copy 指的是:

🔥 用户态 ↔ 内核态之间不发生复制

完全不同层面的概念

📌 面试题 7:如何画 Zero-Copy 的数据流(终极版)

传统 read + write

read()
Disk → Kernel → User
write()
User → Kernel → NIC

总共 4 次拷贝,2 次上下文切换
sendfile()

DMA: Disk → Kernel
Zero-copy: Kernel → Socket
DMA: Socket → NIC

总共 0 次用户态拷贝,仅 1 次 DMA

 Eg:功能:客户端连接后,服务器使用 sendfile() 零拷贝将一个文件直接传给客户端
特点:

✔ epoll + 非阻塞 IO
✔ sendfile 完全零拷贝
✔ 无用户态 buffer
✔ 面试标准写法

#include <sys/types.h>
#include <sys/socket.h>
#include <sys/sendfile.h>
#include <sys/epoll.h>
#include <netinet/in.h>
#include <fcntl.h>
#include <unistd.h>
#include <cstring>
#include <iostream>

#define PORT 8888
#define MAX_EVENTS 1024

int main() {
    int listen_fd = socket(AF_INET, SOCK_STREAM, 0);

    // 设置非阻塞
    fcntl(listen_fd, F_SETFL, O_NONBLOCK);

    sockaddr_in addr{};
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = INADDR_ANY;
    addr.sin_port = htons(PORT);
    bind(listen_fd, (sockaddr*)&addr, sizeof(addr));
    listen(listen_fd, 128);

    // epoll 创建
    int epfd = epoll_create1(0);

    epoll_event ev{}, events[MAX_EVENTS];
    ev.events = EPOLLIN;
    ev.data.fd = listen_fd;
    epoll_ctl(epfd, EPOLL_CTL_ADD, listen_fd, &ev);

    std::cout << "Zero-copy server running on port " << PORT << std::endl;

    const char* filename = "test.txt";   // 要发送的文件

    while (true) {
        int n = epoll_wait(epfd, events, MAX_EVENTS, -1);

        for (int i = 0; i < n; ++i) {
            int fd = events[i].data.fd;

            // 处理新连接
            if (fd == listen_fd) {
                int client_fd = accept(listen_fd, NULL, NULL);
                fcntl(client_fd, F_SETFL, O_NONBLOCK);

                ev.events = EPOLLIN;
                ev.data.fd = client_fd;
                epoll_ctl(epfd, EPOLL_CTL_ADD, client_fd, &ev);

                std::cout << "Client connected: " << client_fd << std::endl;
            }
            else {
                // 处理客户端请求(此处简化为:只要读到数据就发送文件)
                char buf[1024];
                int ret = read(fd, buf, sizeof(buf));

                if (ret <= 0) {
                    close(fd);
                    epoll_ctl(epfd, EPOLL_CTL_DEL, fd, nullptr);
                    continue;
                }

                // 打开文件
                int file_fd = open(filename, O_RDONLY);
                if (file_fd < 0) continue;

                // 获取文件大小
                off_t offset = 0;
                off_t filesize = lseek(file_fd, 0, SEEK_END);
                lseek(file_fd, 0, SEEK_SET);

                // 🔥 核心:sendfile(零拷贝发送)
                ssize_t sent = sendfile(fd, file_fd, &offset, filesize);
                std::cout << "Sent " << sent << " bytes by zero-copy\n";

                close(file_fd);
            }
        }
    }

    return 0;
}

技术用户态拷贝内核态拷贝是否零拷贝用途
mmap + write❌ 有一次✔ 有一次❌ 半零拷贝随机读/写
sendfile❌ 0 次✔ 1 次(内核内部)✔ 完全零拷贝大文件发送

🧠 Windows Zero-Copy API 总表(最简答版)

API类型是否零拷贝常用场景
TransmitFile✔ 文件发送✔ 完全零拷贝大文件、静态资源发送
TransmitPackets✔ 混合数据✔ 零拷贝或部分零拷贝HTTP header + body
AcceptEx✔ 接受连接✔ 零拷贝高级 TCP 服务器/IOCP
WSASend/WSARecv通用 IO⚠ 仅 Buffer 注册时高吞吐 IOCP
ReadFileScatter / WriteFileGather分散/聚集 IO半零拷贝大文件读写

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值