深入理解网络 I/O:mmap、sendfile、Direct I/O

在这里插入图片描述

🔭 嗨,您好 👋 我是 vnjohn,在互联网企业担任 Java 开发,CSDN 优质创作者
📖 推荐专栏:Spring、MySQL、Nacos、Java,后续其他专栏会持续优化更新迭代
🌲文章所在专栏:网络 I/O
🤔 我当前正在学习微服务领域、云原生领域、消息中间件等架构、原理知识
💬 向我询问任何您想要的东西,ID:vnjohn
🔥觉得博主文章写的还 OK,能够帮助到您的,感谢三连支持博客🙏
😄 代词: vnjohn
⚡ 有趣的事实:音乐、跑步、电影、游戏

目录

  • 前言
  • mmap
    • 实现机制
    • 图解分析
    • 缺点
  • sendfile
    • 实现机制
    • 图解分析
    • 使用
    • 缺点
  • Direct I/O
    • 实现机制
    • 缺点
  • 总结

前言

在上一篇文章介绍以下三个类的特征及使用:

深入理解网络 I/O:FileOutputStream、BufferFileOutputStream、ByteBuffer

在 ByteBuffer 中围绕三个子类进行了展开:HeapByteBuffer、MappedByteBuffer、DirectByteBuffer

HeapByteBuffer 使用的是 JVM 堆内的内存进行文件 I/O 操作
DirectByteBuffer 使用的 Java 进程内的堆内存进行文件 I/O 操作
MappedByteBuffer 使用的是用户空间与内核空间之间映射出一块内存区域进行文件 I/O 操作

同时,MappedByteBuffer 也是作为了 DirectByteBuffer 的父类,这两者并没有直接的继承关系,都只是作为 ByteBuffer 类的不同实现

MappedByteBuffer 在操作系统内核中使用的 mmap 函数进行了用户空间与内核空间之间的虚拟内存区域映射的,采用此方式可以减少用户态和内核态之间的拷贝次数以及上下文切换次数

本文还会介绍 sendfile 函数、Direct I/O 的作用以及应用场景的区别.

mmap

void *mmap(void *addr, size_t length, int prot, int flags,
                  int fd, off_t offset);

mmap(Memory-mapped Files)是一种操作系统提供的机制,允许将文件的一部分或全部内容直接映射到进程的虚拟地址空间中,这种技术使得文件内容可以被视为内存的一部分,从而实现了文件与内存之间的无缝衔接

将内核态与用户态内存映射在一起,避免来回的拷贝,采用指针的方式读写操作一段内存,即完成了对文件的操作而不必再调用 read、write 等系统调用函数

实现机制

mmap 在 Java 中基于 MappedByteBuffer 类实现,它是 Java NIO 中用于内存映射文件(Memory-mapped Files)的一种缓冲区,它的实现机制涉及内存映射文件的操作和底层操作系统的支持

1、MappedByteBuffer 使用操作系统提供的内存映射文件机制,将文件的部分或全部内容直接映射到进程的虚拟内存空间

2、通过 FileChannel#map 方法可以创建一个 MappedByteBuffer 对象,操作系统会为文件的指定区域分配虚拟内存地址,文件与虚拟内存地址建立映射关系

3、一旦文件映射到内存,通过 MappedByteBuffer 提供的方法可以直接访问文件内容,读取或写入 MappedByteBuffer 中的数据,实际上是在修改虚拟内存中的数据,而不是直接对文件进行 I/O 操作

4、MappedByteBuffer 的修改可以自动同步到底层文件系统,或者手动调用 force 方法强制将修改的内容刷写到磁盘中的文件.

图解分析

在这里插入图片描述

如上图,使用了 mmap 基于用户态、内核态共享一块虚拟内存区域的情况下,用户态、内核态来回切换的方式就减少了,比如:客户端要读取服务端的数据时,可以直接读取内存区域的数据,无须再切换为用户态进行数据拷贝,这就是避免了切换的次数也就是数据拷贝的次数,意义上的零拷贝

