零拷贝技术-内核源码剖析

news2024/11/16 12:02:53

在网络编程中,如果我们想要提供文件传输的功能,最简单的方法就是用read将数据从磁盘上的文件中读取出来,再将其用write写入到socket中,通过网络协议发送给客户端。

ssize_t read(int fd, void *buf, size_t count);
ssize_t write(int fd, const void *buf, size_t count);

但是就是这两个简单的操作,却带来了大量的性能丢失。

例如我们的服务器需要为客户端提供一个下载操作,此时的操作如下:

从上图可以看出,虽然仅仅只有这两行代码,但是却在发生了四次用户态和内核态的上下文切换,以及四次数据拷贝,也就是在这个地方产生了大量不必要的损耗。

那么为什么会发生这些操作呢?

上下文切换

由于read和recv是系统调用,所以每次调用该函数我们都需要从用户态切换至内核态,等待内核完成任务后再从内核态切换回用户态。

数据拷贝

上面也说了,由于数据的读取与写入都是由系统进行的,那么我们就得将数据从用户的缓冲区中拷贝到内核:

第一次拷贝:将磁盘中的数据拷贝到内核的缓冲区中

第二次拷贝:内核将数据处理完,接着拷贝到用户缓冲区中

第三次拷贝:此时需要通过socket将数据发送出去,将用户缓冲区中的数据拷贝至内核中socket的缓冲区中

第四次拷贝:把内核中socket缓冲区的数据拷贝到网卡的缓冲区中,通过网卡将数据发送出去。

所以要想优化传输性能,就要从减少数据拷贝和用户态内核态的上下文切换下手,这也就是零拷贝技术的由来。

零拷贝

零拷贝的主要任务就是避免CPU将数据从一块存储中拷贝到另一块存储,主要就是利用各种技术,避免让CPU做大量的数据拷贝任务,以此减少不必要的拷贝。或者借助其他的一些组件来完成简单的数据传输任务,让CPU解脱出来专注别的任务,使得系统资源的利用更加有效。

sendfile

sendfile函数的作用是直接在两个文件描述符之间传递数据。由于整个操作完全在内核中(直接从内核缓冲区拷贝到socket缓冲区),从而避免了内核缓冲区和用户缓冲区之间的数据拷贝。

需要注意的是,in_fd必须是一个支持类似mmap函数的文件描述符,不能是socket或者管道,而out_fd必须是一个socket,由此可见sendfile是专门为了在网络上传输文件而实现的函数。

#include <sys/sendfile.h>

ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count);

参数: out_fd : 待写入内容的文件描述符 in_fd : 待读出内容的文件描述符 offset : 文件的偏移量4. DMA拷贝 count : 需要传输的字节数 返回值: 成功:返回传输的字节数 失败:返回-1并设置errno

内核源码简读

sendfile系统调用分32位接口与64位接口,原理都是一样的,这里直接看32位的(inux/fs/read_write.c):

asmlinkage ssize_t sys_sendfile(int out_fd, int in_fd, off_t __user *offset, size_t count)
{
    loff_t pos;
    off_t off;
    ssize_t ret;

    if (offset) {
        if (unlikely(get_user(off, offset)))
            return -EFAULT;
        pos = off;
        ret = do_sendfile(out_fd, in_fd, &pos, count, MAX_NON_LFS);
        if (unlikely(put_user(pos, offset)))
            return -EFAULT;
        return ret;
    }

    return do_sendfile(out_fd, in_fd, NULL, count, 0);
}

殊途同归,都是调用了do_sendfile:

static ssize_t do_sendfile(int out_fd, int in_fd, loff_t *ppos,
               size_t count, loff_t max)
{
    // 一些检查...
    
    // 调用struct file_operations中的sendfile
    retval = in_file->f_op->sendfile(in_file, ppos, count, file_send_actor, out_file);

    if (retval > 0) {
        current->rchar += retval;
        current->wchar += retval;
    }
    current->syscr++;
    current->syscw++;
}

实际调用的是这个接口:

ssize_t generic_file_sendfile(struct file *in_file, loff_t *ppos,
             size_t count, read_actor_t actor, void *target)
{
    read_descriptor_t desc;

    if (!count)
        return 0;

    desc.written = 0;
    desc.count = count;
    desc.arg.data = target;
    desc.error = 0;

    // 读文件,读完通过actor接口发送
    do_generic_file_read(in_file, ppos, &desc, actor);
    if (desc.written)
        return desc.written;
    return desc.error;
}

回到上一步注入actor的地方,找到file_send_actor的实现:

