上架学姐 8 张图,就可以搞懂「零拷贝」了,原来( 四 )


另外 , Nginx也支持零拷贝技术 , 一般默认是开启零拷贝技术 , 这样有利于提高文件传输的效率 , 是否开启零拷贝技术的配置如下:
设置为on表示 , 使用零拷贝技术来传输文件:sendfile , 这样只需要2次上下文切换 , 和2次数据拷贝 。
设置为off表示 , 使用传统的文件传输技术:read+write , 这时就需要4次上下文切换 , 和4次数据拷贝 。
当然 , 要使用sendfile , Linux内核版本必须要2.1以上的版本 。
回顾前面说道文件传输过程 , 其中第一步都是先需要先把磁盘文件数据拷贝「内核缓冲区」里 , 这个「内核缓冲区」实际上是磁盘高速缓存(PageCache) 。
由于零拷贝使用了PageCache技术 , 可以使得零拷贝进一步提升了性能 , 我们接下来看看PageCache是如何做到这一点的 。
读写磁盘相比读写内存的速度慢太多了 , 所以我们应该想办法把「读写磁盘」替换成「读写内存」 。 于是 , 我们会通过DMA把磁盘里的数据搬运到内存里 , 这样就可以用读内存替换读磁盘 。
但是 , 内存空间远比磁盘要小 , 内存注定只能拷贝磁盘里的一小部分数据 。
那问题来了 , 选择哪些磁盘数据拷贝到内存呢?
我们都知道程序运行的时候 , 具有「局部性」 , 所以通常 , 刚被访问的数据在短时间内再次被访问的概率很高 , 于是我们可以用PageCache来缓存最近被访问的数据 , 当空间不足时淘汰最久未被访问的缓存 。
所以 , 读磁盘数据的时候 , 优先在PageCache找 , 如果数据存在则可以直接返回;如果没有 , 则从磁盘中读取 , 然后缓存PageCache中 。
还有一点 , 读取磁盘数据的时候 , 需要找到数据所在的位置 , 但是对于机械磁盘来说 , 就是通过磁头旋转到数据所在的扇区 , 再开始「顺序」读取数据 , 但是旋转磁头这个物理动作是非常耗时的 , 为了降低它的影响 , PageCache使用了「预读功能」 。
比如 , 假设read方法每次只会读32KB的字节 , 虽然read刚开始只会读0~32KB的字节 , 但内核会把其后面的32~64KB也读取到PageCache , 这样后面读取32~64KB的成本就很低 , 如果在32~64KB淘汰出PageCache前 , 进程读取到它了 , 收益就非常大 。
所以 , PageCache的优点主要是两个:
缓存最近被访问的数据;
预读功能;
这两个做法 , 将大大提高读写磁盘的性能 。
但是 , 在传输大文件(GB级别的文件)的时候 , PageCache会不起作用 , 那就白白浪费DMA多做的一次数据拷贝 , 造成性能的降低 , 即使使用了PageCache的零拷贝也会损失性能
这是因为如果你有很多GB级别文件需要传输 , 每当用户访问这些大文件的时候 , 内核就会把它们载入PageCache中 , 于是PageCache空间很快被这些大文件占满 。
另外 , 由于文件太大 , 可能某些部分的文件数据被再次访问的概率比较低 , 这样就会带来2个问题:
PageCache由于长时间被大文件占据 , 其他「热点」的小文件可能就无法充分使用到PageCache , 于是这样磁盘读写的性能就会下降了;
PageCache中的大文件数据 , 由于没有享受到缓存带来的好处 , 但却耗费DMA多拷贝到PageCache一次;
所以 , 针对大文件的传输 , 不应该使用PageCache , 也就是说不应该使用零拷贝技术 , 因为可能由于PageCache被大文件占据 , 而导致「热点」小文件无法利用到PageCache , 这样在高并发的环境下 , 会带来严重的性能问题 。
那针对大文件的传输 , 我们应该使用什么方式呢?
我们先来看看最初的例子 , 当调用read方法读取文件时 , 进程实际上会阻塞在read方法调用 , 因为要等待磁盘数据的返回 , 如下图:
上架学姐 8 张图,就可以搞懂「零拷贝」了,原来
文章图片
当调用read方法时 , 会阻塞着 , 此时内核会向磁盘发起I/O请求 , 磁盘收到请求后 , 便会寻址 , 当磁盘数据准备好后 , 就会向内核发起I/O中断 , 告知内核磁盘数据已经准备好;


推荐阅读