虽然 mmap 为应用程序与操作系统减少了负担,但也会带来一些问题,因为这块虚拟内存区域是基于操作系统的页缓存 page cache 机制实现的,换言之,基于此,它会有丢失数据的风险

关于 page cache 介绍可以阅读博主的另外一篇文章:

深入了解 Linux PageCache 页缓存:优化文件系统的性能、效率

缺点

mmap 带有不好的地方有几点,如下:

  1. mmap 在使用时必须指定好内存映射的大小,它不适合于变长的文件,若映射的文件过大,会消耗大量的内存,内存消耗的增加可能限制了程序的并发性,特别是当多个进程都需要映射大型文件时
  2. 对映射区域的写入操作会异步将修改的内容刷写到磁盘,若系统崩溃或发生断电宕机的情况下,部分尚未同步到磁盘的数据会丢失导致数据不一致问题
  3. 不适合随机访问大文件,因为它是虚拟内存映射的,并不是物理上的,还需要经过大量的分段分页寻址的过程,加载大文件时就需要较长的时间
  4. 不适用所有的场景,对于一次性操作或少量数据访问的场景,根本没必要使用到 mmap

sendfile

它应用在基于网络 I/O 文件描述符之间传输数据.

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

sendfile 在一个文件描述符与另外一个文件描述符之间复制数据,这种复制的操作是之间内核态完成的,无须进行用户态与内核态之间切换
sendfile 对比于 read、write 组合更有效,后者需要在用户空间与内核之间进行切换后传输数据

in_fd:为读打开的文件描述符
out_fd:为写打开的文件描述符

sendfile 是一个系统调用,它基于操作系统内核提供的特性实现数据传输,具体的说:sendfile 利用操作系统的零拷贝机制进行文件数据传输

实现机制

sendfile 实现的主要机制包括:DMA、内核缓冲区、传输描述符、零拷贝机制

1、sendfile 利用 DMA 中断技术,使得传输数据可以直接在设备(磁盘)与内存之间进行,无需 CPU 的参与,它允许设备直接访问系统内存,从而避免了数据从外设到 CPU 再到内核的复制过程

2、sendfile 利用操作系统内核中的内核缓冲区,在内核空间中暂存待传输的数据,数据从文件描述符对应的内核缓冲区出直接传输到另一个文件描述符的内核缓冲区,无须用户空间的参与

3、sendfile 通过传输文件描述符(Socket)实现数据的直接传输,内核负责将文件描述符所指向的数据通过网络传输到另一端,而无需将数据从内核空间复制到用户空间再进行传输

4、sendfile 利用零拷贝的特性,尽量减少了数据的复制,避免了数据在内核空间和用户空间之间的多次复制;数据从一个内核缓冲区到另外一个内核缓冲区,减少了不必要的数据拷贝过程

若只是传输数据,并不对数据作任何处理,譬如服务器存储的静态文件,入:html、css、js 发送客户端用于浏览器渲染,在这种场景下,若依然进行多次的数据拷贝和上下文切换,简直是丧心病狂!这种情况下就可以使用 sendfile 的方式,只做文件传输,而不经过用户态进行干预

图解分析

在这里插入图片描述

如上图,通过用户态调用 sendfile,让内核将数据进行文件描述符之间的拷贝,而无须用户态的参与,直接能让客户端与服务端完成数据的传输

数据拷贝 3 次:设备*(磁盘) —> 内核 —> Socket
上下文切换 2 次:一次 —> 用户态—内核态、一次 —> 内核态—设备

使用

在 Java 应用程序使用 sendfile,涉及到的关键类仍然是上一篇提及到的 FileChannel,它里面提供了两个方法:

1、FileChannel#transferTo:将指定字节数从该 channel 的文件传输到给定可写的 channel 中

public abstract long transferTo(long position, long count, 
								WritableByteChannel target)

