IO与零拷贝
零拷贝基本介绍
- 零拷贝时网络编程的一个关键优化点
- 在Java程序中,常用的零拷贝又mmap(内存映射)和sendFile。那么在OS中的设计时如何,我们需分析mmap和sendFile对比
- 最后通过案例分析
用户进程与操作系统关系
- 我们先看一张图解释“用户进程和操作系统的关系”
- 如上计算机系统运行时候的简化说明,在操作系统中,有用户空间,内核空间,
- 运行在操作系统傻姑娘的进程大多是用户进程,运行在用户空间,在资源管理器里面可以看得到
- 吧操作系统运行的空间成为系统空间
- 为何划分用户进程和系统进程,我们需要先解释内核态(Kernel mode)和用户态(user mode),内核态可以访问系统资源,比如:
- 处理器CPU
- 输入输出IO,包括硬盘,内存,终端等
- 进程管理
- 内存
- 设备:键盘鼠标啥的
- 等
- 以上资源都是用户进程无法直接访问。只能通过操作系统去访问,所以也罢操作系统提供的这些功能称为“系统调用”,我们文件读写,和终端控制都是通过内核进行的。
用户进程缓冲区
- 按照以上的设计,用户进程访问系统资源需要切换到内核态,这对于的一下特殊的内存环境,必须在系统调用之前建立好,而在系统调用结束后,CPU会从内核模式切换到用户模式,而且对照又必须回复成用户进程的上下文,这种切换就会有大量的耗时。
- 因此我们在程序读取文件的时候,会先申请一块内存,成为buffer,每次调用read读取指定字节长度的数据,写入buffer。之后的程序都从buffer中获取数据。当buffer用完后,在进行下一次调用,填充buffer。
- 所以说,用户缓冲区目的时为了减少系统调用次数,从而减低操作系统在用户态和核心态切换的时间。如下图对比
内核缓冲区
- 内核也有自己的缓冲区
- 当用户进程要从磁盘读数据,内核一般步直接读磁盘,而是将CIO缓冲区中数据复制到进程缓冲区
- 单如果内核缓冲区中没用数据,内核会吧对数据块的请求加入到请求队列,然后吧进程挂起,为其他进程提供服务。
- 等数据一句读取到内核缓冲区时候,内核缓冲区中数据读取到用户进程,才会通知进程。
- 此处read是吧数据从高内核缓冲区复制到进程缓冲区。write是吧进程缓冲区复制到内核缓冲区。
- 因此内核缓冲区是为了在OS级别,提高磁盘IO的效率,优化磁盘读写操作。
传统IO模型内存拷贝
-
流程如下:
- DMA拷贝,从磁盘到内核,direct memory access 直接内存拷贝 不使用CPU
- CPU将内核buffer 中的数据拷贝到 用户buffer
- 接着通过socket传递,还是user buffer 拷贝到内核中的socket buffer
- 最后从内核socket buffer 到引擎,就是对应的协议比如TCP/IP
-
如上流传中
- 状态切换3 次
- 拷贝4次
mmap优化
- mmap优化通过内存映射的方式,将文件映射到内核缓冲区,同时用户空间可以贡献内核空间的数据,这样,在进行网络传输的时候,减少了内核空间到用户空间的拷贝
- mmap的优化中
- 状态切换3次
- 数据拷贝3次 比传统IO的形式少了一次拷贝
sendFile 优化
- Linux 2.1版本提供了sendFile函数,基本原理如上图,数据根本不进过用户态,直接从高内核缓冲区到SocketBuffer,同时由于和用户态没关系,减少了一次上下文切换
- sendFile的优化中
- 上下文切换2次 比mmap还少了一次上下文切换
- 数据拷贝3 次 与mmap一样
Linux 2.4 的sendFile
- linux 在2.4 版本中,做了一些修改,避免了从内核缓冲区拷贝到Socketbuffer的操作,直接拷贝到协议栈,从而再次减少了一次数据拷贝
- 入上图中,其实只有一次CPU拷贝, kernel buffer —> socket buffer ,但是拷贝的信息不多,比如length,offset 等消耗低可以忽略
- 优化后的sendFile
- 上下文切换2次
- 数据拷贝 2 次比2.1版本的sendFile 还少一次拷贝
零拷贝的理解
- 零拷贝从操作系统角度看是没用CPU拷贝,而不是一次拷贝都没有,可见在以上几个IO优化中mmap, 2.1版本的sendFile, 2.4版本的sendFile中,只有2.4版本的sendFile实现正在意义上的零拷贝
- 从操作系统角度看,内核缓冲区之间没有数据是重复的(只有kernel buffer 一份数据),以上kernel buffer 到 socket buffer的复制都是在内核中,他们是共享的一块内存,只是同步数据的数据同步了一个offset和length
- 零拷贝不仅带来更少的数据复制,还带来了其他性能的优势,例如更少的上下文切换,更少的CPU缓存伪共享以及无CPU校验和计算
mmap和 sendFile的区别
- mmap 适合小数据量的读写, sendFile适合大文件的传输
- mmap需要3次上下文切换,3次拷贝,sendFile需要 2次上下文切换,2次拷贝
- sendFile 可以利用DMA的方式减少CPU的拷贝,mmap不能,他必须从内核拷贝到Socket buffer
NIO零拷贝的DEMO
*** @author liaojiamin* @Date:Created in 18:36 2022/8/16*/
public class NIOFileChannelTransferFormZeroCopy {private static final String filePath = "E:\\learn\\问题汇总\\MYSQL.md";private static final String filePathResult = "E:\\learn\\问题汇总\\MYSQL_1.md";public static void main(String[] args) throws IOException {FileInputStream fileInputStream = new FileInputStream(filePath);FileOutputStream fileOutputStream = new FileOutputStream(filePathResult);FileChannel inputFileChannel = fileInputStream.getChannel();FileChannel outputFileChannel = fileOutputStream.getChannel();//从目标通道中复制数据到当前通道
// outputFileChannel.transferFrom(inputFileChannel, 0, inputFileChannel.size());//把数据从当前通道复制给目标通道inputFileChannel.transferTo(0, inputFileChannel.size(), outputFileChannel);inputFileChannel.close();outputFileChannel.close();fileInputStream.close();fileOutputStream.close();}
}
- 使用NIO零拷贝的传递方式transferTo或者transferFrom