原文地址:TCP/IP学习(30)——L2数据链路层的数据包处理详细流程 作者:GFree_Wind

本文的copyleft归gfree.wind@gmail.com所有,使用GPL发布,可以自由拷贝,转载。但转载请保持文档的完整性,注明原作者及原链接,严禁用于任何商业用途。
作者:gfree.wind@gmail.com
博客:linuxfocus.blog.chinaunix.net

在前面的博文中,我学习了数据包从L2到L5的流程,但是当时因为时间和水平的限制,整个儿流程并没有涉及太多的细节。前两天大致又过了这个流程,发现有不少细节还是需要注意的。所以决定,将之前略过的一些内容,详细的学习一遍。
今天主要是学习L2数据链路层的数据包的处理机制。在Linux kernel中,由网卡驱动完成L1物理层和L2数据链路层的工作。
首先看函数net_dev_init
  1. static int __init net_dev_init(void)
  2. {
  3. int i, rc = -ENOMEM;
  4. BUG_ON(!dev_boot_phase);
     /* 
     创建对应的/proc文件,如/proc/net/dev, /proc/net/softnet_stat等
     */
  1. if (dev_proc_init())
  2. goto out;
     /* 初始化netdev对应的kobject*/
  1. if (netdev_kobject_init())
  2. goto out;
     /* 
     初始化数据链路层的handle上层数据类型表。
     回忆前文《TCP/IP学习(28)——数据包完整接受流程》中,在inet_init中注册了IP包类型到这个表中。
     */
  1. INIT_LIST_HEAD(&ptype_all);
  2. for (i = 0; i < PTYPE_HASH_SIZE; i++)
  3. INIT_LIST_HEAD(&ptype_base[i]);
     /*
     注册neddev_net_ops subsystem
     */
  1. if (register_pernet_subsys(&netdev_net_ops))
  2. goto out;
  3. /*
  4. *    Initialise the packet receive queues.
  5. */
  6. /*
  7. 为每个CPU初始化PERCPU的全局变量softnet_data,作为该CPU的接收缓存
  8. */
  9. for_each_possible_cpu(i) {
  10. struct softnet_data *sd = &per_cpu(softnet_data, i);
  11. ...... ......
  12. }
  13. dev_boot_phase = 0;
  14. /* The loopback device is special if any other network devices
  15. * is present in a network namespace the loopback device must
  16. * be present. Since we now dynamically allocate and free the
  17. * loopback device ensure this invariant is maintained by
  18. * keeping the loopback device as the first device on the
  19. * list of network devices. Ensuring the loopback devices
  20. * is the first device that appears and the last network device
  21. * that disappears.
  22. */
  23. if (register_pernet_device(&loopback_net_ops))
  24. goto out;
  25. if (register_pernet_device(&default_device_ops))
  26. goto out;
     /*
     enable软中断
     */
  1. open_softirq(NET_TX_SOFTIRQ, net_tx_action);
  2. open_softirq(NET_RX_SOFTIRQ, net_rx_action);
  3. hotcpu_notifier(dev_cpu_callback, 0);
  4. dst_init();
  5. dev_mcast_init();
  6. rc = 0;
  7. out:
  8. return rc;
  9. }
net_dev_init在系统启动时,在注册网卡之前调用,主要就是初始化net device所需要的一些环境。
下面仍然以Intel PRO/1000的网卡驱动为例,e1000_init_module为该驱动的入口。通过e1000_init_module->pci_register_driver->e1000_probe进入初始化函数。
在e1000_probe中,通过下面这条语句绑定了操作函数。
netdev->netdev_ops = &e1000_netdev_ops;
  1. static const struct net_device_ops e1000_netdev_ops = {
  2. .ndo_open        = e1000_open,
  3. ...... ......
  4. };
