1. 用户态直接I/O

关键词:直接操作磁盘I/O,跨过内核传输

用户态直接 I/O 使得应用进程或运行在用户态(user space)下的库函数直接访问硬件设备,数据直接跨过内核进行传输,内核在数据传输过程除了进行必要的虚拟存储配置工作之外,不参与任何其他工作,这种方式能够直接绕过内核,极大提高了性能。

零拷贝技术1: 用户态直接I/O

缺陷:用户态直接I/O 会让用户进程直接进行磁盘I/O操作,由于 CPU 和磁盘 I/O 之间的执行时间差距,会造成大量资源的浪费,解决方案是配合异步 I/O 使用。

2. mmap + write

关键词:内存地址映射

用mmap + write 系统调用的方式代替了传统的read + write模式(本质用mmap代替普通的read),减少了一次CPU拷贝的操作

mmap是一种内存映射文件的方法,即将一个文件或者其它对象映射到进程的地址空间,实现文件磁盘地址和进程虚拟地址空间中一段虚拟地址的一一对映关系。实现这样的映射关系后,进程就可以采用指针的方式读写操作这一段内存,而系统会自动回写脏页面到对应的文件磁盘上,即完成了对文件的操作而不必再调用read,write等系统调用函数。相反,内核空间对这段区域的修改也直接反映用户空间,从而可以实现不同进程间的文件共享

mmap的具体原理如下:

mmap内存映射的原理

mmap + write的流程如下图:

零拷贝技术2: mmap + write系统调用

mamp + write总共进行了 4 次上下文状态转换,1次cpu拷贝,2次DMA拷贝

  • mamp读操作:

    1. 用户进程通过 mmap() 函数向内核(kernel)发起系统调用,上下文从用户态(user space)切换为内核态(kernel space)。
    2. 将用户进程的内核空间的读缓冲区(read buffer)与用户空间的缓存区(user buffer)进行内存地址映射。
    3. CPU利用DMA控制器将数据从主存或硬盘拷贝到内核空间(kernel space)的读缓冲区(read buffer)。
    4. 上下文从内核态(kernel space)切换回用户态(user space),mmap 系统调用执行返回。
  • write操作:

    1. 用户进程通过 write() 函数向内核(kernel)发起系统调用,上下文从用户态(user space)切换为内核态(kernel space)。
    2. CPU将内核空间的读缓冲区(read buffer)中的数据拷贝到的网络缓冲区(socket buffer)。
    3. CPU利用DMA控制器将数据从网络缓冲区(socket buffer)拷贝到网卡进行数据传输。
    4. 上下文从内核态(kernel space)切换回用户态(user space),write 系统调用执行返回。

3. sendfile

Linux 2.1内核添加的系统调用

sendfile(socket_fd, file_fd, len);

通过 sendfile 系统调用,数据可以直接在内核空间内部进行 I/O 传输,从而省去了数据在用户空间和内核空间之间的来回拷贝。与 mmap 内存映射方式不同的是, sendfile 调用中 I/O 数据对用户空间是完全不可见的。也就是说,这是一次完全意义上的数据传输过程。

零拷贝技术3: sendfile系统调用

sendfile操作共进行了2次上下文切换,1次cpu拷贝,2次DMA拷贝

  1. 用户进程通过 sendfile() 函数向内核(kernel)发起系统调用,上下文从用户态(user space)切换为内核态(kernel space)。
  2. CPU 利用 DMA 控制器将数据从主存或硬盘拷贝到内核空间(kernel space)的读缓冲区(read buffer)。
  3. CPU 将读缓冲区(read buffer)中的数据拷贝到的网络缓冲区(socket buffer)。
  4. CPU 利用 DMA 控制器将数据从网络缓冲区(socket buffer)拷贝到网卡进行数据传输。
  5. 上下文从内核态(kernel space)切换回用户态(user space),sendfile 系统调用执行返回。

相比 mmap 内存映射的方式,sendfile 少了 2 次上下文切换,但是仍然有 1 次 CPU 拷贝操作。

缺陷:sendfile 存在的问题是用户程序不能对数据进行修改,而只是单纯地完成了一次数据传输过程。

4. sendfile + DMA scatter/gather copy

关键点:省去了上一节进行sendfile系统调用过程中内核缓冲区到socket缓冲区的那1次CPU拷贝

Linux 2.4内核对sendfile 系统调用进行了修改,为DMA 加入了 scatter/gather 操作。

与逐个映射每个缓冲区然后执行操作的原始DMA操作不同,"DMA scatter/gather copy" 一次映射整个缓冲区列表,并在一个DMA操作中传输它们。在这种方法中需要硬件的支持,因为网络接口从不同的内存空间收集数据。

sendfile(socket_fd, file_fd, len);

零拷贝技术4: sendfile + DMA scatter/gather copy技术

sendfile + DMA gather copy 操作总共产生了2次上下文切换,0次CPU拷贝(严格意义上是1次,因为发送descriptor和length到socket缓冲区需要CPU参与),2次DMA拷贝

  1. 用户进程通过 sendfile() 函数向内核(kernel)发起系统调用,上下文从用户态(user space)切换为内核态(kernel space)。
  2. CPU 利用 DMA 控制器将数据从主存或硬盘拷贝到内核空间(kernel space)的读缓冲区(read buffer)。
  3. 将读缓冲区(read buffer)的文件描述符(file descriptor)发送到套接字缓冲区(socket buffer),而不是将所有数据拷贝到套接字缓冲区,描述符包含了数据的长度以及具体存储位置等信息,使用该信息,可以生成数据分组的报头和报尾。
  4. 通过使用DMA scatter/gather 操作,网卡可以从不同的存储位置收集所有数据,并将组装的分组存储在网卡缓冲区(network card buffer)中。
  5. 上下文从内核态(kernel space)切换回用户态(user space),sendfile 系统调用执行返回。
  6. 此外,由于硬件支持DMA scatter/gather 操作,因此不需要将缓冲区数据存储在连续的内存空间中。

缺陷:

  • sendfile + DMA gather copy 拷贝方式同样存在用户程序不能对数据进行修改的问题
  • 本身需要硬件/驱动的支持
  • 只适用于将数据从文件拷贝到 socket 套接字上的传输过程。

5. 零拷贝方式对比

拷贝方式上下文切换CPU拷贝DMA拷贝系统调用
传统read + wirte422read / wirte
mmap + wirte412mamp / write
sendfile212sendfile
sendfile +DMA scatter / gather copy21(复制文件描述符等信息)2sendfile

参考