Linux传统IO
大家好,我是一段躺在Linux磁盘上的数据。现在要把我从磁盘发到网卡,需要经过以下步骤:
读操作
如上图:操作系统把内存分为了内核空间和用户空间。首先位于用户空间的应用程序使用发起数据读操作,比如JVM发起read()
系统调用。这个时候操作系统会进行一次上下文切换:从用户空间切换到内核空间。
然后内核空间通知磁盘,内核把我从磁盘copy到内核缓冲区。这个过程是由一个叫“DMA(Direct memory access)”的硬件来做的,所以不需要CPU的参与。
然后内核把我从内核缓冲区copy到应用程序缓冲区,这里需要CPU的参与。
最后进行上下文切换,又换回到用户空间的上下文。
整个读操作的过程需要两次上下文切换和两次copy。
相关学习推荐:Java视频教程
写操作
写操作与读操作类似,只是方向相反而已,仍然需要两次上下文切换和两次数据的copy。我可能会被写到磁盘,也可能会被写到网卡。
内存映射
从上面的过程可以看到,如果想把我从磁盘发送到网卡,需要总共4次上下文切换和4次copy操作。我被操作系统在内核空间和用户空间之间来回复制,但其实我在这期间什么也没有做,什么也没有变化,就是复制而已,所以这个IO模型太浪费操作系统资源了,我被复制这么多次,身心疲惫。而且操作系统的资源是非常宝贵滴~
现在主流的操作系统都使用了虚拟内存。简单来说,就是用虚拟地址取代物理地址,这样做可以让多个虚拟内存只想同一个物理地址,虚拟内存的空间可以远远大于物理内存的空间。
那如果操作系统能够把用户空间的应用程序缓冲区和内核空间的内核缓冲区映射到同一个物理地址,那岂不是就少了很多复制的过程?如下图:
Linux零拷贝
所以为了解决这个问题,聪明的Linux开发者们写了一些新的系统调用来做这个事。主要有两种方式:
- mmap + write
- sendfile
mmap + write
mmap()
系统调用首先会使用DMA copy的方式将我从磁盘读取到内核缓冲区,然后通过内存映射的方式,使用户缓冲区和内核读缓冲区的内存地址为同一内存地址,也就是说,不需要CPU再将我从内核读缓冲区复制到用户缓冲区啦!
当使用write()
系统调用的时候,CPU将我从内核缓冲区(等同于用户缓冲区)直接写入到需要发送的内核缓冲区,比如网络发送缓冲区(socket buffer),然后通过DMA的方式将我传入到网卡驱动程序(或磁盘)中准备发送。
mmap + write的方式读写数据总共需要两次系统调用,4次上下文切换,2次DMA Copy和1次CPU Copy。
sendfile
sendfile也是一个系统调用,它其实本质上就是把上述两个系统调用的功能合起来,变成了一个调用。这样做的好处是,操作系统只需要2次上下文切换了,减少了2次上下文切换的开销。
gather
Linux2.4内核对sendfile进行了优化,提供了gather操作,这个操作可以把上图中的最后一次CPU copy去掉,原理就是不复制数据,而是把数据在之前的内核缓冲区(比如图中的案例是Read Buffer)的内存地址、偏移量记录发送给目标内核缓冲区(比如图中案例的Socket Buffer),这样在最后的DMA copy阶段就可以拿着这个指针直接去找数据copy了。
Java NIO使用零拷贝
Linux的零拷贝确实能够节约一些操作系统的资源。所以Java的NIO为了支持零拷贝,提供了一些类:
- DirectByteBuffer
- FileChannel
在之前的《Java NIO - Buffer》这篇文章里大概介绍了DirectByteBuffer。ByteBuffer主要有两种实现,一种是DirectByteBuffer, 一种是HeapByteBuffer。
其中,DirectByteBuffer直接在堆外分配内存,底层是直接通过JNI调用操作系统的NIO系统调用,所以性能会比较高。而HeapByteBuffer是堆内内存,而且数据需要多一次拷贝,所以性能比较低。
FileChannel
是Java NIO提供的用于复制文件的类,可以把文件复制到磁盘或者网络等。
map
方法其实就是采用了操作系统中的内存映射方式,将内核缓冲区的内存和用户缓冲区的内存做了一个地址映射。
transferTo
方法直接将当前通道内容传输到另一个通道,也就是说这种方式不会有内核缓冲区到用户缓冲区的读写问题。底层是sendfile系统调用。transferFrom
方法同理。
示例代码:
File file = new File("test.txt");RandomAccessFile raf = new RandomAccessFile(file, "rw");FileChannel fileChannel = raf.getChannel();SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("", 8080));// 直接使用了transferTo()进行通道间的数据传输fileChannel.transferTo(0, fileChannel.size(), socketChannel);
作者:公众号_xy的技术圈
链接:www.imooc.com/article/289550
来源:慕课网
以上内容来自幕课网
零拷贝的再次理解
零拷贝,是从操作系统的角度来说的。因为内核缓冲区之间,没有数据是重复的(只有 kernel buffer 有一份数据)。
零拷贝不仅仅带来更少的数据复制,还能带来其他的性能优势,例如更少的上下文切换,更少的 CPU 缓存伪共享以及无 CPU 校验和计算。
mmap和sendFile的区别
mmap 适合小数据量读写,sendFile 适合大文件传输。
mmap 需要 4 次上下文切换,3 次数据拷贝;sendFile 需要 3 次上下文切换,最少 2 次数据拷贝。
sendFile 可以利用 DMA 方式,减少 CPU 拷贝,mmap 则不能(必须从内核拷贝到 Socket 缓冲区)。
以上是了解Linux 和 Java 的零拷贝的详细内容。更多信息请关注PHP中文网其他相关文章!