int file_send_actor(read_descriptor_t * desc, struct page *page, unsigned long offset, unsigned long size)
{
    ssize_t written;
    unsigned long count = desc->count;
    struct file *file = desc->arg.data;

    if (size > count)
        size = count;

    // 调用struct file_operations的sendpage接口
    written = file->f_op->sendpage(file, page, offset,
                       size, &file->f_pos, size<count);
    if (written < 0) {
        desc->error = written;
        written = 0;
    }
    desc->count = count - written;
    desc->written += written;
    return written;
}

此时的file是一个socket文件,

ssize_t sock_sendpage(struct file *file, struct page *page,
              int offset, size_t size, loff_t *ppos, int more)
{
    struct socket *sock;
    int flags;

    sock = SOCKET_I(file->f_dentry->d_inode);

    flags = !(file->f_flags & O_NONBLOCK) ? 0 : MSG_DONTWAIT;
    if (more)
        flags |= MSG_MORE;

    // 调用struct proto的发送接口, 比如udp
    return sock->ops->sendpage(sock, page, offset, size, flags);
}

可见源文件数据自始至终只在内核中传递,并没有经过用户态。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/389724.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

学习记录---latent code 潜在编码

文章目录参考文献1. 什么是潜在编码&#xff1f;2.什么是潜在空间&#xff1f;3.同类潜在编码的相似性4.潜在编码的应用4.1 Antoencoders4.2 Generative models5.结论个人学习总结&#xff0c;持续更新中……参考文献 [1] 快速理解深度学习中的latent code潜在编码 1. 什么是…

[一篇读懂]C语言十一讲:单链表的删除和单链表真题实战

[一篇读懂]C语言十一讲&#xff1a;单链表的删除和单链表真题实战1. 与408关联解析及本节内容介绍1 本节内容介绍2. 单链表的删除操作实战3. 单链表真题解读与解题设计1 题目解读2 解题设计第一阶段&#xff1a;双指针找中间结点第二阶段&#xff1a;原地逆置第三阶段&#xff…

ubuntu16.04 python代码自启动和可执行文件自启动

1 python代码自启动 参考 https://blog.csdn.net/qq_38288618/article/details/104096606 准备好python文件 test.py import time c1 while 1:time.sleep(1)cc1print(c)运行 sudo chmod 777 test.py python3 test.py准备run.sh 文件 #!/bin/bash gnome-terminal -x bash -…

【Spring6】IoC容器之基于XML管理Bean

3、容器&#xff1a;IoC IoC 是 Inversion of Control 的简写&#xff0c;译为“控制反转”&#xff0c;它不是一门技术&#xff0c;而是一种设计思想&#xff0c;是一个重要的面向对象编程法则&#xff0c;能够指导我们如何设计出松耦合、更优良的程序。 Spring 通过 IoC 容…

C语言学习笔记——指针(初阶)

前言 指针可以说是C语言基础语法中最难的理解的知识之一&#xff0c;很多新手&#xff08;包括我&#xff09;刚接触指针时都觉得很难。在我之前发布的笔记中都穿插运用了指针&#xff0c;但是我一直没有专门出一期指针的笔记&#xff0c;这是因为我确实还有些细节至今还不太清…

STM32之关门狗

看门狗介绍在由单片机构成的微型计算机系统中&#xff0c;由于单片机的工作常常会受到来自外界电磁场的干扰&#xff0c;造成程序的跑飞&#xff0c;而陷入死循环&#xff0c;程序的正常运行被打断&#xff0c;由单片机控制的系统无法继续工作&#xff0c;会造成整个系统的陷入…

vue3+rust个人博客建站日记5-所有界面

没有数据的前端&#xff0c;是没有灵魂的。明明标题是vue3 rust &#xff0c;但日记撰写至今&#xff0c;似乎只有第一篇提及了Rust&#xff0c;这可不行。是时候一股作气&#xff0c;完成大部分页面绘制工作了&#xff01; 最后再说一次&#xff0c;时间要加速了。 ——普奇神…

EPICS S7nodave手册

第一章&#xff1a;介绍 本手册分为6章(不算次介绍部分)。第一章介绍s7nodave用于EPICS的设备支持的概念和特新。第二章描述启动一个使用s7nodave的IOC项目所需要的几步。第三章描述s7nodave支持的IOC shell命令。之后&#xff0c;第四章解释s7nodave支持的各种记录类型。最后…

【算法】期末复盘,酒店住宿问题——勿向思想僵化前进

文章目录前言题目描述卡在哪里代码&#xff08;C&#xff09;前言 省流&#xff1a;一个人也可以住双人间&#xff0c;如果便宜的话。 害&#xff01;尚正值青春年华&#xff0c;黄金岁月&#xff0c;小脑瓜子就已经不灵光咯。好在我在考试的最后一分钟还是成功通过了这题&am…

