DMA网卡零拷贝实现的设计与_第1页
DMA网卡零拷贝实现的设计与_第2页
DMA网卡零拷贝实现的设计与_第3页
DMA网卡零拷贝实现的设计与_第4页
DMA网卡零拷贝实现的设计与_第5页
已阅读5页,还剩7页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

1、Intel 82571 零拷贝的设计与实现本文主要阐述基于Intel网卡零拷贝的实现过程, 通常情况下网络数据包到达用户应用程序要经过如下几个过程: 1. 网卡的物理硬件从物理媒体(通常情况下网线)上接收到得信号(数据帧)首先放在网卡自己的一个缓冲区(网卡RAM),在这一过程中通常要进行帧校验(比如FCS), 帧过滤等。2. 如果网卡支持DMA就会启动DMA操作, 把收到的数据帧通过DMA操作放到我们事先申请好的buffer中,DMA操作由硬件自动完成,当然用户要提供给DMA硬件操作必要的参数,包括DMA位置,DMA大小等,有可能还有位置对齐等要求。DMA的具体操作后面详细描述。3. 这一步是

2、DMA零拷贝最重要的一个环节,就是把网卡接收到的数据帧直接映射到用户层,不需要经过内核协议栈的处理。后面详细描述。网卡数据从网络到Linux内核的路径简要分析: 网卡的主要工作原理:发送数据时,计算机把要传输的数据并行写到网卡的缓存,网卡对要传输的数据进编码(10M以太网使用曼切斯特码,100M以太网使用差分曼切斯特码),串行发到传输介质上.接收数据时,则相反。对于网卡而言,每块网卡都有一个唯一的网络节点位置,它是网卡生产厂家在生产时烧入ROM(只读存储芯片)中的,我们把它叫做MAC位置(物理位置),且保证绝对不会重复。MAC为48bit,前24比特由IEEE分配,是需要钱买的,后24bit由

3、网卡生产厂家自行分配.我们日常使用的网卡都是以太网网卡。目前网卡按其传输速度来分可分为10M网卡、10100M自适应网卡以及千兆(1000M)网卡。如果只是作为一般用途,如日常办公等,比较适合使用10M网卡和10100M自适应网卡两种。如果应用于服务器等产品领域,就要选择千兆级及更高级别的网卡。本文主要讲解的是Intel 82571 千兆网卡的网卡驱动:Linux 内核目录:linux-3.4.7/drivers/net/ethernet/intel/e1000e关于这款网卡的硬件信息:更详细的信息可以通过 lspci -vvv查看。从上面的信息可以看出这块网卡是基于pci总线的。数据接收流程

4、图:Init_timere1000_sw_initpci_register_driver(&e1000_driver);netif_napi_addalloc_etherdevE1000_probe开始INIT_WORK(&adapter->reset_task, e1000_reset_task);INIT_WORK(&adapter->watchdog_task, e1000_watchdog_task);INIT_WORK(&adapter->downshift_task, e1000e_downshift_workaround);INI

5、T_WORK(&adapter->update_phy_task, e1000e_update_phy_task);INIT_WORK(&adapter->print_hang_task, e1000_print_hw_hang);大概的流程框架就是这个样子的了, 现在一边对照源码一边解析相关的功能。e1000_init_module函数是整个网卡驱动的入口点, 在这个函数中主要做的事情是调用pci_register_driver函数向PCI子系统注册相关的回调函数当模块加载的时候(也就是执行insmod modname.ko)会去调用相关的函数。我们看看e1000

6、_driver这个变量:当执行完网卡注册,然后加载网卡驱动的时候首先执行的是e1000_probe函数,在这个函数中主要完成了:网卡模式的设置, DMA主从设备的设置, 网卡私有数据的分配,中断处理函数的注册,NAPI的设置,存放网卡数据帧的相关接收环,DMA BD结构的初始化,PCI资源的映射,网卡参数的检测,MAC, PHY, NVM相关操作的回调函数的初始化,看门狗,硬件复位等相关的初始化。下面来看看e1000_probe函数, 由于这个函数比较长我们分段讲解: 上面的代码主要完成了设置网卡支持模式,由于有些网卡硬件必须在某些位置对齐的地方才能够执行操作,dma_set_mask就是这个