本文讨论了使用Maven和Gradle进行Java项目管理,构建自动化和依赖性解决方案,以比较其方法和优化策略。

本文使用Maven和Gradle之类的工具讨论了具有适当的版本控制和依赖关系管理的自定义Java库(JAR文件)的创建和使用。

本文讨论了使用咖啡因和Guava缓存在Java中实施多层缓存以提高应用程序性能。它涵盖设置,集成和绩效优势,以及配置和驱逐政策管理最佳PRA

本文讨论了使用JPA进行对象相关映射,并具有高级功能,例如缓存和懒惰加载。它涵盖了设置,实体映射和优化性能的最佳实践,同时突出潜在的陷阱。[159个字符]

Java的类上载涉及使用带有引导,扩展程序和应用程序类负载器的分层系统加载,链接和初始化类。父代授权模型确保首先加载核心类别,从而影响自定义类LOA


热AI工具

Undresser.AI Undress
人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover
用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool
免费脱衣服图片

Clothoff.io
AI脱衣机

AI Hentai Generator
免费生成ai无尽的。

热门文章

热工具

Dreamweaver CS6
视觉化网页开发工具

螳螂BT
Mantis是一个易于部署的基于Web的缺陷跟踪工具,用于帮助产品缺陷跟踪。它需要PHP、MySQL和一个Web服务器。请查看我们的演示和托管服务。

DVWA
Damn Vulnerable Web App (DVWA) 是一个PHP/MySQL的Web应用程序,非常容易受到攻击。它的主要目标是成为安全专业人员在合法环境中测试自己的技能和工具的辅助工具,帮助Web开发人员更好地理解保护Web应用程序的过程,并帮助教师/学生在课堂环境中教授/学习Web应用程序安全。DVWA的目标是通过简单直接的界面练习一些最常见的Web漏洞,难度各不相同。请注意,该软件中

MinGW - 适用于 Windows 的极简 GNU
这个项目正在迁移到osdn.net/projects/mingw的过程中,你可以继续在那里关注我们。MinGW:GNU编译器集合(GCC)的本地Windows移植版本,可自由分发的导入库和用于构建本地Windows应用程序的头文件;包括对MSVC运行时的扩展,以支持C99功能。MinGW的所有软件都可以在64位Windows平台上运行。

SecLists
SecLists是最终安全测试人员的伙伴。它是一个包含各种类型列表的集合,这些列表在安全评估过程中经常使用,都在一个地方。SecLists通过方便地提供安全测试人员可能需要的所有列表,帮助提高安全测试的效率和生产力。列表类型包括用户名、密码、URL、模糊测试有效载荷、敏感数据模式、Web shell等等。测试人员只需将此存储库拉到新的测试机上,他就可以访问到所需的每种类型的列表。