对于今天的主题来说,只需关心e1000_open即可。因为该函数是在激活该网卡时被调用,完成资源的申请,中断的注册,即e1000_intr。
  1. static irqreturn_t e1000_intr(int irq, void *data)
  2. {
  3. ...... ......
  4. /*
  5. 检测是否可以调度NAPI:
  6. 当没有disable NAPI且没有该网卡对应的NAPI在运行时(保证对应一个网卡的NAPI只有一个实例在运行),即可调度一个新的NAPI。
  7. NAPI是一种新的网卡数据检查处理方式。基本上是interrupt+poll。详细信息问google
  8. */
  9. if (likely(napi_schedule_prep(&adapter->napi))) {
  10. /*
  11. 清楚单次的统计信息。
  12. 刚看到这里时,我也奇怪,为什么total的统计信息要被清零。
  13. 实际上这些统计信息只是一次NAPI运行的统计信息,并不是网卡总的统计信息。
  14. 网卡的统计信息为netdev->stats。NAPI运行完会将下面的值加到网卡的统计信息上的。
  15. */
  16. adapter->total_tx_bytes = 0;
  17. adapter->total_tx_packets = 0;
  18. adapter->total_rx_bytes = 0;
  19. adapter->total_rx_packets = 0;
  20. /* 要求调度对应的NAPI实例 */
  21. __napi_schedule(&adapter->napi);
  22. } else {
  23. /* this really should not if it does it is basically a
  24. * bug, but not a hard error, so enable ints and continue */
  25. if (!test_bit(__E1000_DOWN, &adapter->flags))
  26. e1000_irq_enable(adapter);
  27. }
  28. return IRQ_HANDLED;
  29. }
上面为中断的关键流程,其中要求调度对应的NAPI实例时,实际上是引发一个软中断。
__raise_softirq_irqoff(NET_RX_SOFTIRQ)。这个中断函数的主要功能就是要求调度一个NAPI——这里跟以前理解的中断函数不太一样。按照教科书式的概念,网卡的中断函数,应该将数据包从网卡的缓冲中取出放到一个系统缓冲中,然后在引发软中断去做剩下的工作。
下面看NET_RX_SOFTIRQ软中断对应的处理函数net_rx_action。
  1. static void net_rx_action(struct softirq_action *h)
  2. {
  3. struct softnet_data *sd = &__get_cpu_var(softnet_data);
  4. unsigned long time_limit = jiffies + 2;
  5. int budget = netdev_budget;
  6. void *have;
  7. local_irq_disable();
     /* 开始顺序poll所有需要poll的网卡 */
  1. while (!list_empty(&sd->poll_list)) {
  2. struct napi_struct *n;
  3. int work, weight;
  4. /* If softirq window is exhuasted then punt.
  5. * Allow this to run for 2 jiffies since which will allow
  6. * an average latency of 1.5/HZ.
  7. */
  8. if (unlikely(budget <= 0 || time_after(jiffies, time_limit)))
  9. goto softnet_break;
  10. local_irq_enable();
  11. /* Even though interrupts have been re-enabled, this
  12. * access is safe because interrupts can only add new
  13. * entries to the tail of this list, and only ->poll()
  14. * calls can remove this head entry from the list.
  15. */
  16. /* 取得一个网卡的NAPI实例 */
  17. n = list_first_entry(&sd->poll_list, struct napi_struct, poll_list);
         /* 给这个实例上锁 */
  1. have = netpoll_poll_lock(n);
  2. weight = n->weight;
  3. /* This NAPI_STATE_SCHED test is for avoiding a race
  4. * with netpoll's poll_napi(). Only the entity which
  5. * obtains the lock and sees NAPI_STATE_SCHED set will
  6. * actually make the ->poll() call. Therefore we avoid
  7. * accidently calling ->poll() when NAPI is not scheduled.
  8. */
  9. work = 0;
  10. if (test_bit(NAPI_STATE_SCHED, &n->state)) {
  11. /* poll这个网卡 */
  12. work = n->poll(n, weight);
  13. trace_napi_poll(n);
  14. }
  15. WARN_ON_ONCE(work > weight);
  16. budget -= work;
  17. local_irq_disable();
  18. /* Drivers must not modify the NAPI state if they
  19. * consume the entire weight. In such cases this code
  20. * still "owns" the NAPI instance and therefore can
  21. * move the instance around on the list at-will.
  22. */
  23. if (unlikely(work == weight)) {
  24. /* 该NAPI的weight消耗完毕,需要处理下一个 */
  25. if (unlikely(napi_disable_pending(n))) {
  26. local_irq_enable();
  27. napi_complete(n);
  28. local_irq_disable();
  29. } else
  30. list_move_tail(&n->poll_list, &sd->poll_list);
  31. }
  32. netpoll_poll_unlock(have);
  33. }
  34. out:
  35. net_rps_action_and_irq_enable(sd);
  36. #ifdef CONFIG_NET_DMA
  37. /*
  38. * There may not be any more sk_buffs coming right now, so push
  39. * any pending DMA copies to hardware
  40. */
  41. dma_issue_pending_all();
  42. #endif
  43. return;
  44. softnet_break:
  45. sd->time_squeeze++;
  46. __raise_softirq_irqoff(NET_RX_SOFTIRQ);
  47. goto out;
  48. }
