linux-network详解3数据包接收


1 概述

当一个数据包到达的时候,网卡驱动会完成接收并且触发中断。产生中断的每个设备都有一个相应的中断处理程序,每个网卡都有一个中断处理程序,是设备驱动程序的一部分。用于通知网卡该中断已经被接收了,以及把网卡缓冲区的数据包拷贝到内存中。当网卡接收来自网络的数据包时,需要通知内核数据包到了。内核通过执行网卡已注册的中断处理函数来做出应答。中断处理程序开始执行,通知硬件,拷贝最新的网络数据包到内存,然后读取网卡更多的数据包。

这些都是重要、紧迫而又与硬件相关的工作。内核通常需要快速的拷贝网络数据包到系统内存,因为网卡上接收网络数据包的缓存大小固定,而且相比系统内存也要小得多。所以上述拷贝动作一旦被延迟,必然造成网卡缓存溢出 - 进入的数据包占满了网卡的缓存,后续的包只能被丢弃。

一个中断处理函数主要分两个部分,上半部和下半部。中断产生并发送给CPU的时候,对于NAPI和不支持NAPI的设备来说处理结果是不一样的,NAPI调用的函数是napi_schedule,非NAPI调用的函数是netif_rx,这两个函数都是在网卡驱动的中断处理函数上半部分被调用的。

当网络数据包被拷贝到系统内存后,中断的上半部任务算完成了,这时它把控制权交还给被系统中断前运行的程序,处理和操作数据包的其他工作在随后的下半部中进行。

不管是否支持NAPI,对于驱动来说无非是调用napi_schedule或者netif_rx来通知内核,将数据包交给内核。所以如果不知道驱动使用的中断处理程序是哪个,那么只要搜索一下这两个函数就能定位出来了。因为NAPI是基于前者发展出来的,所以先从netif_rx开始分析。

2 函数netif_rx

非NAPI的接收处理,定义位于net/core/dev.c

 1 int netif_rx(struct sk_buff *skb)
 2 {
 3     trace_netif_rx_entry(skb);
 4 
 5     return netif_rx_internal(skb);
 6 }
 7 
 8 static int netif_rx_internal(struct sk_buff *skb)
 9 {
10     int ret;
11 
12     net_timestamp_check(netdev_tstamp_prequeue, skb);//记录接收时间到skb->tstamp
13 
14     trace_netif_rx(skb);
15 #ifdef CONFIG_RPS
16     if (static_key_false(&rps_needed)) {
17         struct rps_dev_flow voidflow, *rflow = &voidflow;
18         int cpu;
19 
20         preempt_disable();
21         rcu_read_lock();
22 
23         cpu = get_rps_cpu(skb->dev, skb, &rflow);////如果有支持rps,则获取这个包交给了哪个cpu处理
24         if (cpu < 0)
25             cpu = smp_processor_id();//如果上面获取失败,则用另外一种方式获取当前cpu的id
26 
27         ret = enqueue_to_backlog(skb, cpu, &rflow->last_qtail);//调用该函数将包添加到queue->input_pkt_queue里面
28 
29         rcu_read_unlock();
30         preempt_enable();
31     } else
32 #endif
33     {
34         unsigned int qtail;
35         ret = enqueue_to_backlog(skb, get_cpu(), &qtail);
36         put_cpu();
37     }
38     return ret;
39 }

2.1 函数enqueue_to_backlog

