ImageVerifierCode 换一换
格式:DOCX , 页数:16 ,大小:21.33KB ,
资源ID:9291050      下载积分:3 金币
快捷下载
登录下载
邮箱/手机:
温馨提示:
快捷下载时,用户名和密码都是您填写的邮箱或者手机号,方便查询和重复下载(系统自动生成)。 如填写123,账号就是123,密码也是123。
特别说明:
请自助下载,系统不会自动发送文件的哦; 如果您已付费,想二次下载,请登录后访问:我的下载记录
支付方式: 支付宝    微信支付   
验证码:   换一换

加入VIP,免费下载
 

温馨提示:由于个人手机设置不同,如果发现不能下载,请复制以下地址【https://www.bdocx.com/down/9291050.html】到电脑端继续下载(重复下载不扣费)。

已注册用户请登录:
账号:
密码:
验证码:   换一换
  忘记密码?
三方登录: 微信登录   QQ登录  

下载须知

1: 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。
2: 试题试卷类文档,如果标题没有明确说明有答案则都视为没有答案,请知晓。
3: 文件的所有权益归上传用户所有。
4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
5. 本站仅提供交流平台,并不能对任何下载内容负责。
6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

版权提示 | 免责声明

本文(网卡驱动和队列层中的数据包接收Linux TCPIP协议栈笔记.docx)为本站会员(b****7)主动上传,冰豆网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对上载内容本身不做任何修改或编辑。 若此文所含内容侵犯了您的版权或隐私,请立即通知冰豆网(发送邮件至service@bdocx.com或直接QQ联系客服),我们立即给予删除!

网卡驱动和队列层中的数据包接收Linux TCPIP协议栈笔记.docx

1、网卡驱动和队列层中的数据包接收Linux TCPIP协议栈笔记原文地址:Linux TCP/IP协议栈笔记网卡驱动和队列层中的数据包接收作者:kendoKernel:2.6.12文章对于我们理解TCP发送数据包以及收取数据包非常有帮助。四、网卡的数据接收内核如何从网卡接受数据,传统的经典过程:引用1、数据到达网卡;2、网卡产生一个中断给内核;3、内核使用I/O指令,从网卡I/O区域中去读取数据;我们在许多网卡驱动中,都可以在网卡的中断函数中见到这一过程。但是,这一种方法,有一种重要的问题,就是大流量的数据来到,网卡会产生大量的中断,内核在中断上下文中,会浪费大量的资源来处理中断本身。所以,一个

2、问题是,“可不可以不使用中断”,这就是轮询技术,所谓NAPI技术,说来也不神秘,就是说,内核屏蔽中断,然后隔一会儿就去问网卡,“你有没有数据啊?”从这个描述本身可以看到,哪果数据量少,轮询同样占用大量的不必要的CPU资源,大家各有所长吧,呵呵OK,另一个问题,就是从网卡的I/O区域,包括I/O寄存器或I/O内存中去读取数据,这都要CPU去读,也要占用CPU资源,“CPU从I/O区域读,然后把它放到内存(这个内存指的是系统本身的物理内存,跟外设的内存不相干,也叫主内存)中”。于是自然地,就想到了DMA技术让网卡直接从主内存之间读写它们的I/O数据,CPU,这儿不干你事,自己找乐子去:引用1、首先

3、,内核在主内存中为收发数据建立一个环形的缓冲队列(通常叫DMA环形缓冲区)。2、内核将这个缓冲区通过DMA映射,把这个队列交给网卡;3、网卡收到数据,就直接放进这个环形缓冲区了也就是直接放进主内存了;然后,向系统产生一个中断;4、内核收到这个中断,就取消DMA映射,这样,内核就直接从主内存中读取数据;呵呵,这一个过程比传统的过程少了不少工作,因为设备直接把数据放进了主内存,不需要CPU的干预,效率是不是提高不少?对应以上4步,来看它的具体实现:1、分配环形DMA缓冲区Linux内核中,用skb来描述一个缓存,所谓分配,就是建立一定数量的skb,然后把它们组织成一个双向链表;2、建立DMA映射内

