好文档就是一把金锄头!
欢迎来到金锄头文库![会员中心]
电子文档交易市场
安卓APP | ios版本
电子文档交易市场
安卓APP | ios版本

DMA网卡零拷贝实现的设计与.docx

12页
  • 卖家[上传人]:平***
  • 文档编号:11122860
  • 上传时间:2017-10-12
  • 文档格式:DOCX
  • 文档大小:874.49KB
  • / 12 举报 版权申诉 马上下载
  • 文本预览
  • 下载提示
  • 常见问题
    • 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 查看从上面的信息可以看出这块网卡是基于 pci 总线的数据接收流程图:INIT_WORK(&adapter->reset_task, e1000_reset_task);INIT_WORK(&adapter->watchdog_task, e1000_watchdog_task);INIT_WORK(&adapter->downshift_task, e1000e_downshift_workaround);INIT_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_driver 这个变量:当执行完网卡注册,然后加载网卡驱动的时候首先执行的是 e1000_probe 函数,在这个函开始pci_register_driver(&e1000_driver);E1000_probealloc_etherdevnetif_napi_adde1000_sw_initInit_timer数中主要完成了:网卡模式的设置, DMA 主从设备的设置, 网卡私有数据的分配,中断处理函数的注册,NAPI 的设置,存放网卡数据帧的相关接收环,DMA BD 结构的初始化,PCI 资源的映射,网卡参数的检测,MAC, PHY, NVM 相关操作的回调函数的初始化,看门狗,硬件复位等相关的初始化下面来看看 e1000_probe 函数, 由于这个函数比较长我们分段讲解:上面的代码主要完成了设置网卡支持DMA模式,由于有些网卡硬件必须在某些地址对齐的地方才能够执行DMA操作,dma_set_mask 就是这个作用。

      继续往下:Pci_save_state, 保存 PCI 配置空间相关信息, alloc_ethernetdev 分配网卡私有数据;得到设备的中断号保存在 netdev->irq 中由与我们操作网卡相关的寄存器是通过把网卡相关的寄存器映射到内核内存空间,然后通过偏移量就可以设置,清除相关的硬件寄存器下面几行代码就是把网卡寄存器空间映射到内核空间便于操作Mmio_stat 为网卡的配置空间的起始地址【以网卡为中心看到的地址】 ,mmio_len 为长度Ioremap 的主要作用是要把网卡的配置空间的资源映射到内核空间【以 CPU 为中心看到的地址】 以后操作 adapter->hw.hw_addr 开始的资源就相当于直接操作网卡寄存器Netdev->netdev_ops = &e1000_netdev_ops;注册网卡相关的回调函数, 主要有 e1000_open, e1000_close 等E1000_set_ethtool_ops; 这个函数主要是提供给用户的操作接口: 主要是当用户执行ifconfig 命令的时候执行的回调函数,显示接收数据的大小,速率,设置 IP 地址等Ifconfig 显示的所有资料都是从这里获取的。

      现在来看看 e1000_sw_init这个函数主要是设置网卡的一些参数,比如接受缓冲区的长度,网卡支持的最大帧大小,最小帧大小,接收环的数目,发送环的数目,设置中断模式,最后关中断;e1000_alloc_queues分配接受环空间大小为 sizeof(struct e1000_ring);上面三行主要是注册和网卡硬件相关操作的回调函数,比如设置 mac 地址,设置网卡 Led灯等Init_timer(&adapter->watchdog_timer), 初始化看门狗定时器;下面的几个 INIT_WORK 初始化相关的任务队列: e1000_reset_task 复位任务,比如拔插网线E1000e_reset 用新的值重新复位硬件Register_netdev 函数是 probe 函数的最后一步,在这个函数中会去调用我们开始设置的回调函数 e1000_open 在这个函数中做进一步的初始化下面讲解 e1000_open, 在 e1000_open函数中主要做了:1. 设置发送缓冲区环相关的初始化 2. 分配接受缓冲区相关的初始化 3.物理 PHY 的初始化 4. 设置中断相关的寄存器,设置 DMA 相关的寄存器 5. 向内核安装中断处理函数 6.NAPI 使能。

      大概流程是这样的: 最开始的时候我们调用 pci_register 向 PCI 总线注册了驱动相关的回调函数, 在 pci_register 完成后会执行我们自己驱动的 e1000_probe 函数,这里面最主要的就是初始化一些寄存器,还有就是设置了 NAPI,然后就会调用 e1000_open 函数这里面主要初始化接收环上面的流程最主要的就是最后四个函数的处理,在这个循环中不断的收取网卡的数据帧,然后向上层传递零拷贝需要修改的地方就主要在这里,原来的驱动存放数据帧的内存是通过__netdev_alloc_skb_ip_align 函数分配的,在 e1000_clean_rx_irq 函数中向协议栈的上层传递也就是说已经脱离了驱动相关的部分,当数据到达用户层后,这块空间由上层释放,这也是为什么说 NAPI 比传统的中断更有效率的原因,当执行 NAPI 的过程中会关中断,但是硬件会继续收数据到我们事先分配好的环形缓冲区中,这也是为什么我们必须先分配一定数量的环形缓冲区,不然的话在我们执行 NAPI 这段时间来的数据就会丢失由于传统的中断是每接受到一个数据包就产生一次中断,如果流量很大的话,CPU 负荷较重基本都在处理中断。

      现在的 NAPI 每次处理的数据帧数我们可以自己设定,在本驱动中为 64 叫做权值,在 e1000_probe 函数中初始化的 netif_napi_add(netdev, &adapter->napi, e1000e_poll, 64); 也就是说每次 NAPI 可以处理 64 个数据帧,当数据帧的个数大于 64 的时候会执行多次NAPI,如果小于 64 执行完 NAPI 后会开中断,把任务从 NAPI 链上移除,下次中断来的时候又会关中断, 添加任务,数据向协议栈上层传递,不停地循环我们现在主要是把原来 DMA 到内存的数据, 替换成由我们自己申请的 buffer 中, 然后我们自己来管理这块 buffer,由我们自己来释放这块 buffer,我们就可以把我们自己管理的缓冲区映射到用户空间这样就不需要经过协议栈,就可以直接取得原始的数据帧主要添加的文件:Mem_poll.c pci_registerE1000_probeE1000_opene1000_request_irq__napi_schedulee1000_clean_rx_irqE1000_poll主要完成: 1. 申请一定数量的缓冲区 2. 把这些缓冲区组织成链表的形式 3. 提供给驱动申请缓冲区的接口 4. 提供给驱动释放缓冲区的接口 5. 把这些缓冲区映射到用户空间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 和 frame_free_list_tail 是连接这些节点链表头和链表尾结构体中的后面相关的变量在代码中具体讲解Mem_poll_init 为内存池的初始化函数主要负责向内核申请连续的页面, 然后格式化成一个一个的大小 2048 的单元Alloc_all_entry_pages() :………..上面的__get_free_pages 主要是向内核申请一定数量连续的页面(一个页面大小一般为4KB)当得到这些页面后,就需要格式化这些页面为一个一个的帧单元并且组成链表的形式(在函数 convert_all_pages_frame()中完成) 。

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

      点击阅读更多内容
      关于金锄头网 - 版权申诉 - 免责声明 - 诚邀英才 - 联系我们
      手机版 | 川公网安备 51140202000112号 | 经营许可证(蜀ICP备13022795号)
      ©2008-2016 by Sichuan Goldhoe Inc. All Rights Reserved.