这个函数最后调用enqueue_to_backlog将包添加到queue->input_pkt_queue的尾部,这个input_pkt_queue是每个cpu都有的一个队列,这个队列的初始化在net_dev_init()中完成。

 1 static int enqueue_to_backlog(struct sk_buff *skb, int cpu,
 2                   unsigned int *qtail)
 3 {
 4     struct softnet_data *sd;
 5     unsigned long flags;
 6     unsigned int qlen;
 7 
 8     sd = &per_cpu(softnet_data, cpu);//获取当前cpu的softnet_data对象
 9 
10     local_irq_save(flags);//保存中断状态
11 
12     rps_lock(sd);
13     if (!netif_running(skb->dev))//确认net_device的dev->state是__LINK_STATE_START状态,如果该网络设备没有运行,直接退出,不进行包的处理
14         goto drop;
15     qlen = skb_queue_len(&sd->input_pkt_queue);//获取input_pkt_queue的当前长度
16     if (qlen <= netdev_max_backlog && !skb_flow_limit(skb, qlen)) {//如果当前长度小于最大长度,且符合流量限制的要求
17         if (qlen) {
18 enqueue:
19             __skb_queue_tail(&sd->input_pkt_queue, skb);//将SKB添加到input_pkt_queue队列的后面
20             input_queue_tail_incr_save(sd, qtail);//队列尾部指针加1
21             rps_unlock(sd);
22             local_irq_restore(flags);//恢复中断状态
23             return NET_RX_SUCCESS;//返回接收成功
24         }
25 
26         /* Schedule NAPI for backlog device
27          * We can use non atomic operation since we own the queue lock
28          */
29         if (!__test_and_set_bit(NAPI_STATE_SCHED, &sd->backlog.state)) {
30             if (!rps_ipi_queued(sd))
31                 ____napi_schedule(sd, &sd->backlog);//把虚拟设备backlog添加到sd->poll_list中以便进行轮询,最后设置NET_RX_SOFTIRQ标志触发软中断
32         }
33         goto enqueue;
34     }
35 
36 drop:
37     sd->dropped++;//若接收队列满了就直接丢弃
38     rps_unlock(sd);
39 
40     local_irq_restore(flags);//恢复本地中断
41 
42     atomic_long_inc(&skb->dev->rx_dropped);
43     kfree_skb(skb);
44     return NET_RX_DROP;
45 }

 2.2 函数net_dev_init

初始化input_pkt_queue队列,每个cpu都有的一个这样的队列。

 1 static int __init net_dev_init(void)
 2 {
 3     ...
 4 
 5     for_each_possible_cpu(i) {
 6         struct work_struct *flush = per_cpu_ptr(&flush_works, i);
 7         struct softnet_data *sd = &per_cpu(softnet_data, i);
 8 
 9         INIT_WORK(flush, flush_backlog);
10 
11         skb_queue_head_init(&sd->input_pkt_queue);//初始化每个cpu的input_pkt_queue队列
12         skb_queue_head_init(&sd->process_queue);
13         INIT_LIST_HEAD(&sd->poll_list);
14         sd->output_queue_tailp = &sd->output_queue;
15 #ifdef CONFIG_RPS
16         sd->csd.func = rps_trigger_softirq;
17         sd->csd.info = sd;
18         sd->cpu = i;
19 #endif
20 
21         sd->backlog.poll = process_backlog;//初始化非NAPI的poll callback函数  ----------------B
22         sd->backlog.weight = weight_p;
23     }
24 
25     ...
26     open_softirq(NET_TX_SOFTIRQ, net_tx_action);//初始化TX中断
27     open_softirq(NET_RX_SOFTIRQ, net_rx_action);//初始化RX中断  ---------------------- A
28 
29     hotcpu_notifier(dev_cpu_callback, 0);
30     dst_subsys_init();
31     rc = 0;
32 out:
33     return rc;
34 }

2.3 函数调用__napi_schedule

作用:触发软中断

1 /* Called with irq disabled */
2 static inline void ____napi_schedule(struct softnet_data *sd,
3                      struct napi_struct *napi)
4 {
5     list_add_tail(&napi->poll_list, &sd->poll_list);
6     __raise_softirq_irqoff(NET_RX_SOFTIRQ);
7 }

此软中断的初始化在2.2节函数net_dev_init中A处。最后调用函数net_rx_action

3 函数napi_schedule

NAPI接收处理,定义位于: include\linux\netdevice.h 和net/core/dev.c

 1 static inline void napi_schedule(struct napi_struct *n)
 2 {
 3     if (napi_schedule_prep(n))
 4         __napi_schedule(n);
 5 }
 6 
 7 void __napi_schedule(struct napi_struct *n)
 8 {
 9     unsigned long flags;
10 
11     local_irq_save(flags);//保存中断
12     ____napi_schedule(this_cpu_ptr(&softnet_data), n);//详见2.3节,触发RX软中断,最后同2.3节调用函数net_rx_action
13     local_irq_restore(flags);//恢复中断
14 }

4 函数net_rx_action

