四、网卡的数据接收

内核如何从网卡接受数据,传统的经典过程:
1、数据到达网卡;
2、网卡产生一个中断给内核;
3、内核使用I/O指令,从网卡I/O区域中去读取数据;

我们在许多网卡驱动中,都可以在网卡的中断函数中见到这一过程。

但是,这一种方法,有一种重要的问题,就是大流量的数据来到,网卡会产生大量的中断,内核在中断上下文中,会浪费大量的资源来处理中断本身。所以,一个问题是,“可不可以不使用中断”,这就是轮询技术,所谓NAPI技术,说来也不神秘,就是说,内核屏蔽中断。

从这个描述本身可以看到,如果数据量少,轮询同样占用大量的不必要的CPU资源,大家各有所长吧,呵呵……

OK,另一个问题,就是从网卡的I/O区域,包括I/O寄存器或I/O内存中去读取数据,这都要CPU去读,也要占用CPU资源,“CPU从I/O区域读,然后把它放到内存(这个内存指的是系统本身的物理内存,跟外设的内存不相干,也叫主内存)中”。于是自然地,就想到了DMA技术——让网卡直接从主内存之间读写它们的I/O数据,CPU,这儿不干你事,自己找乐子去:

DMA:

Direct Memory Access,直接内存访问,运行外围设备和主内存之间直接传输他们的I/O数据,而不需要处理器的参与。驱动程序在初始化阶段分配DMA环形缓冲区。

1、首先,内核在主内存中为收发数据建立一个环形的缓冲队列(通常叫DMA环形缓冲区)。
2、内核将这个缓冲区通过DMA映射,把这个队列交给网卡;
3、网卡收到数据,就直接放进这个环形缓冲区了——也就是直接放进主内存了;然后,向系统产生一个中断;
4、内核收到这个中断,就取消DMA映射,这样,内核就直接从主内存中读取数据;

——呵呵,这一个过程比传统的过程少了不少工作,因为设备直接把数据放进了主内存,不需要CPU的干预,效率是不是提高不少?

对应以上4步,来看它的具体实现:
1. 分配环形DMA缓冲区
Linux内核中,用skb来描述一个缓存,所谓分配,就是建立一定数量的skb,然后把它们组织成一个双向链表;

