linux内核网络收包过程(二)
目录
硬中断处理
软中断处理
数据通过网络发送过来
硬中断处理
上一分析说到网卡硬中断注册的函数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;}
- 从ringbuf中取出数据skb;
- 收取完数据以后,对其进⾏⼀些校验
- 设置 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