此函数为RX中断的下半部分处理,第2-3节是RX中断的上半部处理。

 1 static __latent_entropy void net_rx_action(struct softirq_action *h)
 2 {
 3     struct softnet_data *sd = this_cpu_ptr(&softnet_data);
 4     unsigned long time_limit = jiffies + 2;
 5     int budget = netdev_budget;//指定一次软中断处理的skb的数目,这里是300
 6     LIST_HEAD(list);
 7     LIST_HEAD(repoll);
 8 
 9     local_irq_disable();
10     list_splice_init(&sd->poll_list, &list);
11     local_irq_enable();
12 
13     for (;;) {
14         struct napi_struct *n;
15 
16         if (list_empty(&list)) {//检查POLL队列(poll_list)上是否有设备在准备等待轮询
17             if (!sd_has_rps_ipi_waiting(sd) && list_empty(&repoll))
18                 return;
19             break;
20         }
21 
22         n = list_first_entry(&list, struct napi_struct, poll_list);
23         budget -= napi_poll(n, &repoll);//调用poll函数从网卡驱动中读取一定数量的skb
24 
25         /* If softirq window is exhausted then punt.
26          * Allow this to run for 2 jiffies since which will allow
27          * an average latency of 1.5/HZ.
28          */
29         if (unlikely(budget <= 0 ||   //如果读取的数量超过300,则终止中断处理
30                  time_after_eq(jiffies, time_limit))) {
31             sd->time_squeeze++;
32             break;
33         }
34     }
35 
36     __kfree_skb_flush();
37     local_irq_disable();
38 
39     list_splice_tail_init(&sd->poll_list, &list);
40     list_splice_tail(&repoll, &list);
41     list_splice(&list, &sd->poll_list);
42     if (!list_empty(&sd->poll_list))//如果poll list中不为空,表示还有skb没有读取完成,则继续读取,触发下一次软中断
43         __raise_softirq_irqoff(NET_RX_SOFTIRQ);
44 
45     net_rps_action_and_irq_enable(sd);
46 }

4.1 函数napi_poll

 1 static int napi_poll(struct napi_struct *n, struct list_head *repoll)
 2 {
 3     void *have;
 4     int work, weight;
 5 
 6     list_del_init(&n->poll_list);
 7 
 8     have = netpoll_poll_lock(n);
 9 
10     weight = n->weight;
11 
12     /* This NAPI_STATE_SCHED test is for avoiding a race
13      * with netpoll's poll_napi().  Only the entity which
14      * obtains the lock and sees NAPI_STATE_SCHED set will
15      * actually make the ->poll() call.  Therefore we avoid
16      * accidentally calling ->poll() when NAPI is not scheduled.
17      */
18     work = 0;
19     if (test_bit(NAPI_STATE_SCHED, &n->state)) {
20         work = n->poll(n, weight);//在这里调用驱动的poll函数,如果驱动有支持NAPI,会定义并初始化这个poll函数,默认的poll函数是process_backlog
21         trace_napi_poll(n, work, weight);
22     }
23 
24     WARN_ON_ONCE(work > weight);
25 
26     if (likely(work < weight))
27         goto out_unlock;
28 
29     /* Drivers must not modify the NAPI state if they
30      * consume the entire weight.  In such cases this code
31      * still "owns" the NAPI instance and therefore can
32      * move the instance around on the list at-will.
33      */
34     if (unlikely(napi_disable_pending(n))) {
35         napi_complete(n);
36         goto out_unlock;
37     }
38 
39     if (n->gro_list) {
40         /* flush too old packets
41          * If HZ < 1000, flush all packets.
42          */
43         napi_gro_flush(n, HZ >= 1000);
44     }
45 
46     /* Some drivers may have called napi_schedule
47      * prior to exhausting their budget.
48      */
49     if (unlikely(!list_empty(&n->poll_list))) {
50         pr_warn_once("%s: Budget exhausted after napi rescheduled\n",
51                  n->dev ? n->dev->name : "backlog");
52         goto out_unlock;
53     }
54 
55     list_add_tail(&n->poll_list, repoll);
56 
57 out_unlock:
58     netpoll_poll_unlock(have);
59 
60     return work;
61 }

4.2 非NAPI的poll函数

