零拷贝 | 青训营

2023年 8月 28日 44.6k 0

传统文件传输过程

image-20230824173735578.png

文件传输主要就是文件读写过程

linux传统文件传输过程

文件拷贝过程中发生了四次用户态与内核态的切换

image-20230824195235921.png
四次拷贝(从盘读取文件并发送到网卡)

  • 把磁盘上的数据拷贝到操作系统的内核缓存区中
  • 用户缓存区(应用程序可以访问这部分数据)
  • 内核的socket的缓存区中
  • 网卡
  • 四次上下文切换

    image-20230824195248045.png
    每次系统调用的时候都会从用户态切换到内核,内核任务完成之后再切换回用户态

    用户空间:最外侧,只能访问到受限的资源

    内核空间:进程调度 内存分配 连接硬件资源

    对应不同的内核等级,数字越小权限越大,内核是最高的等级 可以访问所有的资源

    两个模式的切换过程叫做 :特权模式切换

    进程可以在俩个空间中运行

    CPU上下切换

    调用read的时候进行了一次切换,首先会保存原来的cpu寄存器里的用户态的指令位,为了执行内核态代码,CPU寄存器需要更新为内核指令的新位置,然后跳转到内核态运行内核任务。系统调用结束后 CPU寄存器需要回复原来保存的用户态,然后再切换回用户空间 继续之前的进程(几十纳秒-几微秒) 但是在高并发的场景下时间很容易被累计和放大 从而影响系统的整体性能

    之前的操作系统没有区分用户态和核心态,应用程序都是在内核中运行的,这样很容易导致整个操作系统崩溃,如果代码中有些bug 可能会导致运行在这个内核操作系统上的其他程序也随之崩溃 难以保证操作系统的稳定性 所以需要进行用户态和核心态的区分

    CPU拷贝

    用户态的数据是不可信的 所以内核在访问操作之前都需要做必要的检查不同的系统调用 可能要做的检查流程是不同的 但是这个检查如果不拷贝直接在原地操作的话可能会导致在检查和使用之间有小段时间窗口 如果用户在这个时间窗口内对内存进行修改的话会导致之前的检查是无效的 可能会导致内核出现崩溃等其他问题 所以需要先拷贝数据到内核态防止这种变化

    image-20230824215136721.png

    CPU数据拷贝方式

    CPU访问速度远远高于磁盘准备数据的速度

    I/O轮询

    零拷贝 | 青训营-1
    CPU大量时间都在等待 效率太低

    IO中断

    image-20230824220441939.png
    CPU在等待的时间内先去完成其他任务 可以解放CPU

    处理中断也是有一定的开销的

    DMA传输 直接内存访问

    image-20230824220627774.png
    DMA是主板上独立的一块芯片 就是一个协处理器(本身也是个处理器芯片)和CPU一样可以访问内存和外设

    DMA是被设计用来单独处理io数据传输的 成本比CPU低

    CPU和DMA进行交互 首先指定需要读写的数据块大小和需要进行io数据目的地内存地址之后 CPU异步处理别的指令 只有当DMA完成了整体数据块io后才会发起中断通知CPU 相当于做了一个缓存

    异步非阻塞

    内核和用户态的数据拷贝还是需要CPU执行 因为DMA只能支持硬件数据拷贝

    可以把内核和磁盘中两次拷贝替换为DMA拷贝了

    零拷贝的实现方式

    image-20230824221233105.png
    mmap + write

    image-20230824221508431.png
    进程虚拟内存映射的方法 将文件或者一段物理内存映射到进程的虚拟内存地址空间 然后进程可以用指针来读取这一段内存 系统会自动会写脏页到对应的文件磁盘上(完成文件操作 同时也不用调用read操作)

    还是有CPU拷贝 但是不需要从用户态拷贝而是直接从内核中拷贝

    mmap代替了read方法 减少一次用户空间和内核空间的CPU拷贝(不会真正把数据拷贝到用户空间 不占用真实的物理内存 适合大文件传输) 需要磁盘代替内存的时候都可以用mmap

    sendfile

    image-20230824221932405.png
    有写程序不要用到磁盘中的数据 所以可以不用这两次拷贝

    调用sendfile接口将内核缓存区拷贝到socket的缓存区中,仅仅发生在内核

    只用一次CPU拷贝

    sendfile + gather

    image-20230824223027830.png

    将数据直接从内核缓存区通过DMA拷贝到网卡

    CPU会吧内核缓存区中的内存地址和偏移量发送到socket中 然后由DMA根据这些信息直接把数据从内核缓存区拷贝到网卡 最后发生上下文切换 sendfile函数调用返回

    两次上下文切换 + 两次DMA拷贝

    需要硬件支持

    splice

    image-20230824223347614.png
    硬件不支持的话

    主要通过管道进行传输数据

    image-20230824224316275.png

    go语言中的实现

    image-20230824224416526.png
    返回的是内存地址

    sendfile

    read

    image-20230824224519121.png
    io.Copy是调用了copyBuffer方法 主要是readFrom

    image-20230824224649481.png
    优先级调用

    零拷贝的应用

    image-20230824224800005.png

    消息产生:网络数据持久化到磁盘中

    消费:磁盘通过网络发送给consumer进行消费

    image-20230824224919854.png
    磁盘读写性能比较差

    完成io请求:寻到时间 旋转延迟 数据传输(物理操作 耗时高)

    image-20230824225130752.png

    image-20230824225259949.png

    image-20230824225356563.png

    image-20230824225505111.png

    零拷贝 | 青训营-2

    image-20230824225714519.png

    相关文章

    服务器端口转发,带你了解服务器端口转发
    服务器开放端口,服务器开放端口的步骤
    产品推荐:7月受欢迎AI容器镜像来了,有Qwen系列大模型镜像
    如何使用 WinGet 下载 Microsoft Store 应用
    百度搜索:蓝易云 – 熟悉ubuntu apt-get命令详解
    百度搜索:蓝易云 – 域名解析成功但ping不通解决方案

    发布评论