Jack:最近听说了网络处理的“零拷贝”技术,觉得非常神奇,在网上查阅了很多资料。不过,并不是太明白——知其然,而不知其所以然。你能通俗地解释一下吗?
我:这是一个相对比较复杂的话题,说起来话就多了。本质上来说,其实就是80386的分页管理变异而已。
Jack:分页管理?这个跨度有点大,穿越了。
我:你觉得穿越,是因为你基础不过关。基础不过关,看技术就像看天书。如果你“本源技术”学得很好,那些看是玄妙的技巧,一眼就可以看破。
Jack:为什么说“零拷贝”技术的本质是80386的分页管理变异的呢?
我:我们先说一说,零拷贝技术的本质是什么吧。当一个网络数据包通过网卡进入内核,然后再进入用户空间的时候,至少会经过2次data copy。不要小看两次data copy——互联网行业的网络服务器的CPU资源80%都消耗在这2次数据copy上去了,基本上别的事情都做不了。而事实上,这样的数据拷贝完全是没有必要的——这个数据包从网卡到内核再到用户空间,这个拷贝的过程仅仅起到了“传输”的作用,而数据包内容没有任何变化。零拷贝技术正是解决这两次拷贝。它能让两次拷贝完全消除,一次都不拷贝。
Jack:消除这两次拷贝的关键点在哪里呢?
我:这里需要分开讨论。第一次数据拷贝发生在“网卡到内核”。网卡到内核这种说法算是比较边缘性的。专业一点的说法是,外部设备到主存储器的数据传输。这个概念听着熟悉吧?
Jack:是的。这让我想起了外部设备到主存的三种数据传输方式——轮询、中断、DMA。其中轮询太龊了,基本上没有实用价值,而DMA适合在数据传输比较大的情况下使用,而且需要该外部设备支持。一般情况下,中断的方式用得比较多。
我:是的。中断是操作系统的神经中枢。不过,如果要实现零拷贝技术,这个网卡一定要采用DMA技术。
Jack:为什么呢?即便采用了DMA技术,数据任然要从网卡拷贝到主存,并不能算作“零拷贝”。
我:是的。使用DMA技术,仍然需要把数据从网卡拷贝到主存——但是,拷贝的过程中,CPU是完全不需要参与的。既然CPU不参与,就不会有任何CPU资源消耗,data copy带来的损耗就可以避免开了,所以这一层的data copy就被cut了。
Jack:明白了。那内核到用户空间的数据拷贝是怎么消除的呢?
我:我先不回答这一个问题。我先问一个问题,数据如何从内核拷贝到用户空间呢?
Jack:从CPU层面来说,内核与用户空间处于不同的段(segment),段间数据拷贝,必须要有一个起中转作用的段寄存器参与才行。
我:是的。386CPU有fs寄存器充当这样的角色。
Jack:但是,如何才能实现段间数据共享呢?
我:从CPU的内存管理的角度来说,这是不可能实现的。Intel的设计就是段与段间分隔开来,每个段有自己的存储空间、属性等。
Jack:那从CPU的内存管理的角度来说,“零拷贝”岂不是完全不能实现?
我:是的。不仅不能实现,“零拷贝”的思想与操作系统内核的设计思想、CPU的设计思想是完全背道而驰的,属于妖孽那一类。
Jack:不要卖关子了。直接说吧,Linux内核是如何做到“零拷贝”的呢?
我:消除从Linux内核到用户空间的数据拷贝的关键点在于80386的虚拟内存机制——这是CPU层面的技术,并不是操作系统层面的技术。从Linux内核的设计角度来讲,它天生就认为从网卡到内核到用户空间的2次数据拷贝是必须的,拷贝了两次才是完整的(尽管做了无用功、在网络流量比较大的情况下性能低下,但是站在Linux内核的角度,它就要这么做)。而“零拷贝”技术正是打破了操作系统的设计理念,妖孽了一把,从CPU层面打破Linux内核与用户空间的阻隔,使其内存共享。
Jack:CPU的这种功能具体是什么呢?
我:就是80386的虚拟内存管理与分页机制的组合。虚拟内存可以通过页目录、页表映射到物理内存地址,也可以直接映射到物理磁盘上(的文件)去。关键点就在这里了。既然虚拟内存可以映射到文件上去,而文件又是多个进程共享的(不用区分内核与用户进程),内核的某一块内存区域映射到一个文件,而用户进程(server进程)又关联到这一个文件,那么这个用户进程(server进程)就可以直接操作Linux内核的某块内存区域了。如果内核中的这块内存区域再被设定为网卡DMA映射的内存区,“零拷贝”也就实现了。
Jack:Linux为这种操作系统设计了API吗?
我:有的。mmap()就是做这个事儿的。而实际上,Unix进程间通信的“共享内存”方式,其本质原理,也是这个。
Jack:明白了,作为一个操作系统内核,其设计理念势必把内核与用户进程区分开来,而各个用户进程的内存区域也必然是互相隔离的。这样,才是真正的进程的概念。进程间需要通信,最“正当”的方式是在内核区域构建相应的数据结构,某进程需要通信就通过系统调用进入内核区域,修改代码,然后通知到需要通信的进程。被通知的进程通过系统调用进入内核空间,通信完成。如果进程间需要进行数据传输,只能先通过A进程传输到内核,然后再从内核传输到B进程,期间必然发生至少2次数据拷贝。
我:是的。
Jack:但是。“共享内存”与mmap本质上借用了80386的虚拟内存管理模式,打破了操作系统的设计思想。而mmap其实是共享内存的一般使用模式。
我:是的。能说出“而mmap其实是共享内存的一般使用模式”这样的话,说明你把握到关键点了。