零拷贝

DMA进行数据传输过程

DMA:直接内存访问。在进行 I/O 设备和内存数据传输时,数据搬运的工作全交给 DMA 控制器,而 CPU 不再参与任何与数据搬运相关的事,这样CPU就可以去处理别的事务

  • 用户进程调 read 向操作系统发出 I/O 请求,读取数据到自己的内存缓冲区中,进程阻塞
  • 操作系统收到请求后,进一步将 I/O 请求发送 DMA,然后让 CPU 执行其他任务
  • DMA 进一步将 I/O 请求发给磁盘
  • 磁盘收到 DMA 的 I/O 请求,把数据从磁盘读到磁盘控制器的缓冲区中,当缓冲区被读满后,向 DMA 发起中断信号,告知自己缓冲区已满
  • DMA 收到磁盘信号,将磁盘控制器缓冲区中的数据拷贝到内核缓冲区中,此时不占 CPU,CPU 可以执行其他任务
  • 当 DMA 读取了足够多的数据就会发送中断信号给 CPU。CPU 收到 DMA 信号,知道数据已准备好,于是将数据从内核拷贝到用户空间,系统调用返回

传统文件传输

1、4 次用户态与内核态的上下文切换。因为发生了两次系统调用 read()write()

2、还发生了 4 次数据拷贝,两次DMA拷贝两次CPU拷贝:

  • 第一次拷贝,把磁盘上的数据拷到os内核缓冲区里(DMA)
  • 第二次拷贝,把内核缓冲区数据拷到用户缓冲区,此时程序就可以使用这部分数据了(CPU)
  • 第三次拷贝,把刚才拷贝到用户缓冲区里的数据再拷到内核的 socket 缓冲区里(CPU)
  • 第四次拷贝,把内核 socket 缓冲区里的数据拷到网卡缓冲区里(DMA)

零拷贝

  • mmap + write

read() 过程中会把内核缓冲区数据拷到用户缓冲区里,为减少这步开销可以用 mmap() 替换 。mmap() 会直接把内核缓冲区里的数据「映射」到用户空间,这样os内核与用户空间就不需要再进行任何的数据拷贝操作,可以减少一次数据拷贝过程,但上下文还是要切4次。

  • sendfile
1
ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count);
  • 第一步,通过 DMA 将磁盘数据拷到内核缓冲区
  • 第二步,缓冲区描述符和数据长度传到 socket 缓冲区,网卡的DMA控制器就可以直接将内核缓存中的数据拷到网卡缓冲区里,不需要将数据从os内核缓冲区拷到 socket 缓冲区中,这样就减少了一次数据拷贝,只进行 2 次数据拷贝

传大文件

传输大文件时不能用零拷贝,因为可能由于 PageCache 被大文件占据,而导致「热点」小文件无法利用 PageCache;并且大文件缓存命中率不高,这时就需要用「异步 IO + 直接 IO 」