=============
== cxis.me ==
=============
脚踏实地

基础之零拷贝相关知识总结记录

IO操作方式、零拷贝等相关知识总结记录

IO操作方式:轮询方式IO操作、中断方式IO操作、DMA方式IO操作、通道方式IO操作。

传统IO(read、write)

零拷贝:

  • 直接IO
  • mmap + write
  • sendfile
  • sendfile + DMA gather copy
  • splice

IO操作方式

轮询方式的IO操作

对IO设备进行轮询,如果可以进行处理,就进行处理。

缺点:轮询方式会消耗CPU,效率比较低。

中断方式的IO操作

当IO设备完成IO操作后,会向处理器发送中断信号,处理器收到中断信号后进行IO操作处理。

优点:中断方式的IO操作提高了CPU利用率。

缺点:

  • 如果IO设备过多,可能会造成中断次数过多,CPU无法响应中断或者数据丢失等问题。
  • 如果IO设备缓冲区较小,则缓冲区满后会发生中断,也会产生较多中断,占用CPU进行处理。

DMA方式的IO操作

DMA:直接内存存取,可以使得数据在内存和IO设备之间直接进行成块的传输。DMA会在数据传输完成后,通过中断通知CPU,CPU进行处理。

优点:IO设备直接和内存进行数据交换,效率高。

通道方式的IO操作

通道方式的IO操作可参考:https://www.cnblogs.com/niuyourou/p/12588407.html

传统IO操作方式

Linux系统中,传统IO操作是通过write()read()两个系统调用实现的。

传统IO操作的读取操作过程如下:

  1. 用户进程调用read()函数进行系统调用,进程从用户态切换到内核态。
  2. 操作系统将IO请求交给DMA,DMA将数据从磁盘拷贝到内核缓冲区。
  3. CPU将内核缓冲区中的数据拷贝到用户缓冲区。
  4. 进程从内核态切换到用户态。

传统IO操作的写操作过程如下:

  1. 用户进程调用write()函数进行系统调用,进程从用户态切换到内核态。
  2. CPU将用户缓冲区中的数据拷贝到内核缓冲区中。
  3. DMA将数据从内核缓冲区拷贝到网卡。
  4. 进程从内核态切换到用户态。

传统的IO操作需要两次系统调用,产生了4次上下文切换、2次CPU拷贝、2次DMA拷贝。

零拷贝方式的IO操作

传统IO操作中上下文切换次数多,拷贝次数多,是造成IO效率低下的原因,提高效率的方法是想办法减少上下文切换次数以及数据拷贝次数。

直接IO

直接IO的方式,让用户态进程直接访问硬件设备,数据传输直接跨过内核。但是这种直接IO只适用于不需要内核进行处理的数据。另外IO设备的低速也会导致IO操作效率低,可以使用异步IO配合。

mmap + write

mmap是linux中的内存映射,可以将内核缓冲区的地址和用户缓冲区地址进行映射,这样将内核缓冲区的内容共享给用户进程,无需进行将数据从内核缓冲区拷贝到用户缓冲区的操作,减少一次CPU拷贝。

mmap + write的IO操作过程如下:

  1. 用户进程调用mmap()函数进行系统调用,进程从用户态切换到内核态。
  2. DMA将数据从磁盘拷贝到内核缓冲区。
  3. 内核缓冲区地址和用户缓冲区地址进行映射,无需进行数据从内核缓冲区拷贝到用户缓冲区的操作。
  4. 上下文从内核态切换到用户态,mmap()调用返回。
  5. 用户进程调用write()函数进行系统调用,进程从用户态切换到内核态。
  6. CPU将内核缓冲区的数据拷贝到内核缓冲区(网络缓冲区)中。
  7. DMA从内核缓冲区将数据拷贝到网卡中。
  8. 上下文从内核态切换到用户态,write()调用返回。

mmap+write方式,上下文切换有4次、CPU拷贝1次、DMA拷贝2次。

sendfile

sendfile方式的IO操作过程如下:

  1. 用户进程调用sendfile()函数进行系统调用,进程从用户态切换到内核态。
  2. DMA将数据从硬盘拷贝到内核缓冲区。
  3. CPU将数据从内核缓冲区拷贝到内核缓冲区(网络缓冲区)。
  4. DMA将数据冲内核缓冲区拷贝到网卡。
  5. 上下文从内核态切换到用户态,sendfile()调用返回。

sendfile的方式,上下文切换2次、CPU拷贝1次、DMA拷贝2次。sendfile方式传输数据,用户进程不能对数据进行修改。

sendfile + DMA gather copy

sendfile方式中有一次CPU拷贝,可以使用sendfile+DMA gather copy将这一次CPU拷贝给去掉。

sendfile + DMA gather copy方式的IO操作过程如下:

  1. 用户进程调用sendfile()函数进行系统调用,进程从用户态切换到内核态。
  2. DMA将数据从硬盘拷贝到内核缓冲区。
  3. CPU将内核缓冲区的文件描述符和数据长度拷贝到内核缓冲区(网络缓冲区)。
  4. DMA利用已拷贝的文件描述符和数据长度,将数据从内核缓冲区拷贝到网卡。
  5. 上下文从内核态切换到用户态,sendfile()调用返回。

sendfile + DMA gather copy的方式上下文切换2次、CPU拷贝0次、DMA拷贝2次。这种方式传输数据,用户进程不能对数据进行修改,并且依赖硬件的支持。

splice

splice方式的IO操作过程如下:

  1. 用户进程调用splice()函数进行系统调用,进程从用户态切换到内核态。
  2. DMA将数据从硬盘拷贝到内核缓冲区。
  3. CPU在内核缓冲区和内核缓冲区(网络缓冲区)之间建立管道pipeline,
  4. DMA将数据从内核缓冲区拷贝到网卡。
  5. 上下文从内核态切换到用户态,splice()调用返回。

splice方式上下文切换2次、CPU拷贝0次、DMA拷贝2次。这种方式传输数据,用户进行不能对数据进行修改,但是不依赖硬件支持。使用了Linux中的管道缓冲机制。

参考