网卡驱动和队列层中的数据包接收Linux TCPIP协议栈笔记.docx
- 文档编号:9291050
- 上传时间:2023-02-04
- 格式:DOCX
- 页数:16
- 大小:21.33KB
网卡驱动和队列层中的数据包接收Linux TCPIP协议栈笔记.docx
《网卡驱动和队列层中的数据包接收Linux TCPIP协议栈笔记.docx》由会员分享,可在线阅读,更多相关《网卡驱动和队列层中的数据包接收Linux TCPIP协议栈笔记.docx(16页珍藏版)》请在冰豆网上搜索。
网卡驱动和队列层中的数据包接收LinuxTCPIP协议栈笔记
原文地址:
LinuxTCP/IP协议栈笔记
网卡驱动和队列层中的数据包接收
作者:
kendo
Kernel:
2.6.12
文章对于我们理解TCP发送数据包以及收取数据包非常有帮助。
四、网卡的数据接收
内核如何从网卡接受数据,传统的经典过程:
引用
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,这儿不干你事,自己找乐子去:
引用
1、首先,内核在主内存中为收发数据建立一个环形的缓冲队列(通常叫DMA环形缓冲区)。
2、内核将这个缓冲区通过DMA映射,把这个队列交给网卡;
3、网卡收到数据,就直接放进这个环形缓冲区了——也就是直接放进主内存了;然后,向系统产生一个中断;
4、内核收到这个中断,就取消DMA映射,这样,内核就直接从主内存中读取数据;
——呵呵,这一个过程比传统的过程少了不少工作,因为设备直接把数据放进了主内存,不需要CPU的干预,效率是不是提高不少?
对应以上4步,来看它的具体实现:
1、分配环形DMA缓冲区
Linux内核中,用skb来描述一个缓存,所谓分配,就是建立一定数量的skb,然后把它们组织成一个双向链表;
2、建立DMA映射
内核通过调用
dma_map_single(structdevice*dev,void*buffer,size_tsize,enumdma_data_directiondirection)
建立映射关系。
structdevice*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缓冲区中的内容。
关于DMA映射的更多内容,可以参考《Linux设备驱动程序》“内存映射和DMA”章节相关内容!
OK,有了这些知识,我们就可以来看e100的代码了,它跟上面讲的步骤基本上一样的——绕了这么多圈子,就是想绕到e100上面了,呵呵!
在e100_open函数中,调用e100_up,我们前面分析它时,略过了一个重要的东东,就是环形缓冲区的建立,这一步,是通过
e100_rx_alloc_list函数调用完成的:
static
inte100_rx_alloc_list(structnic*nic)
1.{
2.structrx*rx;
3.unsignedinti,count=nic->params.rfds.count;
4.
5.nic->rx_to_use=nic->rx_to_clean=NULL;
6.nic->ru_running=RU_UNINITIALIZED;
7.
8./*结构structrx用来描述一个缓冲区节点,这里分配了count个*/
9.if(!
(nic->rxs=kmalloc(sizeof(structrx)*count,GFP_ATOMIC)))
10.return-ENOMEM;
11.memset(nic->rxs,0,sizeof(structrx)*count);
12.
13./*虽然是连续分配的,不过还是遍历它,建立双向链表,然后为每一个rx的skb指针分员分配空间
14.skb用来描述内核中的一个数据包,呵呵,说到重点了*/
15.for(rx=nic->rxs,i=0;i 16.rx->next=(i+1 rx+1: nic->rxs; 17.rx->prev=(i==0)? nic->rxs+count-1: rx-1; 18.if(e100_rx_alloc_skb(nic,rx)){/*分配缓存*/ 19.e100_rx_clean_list(nic); 20.return-ENOMEM; 21.} 22.} 23. 24.nic->rx_to_use=nic->rx_to_clean=nic->rxs; 25.nic->ru_running=RU_SUSPENDED; 26. 27.return0; 28.} staticinte100_rx_alloc_list(structnic*nic){structrx*rx;unsignedinti,count=nic->params.rfds.count;nic->rx_to_use=nic->rx_to_clean=NULL;nic->ru_running=RU_UNINITIALIZED;/*结构structrx用来描述一个缓冲区节点,这里分配了count个*/if(! (nic->rxs=kmalloc(sizeof(structrx)*count,GFP_ATOMIC)))return-ENOMEM;memset(nic->rxs,0,sizeof(structrx)*count);/*虽然是连续分配的,不过还是遍历它,建立双向链表,然后为每一个rx的skb指针分员分配空间skb用来描述内核中的一个数据包,呵呵,说到重点了*/for(rx=nic->rxs,i=0;i 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;return0;} #defineRFD_BUF_LEN(sizeof(structrfd)+VLAN_ETH_FRAME_LEN) 1.static inline inte100_rx_alloc_skb(structnic*nic,structrx*rx) 2.{ 3./*skb缓存的分配,是通过调用系统函数dev_alloc_skb来完成的,它同内核栈中通常调用alloc_skb的区别在于, 4.它是原子的,所以,通常在中断上下文中使用*/ 5.if(! (rx->skb=dev_alloc_skb(RFD_BUF_LEN+NET_IP_ALIGN))) 6.return-ENOMEM; 7. 8./*初始化必要的成员*/ 9.rx->skb->dev=nic->netdev; 10.skb_reserve(rx->skb,NET_IP_ALIGN); 11./*这里在数据区之前,留了一块sizeof(structrfd)这么大的空间,该结构的 12.一个重要作用,用来保存一些状态信息,比如,在接收数据之前,可以先通过 13.它,来判断是否真有数据到达等,诸如此类*/ 14.memcpy(rx->skb->data,&nic->blank_rfd,sizeof(structrfd)); 15./*这是最关键的一步,建立DMA映射,把每一个缓冲区rx->skb->data都映射给了设备,缓存区节点 16.rx利用dma_addr保存了每一次映射的地址,这个地址后面会被用到*/ 17.rx->dma_addr=pci_map_single(nic->pdev,rx->skb->data, 18.RFD_BUF_LEN,PCI_DMA_BIDIRECTIONAL); 19. 20.if(pci_dma_mapping_error(rx->dma_addr)){ 21.dev_kfree_skb_any(rx->skb); 22.rx->skb=0; 23.rx->dma_addr=0; 24.return-ENOMEM; 25.} 26. 27./*LinktheRFDtoendofRFAbylinkingpreviousRFDto 28.*thisone,andclearingELbitofprevious.*/ 29.if(rx->prev->skb){ 30.structrfd*prev_rfd=(structrfd*)rx->prev->skb->data; 31./*put_unaligned(val,ptr);用到把var放到ptr指针的地方,它能处理处理内存对齐的问题 32.prev_rfd是在缓冲区开始处保存的一点空间,它的link成员,也保存了映射后的地址*/ 33.put_unaligned(cpu_to_le32(rx->dma_addr), 34.(u32*)&prev_rfd->link); 35.wmb(); 36.prev_rfd->command&=~cpu_to_le16(cb_el); 37.pci_dma_sync_single_for_device(nic->pdev,rx->prev->dma_addr, 38.sizeof(structrfd),PCI_DMA_TODEVICE); 39.} 40. 41.return0; 42.} #defineRFD_BUF_LEN(sizeof(structrfd)+VLAN_ETH_FRAME_LEN)staticinlineinte100_rx_alloc_skb(structnic*nic,structrx*rx){/*skb缓存的分配,是通过调用系统函数dev_alloc_skb来完成的,它同内核栈中通常调用alloc_skb的区别在于,它是原子的,所以,通常在中断上下文中使用*/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);/*这里在数据区之前,留了一块sizeof(structrfd)这么大的空间,该结构的一个重要作用,用来保存一些状态信息,比如,在接收数据之前,可以先通过它,来判断是否真有数据到达等,诸如此类*/memcpy(rx->skb->data,&nic->blank_rfd,sizeof(structrfd));/*这是最关键的一步,建立DMA映射,把每一个缓冲区rx->skb->data都映射给了设备,缓存区节点rx利用dma_addr保存了每一次映射的地址,这个地址后面会被用到*/rx->dma_addr=pci_map_single(nic->pdev,rx->skb->data,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;}/*LinktheRFDtoendofRFAbylinkingpreviousRFDto*thisone,andclearingELbitofprevious.*/if(rx->prev->skb){structrfd*prev_rfd=(structrfd*)rx->prev->skb->data;/*put_unaligned(val,ptr);用到把var放到ptr指针的地方,它能处理处理内存对齐的问题prev_rfd是在缓冲区开始处保存的一点空间,它的link成员,也保存了映射后的地址*/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(structrfd),PCI_DMA_TODEVICE);}return0;} e100_rx_alloc_list函数在一个循环中,建立了环形缓冲区,并调用e100_rx_alloc_skb为每个缓冲区分配了空间,并做了 DMA映射。 这样,我们就可以来看接收数据的过程了。 前面我们讲过,中断函数中,调用netif_rx_schedule,表明使用轮询技术,系统会在未来某一时刻,调用设备的poll函数: static inte100_poll(structnet_device*netdev,int*budget) 1.{ 2.structnic*nic=netdev_priv(netdev); 3.unsignedintwork_to_do=min(netdev->quota,*budget); 4.unsignedintwork_done=0; 5.inttx_cleaned; 6. 7.e100_rx_clean(nic,&work_done,work_to_do); 8.tx_cleaned=e100_tx_clean(nic); 9. 10./*IfnoRxandTxcleanupworkwasdone,exitpollingmode.*/ 11.if((! tx_cleaned&&(work_done==0))||! netif_running(netdev)){ 12.netif_rx_complete(netdev); 13.e100_enable_irq(nic); 14.return0; 15.} 16. 17.*budget-=work_done; 18.netdev->quota-=work_done; 19. 20.return1; 21.} staticinte100_poll(structnet_device*netdev,int*budget){structnic*nic=netdev_priv(netdev);unsignedintwork_to_do=min(netdev->quota,*budget);unsignedintwork_done=0;inttx_cleaned;e100_rx_clean(nic,&work_done,work_to_do);tx_cleaned=e100_tx_clean(nic);/*IfnoRxandTxcleanupworkwasdone,exitpollingmode.*/if((! tx_cleaned&&(work_done==0))||! netif_running(netdev)){netif_rx_complete(netdev);e100_enable_irq(nic);return0;}*budget-=work_done;netdev->quota-=work_done;return1;} 目前,我们只关心rx,所以,e100_rx_clean函数就成了我们关注的对像,它用来从缓冲队列中接收全部数据(这或许是取名为clean的原因吧! ): static inline voide100_rx_clean(structnic*nic,unsignedint*work_done, 1.unsignedintwork_to_do) 2.{ 3.structrx*rx; 4.intrestart_required=0; 5.structrx*rx_to_start=NULL; 6. 7./*arewealreadyrnr? thenpayattention! ! ! thisensuresthat 8.*thestatemachineprogressionneverallowsastartwitha 9.*partiallycleanedlist,avoidingaracebetweenhardware 10.*andrx_to_cleanwheninNAPImode*/ 11.if(RU_SUSPENDED==nic->ru_running) 12.restart_required=1; 13. 14./*函数最重要的工作,就是遍历环形缓冲区,接收数据*/ 15.for(rx=nic->rx_to_clean;rx->skb;rx=nic->rx_to_clean=rx->next){ 16.interr=e100_rx_indicate(nic,rx,work_done,work_to_do); 17.if(-EAGAIN==err){ 18./*hitquotasohavemoreworktodo,restartonce 19.*cleanupiscomplete*/ 20.restart_required=0; 21.break; 22.}else if(-ENODATA==err) 23.break;/*Nomoretoclean*/ 24.} 25. 26./*saveourstartingpointastheplacewe'llrestartthereceiver*/ 27.if(restart_required) 28.rx_to_start=nic->rx_to_clean; 29. 30./*Allocnewskbstorefilllist*/ 31.for(rx=nic->rx_to_use;! rx->skb;rx=nic->rx_to_use=rx->next){ 32.if(unlikely(e100_rx_alloc_skb(nic,rx))) 33.break;/*Betterlucknexttime(seewatchdog)*/ 34.} 35. 36.if(restart_required){ 37.//ackthernr? 38.writeb(stat_ack_rnr,&nic->csr->scb.stat_ack); 39.e100_start_receiver(nic,rx_to_start); 40.if(work_done) 41.(*work_done)++; 42.} 43.} staticinlinevoide100_rx_clean(structnic*nic,unsignedint*work_done,unsignedintwork_to_do) { structrx*rx; intrestart_required=0; structrx*rx_to_start=NULL;/*arewealreadyrnr? thenpayattention! ! ! thisensuresthat*thestatemachineprogressionneverallowsastartwitha*partiallycleanedlist,avoidingaracebetweenhardware*andrx_to_cleanwheninNAPImode*/if(RU_SUSPENDED==nic->ru_running) restart_required=1; /*函数最重要的工作,就是遍历环形缓冲区,接收数据*/ for(rx=nic->rx_to_clean;rx->skb;rx=nic->rx_to_clean=rx->next)
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 网卡驱动和队列层中的数据包接收Linux TCPIP协议栈笔记 网卡 驱动 队列 中的 数据包 接收 Linux TCPIP 协议 笔记
