NIO的零拷贝的0指的是,0次需要cpu的拷贝,DMA拷贝不算在里面。零拷贝可以大大提高我们数据传输的效率。
传统IO
- 磁盘 -> 内核空间的缓存(DMA)
- 内核空间的缓存 -> 用户态中的程序缓存
- 对数据做一系列操作
- 用户态中的程序缓存 -> 内核空间的的socket缓存
- 内核空间的socket缓存->网卡缓存(DMA)
- 网络发送(或拷贝到磁盘)!
在一次传统的IO中,操作系统一共进行了2次拷贝,4次操作系统状态转换
2次拷贝:注意,磁盘到内核态的读写是通过DMA拷贝,外部设备(磁盘,U盘)不通过CPU直接与系统内存交换数据。所以是2次拷贝
4次系统状态切换:详情如下
tips: 为什么数据不直接从磁盘拷贝到用户程序空间呢?通过局部性原理我们知道,当我们从磁盘上取一个数据的时候,有很大可能下一次读取的数据就是本次读取数据周围的数据。因此操作系统为了提高性能,在读取数据的时候,会先把该目标数据周围的数据(NTFS下是4KB为单位)也一并读到操作系统的read buffer中,下次读取时,就有很大可能命中read buffer,减少了磁盘IO
NIO 零拷贝
不需要对数据进行操作
windows Linux2.4以下
- sendFile()
该版本下的sendFile,只需要2次状态切换,即 开始: 用户态->内核态(read buffer 到 socket buffer)->用户态,但是还需要把Read buffer中的数据通过cpu完整的拷贝到Socket Buffer,所以不能算是真正的0拷贝。
该种方法是2次切换,1次拷贝
Linux 2.4以后
- sendFile()
该版本下,sendFile()直接将内核态的Read buffer发送到NIC的 buffer,只有部分描述信息,如缓存位移,描述符经过socket buffer,通过这些信息,DMA直接把数据拷贝到外围设备缓存中。由于数据描述信息量很少,基本可以视作0,所以这也是0拷贝
需要对数据进行操作
当我们需要在用户空间对数据进行处理时,上述方法都不能解决问题
- mmap,把内核态的缓存空间和用户态的缓存空间映射到一块物理地址,实现共享,减少了内核态缓存到用户态缓存的一次拷贝。(因为使用mmap后,内存共享了,进程直接对内核空间映射到进程的虚拟地址进行读写就行,无需拷贝,但系统状态还是需要切换的)
该种方法是4次上下文切换,3次拷贝(2次DMA拷贝)。
3次上下文切换:
- 发出mmap系统调用,用户态->内核态,通过DMA将磁盘文件拷贝到read buffer中
- mmap系统调用返回,内核态->用户态,此时用户空间和内核空间中的read buffer共享一块缓冲区内存空间
- 用户修改数据,并发出write调用,用户态->内核态,read buffer中的数据通过cpu拷贝到socket buffer
- write调用返回,socker buffer通过DMA拷贝到外围设备。内核态->用户态
零拷贝的再次理解
- 我们说零拷贝,是从cpu的角度来说的,减少对cpu占用可以大大提升效率,而DMA拷贝无法避免。
- 零拷贝不仅仅带来更少的数据复制,还能带来其他的性能优势,例如更少的上下文切换,更少的CPU缓存伪共享以及无CPU校验和计算。
mmap和sendFile的区别
- mmap适合小数据读写, sendFile适合大文件传输
- mmap需要4次上下文切换,1次数据拷贝(还有);sendFile需要3次上下文切换,最小号2次数据拷贝
- sendFile可以利用DMA,减少CPU拷贝,mmap则不能(必须从内核拷贝到Socket缓冲区)