7、作用。 继续往下:Pci_save_state, 保存PCI配置空间相关信息, alloc_ethernetdev 分配网卡私有数据;得到设备的中断号保存在netdev->irq中。由与我们操作网卡相关的寄存器是通过把网卡相关的寄存器映射到内核内存空间,然后通过偏移量就可以设置,清除相关的硬件寄存器。下面几行代码就是把网卡寄存器空间映射到内核空间便于操作。Mmio_stat 为网卡的配置空间的起始位置【以网卡为中心看到的位置】,mmio_len为长度。Ioremap的主要作用是要把网卡的配置空间的资源映射到内核空间【以CPU为中心看到的位置】。以后操作adapter->hw.hw_

8、addr开始的资源就相当于直接操作网卡寄存器。Netdev->netdev_ops = &e1000_netdev_ops;注册网卡相关的回调函数, 主要有e1000_open, e1000_close等E1000_set_ethtool_ops; 这个函数主要是提供给用户的操作接口: 主要是当用户执行ifconfig命令的时候执行的回调函数,显示接收数据的大小,速率,设置IP位置等Ifconfig显示的所有资料都是从这里获取的。现在来看看e1000_sw_init这个函数主要是设置网卡的一些参数,比如接受缓冲区的长度,网卡支持的最大帧大小,最小帧大小,接收环的数目,发送环的数目

9、,设置中断模式,最后关中断;e1000_alloc_queues分配接受环空间大小为sizeof(struct e1000_ring);上面三行主要是注册和网卡硬件相关操作的回调函数,比如设置mac位置,设置网卡Led灯等。Init_timer(&adapter->watchdog_timer), 初始化看门狗定时器;下面的几个INIT_WORK初始化相关的任务队列: e1000_reset_task复位任务,比如拔插网线。E1000e_reset用新的值重新复位硬件。Register_netdev函数是probe函数的最后一步,在这个函数中会去调用我们开始设置的回调函数e100

10、0_open在这个函数中做进一步的初始化下面讲解e1000_open, 在e1000_open函数中主要做了:1. 设置发送缓冲区环相关的初始化 2. 分配接受缓冲区相关的初始化 3.物理PHY的初始化 4. 设置中断相关的寄存器,设置DMA相关的寄存器 5. 向内核安装中断处理函数 6.NAPI使能。大概流程是这样的: 最开始的时候我们调用pci_register向PCI总线注册了驱动相关的回调函数, 在pci_register完成后会执行我们自己驱动的e1000_probe函数,这里面最主要的就是初始化一些寄存器,还有就是设置了NAPI,然后就会调用e1000_open函数这里面主要初始化

11、接收环。E1000_polle1000_clean_rx_irq_napi_schedulepci_registerE1000_probeE1000_opene1000_request_irq上面的流程最主要的就是最后四个函数的处理,在这个循环中不断的收取网卡的数据帧,然后向上层传递。零拷贝需要修改的地方就主要在这里,原来的驱动存放数据帧的内存是通过_netdev_alloc_skb_ip_align函数分配的,在e1000_clean_rx_irq函数中向协议栈的上层传递也就是说已经脱离了驱动相关的部分,当数据到达用户层后,这块空间由上层释放,这也是为什么说NAPI比传统的中断更有效率的原因

12、,当执行NAPI的过程中会关中断,但是硬件会继续收数据到我们事先分配好的环形缓冲区中,这也是为什么我们必须先分配一定数量的环形缓冲区,不然的话在我们执行NAPI这段时间来的数据就会丢失。由于传统的中断是每接受到一个数据包就产生一次中断,如果流量很大的话,CPU负荷较重基本都在处理中断。 现在的NAPI每次处理的数据帧数我们可以自己设定,在本驱动中为64叫做权值,在e1000_probe函数中初始化的netif_napi_add(netdev, &adapter->napi, e1000e_poll, 64); 也就是说每次NAPI可以处理64个数据帧,当数据帧的个数大于64的时候

