零拷贝(Zero-copy)是指计算机执行操作时,CPU不需要先将数据从某处内存复制到另一个特定区域。这种技术通常用于通过网络传输文件时节省CPU周期和内存带宽。
当用户程序向内核发起系统调用时,CPU将用户进程从用户态切换到内核态;当系统调用返回时,CPU将用户进程从内核态切换回用户态。具体可以参考:你该懂得操作系统知识—内核态和用户态
由CPU直接处理数据的传送,数据拷贝时会一直占用 CPU 的资源。
DMA(Direct Memory Access)中文译为:直接存储器访问,是一种I/O控制方式,它的特点是:
由CPU向DMA磁盘控制器下达指令,让DMA控制器来处理数据的传送,数据传送完毕再把信息反馈给 CPU,从而减轻了CPU资源的占有率。
ssize_t read(int fd, void *buf, size_t count)
ssize_t write(int fd, const void *buf, size_t count);
如上图,假设需求是将一个磁盘文件发布到网络上。具体步骤是:
总共需要2次CPU拷贝、2次DMA拷贝,4次上下文切换,其中read和write各占一半;
直接I/O就是应用程序直接访问磁盘数据,而不经过内核缓冲区,这样做的目的是减少一次从内核缓冲区到用户程序缓存的数据复制。通常使用在数据库管理系统这类应用,它们更倾向于选择它们自己的缓存机制,因为数据库管理系统往往比操作系统更了解数据库中存放的数据,数据库管理系统可以提供一种更加有效的缓存机制来提高数据库中数据的存取性能。
mmap的原理:将用户态缓存空间和内核态缓存空间做一个映射,如此程序在用户态就可以直接操作并使用内核缓存空间的数据。
mmap函数:int munmap(void* start,size_t length);
如图,具体步骤:
相比传统I/O减少了一次CPU拷贝,总共需要2次DMA拷贝,1次CPU拷贝,4次上下文切换。
sendfile原理:Linux2.1版本提供了sendFile函数,其基本原理如下:数据根本不经过用户态,直接从内核缓冲区进入socket buffer。同时,由于和用户态完全无关,就减少了一次CPU拷贝。
sendfile函数:ssize_t sendfile(int out_fd, int in_fd, off_t* offset, size_t count);
如图,步骤是:
相比mmap少了1次上下文切换,总共需要2次DMA拷贝,1次CPU拷贝,2次上下文切换。但是缺点也是很明显的,由于完全没有经过用户态,sendfile只能简单的传送数据而不能对其进行修改;
Linux2.4版本sendFile做了一些优化,避免了从内核缓冲区拷贝到socket buffer的操作,直接拷贝到协议栈,从而再一次减少数据拷贝。(其实这里是有一次CPU拷贝的,从kernel buffer-> socket buffer,但是拷贝信息很少,消耗低,可以忽略)
总共需要2次DMA拷贝,近0次CPU拷贝,2次上下文切换。
mmap和sendFile的区别
splice函数是linux系统提供的高级I/O函数,同sendfile系统调用函数一样,也是零拷贝操作函数。splice函数用于在两个文件描述符之间的移动数据。
tee函数在两个管道文件描述符之间复制数据,也是零拷贝操作。与splice函数不同,它不消耗数据,因此源文件描述符上的数据仍然可以用于后续的读操作。
总的来看,所谓的零拷贝,是从操作系统角度去看,是没有CPU拷贝,只有kernel buffer一份数据。零拷贝不仅带来了更少的数据复制,还能带来其他的性能优势,例如更少的上下文切换,更少的CPU缓存伪共享以及无CPU校验和计算;