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

news2024/11/24 15:03:07

在这里插入图片描述

🔭 嗨,您好 👋 我是 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,订阅一波不再迷路

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

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

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

相关文章

论文中公式怎么降重 papergpt

大家好&#xff0c;今天来聊聊论文中公式怎么降重&#xff0c;希望能给大家提供一点参考。 以下是针对论文重复率高的情况&#xff0c;提供一些修改建议和技巧&#xff0c;可以借助此类工具&#xff1a; 论文中公式怎么降重 一、引言 在论文撰写过程中&#xff0c;公式是表达学…

Spring Environment 注入引起NPE问题排查

文章目录 背景原因分析1&#xff09;Spring Aware Bean 是什么&#xff1f;2&#xff09;从 Spring Bean 的生命周期入手 解决方案 背景 写业务代码遇到使用 Spring Environment 注入为 null 的情况&#xff0c;示例代码有以下两种写法&#xff0c;Environment 实例都无法注入…

机器视觉系统选型-图像清晰度

 确定图像中所要检测的部分处于清晰的焦距之内  相机分辨率和镜头解析度较好的匹配&#xff0c;考虑镜头的景深等 TIPS&#xff1a; 每一款镜头都有一定的“焦距”&#xff0c;每一款镜头都有一定光圈范围&#xff0c;也有一定的景深&#xff1b; 缩小光圈可以加大景深&am…

Spring Boot学习随笔- 拦截器实现和配置(HandlerInterceptor、addInterceptors)、jar包部署和war包部署

学习视频&#xff1a;【编程不良人】2021年SpringBoot最新最全教程 第十三章、拦截器 拦截器 &#xff1a;Interceptor 拦截 中断 类似于javaweb中的Filter&#xff0c;不过没有Filter那么强大 作用 Spring MVC的拦截器是一种用于在请求处理过程中进行预处理和后处理的机制。拦…

ssm420基于JavaEE的企业人事管理信息系统的设计与实现论文

摘 要 现代经济快节奏发展以及不断完善升级的信息化技术&#xff0c;让传统数据信息的管理升级为软件存储&#xff0c;归纳&#xff0c;集中处理数据信息的管理方式。本企业人事管理信息系统就是在这样的大环境下诞生&#xff0c;其可以帮助管理者在短时间内处理完毕庞大的数据…

如何本地搭建Splunk Enterprise平台并公网访问管理界面

文章目录 前言1. 搭建Splunk Enterprise2. windows 安装 cpolar3. 创建Splunk Enterprise公网访问地址4. 远程访问Splunk Enterprise服务5. 固定远程地址 前言 Splunk Enterprise是一个强大的机器数据管理平台&#xff0c;可帮助客户分析和搜索数据&#xff0c;以及可视化数据…

如何选择适合的UI自动化测试工具

随着软件开发项目的复杂性增加&#xff0c;UI自动化测试成为确保应用程序质量的关键步骤之一。然而&#xff0c;在选择UI自动化测试工具时&#xff0c;开发团队需要考虑多个因素&#xff0c;以确保选取的工具适用于项目需求并提供可靠的测试结果。 1. 了解项目需求 在选择UI自动…

计算机网络实验速成

目录 网络实验速成 自动连接类型&#xff1a; 指示灯状态说明&#xff1a; 显示接口&#xff1a; 放置注释信息&#xff1a; 配置计算机&#xff1a; 同理&#xff0c;配置服务器&#xff1a; 配置路由器&#xff1a; router0 配置&#xff1a; router1 配置&…

Hardhat环境搭建(六)---无需翻墙

Hardhat环境搭建 官方地址 node环境 npm环境 git环境 安装hardhat npm init npminit是什么 在node开发中使用npm init会生成一个pakeage.json文件&#xff0c;这个文件主要是用来记录这个项目的详细信息的&#xff0c;它会将我们在项目开发中所要用到的包&#xff0c;以…

用户管理第2节课-idea 2023.2 后端一删除表,从零开始---【本人】

一、清空model文件夹下&#xff0c;所有文件 1.1.1效果如下&#xff1a; 1.1代码内容 package com.daisy.usercenter.model;import lombok.Data;Data public class User {private Long id;private String name;private Integer age;private String email; }二、清空mapper文件…