2、FileChannel#transferFrom:将指定字节数从可读的 channel 中传输到该 channel 的文件中

public abstract long transferFrom(ReadableByteChannel src,
                                  long position, long count)

通过以下源代码来测试模拟 sendfile 写入文件内容和从文件进行内容的读取

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.FileChannel;
import java.nio.channels.SocketChannel;

/**
 * @author vnjohn
 * @since 2023/12/21
 */
public class SendfileIO {
    static String SOURSE_PATH = "/opt/io/sendfile/source.txt";
    static String TARGET_PATH = "/opt/io/sendfile/target.txt";

    public static void main(String[] args) {

        switch (args[0]) {
            case "0":
                transferTo();
                break;
            case "1":
                transferFrom();
                break;
        }
    }
    
    public static void transferTo() {
        String host = "172.16.249.10";
        int port = 8090;
        try (SocketChannel socketChannel = SocketChannel.open();
             FileInputStream fileInputStream = new FileInputStream(SOURSE_PATH);
             FileChannel fileChannel = fileInputStream.getChannel()) {
            socketChannel.connect(new InetSocketAddress(host, port));
            // 将文件内容直接读取到 SocketChannel(模拟 sendfile)
            fileChannel.transferTo(0, fileChannel.size(), socketChannel);
            System.in.read();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    
    public static void transferFrom() {
        try (FileInputStream fis = new FileInputStream(SOURSE_PATH);
             FileOutputStream fos = new FileOutputStream(TARGET_PATH);
             FileChannel sourceChannel = fis.getChannel();
             FileChannel targetChannel = fos.getChannel()) {
            targetChannel.transferFrom(sourceChannel, 0, sourceChannel.size());
            System.in.read();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

在 /opt/io/sendfile 目录下新建一个文件内容为:Hello 的 source.txt 文件、一个文件内容为:vnjohn 的 target.txt 文件

然后通过 nc -l localhost 8090 开启一个服务端

将以上的代码进行编译依次运行运行:

1、strace -ff -o sendfile java SendfileIO 0
2、strace -ff -o sendfile java SendfileIO 1

当执行第一条命令时,服务端窗口输出如下:

在这里插入图片描述

strace 日志输出调用了 sendfile 函数:sendfile(4, 5, [0] => [7], 7)

当执行第二条命令时,文件 target.txt 的内容直接就是 source.txt 的内容了.

缺点

1、仅适用于网络传输,sendfile 主要将文件内容发送到网络套接字中,因此它的应用范围有限,不能用于一般的文件读写操作

2、不支持数据修改,sendfile 通常用于只读操作,无法在传输过程中修改数据

Direct I/O

之前的 mmap 可以让用户态与内核态共用一个内存空间来减少拷贝,还有一种方式就是硬件数据不经过内核态的空间,直接到用户态的内存中,这种方式就是 Direct I/O.

Direct I/O 不会经过内核,而是用户态与设备的直接交互,用户态的写入就是直接写入磁盘,不会再经过操作系统进行刷盘处理

实现机制

Direct I/O 实际上是指使用 readwrite 等系统调用以及相关的文件描述符在用户空间和设备之间直接进行数据传输,绕过了内核的缓冲区 page cache

在 Linux 等系统中,Direct I/O 可以通过系统调用 open 时使用 O_DIRECT 标志来实现,这样可以告诉操作系统绕过缓存,直接将数据传输到磁盘上

int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);

flags:标志包含了O_SYNC、O_APPEND、O_ASYNC、O_CREAT、O_PATH、 O_DIRECT (Since Linux 2.4.10)

采用此方式可以尽量减少进出该文件到 I/O 缓存效果,一般这种会降低性能,但在特殊情况下很有用,既然绕过了内核的缓冲区,应用程序自身就要维护缓存,文件 I/O 直接进出用户空间缓冲区,O_DIRECT 标志本身致力于同步传输数据

mode 参数标志必须包含以下几种访问模式之一:O_RDONLY、O_WRONLY 或 O_RDWR,这三种方式分别要求打开只读、只写或读/写文件

对于 Direct I/O 的实现机制,主要涉及以下几点:

  1. 文件描述符标志 — O_DIRECT:使用 O_DIRECT 标志可以告知操作系统进行 Direct I/O,绕过内核缓冲区
  2. 用户空间和设备直接传输:在 Direct I/O 中,数据直接在用户空间的应用程序缓冲区和设备或文件之间进行传输,绕过了内核缓冲区的中间步骤
  3. 适用性和限制:Direct I/O 适用于某些特定的场景,例如数据库 MySQL、文件传输等需要高性能和低延迟的应用,然而它也存在一些限制和特殊情况,比如一些文件系统不支持 Direct I/O,或者需要特定的对齐等

缺点

1、性能波动,对于小文件和随机访问,Direct I/O 性能可能不如预期,因为 Direct I/O 一般更适用于大文件和顺序读写,而在小文件或随机访问的情况下,由于额外的处理和数据对齐要求,性能可能不稳定或下降

Kafka 中使用了 sendfile 作为顺序读写的操作,后续在 Kafka 专栏展开说说

2、对齐要求:Direct I/O 可能对数据的对齐有一定的要求,如果数据没有按照特定的方式传输,可能会导致性能下降,因此对于一些应用来说,确保对齐可能额外的处理

3、由于 Direct I/O 是用户空间与磁盘设备之间直接交互的,所以会忽略 Linux page cache,由应用程序自身来花费空间来维护缓存以及数据一致性问题、Dirty 脏刷写等一系列复杂的问题.

总结

在这里插入图片描述

如上图,性能对比

JVM 堆 < Java 进程堆 < MappedByteBuffer

该篇博文围绕 mmap、sendfile、Direct I/O 进行了技术点的展开讲解,mmap 由 FileChannel#map 映射出一个 MappedByteBuffer(应用空间与内核空间共享一块内存区域,不会触发系统调用)它适用于文件操作 I/O;sendfile 通过一次系统调用以后,它会在内核态完成数据的拷贝过程,无须用户态的参与,它适用于网络传输;Direct I/O 是由用户空间直接与磁盘设备之间交互,无须内核态的参与,它交由用户程序自身来维护缓存以及数据一致性、Dirty 等问题,希望博文你能够喜欢,感谢三连支持❤️

mmap:由用户空间与内核空间共享同一块内存区域,用户空间对其进行操作同样反映到内核空间,内核空间对其进行操作同样反映到用户空间
sendfile:由用户空间触发一次系统调用后,数据的拷贝过程由内核自身来完成,从一个 Socket Buffer 缓冲区拷贝到另外一个 Socket Buffer 缓冲区
Direct I/O:由用户空间与磁盘设备之间直接交互,无须内核态的参与

mmap、sendfile 依然绕不开内核的 page cache 体系,它基于内存,在极端情况下仍然会丢失数据.

🌟🌟🌟愿你我都能够在寒冬中相互取暖,互相成长,只有不断积累、沉淀自己,后面有机会自然能破冰而行!

博文放在 网络 I/O 专栏里,欢迎订阅,会持续更新!

如果觉得博文不错,关注我 vnjohn,后续会有更多实战、源码、架构干货分享!

推荐专栏:Spring、MySQL,订阅一波不再迷路

大家的「关注❤️ + 点赞👍 + 收藏⭐」就是我创作的最大动力!谢谢大家的支持,我们下文见!

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

scrapy_redis概念作用和流程

scrapy_redis概念作用和流程 学习目标 了解 分布式的概念及特点了解 scarpy_redis的概念了解 scrapy_redis的作用了解 scrapy_redis的工作流程 在前面scrapy框架中我们已经能够使用框架实现爬虫爬取网站数据,如果当前网站的数据比较庞大, 我们就需要使用分布式来更快的爬取数…

PDF文件如何设置限制打印?

想要限制PDF文件的打印功能&#xff0c;想要限制PDF文件打印清晰度&#xff0c;都可以通过设置限制编辑来达到目的。 打开PDF编辑器&#xff0c;找到设置限制编辑的界面&#xff0c;切换到加密状态&#xff0c;然后我们就看到 有印刷许可。勾选【权限密码】输入一个PDF密码&am…

FPGA——XILINX原语(1)

FPGA——XILINX原语&#xff08;1&#xff09; 1.时钟组件&#xff08;1&#xff09;BUFG&#xff08;2&#xff09;BUFH&#xff08;3&#xff09;BUFR&#xff08;4&#xff09;BUFIO&#xff08;5&#xff09;使用场景 2.IO端口组件&#xff08;1&#xff09;IDDR&#xff0…

3. 行为模式 - 迭代器模式

亦称&#xff1a; Iterator 意图 迭代器模式是一种行为设计模式&#xff0c; 让你能在不暴露集合底层表现形式 &#xff08;列表、 栈和树等&#xff09; 的情况下遍历集合中所有的元素。 问题 集合是编程中最常使用的数据类型之一。 尽管如此&#xff0c; 集合只是一组对象的…

flink watermark 实例分析

WATERMARK 定义了表的事件时间属性&#xff0c;其形式为: WATERMARK FOR rowtime_column_name AS watermark_strategy_expression rowtime_column_name 把一个现有的列定义为一个为表标记事件时间的属性。该列的类型必须为 TIMESTAMP(3)/TIMESTAMP_LTZ(3)&#xff0c;且是 sche…

【让云服务器更灵活】iptables转发tcp/udp端口请求

iptables转发tcp/udp端口请求 文章目录 前言一、路由转发涉及点二、转发如何配置本机端口转发到本机其它端口本机端口转发到其它机器 三、固化iptables总结 前言 路由转发是计算机网络中的一种重要概念&#xff0c;特别是在网络设备和系统之间。它涉及到如何处理和传递数据包&…

【湖仓一体尝试】MYSQL和HIVE数据联合查询

爬了两天大大小小的一堆坑&#xff0c;今天把一个简单的单机环境的流程走通了&#xff0c;记录一笔。 先来个完工环境照&#xff1a; mysqlhadoophiveflinkicebergtrino 得益于IBM OPENJ9的优化&#xff0c;完全启动后的内存占用&#xff1a; 1&#xff09;执行联合查询后的…

《A++ 敏捷开发》-1 如何改善

1 如何改善 敏捷开发过程改进案例 5月 A公司一直专门为某电信公司提供针对客服、线上播放等服务。 张工是公司的中层管理者&#xff0c;管理好几个开发团队&#xff0c;有5位项目经理向他汇报。 他听说老同学的团队都开始用敏捷开发&#xff0c;很感兴趣&#xff0c;便参加了…

YACS(上海计算机学会竞赛平台)三星级挑战——两数之和

题目描述 给定 n 个整数 a[1]​,a[2]​,⋯,a[n]​&#xff0c;并且保证 a[1​]≤a[2​]≤⋯≤a[n]​ 再给定一个目标值 t&#xff0c;请判断能否找到 a[i]​ 与 a[j]​&#xff0c;ai​aj​t 且 i≠j。 输入格式 第一行&#xff1a;单个整数n&#xff1b; 第二行&#xf…

油猴脚本教程案例【键盘监听】-编写 ChatGPT 快捷键优化

文章目录 1. 元数据1. name2. namespace3. version4. description5. author6. match7. grant8. icon 2. 编写函数.1 函数功能2.1.1. input - 聚焦发言框2.1.2. stop - 取消回答2.1.3. newFunction - 开启新窗口2.1.4. scroll - 回到底部 3. 监听键盘事件3.1 监听X - 开启新对话…

3D模型人物换装系统(二 优化材质球合批降低DrawCall)

3D模型人物换装系统 介绍原理合批材质对比没有合批材质核心代码完整代码修改总结 介绍 本文使用2018.4.4和2020.3.26进行的测试 本文没有考虑法线贴图合并的问题&#xff0c;因为生成法线贴图有点问题&#xff0c;放在下一篇文章解决在进行优化 如果这里不太明白换装的流程可以…

Python---socket之send和recv原理剖析

1. 认识TCP socket的发送和接收缓冲区 当创建一个TCP socket对象的时候会有一个发送缓冲区和一个接收缓冲区&#xff0c;这个发送和接收缓冲区指的就是内存中的一片空间。 2. send原理剖析 send是不是直接把数据发给服务端? 不是&#xff0c;要想发数据&#xff0c;必须得…

STL--stack、queue实现

STL中&#xff0c;vector、list 是容器&#xff0c;自己存储一系列的数据进行增删查改&#xff0c;而 stack、queue 是一种特殊的容器&#xff0c;叫容器适配器&#xff0c;提供一种特定的接口来访问底层容器。 STL--stack实现 template<class T, class Container deque&…

Springboot实现定时任务

一、定时任务是什么&#xff1f; 定时执行任务&#xff0c;只有电脑不关机就可以在特定的时间去执行相应的代码&#xff0c;例如抢购脚本等 二、使用步骤 1.无需引入springboot自带 package com.ltx.blog_ltx;import org.springframework.boot.SpringApplication; import o…

2023 年人工智能研究与技术排名前 10 的国家

人工智能研究是一项全球性的工作。虽然美国和中国因其对人工智能的贡献而备受关注&#xff0c;但事实是&#xff0c;世界各国都在涉足这项技术&#xff0c;尝试新的突破&#xff0c;并吸引投资者的关注。 斯坦福大学的《2023年人工智能报告》估计&#xff0c;到 2022 年&#…

hbase用shell命令新建表报错ERROR: KeeperErrorCode = NoNode for /hbase/master

或者HMster开启后几秒消失问题解决 报错如图&#xff1a; 首先jps命令查看当前运行的内容有没有HMaster,如果没有&#xff0c;开启一下hbase,稍微等一会儿&#xff0c;再看一下HMaster,如果仍和下图一样没有&#xff0c;就基本找到问题了 本人问题原因&#xff1a;hbase-site…

嵌入式中断理解

一、概念 中断&#xff1a; 在主程序运行过程中&#xff0c;出现了特定的中断触发条件&#xff08;中断源&#xff09;&#xff0c;使得CPU暂停当前正在运行的程序&#xff0c;转而去处理中断程序&#xff0c;处理完成后又返回原来被暂停的位置继续运行。 中断优先级&#x…

AIGC绘画Midjourney光线关键词、构图关键词、色调关键词

Unity3D特效百例案例项目实战源码Android-Unity实战问题汇总游戏脚本-辅助自动化Android控件全解手册再战Android系列Scratch编程案例软考全系列Unity3D学习专栏蓝桥系列ChatGPT和AIGC &#x1f449;关于作者 专注于Android/Unity和各种游戏开发技巧&#xff0c;以及各种资源分…

树莓派,opencv,Picamera2利用舵机云台追踪特定颜色对象(PID控制)

一、需要准备的硬件 Raspiberry 4b两个SG90 180度舵机&#xff08;注意舵机的角度&#xff0c;最好是180度且带限位的&#xff0c;切勿选360度舵机&#xff09;二自由度舵机云台&#xff08;如下图&#xff09;Raspiberry CSI 摄像头 组装后的效果&#xff1a; 二、项目目标…

STM32 使用ARM仿真器设置

STM32单片机程序下载到单片机芯片中有两种方式&#xff0c;①编译生成HEX&#xff0c;使用程序烧录软件刷到单片机芯片里。②使用ARM仿真器下载程序。使用ARM仿真器的优势是&#xff0c;在工程编译没问题直接在Keil软件里就可以将程序下载到单片机里&#xff0c;并且程序可以在…