目录
一、缓冲I/O和直接I/O
1、应用程序内存
2、用户缓冲区
3、内核缓冲区
二、内存映射文件与零拷贝
1、内存映射文件
2、零拷贝
实现方法1:利用直接I/O
实现方法2:利用内存映射文件
实现方式2:利用零拷贝技术
理解缓冲I/O和直接I/O,先搞清以下几个概念:
通常是指在代码中通过 malloc/free、new/delete等分配出来的内存。
用户进程通过系统调用访问系统资源的时候,需要切换到内核态,而这对应一些特殊的堆栈和内存环境,必须在系统调用前建 立好。而在系统调用结束后,cpu会从核心模式切回到用户模式,而堆栈又必须恢复成用户进程的上下文。而这种切换就会有大量的耗时。
你看一些程序在读取文件时,会先申请一块内存数组,称为buffer,然后每次调用read,读取设定字节长度的数据,写入buffer。(用较小的次数填满buffer)。之后的程序都是从buffer中获取数据,当buffer使用完后,在进行下一次调用,填充buffer。所以说:用户缓冲区的目的是为了减少系统调用次数,从而降低操作系统在用户态与核心态切换所耗费的时间。除了在进程中设计缓冲区,内核也有自己的缓冲区。
比如在C语言的FILE结构体里面的buffer。FILE结构体的定义如下,可以看到里面有定义的buffer
typedef struct {
short level;
short token;
short bsize;
char fd;
unsigned flags;
unsigned char hold;
unsigned char *buffer;
unsigned char * curp;
unsigned istemp;
}FILE;
Linux操作系统的Page Cache。为了加快磁盘的I/O,Linux系统会把磁盘上的数据以Page 为单位缓存在操作系统的内存里,这里的Page是Linux系统定义的一个逻辑概念,一个Page一般为4K。
对于缓冲I/O,一个读操作有3次数据拷贝,一个写操作,有反向的3次数据拷贝:
对于直接I/O,一个读操作会有2次数据拷贝,一个写操作,有反向的2次数据拷贝
所以,所谓的“直接I/O”,其中直接的意思是指没有用户级的缓冲区,但操作系统本身的缓冲还是有的。
缓冲I/O和直接I/O两者对比如下图所示:
相比于直接I/O,内存映射文件往前更近了一步,当用户空间不再有物理内存,直接拿应用程序的逻辑内存地址映射到Linux操作系统内核缓冲区,应用程序虽然读写是自己的内存,但这个内存只是一个“逻辑地址”,实际读写的内存是内核缓冲区!
如:Java中的 MappedByteBuffer类实现的就是内存映射文件
数据拷贝次数从缓冲I/O的3次,到直接I/O的2次,再到内存映射文件,变成了1次。
零拷贝(Zero Copy)是提升I/O效率的一大利器,熟悉Kafka,Netty等原理的都知道,其实现原理就是通过零拷贝技术来实现读写性能的。
当我们把数据发送到网络中时,如果不使用零拷贝。 伪代码:
fd1 = 打开文件描述符
fd2 = 打开Socket描述符
buffer = 应用程序内存
read(fd1, buffer) //先把数据从文件读取到应用程序内存
write(fd2, buffer) //再把应用内存中的数据写入到网络中发出
如下图所示:整个文件会有4次数据拷贝,读文件2次,写网络2次。
磁盘 ——> 内核缓冲区 ——> 应用程序内存 ——> Socket缓冲区 ——> 网络
此种方式,整个过程会有3次数据拷贝,不再经过应用程序内存,直接把内核空间中从内核缓冲区拷贝到Socket缓冲区。,伪代码如下:
fd1 = 打开文件描述符
fd2 = 打开Socket描述符
buffer = 应用程序内存
mmap(fd1, buffer) //先把磁盘数据映射到buffer上
write(fd2, buffer) //再通过网络发送数据
如下图所示:
注意:在这里需要分清“映射”和“拷贝”的区别。
拷贝:是把数据从一块内存中复制到另外一块内存里;
映射:相当于只是持有了数据的一个引用(或者叫地址),数据本身只有1分。
如果使用零拷贝,可能连内核缓冲区到Socket缓冲区的拷贝也省略了。在内核缓冲区和Socket缓冲区之间并没有做数据拷贝,只是一个地址的映射,底层的网卡驱动程序要读去数据并发送到网络的时候,看似读取的是Socket缓冲区的数据,但实际上直接读的是内核缓冲区的数据。
在这里,我们看到虽然叫零拷贝,实际是2次数据拷贝,1次是从磁盘到内核缓冲区,1次是从内核缓冲区到网络。之所以叫零拷贝,是从内存的角度来看的,数据在内存中没有发生过数据拷贝,只是在内存和I/O之间传输。
说明:对于把文件数据发送到网络的场景,直接I/O、内存映射文件、零拷贝对应的数据拷贝次数分别是4次、3次、2次,内存拷贝次数分别是2次、1次、0次。
参考资料 《软件架构设计——大型网站技术架构与业务架构融合之道》
更纯洁的个人博客