Jetpack Compose 中的 CompositionLocal

要在可组合函数之间共享数据时&#xff0c;可以通过参数传递显式地调用&#xff0c;这通常是最简单和最好的方式。 但随着参数越来越多&#xff0c;组件也越来越多&#xff0c;并且有些数据还需要保持私有性&#xff0c;这时这种方式就会显得很繁琐臃肿&#xff0c;难以维护。…

vscode插件推荐

文章目录前言一、vscode插件推荐&#xff1f;1、 Chinese (Simplified) (简体中文) Language Pack for Visual Studio Code2、Auto Close Tag3、Auto Import3、Error Lens4、vscode-icons5、ES7 React/Redux/React-Native snippets6、GitLens — Git supercharged7、JavaScript…

【FPGA】Verilog:时序电路应用 | 序列发生器 | 序列检测器

前言&#xff1a;本章内容主要是演示Vivado下利用Verilog语言进行电路设计、仿真、综合和下载 示例&#xff1a;序列发生器与序列检测器 ​ 功能特性&#xff1a; 采用 Xilinx Artix-7 XC7A35T芯片 配置方式&#xff1a;USB-JTAG/SPI Flash 高达100MHz 的内部时钟速度 存储器…

车道线检测CondLaneNet论文和源码解读

CondLaneNet: a Top-to-down Lane Detection Framework Based on Conditional Convolution Paper&#xff1a;https://arxiv.org/pdf/2105.05003.pdf code&#xff1a;GitHub - aliyun/conditional-lane-detection 论文解读&#xff1a; 一、摘要 这项工作作为车道线检测任…

js垃圾回收机制

内存的生命周期 ]S环境中分配的内存&#xff0c;一般有如下生命周期 1.内存分配:当我们声明变量、函数、对象的时候&#xff0c;系统会自动为他们分配内存 2.内存使用:即读写内存&#xff0c;也就是使用变量、函数等 3.内存回收: 使用完毕&#xff0c;由垃圾回收器自动回收不再…

MySQL实战解析底层---事务到底是隔离的还是不隔离的

目录 前言 “快照”在 MVCC 里是怎么工作的&#xff1f; 更新逻辑 前言 讲事务隔离级别的时候提到过&#xff0c;如果是可重复读隔离级别&#xff0c;事务 T 启动的时候会创建一个视图 read-view之后事务 T 执行期间&#xff0c;即使有其他事务修改了数据&#xff0c;事务 T…

​ ​​ ​IIS之FTP服务器 部署 (图文详细) 千锋

目录 概述 部署 步骤&#xff1a; 二重新配置FTP服务器 概述 1、File Transfor Protocol 文件传输协议 2、端口号&#xff1a; TCP 20/21 3、工作方式&#xff1a; 1)主动模式 2&#xff09;被动模式 部署 步骤&#xff1a; 配置静态IP 安装IIS-ftp软件 使用默认站…

学python的第三天---基础(1)

一、圆的面积print("A{:.4f}".format(s))二、两点间的距离![在这里插入图片描述](https://img-blog.csdnimg.cn/0d07c41d856d470796c79067b78c41b6.png)写法一&#xff1a;写法二&#xff1a;三、钞票和硬币写法一&#xff1a;写法二&#xff1a;四、倍数在python中实…

Spring Aware总结

概述 Spring中Aware到底是什么意思&#xff1f; 我们在看Spring源码的时候&#xff0c;经常可以看到xxxAwarexxx的身影&#xff0c;通常我会很疑惑&#xff0c;Aware到底是什么意思呢&#xff1f; 比如图片中这些包含Aware关键字的类或者接口。 我对下面3个类或接口进行了解…

【FMCW 02】测距

承接上篇博文 中频IF信号 &#xff0c;我们已经知道得到的中频IF信号的形式为&#xff1a; xIF(t)A′′cos⁡(2πKτt2πfoτ)x_{\tiny{IF}}(t) A^{\prime \prime} \cos(2\pi K\tau t2\pi f_o \tau ) xIF​(t)A′′cos(2πKτt2πfo​τ) 其中时延τ2dc\tau \frac{2d}{c}τc2…

【数据库】15分钟了解TiDB

由于目前的项目把mysql换成了TiDb&#xff0c;所以特意来了解下tidb。其实也不能说换&#xff0c;由于tidb和mysql几乎完全兼容&#xff0c;所以我们的程序没有任何改动就完成了数据库从mysql到TiDb的转换&#xff0c;TiDB 是一个分布式 NewSQL (SQL 、 NoSQL 和 NewSQL 的优缺…