通过上面这个软中断处理函数,对应每个网卡来说,又需要跳回驱动,去学习对应的poll函数。对于本文的这个驱动来说,poll函数就是e1000_clean->e1000_clean_rx_irq。这个函数是真正用于处理网卡接收数据包的工作。
  1. static bool e1000_clean_rx_irq(struct e1000_adapter *adapter,
  2. struct e1000_rx_ring *rx_ring,
  3. int *work_done, int work_to_do)
  4. {
  5. ...... ......
     
     /* 得到当前需要处理buffer*/
  1. i = rx_ring->next_to_clean;
  2. rx_desc = E1000_RX_DESC(*rx_ring, i);
  3. buffer_info = &rx_ring->buffer_info[i];
  4. while (rx_desc->status & E1000_RXD_STAT_DD) {
  5. struct sk_buff *skb;
  6. u8 status;
  7. if (*work_done >= work_to_do) //如果已经poll到足够的包,可以跳出返回
  8. break;
  9. (*work_done)++;
  10. rmb(); /* read descriptor and rx_buffer_info after status DD */
          
         /* 得到数据包buffer对应的skb buffer结构地址 */
  1. status = rx_desc->status;
  2. skb = buffer_info->skb;
  3. buffer_info->skb = NULL;
  
 /* 
         然后做一些网卡硬件相关,及一些sanity check
         */
         ...... ......
  1. /*
  2. 设置skb->pkt_type:PACKET_BROADCAST等;
  3. 即数据链路层协议类型
  4. */
  5. skb->protocol = eth_type_trans(skb, netdev);
         /* 将数据包传递给上层,并做一些通用数据链路层的处理 */
  1. e1000_receive_skb(adapter, status, rx_desc->special, skb);
  2. next_desc:
  3. /* 处理下一个数据包 */
  4. ...... ......
  5. }
     /* 更新统计信息等*/
  1. ...... ......
  2. return cleaned;
  3. }
