> 文档中心 > linux内核网络收包过程(二)

linux内核网络收包过程(二)

目录

硬中断处理

软中断处理


        数据通过网络发送过来

硬中断处理

  1. 数据帧首先到达网卡的接收队列,分配RingBuffer
  2. DMA把数据搬运到网卡关联的内存
  3. 网卡向CPU发起硬中断,通知CPU有数据
  4. 调用驱动注册的硬中断处理函数
  5. 启动NAPI,触发软中断

上一分析说到网卡硬中断注册的函数igb_msix_ring

static irqreturn_t igb_msix_ring(int irq, void *data){struct igb_q_vector *q_vector = data;/* Write the ITR value calculated from the previous interrupt. */igb_write_itr(q_vector);napi_schedule(&q_vector->napi);return IRQ_HANDLED;}

igb_write_itr仅记录硬件中断频率

static inline void ____napi_schedule(struct softnet_data *sd,     struct napi_struct *napi){list_add_tail(&napi->poll_list, &sd->poll_list);    //触发一个软中断NET_RX_SOFTIRQ__raise_softirq_irqoff(NET_RX_SOFTIRQ);}
  • list_add_tail修改了napi的poll_list(双向链表,数据帧等着被处理),
  • 触发一个软中断NET_RX_SOFTIRQ
  • 网络包硬中断的工作到此结束。

软中断处理

判断softirq_pending标志

static int ksoftirqd_should_run(unsigned int cpu){return local_softirq_pending();}

执行run_ksoftirqd->__do_softirq

asmlinkage __visible void __softirq_entry __do_softirq(void){while ((softirq_bit = ffs(pending))) {trace_softirq_entry(vec_nr);h->action(h);trace_softirq_exit(vec_nr);wakeup_softirqd();}...}

调用action中断函数

static __latent_entropy void net_rx_action(struct softirq_action *h){struct softnet_data *sd = this_cpu_ptr(&softnet_data);unsigned long time_limit = jiffies +usecs_to_jiffies(netdev_budget_usecs);int budget = netdev_budget;for (;;) {struct napi_struct *n;n = list_first_entry(&list, struct napi_struct, poll_list); //变量sd,调用poll函数budget -= napi_poll(n, &repoll); //budget 与 time_limit控制退出if (unlikely(budget time_squeeze++;break;}}

核⼼逻辑是获取到当前 CPU变量 softnet_data ,对其 poll_list 进⾏遍历 , 然后执⾏到⽹卡驱动注册到的 poll 函数。

static int igb_poll(struct napi_struct *napi, int budget){if (q_vector->tx.ring)clean_complete = igb_clean_tx_irq(q_vector, budget);if (q_vector->rx.ring) {int cleaned = igb_clean_rx_irq(q_vector, budget);}}static int igb_clean_rx_irq(struct igb_q_vector *q_vector, const int budget){while (likely(total_packets len;/* populate checksum, timestamp, VLAN, and protocol */igb_process_skb_fields(rx_ring, rx_desc, skb);napi_gro_receive(&q_vector->napi, skb);/* update budget accounting */total_packets++;}return total_packets;}
  1. 从ringbuf中取出数据skb;
  2. 收取完数据以后,对其进⾏⼀些校验
  3. 设置 sbk 变量的 timestamp, VLAN id, protocol 
gro_result_t napi_gro_receive(struct napi_struct *napi, struct sk_buff *skb){skb_gro_reset_offset(skb);ret = napi_skb_finish(napi, skb, dev_gro_receive(napi, skb));trace_napi_gro_receive_exit(ret);}

napi_gro_receive函数代表的是⽹卡 GRO 特性,可以简单理解成把相关的⼩包合并成⼀个⼤包。

/* Pass the currently batched GRO_NORMAL SKBs up to the stack. */static void gro_normal_list(struct napi_struct *napi){if (!napi->rx_count)return;netif_receive_skb_list_internal(&napi->rx_list);INIT_LIST_HEAD(&napi->rx_list);napi->rx_count = 0;}/* Queue one GRO_NORMAL SKB up for list processing. If batch size exceeded, * pass the whole batch up to the stack. */static void gro_normal_one(struct napi_struct *napi, struct sk_buff *skb){list_add_tail(&skb->list, &napi->rx_list);if (++napi->rx_count >= gro_normal_batch)gro_normal_list(napi);}static gro_result_t napi_skb_finish(struct napi_struct *napi,    struct sk_buff *skb,    gro_result_t ret){switch (ret) {case GRO_NORMAL:gro_normal_one(napi, skb);break;    ...}

最终调用 gro_normal_list将数据发送到网络协议栈。

 参考

https://course.0voice.com/v1/course/intro?courseId=2&agentId=0