零拷贝技术/操作系统对磁盘读取的优化
零拷贝技术
零拷贝技术的场景
应用程序需要将磁盘中的文件进行网络传输,不对文件做任何处理和更改。
传统传输过程
从上图可以看到,整个过程,进行了4次由用户态内核态转换导致的上下文切换(一次计时纳秒到几微秒)。紫色箭头为CPU做的事,CPU参与了多次数据拷贝,非常占用CPU。
第一个优化 - DMA技术
DMA(Direct Memory Access)直接内存访问,是在I/O设备与内存做数据传输时,数据拷贝工作都由DMA控制器执行,而CPU不再参与数据拷贝,这样可以把宝贵的CPU资源让出来做其他的事,优化之后如下图,可以看出在I/O设备和内存进行数据传输拷贝的工作都交给DMA去做了。但是仍然,数据一共经过了4次拷贝,2次CPU拷贝,2次DMA拷贝。
零拷贝技术实现
mmap + write
传统文件传输,使用了两个系统调用,如下:
mmap实现的零拷贝则将read()
替换为mmap()
,而mmap()
系统调用会将内核缓冲区里的数据直接映射到用户空间,这样,操作系统内核和用户空间不用进行数据拷贝。
由下图可知,通过mmap()
我们减少了一次CPU拷贝,但是由于2次系统调用,上下文切换仍然是4次。
sendfile
在Linux 2.1中,提供了一个专门用于发送文件的系统调用函数sendfile()
,如下:
这个指令用于替换之前的read()
和 write()
,由于只有1次系统调用,上下文切换变成2次。
如果网卡支持SG-DMA(The Scatter-Gather Direct Memory Access)技术,可以进一步减少CPU将内核缓冲区数据拷贝到socket缓冲区的过程,使用linux命令查看系统是否支持SG-DMA:
从Linux 2.4开始,对于网卡支持SG-DMA技术的,只需要将缓冲区描述符和数据长度传到socket缓冲区,这样网卡的SG-DMA可以直接将内核缓冲区的数据拷贝到网卡缓冲区中,而不需要操作系统来进行数据搬运。整个过程只进行两次数据拷贝,且不需要CPU参与。
零拷贝技术运用
很多开源项目都利用了零拷贝来提高I/O吞吐率,如Kafka, Nginx, rocketMQ etc.
kafka中的transferTo
它使用了Java NIO库里的transferTo
方法,系统支持sendfile()
时,transferTo()
实际上使用的就是sendfile()
系统调用。
nginx提供的sendfile指令
PageCache的作用
PageCache其实就是所谓的磁盘高速缓存,它是面向文件系统(VFS)的,作用是为了加快磁盘读。因为读磁盘的速度比读内存慢特别多,而随机读(寻道,磁头找扇区这个过程很耗时)又要比顺序读磁盘慢的多。
由于刚被访问过的数据在短时间内再次被访问的概率很高,且被访问数据的临近数据在短时间内被访问的概率也很高,PageCache将磁盘数据弄到内存来,这样再次访问磁盘时,优先在PageCache里面找,如果存在直接返回,如果没有再去读磁盘。
PageCache的预读
上文提到,顺序读比随机读性能好一点,再加上被访问数据的临近数据在短时间内被访问的概率也很高的特点,读磁盘时,会把后面的数据也读到PageCache中,比如read()
读取32KB,但内核会把后面32~64KB也读到PageCache。
Cache和Buffer有什么区别
使用Linux指令free -h -w
可以看到里面有buffer,也有cache。
首先,经常讲到的磁盘缓冲区(disk cache)是个非常笼统的概念,大概就是存在于内存且为了加速磁盘读取而存在的缓冲区。细分下来,可以分为
- dentry cache : 加速filepath到inode映射查找的
- buffer cache: 面向块设备的,块设备的读取方法是以block为单位,一个buffer对应一个disk block(512KB),它的存在是短暂的
- page cache: 面向文件系统的,一个page可以有多个连续的block;PageCache为了加速磁盘上文件读取,会使用LRU进行淘汰,它的存在更长久
参考:https://www.oreilly.com/library/view/understanding-the-linux/0596002130/ch14.html
大文件传输
大文件传输如果使用PageCache的问题就是,内核都载入PageCache挤掉了原本热点的小文件,而使得磁盘的读写性能下降;且PageCache中的大文件由于太大(几GB)没有享受到缓存带来的好处,但却耗费DMA多拷贝到PageCache一次。所以针对大文件的传输,不应该使用PageCache。
所以对于大文件的读,需要使用直接I/O, 绕开PageCache。
在nginx中,用如下配置,根据文件大小的不同使用不同的方式在文件大小大于directio
后,使用异步I/O+直接I/O的方式,否则使用零拷贝技术。