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

简介:

原文地址: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.     /*
  36.     通过2层协议类型作为key,得到相应链表。
  37.     */
  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层的数据包处理流程。当然,还有很多很多的细节没有涉及,道路还很漫长啊。
目录
相关文章
|
1天前
|
网络协议 网络架构
【网络】TCP/IP 五层网络模型:数据链路层
【网络】TCP/IP 五层网络模型:数据链路层
10 1
|
3月前
|
网络协议 程序员 定位技术
学习网络的第一步:全面解析OSI与TCP/IP模型
**网络基础知识概览:** 探索网络通信的关键模型——OSI七层模型和TCP/IP五层模型。OSI模型(物理、数据链路、网络、传输、会话、表示、应用层)提供理论框架,而TCP/IP模型(物理、数据链路、网络、传输、应用层)更为实际,合并了会话、表示和应用层。两者帮助理解数据在网络中的传输过程,为网络设计和管理提供理论支持。了解这些模型,如同在复杂的网络世界中持有了地图。
73 2
|
运维 Kubernetes 网络协议
kubernetes 的TCP 数据包可视化
kubernetes 的TCP 数据包可视化
320 0
|
存储 缓存 网络协议
网络基础学习:什么是tcp/ip协议
网络基础学习:什么是tcp/ip协议
146 0
|
网络协议
计算机网络学习27:TCP连接与连接释放
客户端和服务端都是先建立传输控制模块
计算机网络学习27:TCP连接与连接释放
|
缓存 网络协议 算法
计算机网络学习26:TCP/UDP对比区别、TCP流量控制、拥塞控制、超时重传时间的选择、可靠传输的实现
UDP: User Datagram Protocol 用户数据报协议 TCP: Transmission Control Protocol 传输控制协议 同时这里指的连接是指逻辑连接,而不是物理连接。
计算机网络学习26:TCP/UDP对比区别、TCP流量控制、拥塞控制、超时重传时间的选择、可靠传输的实现
|
网络协议 网络性能优化 网络安全
网络协议报文理解刨析篇二(再谈Http和Https), 加上TCP/UDP/IP协议分析(理解着学习), 面试官都惊讶你对网络的见解(2)
网络协议报文理解刨析篇二(再谈Http和Https), 加上TCP/UDP/IP协议分析(理解着学习), 面试官都惊讶你对网络的见解(2)
网络协议报文理解刨析篇二(再谈Http和Https), 加上TCP/UDP/IP协议分析(理解着学习), 面试官都惊讶你对网络的见解(2)
|
域名解析 网络协议 安全
网络协议报文理解刨析篇二(再谈Http和Https), 加上TCP/UDP/IP协议分析(理解着学习), 面试官都惊讶你对网络的见解(1)
网络协议报文理解刨析篇二(再谈Http和Https), 加上TCP/UDP/IP协议分析(理解着学习), 面试官都惊讶你对网络的见解(1)
网络协议报文理解刨析篇二(再谈Http和Https), 加上TCP/UDP/IP协议分析(理解着学习), 面试官都惊讶你对网络的见解(1)
|
网络协议 算法 程序员
坦白局,TCP粘包:我只是犯了每个数据包都会犯的错
李东,自称亚健康终结者,尝试使用互联网+的模式拓展自己的业务。在某款新开发的聊天软件琛琛上发布广告。 键盘说来就来。疯狂发送"李东",回车发送!,"亚健康终结者",再回车发送!