零拷贝
零拷贝
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 」