面试官: 零拷贝知道吗?
..
面试官:MMAP知道吗?
不知道..
面试官: 回去等结果吧!
复制一个文件的内容到另外一个文件,有极好的处理方式,也有极差的处理方式。不过如果一般我们使用的就是极差的方式。
来看看应该如何做这个拷贝的优化,这需要用到linux内核的用户态和内核态切换的概念,以及一点点内核的知识。
看看极差情况下的代码是如何写File file = new File("test.txt");
RandomAccessFile raf = new RandomAccessFile(file, "rw");
byte[] arr = new byte[(int) file.length()];
raf.read(arr);
Socket socket = new ServerSocket(8080).accept();
socket.getOutputStream().write(arr);
看看极差的传统IO的原理图
- 先说一下linux内核的用户态和核心态转换的问题。这实际上是linux内核的一种设计方式。linux的设计者为了保护系统的安全性,把核心的内容封装在了linux内核。然后对外提供了调用的api,也就是提供了调用的方法,而使用这些方法的正是用户态,我们的程序实际上是和用户态交互的,我们把命令交给用户态,然后用户态去调用内核态真正的执行。所以这就设计到了用户态和核心态转换的问题,虽然这个花费的时间是极短的,但是也是需要注意到的点。 结合上边的图,其实红绿色的圈,就是代表一次状态的切换。我们一个传统的IO,实际上要经历四次的状态切换。
- 关于拷贝。上边说了状态的切换,实际上还要知道两个空间。用户态对应的用户空间,内核态对应的是内核空间。它们都是独立的,不相互访问。所以最差的情况下,就是上图,经历一次 DMA的直接内存的拷贝,把内容拷贝到了内核空间,然后走了一次CPU拷贝,把内容从内核空间拷贝到了用户空间。在用户空间上的内容,是我们的写的程序可以操作的内容,经由我们的程序操作完了以后,由经CPU拷贝到了内核态,然后又经过DMA直接拷贝到了协议栈。
- MDA:直接内存拷贝,不经过不占用CPU。
mmap实际上就是通过开辟共享空间的方式,可以让用户空间和内核空间都能访问到。这样就不用从内核态经由CPU拷贝到用户态去操作了,因为是共享的,所以我们的程序可以直接的操作。如下图我用红框圈起来的,就可以看做是MMAP空间。
这是linux内核给我们带来的提升。
这也是基于linux内核的升级,为我们真正的去掉了最后的一次CPU拷贝,达到了零拷贝的效果。但是所谓的领拷贝,它是指没有了CPU的拷贝。从图上还可以看到,从内存够直接拷贝到内核空间的操作还是有的,但是它不走CPU。
Linux2.1
版本提供了 sendFile
函数,其基本原理如下:数据根本不经过用户态,直接从内核缓冲区进入到 SocketBuffer
,同时,由于和用户态完全无关,就减少了一次上下文切换