windows下使用gtest

我是在window下使用clion来写c的&#xff0c;最近学习了gtest&#xff0c;中间遇到了一些问题&#xff0c;记录一下。 整体目录 先看一下目录结构 两个测试case&#xff0c;前面就有运行的标志&#xff0c;直接点击就能运行 具体的代码 CMakeLists.txt cmake_minimum_req…

HarmonyOS 学习

语言是 ArkTS UI框架是ArkUI TypeScript 1、基础类型 布尔类型&#xff1a;boolean 浮点型&#xff1a;number 字符串&#xff1a;string 数组&#xff1a;数组&#xff1a;number[] 数组泛型&#xff1a;Array<number> 元组&#xff1a;let x&#xff1a;[strin…

版本化数据库管理工具Flyway介绍和Spring Boot集成使用

文章目录 核心功能如何使用 Flyway最佳实践Spring Boot使用 Flyway 是一个版本化数据库管理工具&#xff0c;用于跟踪、管理和应用数据库的变化。它非常适合在团队开发环境中使用&#xff0c;其中多个人员可能会在数据库结构进行更改。Flyway 通过版本控制可以帮助你确保所有人…

yocto系列讲解[实战篇]93 - 添加Qtwebengine和Browser实例

By: fulinux E-mail: fulinux@sina.com Blog: https://blog.csdn.net/fulinus 喜欢的盆友欢迎点赞和订阅! 你的喜欢就是我写作的动力! 目录 概述集成meta-qt5移植过程中的问题问题1:virtual/libgl set to mesa, not mesa-gl问题2:dmabuf-server-buffer tries to use undecl…

红队打靶练习:DIGITALWORLD.LOCAL: DEVELOPMENT

信息收集 1、arp ┌──(root㉿ru)-[~/kali] └─# arp-scan -l Interface: eth0, type: EN10MB, MAC: 00:0c:29:69:c7:bf, IPv4: 192.168.12.128 Starting arp-scan 1.10.0 with 256 hosts (https://github.com/royhills/arp-scan) 192.168.12.1 00:50:56:c0:00:08 …

Docker 学习总结(80)—— 轻松驾驭容器,玩转 LazyDocker

前言 LazyDocker 是一个用户友好的命令行工具,简化了 Docker 的管理。它能够通过单一命令执行常见的 Docker 任务,如启动、停止、重启和移除容器。LazyDocker 还能轻松查看日志、清理未使用的容器和镜像,并自定义指标。 简绍 LazyDocker 是一个用户友好的 CLI 工具,可以轻…

逻辑回归(LR,Logistic Regression)算法 简介

逻辑回归&#xff08;LR&#xff0c;Logistic Regression&#xff09;算法 当线性回归的预测结果&#xff0c;由于受到个别极端数值的影响而不准的时候, 可以用逻辑回归来解决. 逻辑回归模型的输出只能在 0 到 1 之间&#xff0c;也就是表达一个事件会发生的概率&#xff0c;…

Java---泛型讲解

文章目录 1. 泛型类2. 泛型方法3. 泛型接口4. 类型通配符5. 可变参数6. 可变参数的使用 1. 泛型类 1. 格式&#xff1a;修饰符 class 类名 <类型>{ }。例如&#xff1a;public class Generic <T>{ }。 2. 代码块举例&#xff1a; public class Generic <T>{…

【UML】第10篇 类图(属性、操作和接口)(2/3)

目录 3.3 类的属性&#xff08;Attribute&#xff09; 3.3.1 可见性&#xff08;Visibility&#xff09; 3.3.2 属性的名称 3.3.3 数据类型 3.3.4 初始值 3.3.5 属性字符串 3.4 类的操作&#xff08;Operations&#xff09; 3.4.1 参数表 3.4.2 返回类型 3.5 类的职责…

java并发编程六 共享模型之内存

文章目录 Java 内存模型可见性解决方法 有序性解决方法 Java 内存模型 JMM 即 Java Memory Model&#xff0c;它定义了主存、工作内存抽象概念&#xff0c;底层对应着 CPU 寄存器、缓存、硬件内存、CPU 指令优化等。 JMM 体现在以下几个方面 原子性 - 保证指令不会受到线程上…