非NAPI的poll函数在上述2.2节B处赋值,即为函数process_backlog

 1 static int process_backlog(struct napi_struct *napi, int quota)
 2 {
 3     struct softnet_data *sd = container_of(napi, struct softnet_data, backlog);
 4     bool again = true;
 5     int work = 0;
 6 
 7     /* Check if we have pending ipi, its better to send them now,
 8      * not waiting net_rx_action() end.
 9      */
10     if (sd_has_rps_ipi_waiting(sd)) {
11         local_irq_disable();
12         net_rps_action_and_irq_enable(sd);
13     }
14 
15     napi->weight = weight_p;
16     while (again) {
17         struct sk_buff *skb;
18 
19         while ((skb = __skb_dequeue(&sd->process_queue)))//从队列头部读取一个skb {
20             rcu_read_lock();
21             __netif_receive_skb(skb);//调用此函数将skb传给网路层 ------------------ A
22             rcu_read_unlock();
23             input_queue_head_incr(sd);//将队列头部往后偏移一个单位
24             if (++work >= quota)
25                 return work;
26 
27         }
28 
29         local_irq_disable();
30         rps_lock(sd);
31         if (skb_queue_empty(&sd->input_pkt_queue)) {//如果队列为空,表示skb读取完了
32             
33             napi->state = 0;//状态置0并退出读取循环
34             again = false;
35         } else {
36             skb_queue_splice_tail_init(&sd->input_pkt_queue,
37                            &sd->process_queue);
38         }
39         rps_unlock(sd);
40         local_irq_enable();
41     }
42 
43     return work;
44 }

4.3 NAPI类的poll函数

对于NAPI来说,它的poll函数是在驱动加载初始化的时候指定的。以linux-4.9.73\drivers\net\wireless\ath\wil6210\pcie_bus.c

为例来分析NAPI类的poll函数赋值过程。

