안녕하세요 여러분, 저는 Linux 디스크에 있는 데이터 조각입니다. 이제 디스크에서 네트워크 카드로 보내려면 다음 단계를 거쳐야 합니다.
위 그림과 같이 운영 체제는 메모리를 커널 공간과 사용자 공간으로 나눕니다. 공간. 먼저, 사용자 공간의 애플리케이션은 read()
시스템 호출을 시작하는 JVM과 같은 데이터 읽기 작업을 시작합니다. 이때 운영 체제는 read()
系统调用。这个时候操作系统会进行一次上下文切换:从用户空间切换到内核空间。
然后内核空间通知磁盘,内核把我从磁盘copy到内核缓冲区。这个过程是由一个叫“DMA(Direct memory access)”的硬件来做的,所以不需要CPU的参与。
然后内核把我从内核缓冲区copy到应用程序缓冲区,这里需要CPU的参与。
最后进行上下文切换,又换回到用户空间的上下文。
整个Linux 및 Java의 제로 카피에 대해 알아보기的过程需要两次上下文切换和两次copy。
相关学习推荐:Java视频教程
Linux 및 Java의 제로 카피에 대해 알아보기与Linux 및 Java의 제로 카피에 대해 알아보기类似,只是方向相反而已,仍然需要两次上下文切换和两次数据的copy。我可能会被写到磁盘,也可能会被写到网卡。
从上面的过程可以看到,如果想把我从磁盘发送到网卡,需要总共4次上下文切换和4次copy操作。我被操作系统在内核空间和用户空间之间来回复制,但其实我在这期间什么也没有做,什么也没有变化,就是复制而已,所以这个IO模型太浪费操作系统资源了,我被复制这么多次,身心疲惫。而且操作系统的资源是非常宝贵滴~
现在主流的操作系统都使用了虚拟内存。简单来说,就是用虚拟地址取代物理地址,这样做可以让多个虚拟内存只想同一个物理地址,虚拟内存的空间可以远远大于物理内存的空间。
那如果操作系统能够把用户空间的应用程序缓冲区和内核空间的内核缓冲区映射到同一个物理地址,那岂不是就少了很多复制的过程?如下图:
所以为了解决这个问题,聪明的Linux开发者们写了一些新的系统调用来做这个事。主要有两种方式:
mmap()
系统调用首先会使用DMA copy的方式将我从磁盘读取到内核缓冲区,然后通过Linux 및 Java의 제로 카피에 대해 알아보기的方式,使用户缓冲区和内核读缓冲区的内存地址为同一内存地址,也就是说,不需要CPU再将我从内核读缓冲区复制到用户缓冲区啦!
当使用write()
컨텍스트 전환을 수행합니다. 즉, 사용자 공간에서 커널 공간으로 전환합니다.
그러면 커널은 나를 커널 버퍼에서 애플리케이션 버퍼로 복사하는데, 여기에는 CPU의 참여가 필요합니다.
마지막으로 컨텍스트 전환을 수행하고 사용자 공간 컨텍스트로 다시 전환합니다.
🎜🎜🎜쓰기 작업🎜🎜쓰기 작업은 반대 방향을 제외하면 읽기 작업과 유사합니다. 여전히 두 개의 컨텍스트 전환과 두 개의 데이터 복사본이 필요합니다. 디스크에 기록될 수도 있고 네트워크 카드에 기록될 수도 있습니다. 🎜🎜🎜🎜🎜🎜메모리 매핑🎜🎜From 위의 과정을 보면 알 수 있듯이 디스크에서 네트워크 카드로 보내려면 총 4번의 컨텍스트 스위치와 4번의 복사 작업이 필요합니다. 운영 체제에 의해 커널 공간과 사용자 공간 사이를 오가며 복사를 받았지만 사실 이 기간 동안 아무것도 하지 않았고, 아무것도 변경하지 않고 복사만 했을 뿐이어서 이 IO 모델은 운영 체제 리소스를 너무 낭비했고, 너무 많이 복사되어 육체적으로나 정신적으로 지쳤습니다. 게다가 운영 체제의 리소스는 매우 소중합니다~🎜🎜이제 모든 주류 운영 체제는 🎜가상 메모리🎜를 사용합니다. 간단히 말해서 물리적 주소 대신 🎜가상 주소🎜를 사용하면 여러 가상 메모리가 동일한 물리적 주소만 원할 수 있으며 가상 메모리 공간은 물리적 메모리 공간보다 훨씬 커질 수 있습니다. 🎜🎜그러면 운영체제가 사용자 공간의 애플리케이션 버퍼와 커널 공간의 커널 버퍼를 동일한 물리적 주소에 매핑할 수 있다면 많은 복사 과정이 제거되지 않을까요? 아래와 같이: 🎜🎜🎜🎜🎜🎜Linux Zero Copy 🎜🎜이 문제를 해결하기 위해 똑똑한 Linux 개발자는 이를 수행하는 몇 가지 새로운 시스템 호출을 작성했습니다. 두 가지 주요 방법이 있습니다: 🎜관련 학습 권장사항: Java 동영상 튜토리얼
mmap()
시스템 호출은 먼저 DMA 복사를 사용하여 디스크에서 커널 버퍼로 읽은 다음 메모리 매핑을 사용하여 사용자 버퍼와 커널 읽기 버퍼의 메모리 주소를 동일한 메모리 주소로 만듭니다. 즉, CPU가 필요하지 않습니다. 커널 읽기 버퍼에서 사용자 버퍼로 나를 복사하려면! 🎜🎜🎜 write()
시스템 호출을 사용하면 CPU는 커널 버퍼(사용자 버퍼와 동일)에서 전송해야 하는 커널 버퍼(예: 🎜네트워크 전송 버퍼)에 직접 씁니다. 영역(소켓 버퍼) 🎜을 선택한 다음 DMA를 통해 네트워크 카드 드라이버(또는 디스크)에 전달하여 전송을 준비합니다. 🎜🎜🎜🎜🎜Mmap + 쓰기 방법에는 데이터를 읽고 쓰기 위해 총 2개의 시스템 호출, 4개의 컨텍스트 스위치, 2개의 DMA Copy 및 1개의 CPU Copy가 필요합니다. 🎜🎜🎜🎜sendfile🎜🎜sendfile도 시스템 호출이며 기본적으로 위 두 시스템 호출의 기능을 하나의 호출로 결합합니다. 이것의 장점은 운영 체제에 두 개의 컨텍스트 스위치만 필요하므로 두 개의 컨텍스트 스위치로 인한 오버헤드가 줄어든다는 것입니다. 🎜Linux2.4 커널은 sendfile을 최적화하고 수집 작업을 제공합니다. 이 작업은 위 그림에서 마지막 CPU 복사본을 제거할 수 있습니다. 원칙은 데이터를 복사하는 것이 아니라 이전 커널에 데이터를 버퍼링하는 것입니다. 해당 영역(예: 그림의 경우 Read Buffer)의 주소 및 오프셋 레코드를 대상 커널 버퍼(예: 그림의 경우 Socket Buffer)로 전송하여 최종 DMA 복사 단계에서, 이 포인터를 사용하여 데이터 복사본 찾기로 직접 이동할 수 있습니다.
Linux의 제로 복사는 실제로 일부 운영 체제 리소스를 절약할 수 있습니다. 따라서 Java의 NIO는 Zero Copy를 지원하는 몇 가지 클래스를 제공합니다.
이전 기사 "Java NIO - Buffer"에서 DirectByteBuffer에 대해 대략적으로 소개했습니다. ByteBuffer에는 두 가지 주요 구현이 있는데, 하나는 DirectByteBuffer이고 다른 하나는 HeapByteBuffer입니다.
그중 DirectByteBuffer는 힙 외부에서 직접 메모리를 할당하고, 최하위 계층에서는 JNI를 통해 운영체제의 NIO 시스템 호출을 직접 호출하므로 성능이 상대적으로 높을 것입니다. HeapByteBuffer는 힙 내 메모리이고, 데이터를 한 번 더 복사해야 하므로 성능이 상대적으로 낮습니다.
FileChannel
은 파일을 디스크나 네트워크 등에 복사하기 위해 Java NIO에서 제공하는 클래스입니다. FileChannel
是Java NIO提供的用于复制文件的类,可以把文件复制到磁盘或者网络等。
map
方法其实就是采用了操作系统中的Linux 및 Java의 제로 카피에 대해 알아보기方式,将内核缓冲区的内存和用户缓冲区的内存做了一个地址映射。
transferTo
方法直接将当前通道内容传输到另一个通道,也就是说这种方式不会有内核缓冲区到用户缓冲区的读写问题。底层是sendfile系统调用。transferFrom
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);
위 내용은 Linux 및 Java의 제로 카피에 대해 알아보기의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!