在这个函数中,真正的从网卡buffer中取出数据包,然后根据硬件的特性做一些特定处理,并简单的设置了数据包的一些field,完成L1的操作,设置好L2的报头。这时,数据包已经为TCP/IP协议栈所需要的skb_buff结构。
然后调用e1000_receive_skb->netif_receive_skb->__netif_receive_skb
  1. static int __netif_receive_skb(struct sk_buff *skb)
  2. {
  3. struct packet_type *ptype, *pt_prev;
  4. rx_handler_func_t *rx_handler;
  5. struct net_device *orig_dev;
  6. struct net_device *master;
  7. struct net_device *null_or_orig;
  8. struct net_device *orig_or_bond;
  9. int ret = NET_RX_DROP;
  10. __be16 type;
      /* 为skb打时间戳 */
  1. if (!netdev_tstamp_prequeue)
  2. net_timestamp_check(skb);
     /* vlan下硬件加速处理 */
  1. if (vlan_tx_tag_present(skb) && vlan_hwaccel_do_receive(skb))
  2. return NET_RX_SUCCESS;
  3. /* if we've gotten here through NAPI, check netpoll */
  4. if (netpoll_receive_skb(skb))
  5. return NET_RX_DROP;
     /* 设置skb的iif为接收网卡的索引 */
  1. if (!skb->skb_iif)
  2. skb->skb_iif = skb->dev->ifindex;
  1. /*
  2. * bonding note: skbs received on inactive slaves should only
  3. * be delivered to pkt handlers that are exact matches. Also
  4. * the deliver_no_wcard flag will be set. If packet handlers
  5. * are sensitive to duplicate packets these skbs will need to
  6. * be dropped at the handler. The vlan accel path may have
  7. * already set the deliver_no_wcard flag.
  8. */
  9. /*关于网卡的bond的处理, 这个feature我只是了解,所以略过 */
  10. null_or_orig = NULL;
  11. orig_dev = skb->dev;
  12. master = ACCESS_ONCE(orig_dev->master);
  13. if (skb->deliver_no_wcard)
  14. null_or_orig = orig_dev;
  15. else if (master) {
  16. if (skb_bond_should_drop(skb, master)) {
  17. skb->deliver_no_wcard = 1;
  18. null_or_orig = orig_dev; /* deliver only exact match */
  19. } else
  20. skb->dev = master;
  21. }
  22. __this_cpu_inc(softnet_data.processed);
  23. /* 初始化l3 header 和 l4 header 的地址*/
  24. skb_reset_network_header(skb);
  25. skb_reset_transport_header(skb);
  26. /* 得到mac地址长度,准确来说是2层地址的长度 */
  27. skb->mac_len = skb->network_header - skb->mac_header;
  28. pt_prev = NULL;
  29. rcu_read_lock();
  30. /*
  31. 省略一些不太相关的代码
  32. */
  33. ...... ......
  34. /*
  35. 通过2层协议类型作为key,得到相应链表。
  36. */
  1. type = skb->protocol;
  2. list_for_each_entry_rcu(ptype,
  3. &ptype_base[ntohs(type) & PTYPE_HASH_MASK], list) {
  4. if (ptype->type == type && (ptype->dev == null_or_orig ||
  5. ptype->dev == skb->dev || ptype->dev == orig_dev ||
  6. ptype->dev == orig_or_bond)) {
  7. if (pt_prev) //找到匹配的协议类型,上传给L3层
  8. ret = deliver_skb(skb, pt_prev, orig_dev);
  9. pt_prev = ptype;
  10. }
  11. }
  12. if (pt_prev) {
  13. ret = pt_prev->func(skb, skb->dev, pt_prev, orig_dev);
  14. } else {
  15. kfree_skb(skb);
  16. /* Jamal, now you will not able to escape explaining
  17. * me how you were going to use this. :-)
  18. */
  19. ret = NET_RX_DROP;
  20. }
  21. out:
  22. rcu_read_unlock();
  23. return ret;
  24. }
现在基本上已经比较详细的学习了L2层的数据包处理流程。当然,还有很多很多的细节没有涉及,道路还很漫长啊。