4、核通过调用dma_map_single(struct device *dev,void *buffer,size_t size,enum dma_data_direction direction)建立映射关系。struct device *dev,描述一个设备;buffer:把哪个地址映射给设备;也就是某一个skb要映射全部,当然是做一个双向链表的循环即可;size:缓存大小;direction:映射方向谁传给谁:一般来说,是“双向”映射,数据在设备和内存之间双向流动;对于PCI设备而言(网卡一般是PCI的),通过另一个包裹函数pci_map_single,这样,就把buffer交给设备了!设

5、备可以直接从里边读/取数据。3、这一步由硬件完成;4、取消映射dma_unmap_single,对PCI而言,大多调用它的包裹函数pci_unmap_single,不取消的话,缓存控制权还在设备手里,要调用它,把主动权掌握在CPU手里因为我们已经接收到数据了,应该由CPU把数据交给上层网络栈;当然,不取消之前,通常要读一些状态位信息,诸如此类,一般是调用dma_sync_single_for_cpu()让CPU在取消映射前,就可以访问DMA缓冲区中的内容。关于DMA映射的更多内容,可以参考Linux设备驱动程序“内存映射和DMA”章节相关内容!OK,有了这些知识,我们就可以来看e100的代码了

6、,它跟上面讲的步骤基本上一样的绕了这么多圈子,就是想绕到e100上面了,呵呵!在e100_open函数中,调用e100_up,我们前面分析它时,略过了一个重要的东东,就是环形缓冲区的建立,这一步,是通过e100_rx_alloc_list函数调用完成的:staticint e100_rx_alloc_list(struct nic *nic) 1. 2. struct rx *rx; 3. unsigned int i, count = nic-params.rfds.count; 4. 5. nic-rx_to_use = nic-rx_to_clean = NULL; 6. nic-ru_

