概念
零拷贝是一种优化技术,通过减少数据在不同内存区域之间的复制操作,从而提高数据传输效率和降低系统开销。
零拷贝并不是没有拷贝数据,而是减少用户态/内核态的切换次数以及CPU拷贝的次数。
传统IO
流程
- CPU从用户态切换到内核态,DMA控制器将磁盘数据拷贝到内核缓冲区。
- CPU从内核态切换回用户态,复制内核缓冲区数据到用户缓冲区。
- CPU从用户态切换到内核态,复制用户缓冲区数据到套接字缓冲区。
- DMA控制器将套接字缓冲区数据拷贝到网卡,CPU从内核态切换回用户态。
- 总结:上下文切换次数4次,CPU拷贝次数2次,DMA控制器拷贝2次
DMA
在传统的内存拷贝过程中,CPU负责控制数据的传输,首先从源地址读取数据,再将数据写入目标地址。而使用DMA时,数据传输不需要CPU介入,外设通过DMA控制器直接与内存进行数据交换。DMA控制器负责从源设备(如硬盘或网络卡)读取数据并直接写入目标内存,或者将内存中的数据传输到设备。
概述: DMA拷贝通过直接内存访问技术,允许外设和内存之间进行数据传输而
,显著提高了数据传输效率,减少了系统资源的消耗
内核态和用户态
操作系统运行在两种不同的权限级别或工作模式中:用户态和内核态。
- 用户态:应用程序在这种模式下运行,受到操作系统的限制,不能直接访问硬件资源或执行特权操作。
- 内核态:操作系统内核在这种模式下运行,拥有对硬件资源的完全控制权限,可以执行特权操作,如内存管理、I/O控制和中断处理等。
实现方式
mmap+write
流程
- 用户进程调用
mmap
时,CPU从用户态切换到内核态, DMA 控制器将磁盘数据直接拷贝到内核缓冲区。数据加载完成后,操作系统将文件映射到进程的虚拟内存,允许进程直接访问文件内容,CPU从内核态切换到用户态 - 用户进程通过
write
方法向操作系统内核发起IO调用,上下文从用户态切换为内核态,CPU将内核缓冲区的数据拷贝到的socket缓冲区。 - CPU利用DMA控制器,把数据从socket缓冲区拷贝到网卡,上下文从内核态切换回用户态,write调用返回。
- 总结:上下文切换次数4次,CPU拷贝次数1次,DMA控制器拷贝2次
java示例代码
Java NIO有一个MappedByteBuffer
的类,可以用来实现内存映射。它的底层是调用了Linux内核的mmap的API。
import java.io.*;
import java.nio.*;
import java.nio.channels.*;
public class MMapFileCopy {
public static void main(String[] args) throws IOException {
// 定义源文件路径和目标文件路径
try (RandomAccessFile sourceFile = new RandomAccessFile("source.txt", "r");
RandomAccessFile targetFile = new RandomAccessFile("target.txt", "rw")) {
// 获取文件通道
FileChannel sourceChannel = sourceFile.getChannel();
FileChannel targetChannel = targetFile.getChannel();
// 将源文件映射到内存,并将数据写入目标文件
targetChannel.write(sourceChannel.map(FileChannel.MapMode.READ_ONLY,
0, sourceChannel.size()));
}
System.out.println("文件拷贝完成!");
}
}
在 Java 中,使用 java.nio
包中的 FileChannel
和 MappedByteBuffer
类来实现内存映射文件的功能。
关键类与方法:
FileChannel
:用于打开文件并提供对文件的读写操作。MappedByteBuffer
:通过文件映射到内存的缓冲区,提供对文件内容的直接访问。
write
write
是系统调用之一,用于将数据从用户空间写入到文件或设备。它常用于传统的文件 I/O 操作,涉及到内核缓冲区和目标设备的直接数据传输。
ssize_t write(int fd, const void *buf, size_t count);
fd
:文件描述符,指定要写入的目标文件或设备。buf
:指向待写入数据的缓冲区。count
:要写入的字节数。
mmap
当程序调用 mmap
时,操作系统会为该进程分配一段虚拟内存空间,并将文件或设备的内容映射到这段内存中。进程通过访问这块内存,就可以访问映射文件或设备的数据,而不需要进行显式的数据拷贝。
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
addr
:映射的起始地址,通常设置为NULL
,由操作系统自动选择。length
:映射的大小。prot
:内存区域的保护标志,如PROT_READ
(可读),PROT_WRITE
(可写)。flags
:映射类型,如MAP_SHARED
(共享映射),MAP_PRIVATE
(私有映射)。fd
:文件描述符,指定需要映射的文件。offset
:文件的起始,通常是映射的开始位置。
sendfile实现的零拷贝
流程
- 用户进程调用sendfile,CPU从用户态切换为内核态,DMA控制器将磁盘数据拷贝到内核缓冲区
- CPU将数据拷贝到套接字缓冲区 ,DMA控制器将套接字缓冲区数据拷贝到网卡
- CPU从内核态切换为用户态
- 总结:上下文切换次数2次,CPU拷贝次数1次,DMA控制器拷贝2次
sendfile解释
sendfile
是一种系统调用,用于在 内核态 直接将文件从一个文件描述符发送到另一个文件描述符,常用于将文件内容高效地传输到网络套接字或其他设备。它避免了传统 I/O 操作中的多次用户态和内核态之间的上下文切换和数据拷贝,从而提高了数据传输的效率,尤其是在处理大文件时。
Linux2.1内核版本后引入的一个系统调用函数
ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count);
参数说明:
out_fd
:目标文件描述符,通常是一个套接字(例如网络连接的套接字)。in_fd
:源文件描述符,通常是一个文件。offset
:从源文件开始的偏移量。可以为NULL
,如果不指定偏移量,则默认从文件的当前指针开始。count
:要发送的字节数。如果文件的剩余内容小于指定字节数,则sendfile
会尽可能传输更多数据。
返回值:
- 返回实际传输的字节数,如果发生错误,返回
-1
,并设置errno
。
sendfile+DMA实现的零拷贝(优化版本)
在 Linux 2.4 版本之后,sendfile
系统调用经历了一些优化,引入了 SG-DMA(Scatter/Gather DMA) 技术,这大大提高了文件传输的性能。SG-DMA 技术使得 DMA 能够处理 散布(scatter)和聚集(gather) 模式的数据,从而减少了 CPU 的参与,提高了数据传输效率。
流程
- 用户进程发起
sendfile
系统调用,上下文(切换1)从用户态转向内核态。 - DMA 控制器 将数据从磁盘(硬盘)直接拷贝到内核缓冲区。此过程避免了用户空间的拷贝,保持零拷贝特性。
- CPU 通过
sendfile
系统调用,将内核缓冲区中的文件描述符信息(包括内核缓冲区的内存地址和偏移量)提供给网络套接字(socket)缓冲区,准备将数据写入套接字。 - DMA 控制器 根据文件描述符信息,直接将数据从内核缓冲区拷贝到网卡的发送缓冲区(即网络传输的准备阶段)。
- 上下文(切换2) 从内核态切换回用户态,
sendfile
调用返回。 - 总结:上下文切换次数2次,CPU拷贝次数0次,DMA控制器拷贝2次
java示例代码
Java NIO 的 FileChannel
和 SocketChannel
通过 transferTo()
或 write()
方法提供了类似零拷贝的功能。这些方法在底层实现中也可能使用操作系统的零拷贝机制(如 Linux 上的 sendfile
系统调用)来高效地将数据从文件直接传输到网络套接字。
import java.io.*;
import java.nio.*;
import java.nio.channels.*;
import java.net.*;
public class FileTransfer {
public static void main(String[] args) throws IOException {
String sourcePath = "source.txt"; // 源文件路径
String targetHost = "localhost"; // 目标主机
int targetPort = 8080; // 目标端口
// 创建文件通道
RandomAccessFile sourceFile = new RandomAccessFile(sourcePath, "r");
FileChannel fileChannel = sourceFile.getChannel();
// 创建Socket通道
SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress(targetHost, targetPort));
socketChannel.configureBlocking(true);
// 使用 transferTo 将文件内容传输到网络
fileChannel.transferTo(0, fileChannel.size(), socketChannel);
// 关闭通道
fileChannel.close();
socketChannel.close();
System.out.println("File transferred successfully!");
}
}
理解上有什么错误,烦请大家指出!
标签:DMA,文件,技术,内核,缓冲区,拷贝,CPU From: https://blog.csdn.net/m0_46318653/article/details/144408553