Netty 系列目录 (http://www.javashuo.com/article/p-hskusway-em.html)html
本文探讨 Linux 中主要的几种零拷贝技术以及零拷贝技术适用的场景。linux
操做系统的核心是内核,独立于普通的应用程序,能够访问受保护的内存空间,也有访问底层硬件设备的全部权限。为了保证用户进程不能直接操做内核 (kernel),保证内核的安全,操做系统将虚拟空间划分为两部分,一部分为内核空间,一部分为用户空间。缓存
网络 IO 的本质是 socket 的读取,socket 在 linux 系统被抽象为流,IO 能够理解为对流的操做。刚才说了,对于一次 IO 访问 (以 read 举例),数据会先被拷贝到操做系统内核的缓冲区中,而后才会从操做系统内核的缓冲区拷贝到应用程序的地址空间。因此说,当一个 read 操做发生时,它会经历两个阶段:安全
本文关注的是第二个过程:如何减小拷贝,即零拷贝。网络
Linux 的 I/O 操做默认是缓冲 I/O。使用了 read 和 write 两个系统调用,咱们并不知道操做系统在其中作了什么。实际上在以上 I/O 操做中,发生了屡次的数据拷贝。socket
当应用程序访问某块数据时,操做系统首先会检查,是否是最近访问过此文件,文件内容是否缓存在内核缓冲区,若是是,操做系统则直接根据 read 系统调用提供的 buf 地址,将内核缓冲区的内容拷贝到 buf 所指定的用户空间缓冲区中去。若是不是,操做系统则首先将磁盘上的数据拷贝的内核缓冲区,这一步目前主要依靠 DMA 来传输,而后再把内核缓冲区上的内容拷贝到用户缓冲区中。
接下来,write 系统调用再把用户缓冲区的内容拷贝到网络堆栈相关的内核缓冲区中,最后 socket 再把内核缓冲区的内容发送到网卡上。优化
整个过程当中共产生了屡次数据拷贝,即便用了 DMA 来处理了与硬件的通信,用户空间和系统空间 CPU 仍然须要处理两次数据拷贝,与此同时,在用户态与内核态也发生了 4 次上下文切换,无疑也加剧了 CPU 负担。spa
在此过程当中,咱们没有对文件内容作任何修改,那么在内核空间和用户空间来回拷贝数据无疑就是一种浪费,而零拷贝主要就是为了解决这种低效性。操作系统
零拷贝主要的任务就是避免 CPU 将数据从一块存储拷贝到另一块存储,主要就是利用各类零拷贝技术,避免让 CPU 作大量的数据拷贝任务,减小没必要要的拷贝,或者让别的组件来作这一类简单的数据传输任务,让 CPU 解脱出来专一于别的任务。这样就可让系统资源的利用更加有效。3d
在 OS 层面上的 Zero-copy 一般指避免在用户态(User-space)与内核态(Kernel-space)之间来回拷贝数据。
但 Netty 中的 Zero-copy 与 OS 的 Zero-copy 不太同样,Netty 的 Zero-coyp 彻底是在用户态(Java 层面)的,它的 Zero-copy 的更多的是偏向于优化数据操做 。
如何减小数据拷贝的次数呢?一个很明显的着力点就是减小数据在内核空间和用户空间来回拷贝,这也引入了零拷贝的一个类型:减小用户空间到内核空间的拷贝。
应用程序调用 sendfile,磁盘上的数据会经过 DMA 被拷贝的内核缓冲区,接着操做系统会把这段内核缓冲区与应用程序共享,这样就不须要把内核缓冲区的内容往用户空间拷贝。应用程序再调用 write(),操做系统直接将内核缓冲区的内容拷贝到 socket 缓冲区中,这一切都发生在内核态,最后,socket 缓冲区再把数据发到网卡去。
目前为止,咱们已经减小了数据拷贝的次数,可是仍然存在一次拷贝,就是页缓存到 socket 缓存的拷贝。那么能不能把这个拷贝也省略呢?
在上一种方案中是将页缓存的数据拷贝到 socket 缓存中,实际上,咱们仅仅须要把缓冲区描述符传到 socket 缓冲区,再把数据长度传过去,这样 DMA 控制器直接将页缓存中的数据打包发送到网络中就能够了。不过这种收集拷贝功能是须要硬件以及驱动程序支持的。
在服务端响应客户端的场景中,若是使用非直接缓冲区第一步就须要将响应的数据从 JVM 内存拷贝到系统内核中再发送,而使用直接缓冲区就能够省略这个步骤,这就是零拷贝。
传统的 IO 读和写都须要在操做系统内核和用户空间之间拷贝,Linus 优化了内核空间和用户空间的拷贝过程,内核空间也能够经过传递文件描述符进一步减小内核中的一次拷贝过程。Linux 零拷贝演进过程:
天天用心记录一点点。内容也许不重要,但习惯很重要!