7、running = RU_UNINITIALIZED; 7. 8. /*结构struct rx用来描述一个缓冲区节点,这里分配了count个*/ 9. if(!(nic-rxs = kmalloc(sizeof(struct rx) * count, GFP_ATOMIC) 10. return -ENOMEM; 11. memset(nic-rxs, 0, sizeof(struct rx) * count); 12. 13. /*虽然是连续分配的,不过还是遍历它,建立双向链表,然后为每一个rx的skb指针分员分配空间 14. skb用来描述内核中的一个数据包,呵呵,说到重点了*/ 15.

8、for(rx = nic-rxs, i = 0; i next = (i + 1 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. return

9、 0; 28. 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;/*结构struct rx用来描述一个缓冲区节点,这里分配了count个*/if(!(nic-rxs = kmalloc(sizeof(struct rx) * count, GFP_ATOMIC) return -ENOME

10、M;memset(nic-rxs, 0, sizeof(struct rx) * count);/*虽然是连续分配的,不过还是遍历它,建立双向链表,然后为每一个rx的skb指针分员分配空间skb用来描述内核中的一个数据包,呵呵,说到重点了*/for(rx = nic-rxs, i = 0; i next = (i + 1 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-r

11、x_to_use = nic-rx_to_clean = nic-rxs;nic-ru_running = RU_SUSPENDED;return 0;#define RFD_BUF_LEN (sizeof(struct rfd) + VLAN_ETH_FRAME_LEN) 1. staticinlineint e100_rx_alloc_skb(struct nic *nic, struct rx *rx) 2. 3. /*skb缓存的分配,是通过调用系统函数dev_alloc_skb来完成的,它同内核栈中通常调用alloc_skb的区别在于, 4. 它是原子的,所以,通常在中断上下文中使用

12、*/ 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(struct rfd) 这么大的空间,该结构的 12. 一个重要作用,用来保存一些状态信息,比如,在接收数据之前,可以先通过 13. 它,来判断是否真有数据到达等,诸如此类*/ 14. memcpy(rx-skb

13、-data, &nic-blank_rfd, sizeof(struct rfd); 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_

14、any(rx-skb); 22. rx-skb = 0; 23. rx-dma_addr = 0; 24. return -ENOMEM; 25. 26. 27. /* Link the RFD to end of RFA by linking previous RFD to 28. * this one, and clearing EL bit of previous. */ 29. if(rx-prev-skb) 30. struct rfd *prev_rfd = (struct rfd *)rx-prev-skb-data; 31. /*put_unaligned(val,ptr);用

15、到把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(struct rfd), PCI_DM

16、A_TODEVICE); 39. 40. 41. return 0; 42. #define RFD_BUF_LEN (sizeof(struct rfd) + VLAN_ETH_FRAME_LEN)static inline int e100_rx_alloc_skb(struct nic *nic, struct rx *rx)/*skb缓存的分配,是通过调用系统函数dev_alloc_skb来完成的,它同内核栈中通常调用alloc_skb的区别在于,它是原子的,所以,通常在中断上下文中使用*/if(!(rx-skb = dev_alloc_skb(RFD_BUF_LEN + NET_IP

17、_ALIGN) return -ENOMEM;/*初始化必要的成员 */rx-skb-dev = nic-netdev;skb_reserve(rx-skb, NET_IP_ALIGN);/*这里在数据区之前,留了一块sizeof(struct rfd) 这么大的空间,该结构的一个重要作用,用来保存一些状态信息,比如,在接收数据之前,可以先通过它,来判断是否真有数据到达等,诸如此类*/memcpy(rx-skb-data, &nic-blank_rfd, sizeof(struct rfd);/*这是最关键的一步,建立DMA映射,把每一个缓冲区rx-skb-data都映射给了设备,缓存区节点r

18、x利用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;/* Link the RFD to end of RFA by linking previous RFD to * this one,

19、 and clearing EL bit of previous. */if(rx-prev-skb) struct rfd *prev_rfd = (struct rfd *)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 &= c

20、pu_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函数:staticint e100_

21、poll(struct net_device *netdev, int *budget) 1. 2. struct nic *nic = netdev_priv(netdev); 3. unsigned int work_to_do = min(netdev-quota, *budget); 4. unsigned int work_done = 0; 5. int tx_cleaned; 6. 7. e100_rx_clean(nic, &work_done, work_to_do); 8. tx_cleaned = e100_tx_clean(nic); 9. 10. /* If no R

22、x and Tx cleanup work was done, exit polling mode. */ 11. if(!tx_cleaned & (work_done = 0) | !netif_running(netdev) 12. netif_rx_complete(netdev); 13. e100_enable_irq(nic); 14. return 0; 15. 16. 17. *budget -= work_done; 18. netdev-quota -= work_done; 19. 20. return 1; 21. static int e100_poll(struc

23、t 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 no Rx and Tx cleanup work was done, exit polling mod

24、e. */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的原因吧!):staticinlinevoid e100_rx_clean(struct nic *nic,

25、unsigned int *work_done, 1. unsigned int work_to_do) 2. 3. struct rx *rx; 4. int restart_required = 0; 5. struct rx *rx_to_start = NULL; 6. 7. /* are we already rnr? then pay attention! this ensures that 8. * the state machine progression never allows a start with a 9. * partially cleaned list, avoi

26、ding a race between hardware 10. * and rx_to_clean when in NAPI mode */ 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. int err = e100_rx_indicate(nic, rx, work_done, work_t

27、o_do); 17. if(-EAGAIN = err) 18. /* hit quota so have more work to do, restart once 19. * cleanup is complete */ 20. restart_required = 0; 21. break; 22. elseif(-ENODATA = err) 23. break; /* No more to clean */ 24. 25. 26. /* save our starting point as the place well restart the receiver */ 27. if(r

28、estart_required) 28. rx_to_start = nic-rx_to_clean; 29. 30. /* Alloc new skbs to refill list */ 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; /* Better luck next time (see watchdog) */ 34. 35. 36. if(restart_required) 37. / a

29、ck the rnr? 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. 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_

30、to_start = NULL;/* are we already rnr? then pay attention! this ensures that * the state machine progression never allows a start with a * partially cleaned list, avoiding a race between hardware * and rx_to_clean when in NAPI mode */if(RU_SUSPENDED = nic-ru_running)restart_required = 1;/* 函数最重要的工作,就是遍历环形缓冲区,接收数据*/for(rx = nic-rx_to_clean; rx-skb; rx = nic-rx_to_clean = rx-next)

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1