本文共 3056 字,大约阅读时间需要 10 分钟。
在Linux中 标准I/O操作都是基于数据拷贝的缓冲机制,从内核中拷贝数据到用户空间的缓冲区中,然后将用户缓冲区中的数据拷贝至内核中。所以I/O操作频繁的使用会导致数据在内核和用户空间之间进行频繁的切换,这样做的好处虽然是可以通过缓冲机制减少实际的I/O系统调用,但是在数据拷贝的过程中会额外增加CPU的开销。
这里我们主要讨论如何在I/O操作的时候,有效减少数据拷贝,数据拷贝就是通过 copy_to_user 或者 copy_from_user将数据在内核和用户空间之间相互拷贝的操作,如下:
DMA(Direct Memory Access):直接存储器访问。DMA是一种无需CPU的参与,让外设和系统内存之间进行双向数据传输的硬件机制。使用DMA可以使系统CPU从实际的I/O数据传输过程中摆脱出来,从而大大提高系统的吞吐率。
零拷贝技术,就是避免将数据从一块存储区拷贝至另一块存储区的技术,减少了数据拷贝所带来的CPU开销。但是零拷贝并不是将拷贝操作完全消除,百度如下:
零复制(英语:Zero-copy;也译零拷贝)技术是指执行操作时,不需要先将数据从某处复制到另一个特定区域。这种技术通常用于通过网络传输文件时节省CPU周期和内存带宽。
目前零拷贝技术主要有三种类型:
下面主要讨论在网络编程时,读取本地文件,通过socket发送出去 场景下的零拷贝技术。
直接跨过内核,使运行在用户态下直接访问硬件设备,数据跨过内核进行传输,内核仅仅做一些必要的虚拟存储配置工作,工作逻辑如下:
缺陷:
首先了解虚拟内存与物理地址的映射关系,虚拟内存是OS为了方便用户操作而实现对物理地址的抽象,虚拟内存与物理内存之间通过页表进行关联。每个进程都有自己的页表,页表负责从虚拟内存到物理内存的映射,如下:
每个进程都有自己的PageTable,进程的虚拟内存地址通过PageTable对应于物理内存,内存分配具有惰性,它的过程一般是这样的:进程创建后新建与进程对应的PageTable,当进程需要内存时会通过PageTable寻找物理内存,如果没有找到对应的页帧就会发生缺页中断,从而创建PageTable与物理内存的对应关系。虚拟内存不仅可以对物理内存进行扩展,还可以更方便地灵活分配,并对编程提供更友好的操作
内存映射(mmap)就是说,将用户空间和内核空间的虚拟地址映射到同一块物理内存中,所以用户就可以在用户空间直接访问内核中的数据了,如下:
所以mmap并没有为用户提供直接操作内核地址空间的能力,而是通过内存映射机制,将内核中的部分内存空间映射到用户空间中(相同的物理内存),从而使用户可以直接访问内核空间的地址。mmap函数原型如下:
#includevoid *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);int munmap(void *addr, size_t length);
注意,调用mmap之后,并不会立即读取文件内容并加载到物理内存中,而是会在虚拟内存中分配地址空间,而实际要访问数据的时候,会因为内存地址对应的物理内存中没有数据,产生“缺页”异常,然后触发数据加载。
在mmap支持下的操作流程如下:
缺点:
通过sendfile,可以实现在内核态中传递数据,即内核态中在两个文件描述符之间传递数据,这样就避免了用户空间与内核之间的数据拷贝,sendfile函数原型如下:
#includessize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count);
in_fd参数是数据源的文件描述符,out_fd参数是待输出的文件描述符,in_fd必须是一个可以mmap的文件描述符,必须指向真实的文件,不能是socket等,而out_fd必须是一个socket。
在2.6.33之前Sendfile的out_fd必须是socket,因此sendfile几乎成了专为网络传输而设计的,限制了其使用范围比较狭窄。2.6.33之后out_fd才可以是任何file,于是乎出现了splice。
执行流程如下:
1.用户进程调用sendfile系统调用,进入内核态
2.CPU通知DMA将数据拷贝至内核缓冲区
3.内核自动将数据从内核缓冲区拷贝至网络缓冲区
4.通过DMA向网卡发送数据
5.sendfile调用完成,有内核态返回用户态
PS:在Linux 2.4版本中,对sendfile进一步做了优化,之前从“文件数据缓存”到“socket缓存”时候,也需要一次拷贝,优化之后,“socket缓存”中只存储要发送的数据在“文件数据缓存”中的位置和偏移量,在实际发送时,根据位置和偏移量直接将“文件数据缓存”中的数据拷贝到网卡设备中,又省掉了一次拷贝操作。如下:
在对sendfile进行优化之后,唯一的一次内核中的数据拷贝也可以通过DMA直接发送到网卡,整个过程中只有两次上下文切换和两次DMA操作,实现了真正意义上的零拷贝!!!但是需要硬件DMA支持,虽然可以设置偏移量,但不能对数据进行任何的修改。
参考:
转载地址:http://oxwzi.baihongyu.com/