Linux平台下基于Intel千兆网卡的零拷贝技术的研究与实现
- 格式:pdf
- 大小:189.30 KB
- 文档页数:2
Linux 中的零拷贝技术概述黄晓晨, 软件工程师, IBM黄晓晨,IBM system Z 自动化技术支持软件工程师。
冯瑞, 软件工程师, IBM简介:本系列由两篇文章组成,介绍了当前用于Linux 操作系统上的几种零拷贝技术,简单描述了各种零拷贝技术的实现,以及它们的特点和适用场景。
本文是本系列文章的第一部分,主要是介绍一些零拷贝技术的相关背景知识,简要概述了Linux 为什么需要零拷贝技术以及Linux 中都有哪几种零拷贝技术。
引言传统的Linux 操作系统的标准I/O 接口是基于数据拷贝操作的,即I/O 操作会导致数据在操作系统内核地址空间的缓冲区和应用程序地址空间定义的缓冲区之间进行传输。
这样做最大的好处是可以减少磁盘I/O 的操作,因为如果所请求的数据已经存放在操作系统的高速缓冲存储器中,那么就不需要再进行实际的物理磁盘I/O 操作。
但是数据传输过程中的数据拷贝操作却导致了极大的CPU 开销,限制了操作系统有效进行数据传输操作的能力。
零拷贝(zero-copy )这种技术可以有效地改善数据传输的性能,在内核驱动程序(比如网络堆栈或者磁盘存储驱动程序)处理I/O 数据的时候,零拷贝技术可以在某种程度上减少甚至完全避免不必要CPU 数据拷贝操作。
现代的CPU 和存储体系结构提供了很多特征可以有效地实现零拷贝技术,但是因为存储体系结构非常复杂,而且网络协议栈有时需要对数据进行必要的处理,所以零拷贝技术有可能会产生很多负面的影响,甚至会导致零拷贝技术自身的优点完全丧失。
为什么需要零拷贝技术如今,很多网络服务器都是基于客户端- 服务器这一模型的。
在这种模型中,客户端向服务器端请求数据或者服务;服务器端则需要响应客户端发出的请求,并为客户端提供它所需要的数据。
随着网络服务的逐渐普及,video 这类应用程序发展迅速。
当今的计算机系统已经具备足够的能力去处理video 这类应用程序对客户端所造成的重负荷,但是对于服务器端来说,它应付由video 这类应用程序引起的网络通信量就显得捉襟见肘了。
Linux 中的直接I/O如果应用程序可以直接访问网络接口存储,那么在应用程序访问数据之前存储总线就不需要被遍历,数据传输所引起的开销将会是最小的。
应用程序或者运行在用户模式下的库函数可以直接访问硬件设备的存储,操作系统内核除了进行必要的虚拟存储配置工作之外,不参与数据传输过程中的其它任何事情。
直接I/O 使得数据可以直接在应用程序和外围设备之间进行传输,完全不需要操作系统内核页缓存的支持。
关于直接I/O 技术的具体实现细节可以参看developerWorks 上的另一篇文章”Linux 中直接I/O 机制的介绍” ,本文不做过多描述。
图 1. 使用直接I/O 的数据传输针对数据传输不需要经过应用程序地址空间的零拷贝技术利用mmap()在Linux 中,减少拷贝次数的一种方法是调用mmap() 来代替调用read,比如:首先,应用程序调用了mmap() 之后,数据会先通过DMA 拷贝到操作系统内核的缓冲区中去。
接着,应用程序跟操作系统共享这个缓冲区,这样,操作系统内核和应用程序存储空间就不需要再进行任何的数据拷贝操作。
应用程序调用了write() 之后,操作系统内核将数据从原来的内核缓冲区中拷贝到与socket 相关的内核缓冲区中。
接下来,数据从内核socket 缓冲区拷贝到协议引擎中去,这是第三次数据拷贝操作。
图 2. 利用mmap() 代替read()通过使用mmap() 来代替read(), 已经可以减半操作系统需要进行数据拷贝的次数。
当大量数据需要传输的时候,这样做就会有一个比较好的效率。
但是,这种改进也是需要代价的,使用mma()p 其实是存在潜在的问题的。
当对文件进行了内存映射,然后调用write() 系统调用,如果此时其他的进程截断了这个文件,那么write() 系统调用将会被总线错误信号SIGBUS 中断,因为此时正在执行的是一个错误的存储访问。
这个信号将会导致进程被杀死,解决这个问题可以通过以下这两种方法:1.为SIGBUS 安装一个新的信号处理器,这样,write() 系统调用在它被中断之前就返回已经写入的字节数目,errno 会被设置成success。
剖析linux下的零拷贝技术背景大多数的网络服务器是基于server-client模式的。
在这当中,下载是一个很常见的功能。
此时服务器端需要将主机磁盘上的文件发送到客户端上去。
传统的Linux操作系统的标准I/O接口是基于数据拷贝操作的,即I/O操作会导致数据在操作系统内核地址空间的缓冲区和应用程序地址空间定义的缓冲区之间进行传输。
那么传统的I/O操作过程是咋样的呢?(下面是具体说明,以read和write为例)在执行read操作时,操作系统首先会检查,文件内容是否缓存在内核缓冲区,如果在内核缓冲区,则不用去磁盘中读取文件,而是直接将内核缓冲区的内容拷贝到用户空间缓冲区中去。
如果不是,操作系统则首先将磁盘上的数据拷贝的内核缓冲区(DMA),然后再把内核缓冲区上的内容拷贝到用户缓冲区中。
接下来,write系统调用再把用户缓冲区的内容拷贝到网络堆栈相关的内核缓冲区中,最后再往对方的sockfd中些数据。
并且在这个过程中还涉及到了四次的上下文切换。
那传统的I/O操作会带来什么问题呢?在高速网络中,大量传统的I/O操作导致的数据拷贝工作会占用CPU 时间片,同时也需要占用额外的内存带宽。
使cpu将大多数的时间用于I/O操作上,从而无法处理其他的任务,很大程度上影响了系统的性能,使服务器成为性能瓶颈。
而本身我们可以看出:很多数据复制操作并不是真正需要的。
所以可以消除一些复制操作以减少开销并提高性能。
这就引出了我们今天要介绍的“零拷贝”技术。
概念零拷贝(Zero-copy)技术是指计算机执行操作时,CPU不需要先将数据从某处内存复制到另一个特定区域。
这种技术通常用于通过网络传输文件时节省CPU周期和内存带宽。
零拷贝技术可以减少数据拷贝和共享总线操作的次数,消除传输数据在存储器之间不必要的中间拷贝次数,从而有效地提高数据传输效率。
而且,零拷贝技术减少了用户进程地址空间和内核地址空间之间因为上下文切换而带来的开销。
Intel 82571 零拷贝的设计与实现本文主要阐述基于Intel网卡零拷贝的实现过程, 通常情况下网络数据包到达用户应用程序要经过如下几个过程:1.网卡的物理硬件从物理媒体(通常情况下网线)上接收到得信号(数据帧)首先放在网卡自己的一个缓冲区(网卡RAM),在这一过程中通常要进行帧校验(比如FCS), 帧过滤等。
2.如果网卡支持DMA就会启动DMA操作,把收到的数据帧通过DMA操作放到我们事先申请好的buffer中,DMA操作由硬件自动完成,当然用户要提供给DMA硬件操作必要的参数,包括DMA 地址,DMA大小等,有可能还有地址对齐等要求。
DMA的具体操作后面详细描述。
3.这一步是DMA零拷贝最重要的一个环节,就是把网卡接收到的数据帧直接映射到用户层,不需要经过内核协议栈的处理。
后面详细描述。
网卡数据从网络到Linux内核的路径简要分析:网卡的主要工作原理:发送数据时,计算机把要传输的数据并行写到网卡的缓存,网卡对要传输的数据进编码(10M以太网使用曼切斯特码,100M以太网使用差分曼切斯特码),串行发到传输介质上.接收数据时,则相反。
对于网卡而言,每块网卡都有一个唯一的网络节点地址,它是网卡生产厂家在生产时烧入ROM(只读存储芯片)中的,我们把它叫做MAC地址(物理地址),且保证绝对不会重复。
MAC为48bit,前24比特由IEEE分配,是需要钱买的,后24bit由网卡生产厂家自行分配.我们日常使用的网卡都是以太网网卡。
目前网卡按其传输速度来分可分为10M网卡、10/100M自适应网卡以及千兆(1000M)网卡。
如果只是作为一般用途,如日常办公等,比较适合使用10M网卡和10/100M 自适应网卡两种。
如果应用于服务器等产品领域,就要选择千兆级及更高级别的网卡。
本文主要讲解的是Intel 82571 千兆网卡的网卡驱动:Linux 内核目录:linux-3.4.7/drivers/net/ethernet/intel/e1000e关于这款网卡的硬件信息:更详细的信息可以通过lspci -vvv查看。
本文档的Copyleft归wwwlkk所有,使用GPL发布,可以自由拷贝、转载,转载时请保持文档的完整性,严禁用于任何商业用途。
E-mail: wwwlkk@来源: /?business&aid=6&un=wwwlkk#7零拷贝技术交流文档目录:(一)linux中的分页机制 (1)(二)实现零拷贝的两个理论方案 (2)(三)为防火墙定制的零拷贝技术方案 (3)(四)参考文献 (5)前言:零拷贝技术是比较实用的技术,但是网络上很少有人介绍这种技术的实现原理,本文的目的就是详细的讲解零拷贝的实现原理,但是不讨论技术细节,因为技术细节不好表述,而且不同的需求或者不同的程序员,都会选择不同技术来实现,但是最终的实现原理是不变的。
(一)linux中的分页机制CPU要访问某块物理内存,必须要获得内存的物理地址,CPU集成有寻址硬件,会根据机器语言指令中提供的地址,执行地址转换,获得的物理地址。
CPU有两种转换模式:1.实模式2.保护模式。
实模式下的物理地址=线性地址,保护模式下的物理地址=线性地址通过分页机制转化为物理地址。
启动保护模式:把CPU控制寄存器CR0中的最高位置1。
CPU保护模式寻址方式:图1 分页机制寻址说明:CR3控制寄存器的值是物理地址。
由于寻找的是页框的物理地址,页框的物理地址是4096的整数倍,所以CR3,页目录和页表中存储的物理地址后12位都为0。
也就是这12位的空间不存储物理地址信息,而是用于存储访问控制信息(可读/可写/CPU特权级别),或者对应的页是否存在等信息。
其中访问控制信息(可读/可写/CPU特权级别)可以特别利用:例如:如果某个页框的范围控制标记是只读的,那么进程如果对这个页框进行写操作,就会产生段错误。
注意:从2.6.11版本开始,采用了四级分页模型,但基本原理是一样的。
每个进程的CR3的值是不同,进程切换时保存或者恢复CR3的值。
只要设置了CR3的值CPU 将自动进行寻址。
∙直接I/O:对于这种数据传输方式来说,应用程序可以直接访问硬件存储,操作系统内核只是辅助数据传输:这类零拷贝技术针对的是操作系统内核并不需要对数据进行直接处理的情况,数据可以在应用程序地址空间的缓冲区和磁盘之间直接进行传输,完全不需要Linux 操作系统内核提供的页缓存的支持。
∙在数据传输的过程中,避免数据在操作系统内核地址空间的缓冲区和用户应用程序地址空间的缓冲区之间进行拷贝。
有的时候,应用程序在数据进行传输的过程中不需要对数据进行访问,那么,将数据从Linux 的页缓存拷贝到用户进程的缓冲区中就可以完全避免,传输的数据在页缓存中就可以得到处理。
在某些特殊的情况下,这种零拷贝技术可以获得较好的性能。
Linux 中提供类似的系统调用主要有mmap(),sendfile() 以及splice()。
∙对数据在Linux 的页缓存和用户进程的缓冲区之间的传输过程进行优化。
该零拷贝技术侧重于灵活地处理数据在用户进程的缓冲区和操作系统的页缓存之间的拷贝操作。
这种方法延续了传统的通信方式,但是更加灵活。
在Linux 中,该方法主要利用了写时复制技术。
以标准的方式对文件进行读写利用mmap 代替read直接IO方式sendfile ()sendfile(socket, file, len);零拷贝带有DMA 收集拷贝功能的sendfile()上小节介绍的sendfile() 技术在进行数据传输仍然还需要一次多余的数据拷贝操作,通过引入一点硬件上的帮助,这仅有的一次数据拷贝操作也可以避免。
为了避免操作系统内核造成的数据副本,需要用到一个支持收集操作的网络接口,这也就是说,待传输的数据可以分散在存储的不同位置上,而不需要在连续存储中存放。
这样一来,从文件中读出的数据就根本不需要被拷贝到socket 缓冲区中去,而只是需要将缓冲区描述符传到网络协议栈中去,之后其在缓冲区中建立起数据包的相关结构,然后通过DMA 收集拷贝功能将所有的数据结合成一个网络数据包。
零拷贝(zero-copy)原理详解前置概念⽤户空间与内核空间CPU 将指令分为特权指令和⾮特权指令,对于危险指令,只允许操作系统及其相关模块使⽤,普通应⽤程序只能使⽤那些不会造成灾难的指令。
⽐如 Intel 的 CPU 将特权等级分为4 个级别:Ring0~Ring3。
其实 Linux 系统只使⽤了 Ring0 和 Ring3 两个运⾏级别(Windows 系统也是⼀样的)。
当进程运⾏在 Ring3 级别时被称为运⾏在⽤户态,⽽运⾏在 Ring0 级别时被称为运⾏在内核态。
简单来说:内核空间和⽤户空间本质上是要提⾼操作系统的稳定性及可⽤性,当进程运⾏在内核空间时就处于内核态,当进程运⾏在⽤户空间时就处于⽤户态。
DMA(直接存储器访问)DMA 即Direct Memory Access ,直接存储器访问。
DMA 控制⽅式是以存储器为中⼼,在主存和I/O设备之间建⽴⼀条直接通路,在DMA 控制器的控制下进⾏设备和主存之间的数据交换。
这种⽅式只在传输开始和传输结束时才需要CPU的⼲预。
它⾮常适⽤于⾼速设备与主存之间的成批数据传输。
传统I/O下⾯通过⼀个Java ⾮常常见的应⽤场景:将系统中的⽂件发送到远端(磁盘⽂件 -> 内存(字节数组) -> 传输给⽤户/⽹络)来详细展开I/O操作。
如下图所⽰:JVM 发出read() 系统调⽤,上下⽂从⽤户态切换到内核态(第⼀次上下⽂切换)。
通过DMA(Direct Memory Access,直接存储器访问)引擎将⽂件中的数据从磁盘上读取到内核空间缓冲区(第⼀次拷贝: hard drive -> kernel buffer)。
将内核空间缓冲区的数据拷贝到⽤户空间缓冲区(第⼆次拷贝:kernel buffer -> user buffer),然后read系统调⽤返回。
⽽系统调⽤的返回⼜会导致⼀次内核态到⽤户态的上下⽂切换(第⼆次上下⽂切换)。
零拷贝(Zero-Copy)概述考虑这样⼀种常⽤的情形:你需要将静态内容(类似图⽚、⽂件)展⽰给⽤户。
那么这个情形就意味着你需要先将静态内容从磁盘中拷贝出来放到⼀个内存buf中,然后将这个buf通过socket传输给⽤户,进⽽⽤户或者静态内容的展⽰。
这看起来再正常不过了,但是实际上这是很低效的流程,我们把上⾯的这种情形抽象成下⾯的过程:read(file, tmp_buf, len);write(socket, tmp_buf, len);⾸先调⽤read将静态内容,这⾥假设为⽂件A,读取到tmp_buf, 然后调⽤write将tmp_buf写⼊到socket中,如图:在这个过程中⽂件A的经历了4次copy的过程:1. ⾸先,调⽤read时,⽂件A拷贝到了kernel模式;2. 之后,CPU控制将kernel模式数据copy到user模式下;3. 调⽤write时,先将user模式下的内容copy到kernel模式下的socket的buffer中;4. 最后将kernel模式下的socket buffer的数据copy到⽹卡设备中传送;从上⾯的过程可以看出,数据⽩⽩从kernel模式到user模式⾛了⼀圈,浪费了2次copy(第⼀次,从kernel模式拷贝到user模式;第⼆次从user模式再拷贝回kernel模式,即上⾯4次过程的第2和3步骤。
)。
⽽且上⾯的过程中kernel和user模式的上下⽂的切换也是4次。
幸运的是,你可以⽤⼀种叫做Zero-Copy的技术来去掉这些⽆谓的copy。
应⽤程序⽤Zero-Copy来请求kernel直接把disk的data传输给socket,⽽不是通过应⽤程序传输。
Zero-Copy⼤⼤提⾼了应⽤程序的性能,并且减少了kernel和user模式上下⽂的切换。
详述Zero-Copy技术省去了将操作系统的read buffer拷贝到程序的buffer,以及从程序buffer拷贝到socket buffer的步骤,直接将read buffer拷贝到socket buffer. Java NIO中的FileChannal.transferTo()⽅法就是这样的实现,这个实现是依赖于操作系统底层的sendFile()实现的。
linux内核开发(零拷贝)linux 内核开发(零拷贝)我正在写一个linux网卡零拷贝的驱动,是在intel e1000驱动基础之上改动。
查了查资料已经搞清楚DMA是如何运作,而且也知道如何使DMA将数据直接写入自己申请的内存空间。
已经有了一个大致的解决方案,但有以下下几个细节搞不定,请同学们帮帮。
我先说下我的思路:首先从用户太说起,数据包从经数据线至网卡驱动至协议栈最后至用户太要经过至少两次拷贝才能完成,期间cpu参与过多,造成cpu负担过重,效率低下。
从e1000的驱动看到:pci_map_single()这个函数完成了将内核内存映射到DMA设备的过程。
我所做的就是将pci_mag_single()映射的内存替换成我申请的内存。
这样DMA设备就把数据直接写到指定的内存了,用户台进程读这块内存就OK了。
其中没有cpu的参与。
如何替换呢?写过内核的同学们都知道skb buffer这个东西。
当网卡驱动被初始化的时候,会n etdev_alloc_skb这个系统函数,预先申请一堆的skb buffer。
从源码中看出,申请过程分两个过程,首先申请一个skb buffer结构。
skb buffer中有一个指针类型的成员变量data,此时的data是没有指向任何有效数据块的。
然后在用kmalloc()申请一块内存,赋给skb->data。
网卡驱动就是用pci_map_single()将skb->data映射入DMA设备。
然后DMA设备会将收到的数据直接写入skb->data指向的内存区。
所以我们只需增加一个内存分配模块,替换网卡驱动skb buffer的分配函数即可。
那是DMA设备会自动的将收到的数据写入我们指定的内存中。
以上是我的思路。
不对的地方请指正。
下面是我的问题。
DMA将数据写入我们分配的内存后,就需要通知用户态的进程来读取了。
问题如下:1.用户态如何读取这部分内存。
我查了查资料,有两种方式可以完成。