从wil6210 驱动的probe函数开始:

 1 static int wil_pcie_probe(struct pci_dev *pdev, const struct pci_device_id *id)
 2 {
 3   ...
 4   rc = wil_if_add(wil);
 5     if (rc) {
 6         wil_err(wil, "wil_if_add failed: %d\n", rc);
 7         goto bus_disable;
 8   ...
 9 }
10 
11 int wil_if_add(struct wil6210_priv *wil)
12 {
13     struct wireless_dev *wdev = wil_to_wdev(wil);
14     struct wiphy *wiphy = wdev->wiphy;
15     struct net_device *ndev = wil_to_ndev(wil);
16     int rc;
17 
18     wil_dbg_misc(wil, "entered");
19 
20     strlcpy(wiphy->fw_version, wil->fw_version, sizeof(wiphy->fw_version));
21 
22     rc = wiphy_register(wiphy);//注册wiphy
23     if (rc < 0) {
24         wil_err(wil, "failed to register wiphy, err %d\n", rc);
25         return rc;
26     }
27 
28     netif_napi_add(ndev, &wil->napi_rx, wil6210_netdev_poll_rx,//注册poll函数wil6210_netdev_poll_rx
29                WIL6210_NAPI_BUDGET);
30   ...
31 }
32 void netif_napi_add(struct net_device *dev, struct napi_struct *napi,
33             int (*poll)(struct napi_struct *, int), int weight)
34 {
35     ...
36     napi->timer.function = napi_watchdog;
37     napi->gro_count = 0;
38     napi->gro_list = NULL;
39     napi->skb = NULL;
40     napi->poll = poll;//赋值poll callback函数
41   ...
42 }

4.3.1 wil6210的poll函数

 1 static int wil6210_netdev_poll_rx(struct napi_struct *napi, int budget)
 2 {
 3     struct wil6210_priv *wil = container_of(napi, struct wil6210_priv,
 4                         napi_rx);
 5     int quota = budget;
 6     int done;
 7 
 8     wil_rx_handle(wil, &quota);
 9   ...
10 }
11 void wil_rx_handle(struct wil6210_priv *wil, int *quota)
12 {
13     ...
14 
15    wil_netif_rx_any(skb, ndev);
16   ...
17 }
18 void wil_netif_rx_any(struct sk_buff *skb, struct net_device *ndev)
19 {
20   ...
21   if (skb) { /* deliver to local stack */
22 
23         skb->protocol = eth_type_trans(skb, ndev);
24         rc = napi_gro_receive(&wil->napi_rx, skb);//将分散的skb进行组装,形成一个skb
25         wil_dbg_txrx(wil, "Rx complete %d bytes => %s\n",
26                  len, gro_res_str[rc]);
27     }
28   ...
29 }

4.3.2 函数napi_gro_receive

 1 gro_result_t napi_gro_receive(struct napi_struct *napi, struct sk_buff *skb)
 2 {
 3     skb_mark_napi_id(skb, napi);
 4     trace_napi_gro_receive_entry(skb);
 5 
 6     skb_gro_reset_offset(skb);
 7 
 8     return napi_skb_finish(dev_gro_receive(napi, skb), skb);
 9 }
10 static gro_result_t napi_skb_finish(gro_result_t ret, struct sk_buff *skb)
11 {
12     switch (ret) {
13     case GRO_NORMAL:
14         if (netif_receive_skb_internal(skb))
15             ret = GRO_DROP;
16         break;
17   ...
18 }
19 static int netif_receive_skb_internal(struct sk_buff *skb)
20 {
21     ...
22     ret = __netif_receive_skb(skb);//将skb传送给网路层 ------------------ A
23     rcu_read_unlock();
24     return ret;
25 }

4.4 NAPI和非NAPI

经上分析4.2节非NAPI的poll函数A处和NAPI类poll函数的4.3.2节的A处,最终都调用函数__netif_receive_skb,将skb传送给网路层。下面来看此函数:

  1 static int __netif_receive_skb_core(struct sk_buff *skb, bool pfmemalloc)
  2 {
  3     struct packet_type *ptype, *pt_prev;//用于操作包类型
  4     rx_handler_func_t *rx_handler;
  5     struct net_device *orig_dev;//存放报文的原始设备
  6     bool deliver_exact = false;
  7     int ret = NET_RX_DROP;
  8     __be16 type;
  9 
 10     net_timestamp_check(!netdev_tstamp_prequeue, skb);//check时间戳,并且会更新skb的时间戳,skb->tstamp
 11 
 12     trace_netif_receive_skb(skb);
 13 
 14     orig_dev = skb->dev;//将原始的dve做一个备份
 15 
 16     skb_reset_network_header(skb);//重置network header,此时skb已经指向IP头(没有vlan的情况下)
 17     if (!skb_transport_header_was_set(skb))
 18         skb_reset_transport_header(skb);
 19     skb_reset_mac_len(skb);//重置mac len
 20 
 21     pt_prev = NULL;
 22 
 23 another_round:
 24     skb->skb_iif = skb->dev->ifindex;
 25 
 26     __this_cpu_inc(softnet_data.processed);
 27 
 28     if (skb->protocol == cpu_to_be16(ETH_P_8021Q) ||
 29         skb->protocol == cpu_to_be16(ETH_P_8021AD)) {
 30         skb = skb_vlan_untag(skb);//去除vlan tag
 31         if (unlikely(!skb))
 32             goto out;
 33     }
 34 
 35 #ifdef CONFIG_NET_CLS_ACT
 36     if (skb->tc_verd & TC_NCLS) {
 37         skb->tc_verd = CLR_TC_NCLS(skb->tc_verd);
 38         goto ncls;
 39     }
 40 #endif
 41 
 42     if (pfmemalloc)
 43         goto skip_taps;
 44 /*把包交给特定协议相关的处理函数前,先调用ptype_all中注册的函数。最常见的为tcpdump,该工具就是从这里拿到所有收到的包的,例如raw socket和tcpdump实现*/
 45     list_for_each_entry_rcu(ptype, &ptype_all, list) {
 46         if (pt_prev)
 47             ret = deliver_skb(skb, pt_prev, orig_dev);//将包直接传给应用层
 48         pt_prev = ptype;//pt_prev的加入是为了优化,只有当找到下一个匹配的时候,才执行这一次的回调函数
 49     }
 50 /*设备上注册ptype_all,做相应的处理,更加精细的控制,ptype_all里面包括IP和arp等 */
 51     list_for_each_entry_rcu(ptype, &skb->dev->ptype_all, list) {
 52         if (pt_prev)
 53             ret = deliver_skb(skb, pt_prev, orig_dev);
 54         pt_prev = ptype;
 55     }
 56 
 57 skip_taps:
 58 #ifdef CONFIG_NET_INGRESS
 59     if (static_key_false(&ingress_needed)) {
 60         skb = sch_handle_ingress(skb, &pt_prev, &ret, orig_dev);
 61         if (!skb)
 62             goto out;
 63 
 64         if (nf_ingress(skb, &pt_prev, &ret, orig_dev) < 0)
 65             goto out;
 66     }
 67 #endif
 68 #ifdef CONFIG_NET_CLS_ACT
 69     skb->tc_verd = 0;
 70 ncls:
 71 #endif
 72     if (pfmemalloc && !skb_pfmemalloc_protocol(skb))
 73         goto drop;
 74 
 75     if (skb_vlan_tag_present(skb)) {//如果需要将vlan的信息提供给上层,则执行下面的代码
 76         if (pt_prev) {
 77             ret = deliver_skb(skb, pt_prev, orig_dev);
 78             pt_prev = NULL;
 79         }
 80         if (vlan_do_receive(&skb))
 81             goto another_round;
 82         else if (unlikely(!skb))
 83             goto out;
 84     }
 85 
 86     rx_handler = rcu_dereference(skb->dev->rx_handler);//设备rx_handler,加入OVS时会注册为OVS的入口函数 
 87     if (rx_handler) {
 88         if (pt_prev) {
 89             ret = deliver_skb(skb, pt_prev, orig_dev);
 90             pt_prev = NULL;
 91         }
 92         switch (rx_handler(&skb)) {//执行rx_handler处理,例如进入OVS,OVS不支持报头中携带vlan的报文 
 93         case RX_HANDLER_CONSUMED:
 94             ret = NET_RX_SUCCESS;
 95             goto out;
 96         case RX_HANDLER_ANOTHER:
 97             goto another_round;
 98         case RX_HANDLER_EXACT:
 99             deliver_exact = true;
100         case RX_HANDLER_PASS:
101             break;
102         default:
103             BUG();
104         }
105     }
106 
107     if (unlikely(skb_vlan_tag_present(skb))) {
108         if (skb_vlan_tag_get_id(skb))
109             skb->pkt_type = PACKET_OTHERHOST;
110         /* Note: we might in the future use prio bits
111          * and set skb->priority like in vlan_do_receive()
112          * For the time being, just ignore Priority Code Point
113          */
114         skb->vlan_tci = 0;
115     }
116 
117     type = skb->protocol;
118 
119     /* deliver only exact match when indicated */
120     if (likely(!deliver_exact)) {
121         deliver_ptype_list_skb(skb, &pt_prev, orig_dev, type,//根据全局定义的协议处理报文
122                        &ptype_base[ntohs(type) &
123                            PTYPE_HASH_MASK]);
124     }
125 
126     deliver_ptype_list_skb(skb, &pt_prev, orig_dev, type,//根据设备上注册的协议进行处理
127                    &orig_dev->ptype_specific);
128 
129     if (unlikely(skb->dev != orig_dev)) {//如果设备发生变化,那么还需要针对新设备的注册协议进行处理
130         deliver_ptype_list_skb(skb, &pt_prev, orig_dev, type,
131                        &skb->dev->ptype_specific);
132     }
133 
134     if (pt_prev) {
135         if (unlikely(skb_orphan_frags(skb, GFP_ATOMIC)))
136             goto drop;
137         else
138             ret = pt_prev->func(skb, skb->dev, pt_prev, //调用协议处理orig_dev);
139     } else {
140 drop:
141         if (!deliver_exact)
142             atomic_long_inc(&skb->dev->rx_dropped);
143         else
144             atomic_long_inc(&skb->dev->rx_nohandler);
145         kfree_skb(skb);
146         /* Jamal, now you will not able to escape explaining
147          * me how you were going to use this. :-)
148          */
149         ret = NET_RX_DROP;
150     }
151 
152 out:
153     return ret;
154 }

到此即为数据包从驱动接收到接口层的过程,接下来会通过接口层传送给网路层。

4.4.1 补充下函数__netif_receive_skb

补充说明下这两个链表ptype_base和ptype_all,在内核中存储情况如下图:

从图中可以看到,ptype_all是一个链表,这个链表里面最大的区别是func=packet_rcv,即这个链表一般是提供给一些抓包程序使用的,比如tcp_dump。它可以不区分包的类型而将所有的包的抓取过来,它的统一处理函数都是packet_rcv,在这里面可以对一些过滤选项进行处理。对象中的type一般使用的是以太网类型,而dev表示在哪个接口上抓包。

但是ptype_base则是一个哈希表,注意这个表是以type来进行分类的,比如ip协议可以指定不同的dev接口,但是他们都在同一张表上。不同的协议类型对应了不同的接收函数,比如IP报文的接收函数是ip_rcv, 802.2对应的是llc_rcv等。总的来说,报文从网卡驱动里面上来以后,第一次在这里进行分流,不同的报文类型交给不同的协议处理函数进行处理。

5 总结整个flow:

参考博文:https://blog.csdn.net/lee244868149/article/details/77625367


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM