Linux 百万并发「零拷贝」实现原理
共 2965字,需浏览 6分钟
·
2021-02-13 10:57
传统的I/O操作读取文件并通过Socket发送,需要经过4次上下文切换、2次CPU数据拷贝和2次DMA控制器数据拷贝,如下图:
操作系统层面的减少数据拷贝次数主要是指用户空间和内核空间的数据拷贝,因为只有他们的拷贝是大量消耗CPU时间片的,而DMA控制器拷贝数据CPU参与的工作较少,只是辅助作用。
现实中对零拷贝的概念有广义和狭义之分,广义上是指只要减少了数据拷贝的次数就称之为零拷贝;狭义上是指真正的零拷贝,比如上例中避免2和3的CPU拷贝。
下面我们逐一看看他们的设计思想和实现方案
每个进程都有自己的PageTable,进程的虚拟内存地址通过PageTable对应于物理内存,内存分配具有惰性,它的过程一般是这样的:进程创建后新建与进程对应的PageTable,当进程需要内存时会通过PageTable寻找物理内存,如果没有找到对应的页帧就会发生缺页中断,从而创建PageTable与物理内存的对应关系。虚拟内存不仅可以对物理内存进行扩展,还可以更方便地灵活分配,并对编程提供更友好的操作。
内存映射(mmap)是指用户空间和内核空间的虚拟内存地址同时映射到同一块物理内存,用户态进程可以直接操作物理内存,避免用户空间和内核空间之间的数据拷贝。
它的具体执行流程是这样的
用户进程通过系统调用mmap函数进入内核态,发生第1次上下文切换,并建立内核缓冲区;
发生缺页中断,CPU通知DMA读取数据;
DMA拷贝数据到物理内存,并建立内核缓冲区和物理内存的映射关系;
建立用户空间的进程缓冲区和同一块物理内存的映射关系,由内核态转变为用户态,发生第2次上下文切换;
用户进程进行逻辑处理后,通过系统调用Socket send,用户态进入内核态,发生第3次上下文切换;
系统调用Send创建网络缓冲区,并拷贝内核读缓冲区数据;
DMA控制器将网络缓冲区的数据发送网卡,并返回,由内核态进入用户态,发生第4次上下文切换;
避免了内核空间和用户空间的2次CPU拷贝,但增加了1次内核空间的CPU拷贝,整体上相当于只减少了1次CPU拷贝;
针对大文件比较适合mmap,小文件则会造成较多的内存碎片,得不偿失;
当mmap一个文件时,如果文件被另一个进程截获可能会因为非法访问导致进程被SIGBUS 信号终止;
ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count);
out_fd为文件描述符,in_fd为网络缓冲区描述符,offset偏移量(默认NULL),count文件大小。
它的内部执行流程是这样的
用户进程系统调用senfile,由用户态进入内核态,发生第1次上下文切换;
CPU通知DMA控制器把文件数据拷贝到内核缓冲区;
内核空间自动调用网络发送功能并拷贝数据到网络缓冲区;
CPU通知DMA控制器发送数据;
sendfile系统调用结束并返回,进程由内核态进入用户态,发生第2次上下文切换;
总结
数据处理完全是由内核操作,减少了2次上下文切换,整个过程2次上下文切换、1次CPU拷贝,2次DMA拷贝;
虽然可以设置偏移量,但不能对数据进行任何的修改;
用户进程系统调用senfile,由用户态进入内核态,发生第1次上下文切换;
CPU通知DMA控制器把文件数据拷贝到内核缓冲区;
把内核缓冲区地址和sendfile的相关参数作为数据描述信息存在网络缓冲区中;
CPU通知DMA控制器,DMA根据网络缓冲区中的数据描述截取数据并发送;
sendfile系统调用结束并返回,进程由内核态进入用户态,发生第2次上下文切换;
需要硬件支持,如DMA;
整个过程2次上下文切换,0次CPU拷贝,2次DMA拷贝,实现真正意义上的零拷贝;
依然不能修改数据;
但那时的sendfile有个致命的缺陷,如果你查看Sendfild手册,你会发现如下描述
in_fd不仅仅不能是socket,而且在2.6.33之前Sendfile的out_fd必须是socket,因此sendfile几乎成了专为网络传输而设计的,限制了其使用范围比较狭窄。2.6.33之后out_fd才可以是任何file,于是乎出现了splice。
ssize_t splice(int fd_in, loff_t *off_in, int fd_out, loff_t *off_out, size_t len, unsigned int flags);
它的执行流程如下
用户进程系统调用splice,由用户态进入内核态,发生第1次上下文切换;
CPU通知DMA控制器把文件数据拷贝到内核缓冲区;
建立内核缓冲区和网络缓冲区的管道;
CPU通知DMA控制器,DMA从管道读取数据并发送;
splice系统调用结束并返回,进程由内核态进入用户态,发生第2次上下文切换;
整个过程2次上下文切换,0次CPU拷贝,2次DMA拷贝,实现真正意义上的零拷贝;
依然不能修改数据;
fd_in和fd_out必须有一个是管道;
推荐阅读:
推荐一款,比 Navicat 还要好用,功能还很强大的 工具!
5T技术资源大放送!包括但不限于:C/C++,Linux,Python,Java,PHP,人工智能,单片机,树莓派,等等。在公众号内回复「1024」,即可免费获取!!