TCP/IP学习(30)——L2数据链路层的数据包处理详细流程相关推荐

  1. 沁恒微电子CH9121 集成TCP/IP 协议栈,可实现网络数据包和串口数据的双向透明传输

    概述 沁恒微电子CH9121 集成TCP/IP 协议栈,可实现网络数据包和串口数据的双向透明传输,具有TCPCLIENT.TCP SERVER.UDP 3 种工作模式,串口波特率最高可支持到92160 ...

  2. 【TCP/IP学习笔记1】 C语言讲解

    TCP/IP学习笔记(一) 一. TCP/IP结构:      TCP/IP是一个四层协议,结构如下:      1.应用层:各种应用程序和协议,如Http.FTP等.      2.传输层:TCP和 ...

  3. TCP/IP学习笔记(一)(转载)

    一.TCP/IP结构:      TCP/IP是一个四层协议,结构如下:      1.应用层:各种应用程序和协议,如Http.FTP等.      2.传输层:TCP和UDP      TCP提供一 ...

  4. TCP/IP学习笔记:TCP/IP协议介绍

    TCP/IP的通讯协议 这部分简要介绍一下TCP/IP的内部结构,为讨论与互联网有关的安全问题打下基础.TCP/IP协议组之所以流行,部分原因是因为它可以用在各种各样的信道和底层协议(例如T1和X.2 ...

  5. TCP / IP学习笔记(9)-dns域名系统

    TCP / IP学习笔记(9)-dns域名系统 前面已经提到了访问一台机器要靠IP地址和MAC地址,其中,MAC地址可以通过ARP协议得到,所以这对用户是透明的,但是IP地址就不行,无论如何用户都需要 ...

  6. linux下用C语言实现TCP/IP服务器与客户端互相发送数据的socket编程

    linux下用C语言实现TCP/IP服务器与客户端互相发送数据的socket编程 server.c #include <sys/stat.h>#include <fcntl.h> ...

  7. TCP/IP详解卷1 - wireshark抓包分析

    TCP/IP详解卷1 - 系列文 TCP/IP详解卷1 - 思维导图(1) TCP/IP详解卷1 - wireshark抓包分析 引言 在初学TCP/IP协议时,会觉得协议是一种很抽象的东西,通过wi ...

  8. DPDK 数据包捕获基本流程(十六)

    内核组件架构 rte_eal+libc:内存的统一组织管理者,但是在这它不只是做内存工作. librte_malloc:对外提供分配释放内存的API,分配的内存都是rte_eal中所管理的内存. li ...

  9. TCP/IP学习笔记(2)-数据链路层

    数据链路层有三个目的: 为IP模块发送和接收IP数据报. 为ARP模块发送ARP请求和接收ARP应答. 为RARP发送RARP请求和接收RARP应答 ip大家都听说过.至于ARP和RARP,ARP叫做 ...

最新文章

  1. Unity GUI(uGUI)使用心得与性能总结
  2. 基于网络音频的Android播放程序简单示例
  3. nodejs即时聊天
  4. 2018年终总结—努力做一个有趣的人
  5. c# url编码 字母编码_我如何通过每天30分钟编码来完成#100DaysOfCode挑战
  6. 初探下一代SIEM核心技术发展趋势
  7. 高德百度坐标系转换方法
  8. h3c服务器显示非法的文件,H3C License server 故障处理手册-5W201
  9. 数据库基础:MySQL必备的三个工具
  10. klwp主题大全_klwp主题包百度网盘版下载-klwp主题包百度云版_5577安卓网
  11. 监控数据恢复取证-盘点进水监控硬盘的数据恢复
  12. 【408:计算机组成原理】起源:带你速看计算机伟大历史
  13. 51单片机实验 7段数码管静态显示数字
  14. 禾穗HERS | 不结婚就不孝?催婚季必备三招快学起来!
  15. 教你如何不显示excel中 N/A
  16. 理财公司天基实业如何投资理财收益最大化
  17. Linux内存机制浅见——从内存布局到线程局部存储TLS
  18. Python123.io---星号下三角形
  19. 盲盒商城系统创业是怎么一回事儿?
  20. 马斯克「萌生退意」:这推特我还干不干,你们说了算

热门文章

  1. 人工神经网络与生物神经网络
  2. LeetCode 524 通过删除字母匹配到字典里最长单词
  3. Python文件分享(3为http.server、2为SimpleHTTPServer)
  4. Ubuntu更换国内源(apt更换源)
  5. php生成随机验证码
  6. oracle 导入导出指定表
  7. kohana 简单使用
  8. uestc 851 方老师与素数
  9. 码农30岁后的体检——你最需要的是直面的勇气
  10. mysql 关闭in自动排序_为什么MySQL的in查询会自动排序