2. 建立DMA映射
内核通过调用
dma_map_single(struct device *dev,void *buffer,size_t size,enumdma_#_direction direction)建立映射关系。
struct device *dev,描述一个设备;
buffer:把哪个地址映射给设备;也就是某一个skb——要映射全部,当然是做一个双向链表的循环即可;
size:缓存大小;
direction:映射方向——谁传给谁:一般来说,是“双向”映射,数据在设备和内存之间双向流动;

对于PCI设备而言(网卡一般是PCI的),通过另一个包裹函数pci_map_single,这样,就把buffer交给设备了!设备可以直接从里边读/取数据。

3. 这一步由硬件完成;

4. 取消映射
dma_unmap_single,对PCI而言,大多调用它的包裹函数pci_unmap_single,不取消的话,缓存控制权还在设备手里,要调用它,把主动权掌握在CPU手里——因为我们已经接收到数据了,应该由CPU把数据交给上层网络栈;
当然,不取消之前,通常要读一些状态位信息,诸如此类,一般是调用dma_sync_single_for_cpu()让CPU在取消映射前,就可以访问DMA缓冲区中的内容。

OK,有了这些知识,我们就可以来看e100的代码了,它跟上面讲的步骤基本上一样的——绕了这么多圈子,就是想绕到e100上面了,呵呵!

e100网卡收报流程分析

在e100_open函数中,调用e100_up,我们前面分析它时,略过了一个重要的东东,就是环形缓冲区的建立,这一步,是通过

e100_rx_alloc_list函数调用完成的:

1. e100_rx_alloc_list()

建立环形缓冲区

static int e100_rx_alloc_list(struct nic *nic)
{struct rx *rx;unsigned int i, count = nic->params.rfds.count;nic->rx_to_use = nic->rx_to_clean = NULL;nic->ru_running = RU_UNINITIALIZED;if(!(nic->rxs = kmalloc(sizeof(struct rx) * count, GFP_ATOMIC)))return -ENOMEM;memset(nic->rxs, 0, sizeof(struct rx) * count);for(rx = nic->rxs, i = 0; i < count; rx++, i++) {rx->next = (i + 1 < count) ? rx + 1 : nic->rxs;rx->prev = (i == 0) ? nic->rxs + count - 1 : rx - 1;if(e100_rx_alloc_skb(nic, rx)) {               e100_rx_clean_list(nic);return -ENOMEM;}}nic->rx_to_use = nic->rx_to_clean = nic->rxs;nic->ru_running = RU_SUSPENDED;return 0;
}

2.e100_rx_alloc_skb()

具体用来分配skb内存。

#define RFD_BUF_LEN (sizeof(struct rfd) + VLAN_ETH_FRAME_LEN)
static inline int e100_rx_alloc_skb(struct nic *nic, struct rx *rx)
{if(!(rx->skb = dev_alloc_skb(RFD_BUF_LEN + NET_IP_ALIGN)))return -ENOMEM;rx->skb->dev = nic->netdev;skb_reserve(rx->skb, NET_IP_ALIGN);memcpy(rx->skb->#, &nic->blank_rfd, sizeof(struct rfd));rx->dma_addr = pci_map_single(nic->pdev, rx->skb->#,RFD_BUF_LEN, PCI_DMA_BIDIRECTIONAL);if(pci_dma_mapping_error(rx->dma_addr)) {dev_kfree_skb_any(rx->skb);rx->skb = 0;rx->dma_addr = 0;return -ENOMEM;}if(rx->prev->skb) {struct rfd *prev_rfd = (struct rfd *)rx->prev->skb->#;put_unaligned(cpu_to_le32(rx->dma_addr),(u32 *)&prev_rfd->link);wmb();prev_rfd->command &= ~cpu_to_le16(cb_el);pci_dma_sync_single_for_device(nic->pdev, rx->prev->dma_addr,sizeof(struct rfd), PCI_DMA_TODEVICE);}return 0;
}

e100_rx_alloc_list函数在一个循环中,建立了环形缓冲区,并调用e100_rx_alloc_skb为每个缓冲区分配了空间,并做了
DMA映射。这样,我们就可以来看接收数据的过程了。

前面我们讲过,中断函数中,调用netif_rx_schedule,表明使用轮询技术,系统会在未来某一时刻,调用设备的poll函数

3.e100_poll()

e100驱动轮训poll()函数

static int e100_poll(struct net_device *netdev, int *budget)
{struct nic *nic = netdev_priv(netdev);unsigned int work_to_do = min(netdev->quota, *budget);unsigned int work_done = 0;int tx_cleaned;e100_rx_clean(nic, &work_done, work_to_do);tx_cleaned = e100_tx_clean(nic);if((!tx_cleaned && (work_done == 0)) || !netif_running(netdev)) {netif_rx_complete(netdev);e100_enable_irq(nic);return 0;}*budget -= work_done;netdev->quota -= work_done;return 1;
}

目前,我们只关心rx,所以,e100_rx_clean函数就成了我们关注的对像,它用来从缓冲队列中接收全部数据(这或许是取名为clean的原因吧!

4. e100_rx_clean()

遍历环形缓冲区,接收数据

static inline void e100_rx_clean(struct nic *nic, unsigned int *work_done,unsigned int work_to_do)
{struct rx *rx;int restart_required = 0;struct rx *rx_to_start = NULL;if(RU_SUSPENDED == nic->ru_running)restart_required = 1;for(rx = nic->rx_to_clean; rx->skb; rx = nic->rx_to_clean = rx->next) {int err = e100_rx_indicate(nic, rx, work_done, work_to_do);if(-EAGAIN == err) {restart_required = 0;break;} else if(-ENO# == err)break;}if(restart_required)rx_to_start = nic->rx_to_clean;for(rx = nic->rx_to_use; !rx->skb; rx = nic->rx_to_use = rx->next) {if(unlikely(e100_rx_alloc_skb(nic, rx)))break;}if(restart_required) {// ack the rnr?writeb(stat_ack_rnr, &nic->csr->scb.stat_ack);e100_start_receiver(nic, rx_to_start);if(work_done)(*work_done)++;}
}

5. e100_rx_indicate()

建立skb?,把环形缓冲区中数据取出来。


static int e100_rx_indicate(struct nic *nic, struct rx *rx,unsigned int *work_done, unsigned int work_to_do)
{struct net_device *dev = nic->netdev;struct sk_buff *skb = rx->skb;struct rfd *rfd = (struct rfd *)skb->data;u16 rfd_status, actual_size;u16 fcs_pad = 0;if (unlikely(work_done && *work_done >= work_to_do))return -EAGAIN;/* Need to sync before taking a peek at cb_complete bit */pci_dma_sync_single_for_cpu(nic->pdev, rx->dma_addr,sizeof(struct rfd), PCI_DMA_BIDIRECTIONAL);rfd_status = le16_to_cpu(rfd->status);netif_printk(nic, rx_status, KERN_DEBUG, nic->netdev,"status=0x%04X\n", rfd_status);dma_rmb(); /* read size after status bit *//* If data isn't ready, nothing to indicate */if (unlikely(!(rfd_status & cb_complete))) {/* If the next buffer has the el bit, but we think the receiver* is still running, check to see if it really stopped while* we had interrupts off.* This allows for a fast restart without re-enabling* interrupts */if ((le16_to_cpu(rfd->command) & cb_el) &&(RU_RUNNING == nic->ru_running))if (ioread8(&nic->csr->scb.status) & rus_no_res)nic->ru_running = RU_SUSPENDED;pci_dma_sync_single_for_device(nic->pdev, rx->dma_addr,sizeof(struct rfd),PCI_DMA_FROMDEVICE);return -ENODATA;}/* Get actual data size */if (unlikely(dev->features & NETIF_F_RXFCS))fcs_pad = 4;actual_size = le16_to_cpu(rfd->actual_size) & 0x3FFF;if (unlikely(actual_size > RFD_BUF_LEN - sizeof(struct rfd)))actual_size = RFD_BUF_LEN - sizeof(struct rfd);/* Get data */pci_unmap_single(nic->pdev, rx->dma_addr,RFD_BUF_LEN, PCI_DMA_BIDIRECTIONAL);/* If this buffer has the el bit, but we think the receiver* is still running, check to see if it really stopped while* we had interrupts off.* This allows for a fast restart without re-enabling interrupts.* This can happen when the RU sees the size change but also sees* the el bit set. */if ((le16_to_cpu(rfd->command) & cb_el) &&(RU_RUNNING == nic->ru_running)) {if (ioread8(&nic->csr->scb.status) & rus_no_res)nic->ru_running = RU_SUSPENDED;}/* Pull off the RFD and put the actual data (minus eth hdr) */skb_reserve(skb, sizeof(struct rfd));skb_put(skb, actual_size);skb->protocol = eth_type_trans(skb, nic->netdev);/* If we are receiving all frames, then don't bother* checking for errors.*/if (unlikely(dev->features & NETIF_F_RXALL)) {if (actual_size > ETH_DATA_LEN + VLAN_ETH_HLEN + fcs_pad)/* Received oversized frame, but keep it. */nic->rx_over_length_errors++;goto process_skb;}if (unlikely(!(rfd_status & cb_ok))) {/* Don't indicate if hardware indicates errors */dev_kfree_skb_any(skb);} else if (actual_size > ETH_DATA_LEN + VLAN_ETH_HLEN + fcs_pad) {/* Don't indicate oversized frames */nic->rx_over_length_errors++;dev_kfree_skb_any(skb);} else {
process_skb:dev->stats.rx_packets++;dev->stats.rx_bytes += (actual_size - fcs_pad);netif_receive_skb(skb);if (work_done)(*work_done)++;}rx->skb = NULL;return 0;
}

网卡驱动执行到这里,数据接收的工作,也就处理完成了。但是,使用这一种方法的驱动,省去了网络栈中一个重要的内容,就是“队列层”,让我们来看看,传统中断接收数据包模式下,使用netif_rx函数调用,又会发生什么。

这一系列三篇以后再看看:

https://blog.csdn.net/newnewman80/article/details/7987138

https://blog.csdn.net/newnewman80/article/details/7987160

https://blog.csdn.net/newnewman80/article/details/7987210

e100网卡收包流程分析相关推荐

  1. 网卡收包流程分析(一)

    由于本人工作内容主要集中于kernel的网络子系统,刚接触这个模块,于是想梳理一下网卡驱动的收包过程,以下内容为个人理解,如有不对,希望大家能够多多指正,相互成长~ 后续会持续更新有关kernel网络 ...

  2. DPDK 网卡收包流程

    Table of Contents 1.Linux网络收发包流程 1.1 网卡与liuux驱动交互 1.2  linux驱动与内核协议栈交互 题外1: 中断处理逻辑 题外2:中断的弊端 2.linux ...

  3. 【无线】【流程】QCA无线驱动收包流程分析

    概述: 无线驱动的收包过程是基于中断的处理方式.在准备接收数据之前,驱动需要先进行初始化接收数据使用到的相关结构( sc_rxbuf和rxfifo ).当数据包到达时,硬件会首先进行 DMA,完成以后 ...

  4. 32位网卡驱动 2008_DPDK之网卡收包流程

    1.导读 一个网络报文从网卡接收到被应用处理,中间主要需要经历两个阶段: 阶段一:网卡通过其DMA硬件将收到的报文写入到收包队列中(入队) 阶段二:应用从收包队列中读取报文(出队) 下面以ixgbe网 ...

  5. Linux网络协议栈:网卡收包分析

    Table of Contents 网卡收包 一,框架 二,初始化 三,驱动收包 四,内核处理 参考文章 推荐阅读 网卡收包 内核网络模块如何初始化? 内核如何通过网卡驱动收发数据包? 驱动收到的数据 ...

  6. 代码学习-Linux内核网卡收包过程(NAPI)

    本文通过学习RealTek8169/8168/8101网卡的驱动代码(drivers/net/r8169.c).梳理一下Linux下网卡的收包过程. 在下水平相当有限,有不当之处,还请大家斧正^_^ ...

  7. openstack ovs-vswitch收包流程

    数据包从物理网卡进入虚拟机的流程 物理网卡处理 NIC 收到数据包,会先将高低电信号转换到网卡 FIFO 存储器.NIC 首先申请 Ring buffer 的描述,根据描述找到具体的物理地址,NIC ...

  8. 网卡收包基础: 中断-轮询-ring buffer-DMA-NAPI

    参考链接: NAPL模式 NAPL简介 硬中断和软中断 中断与轮询的区别一 ring buffer 一. 中断 从本质上来讲,中断是一种电信号,当设备有某种事件发生时,它就会产生中断,通过总线把电信号 ...

  9. 网络收包流程-收包函数__netif_receive_skb的核心函数__netif_receive_skb_core(三)

    调用关系:netif_receive_skb-->netif_receive_skb-->netif_receive_skb_internal(->__netif_receive_s ...

  10. 服务器网卡收包性能测试

    之前写过不少跟网络相关的 benchmark,比如: * <网络质量评估> * <10G(82599EB) 网卡测试优化(总)> 上面的更多的是放在带宽使用率上,即如何尽可能的 ...

最新文章

  1. 使用Windows live Writer 2012发布ChinaUnix博客
  2. 最近的工作生活的心得感悟,给自己的表现打50分,不及格
  3. 双11猫晚直播:看阿里文娱如何“擒住”高并发、多视角、低卡顿!
  4. 怎么使用PDF编辑器在PDF中插入图片?PDF插入图片的教程
  5. 数据库系统概论(各章知识点总结)
  6. matlab画圆的命令_matlab画圆
  7. 人工智能的算法黑箱与数据正义
  8. 思科、华为路由器破解过程
  9. BLP模型(Bell-La Padula模型)
  10. mumu模拟器android调试,如何使用网易mumu模拟器调试安卓程序?
  11. html模板查询,前台模板查找
  12. 新特汽车在重庆“复活”:打造新品牌“电动屋”,已获网约车牌照
  13. TiDB 社区专栏:让技术人员成为更好的读者/作家
  14. “把每天当倒计时过”是我俩的幸福秘方
  15. ahk写入excel单元格_输出excel数据到GUI 获取excel所有Sheet及字段 Autohotkey
  16. 字符串算法——KMP匹配及Next数组
  17. 消息模板占位符的使用
  18. ug建模文本怎么竖着_UG编程文字加工,全方位实例讲解,文末有作业哦!
  19. Confluence 6 设置一个空间主页
  20. 科普文章:公众电磁辐射与防护的研究

热门文章

  1. css 解决因为书名号不满一行就换行情况
  2. 行业分析报告|全球与中国项目货物物流市场现状及未来发展趋势
  3. ecshop模板如何修改详细图解
  4. [NOIP2012模拟10.25] 剪草 [贪心+dp]
  5. cisco交换机配置记录(一)
  6. JavaScript格式化数字
  7. 《英雄无敌3》的一个独立的扩展版-英雄无敌3死亡阴影下载
  8. Matlab 直方图绘制
  9. oracle中间人投毒漏洞,ORACLE远程投毒漏洞修复(RAC环境)
  10. ubuntu服务器设置定时自动开关机