13、会执行多次NAPI,如果小于64执行完NAPI后会开中断,把任务从NAPI链上移除,下次中断来的时候又会关中断, 添加任务,数据向协议栈上层传递,不停地循环。 我们现在主要是把原来DMA到内存的数据, 替换成由我们自己申请的buffer中, 然后我们自己来管理这块buffer,由我们自己来释放这块buffer,我们就可以把我们自己管理的缓冲区映射到用户空间。这样就不需要经过协议栈,就可以直接取得原始的数据帧。主要添加的文件:Mem_poll.c 主要完成: 1. 申请一定数量的缓冲区 2. 把这些缓冲区组织成链表的形式 3. 提供给驱动申请缓冲区的接口 4. 提供给驱动释放缓冲区的接口 5.

14、把这些缓冲区映射到用户空间Sniffer.c 主要实现了把这些用户层和内核的接口及其直接把数据帧从内核中取走,修改相关的指针。现在来看看mem_poll.c的实现:先来看看一个重要的数据结构,mem_poll.c主要是围绕这个数据结构展开(在mem_poll.h中)我们把申请到得缓冲区组织成一个链表,链表的每个节点我们来装一个数据帧,大小为2048字节,当然这个节点的开始一段有很小一部分的管理结构大小为sizeof(struct frame_format)也就是说我们可以处理大最帧为2048-sizeof(struct frame_format)。Frame_free_list_head和fr

15、ame_free_list_tail 是连接这些节点链表头和链表尾。结构体中的后面相关的变量在代码中具体讲解。Mem_poll_init为内存池的初始化函数主要负责向内核申请连续的页面, 然后格式化成一个一个的大小2048的单元。Alloc_all_entry_pages() :.上面的_get_free_pages主要是向内核申请一定数量连续的页面(一个页面大小一般为4KB)当得到这些页面后,就需要格式化这些页面为一个一个的帧单元并且组成链表的形式(在函数convert_all_pages_frame()中完成)。我们主要提供给驱动四个个函数:第一个函数主要是当网卡收到数据帧的后,我们从我们

16、自己的链表中取出一个帧节点给网卡DMA使用,当数据放到我们的缓冲区后驱动就会执行e1000_clean_rx_irq就会释放就这个节点(调用第二个函数free_one_frame_node),然后我们把这个节点应经映射到用户层,用户层取走数据,修改相关的指针,然后我们自己会回收这个节点,把这个节点又挂回链表。这样就形成了一个取数据释放数据的循环。当网卡驱动最开始加载的时候我们要调用mem_poll_init初始化, 驱动卸载的时候要调用mem_poll_destroy释放相关的资源。 在网卡驱动加载到卸载的整个过程中,我们所申请的内存资源一直由我们自己管理。我们最主要的工作时要实现内核到用户层

17、的映射mmap, 首先我们要产生一个设备文件本文中的/dev/ZeroCopy,通过自己实现内核层的mmap然后和用户层了解起来。Misc_register(&zerocopy_device)的主要目的是生成设备文件,然后用户层和内核层通过这个设备文件把内存buffer关联起来。 我们要实现这个设备文件的mmap函数。上面的update_list_timer主要是当用户取走数据帧,内核层回收帧节点, 每隔一个jiffies执行一次。Generate_proc_file主要是导出一些信息到/proc目录下:最后我们来看看mmap的实现:mmap 把内核内存缓冲区映射到用户层,当我们在用户

18、层执行mmap函数的时候得到的位置就可以执行取数据。来看看用户层的代码:sniffer.c当用户层执行mmap后我们就得到了一些位置,在这些位置开始的地方有网卡通过DMA存放的原始帧,在管理帧的结构struct frame_fromat中存放有帧的大小。用户就可直接取得数据。上面的为用户的态mmap上面的两个变量指示了帧的大小,及其这个帧数据部分的开始位置(这个位置用户层可以直接操作)。 这样就得到了数据。当数据取走后修改控制环指针,等待内核回收帧节点。回收帧节点的处理函数我们刚才讲过,每个jiffies执行一次。这样循环的收数据,释放节点。现在来看看怎么把我们的代码融入到整个驱动模块中去:当分配DMA缓冲区的时候我们把我们自己的buffer提供给DMA使用, 599行换成600行的代码.当申

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论