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


    目录

    硬中断处理

    软中断处理


            数据通过网络发送过来

    中断处理

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

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

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

    igb_write_itr仅记录硬件中断频率

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

    软中断处理

    判断softirq_pending标志

    1. static int ksoftirqd_should_run(unsigned int cpu)
    2. {
    3. return local_softirq_pending();
    4. }

    执行run_ksoftirqd->__do_softirq

    1. asmlinkage __visible void __softirq_entry __do_softirq(void)
    2. {
    3. while ((softirq_bit = ffs(pending))) {
    4. trace_softirq_entry(vec_nr);
    5. h->action(h);
    6. trace_softirq_exit(vec_nr);
    7. wakeup_softirqd();
    8. }
    9. ...
    10. }

    调用action中断函数

    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 +
    5. usecs_to_jiffies(netdev_budget_usecs);
    6. int budget = netdev_budget;
    7. for (;;) {
    8. struct napi_struct *n;
    9. n = list_first_entry(&list, struct napi_struct, poll_list);
    10. //变量sd,调用poll函数
    11. budget -= napi_poll(n, &repoll);
    12. //budget 与 time_limit控制退出
    13. if (unlikely(budget <= 0 ||
    14. time_after_eq(jiffies, time_limit))) {
    15. sd->time_squeeze++;
    16. break;
    17. }
    18. }
    核⼼逻辑是获取到当前 CPU变量 softnet_data ,对其 poll_list 进⾏遍历 , 然后执⾏到⽹卡驱动注册到的 poll 函数。
    1. static int igb_poll(struct napi_struct *napi, int budget)
    2. {
    3. if (q_vector->tx.ring)
    4. clean_complete = igb_clean_tx_irq(q_vector, budget);
    5. if (q_vector->rx.ring) {
    6. int cleaned = igb_clean_rx_irq(q_vector, budget);
    7. }
    8. }
    9. static int igb_clean_rx_irq(struct igb_q_vector *q_vector, const int budget)
    10. {
    11. while (likely(total_packets < budget)) {
    12. union e1000_adv_rx_desc *rx_desc;
    13. struct igb_rx_buffer *rx_buffer;
    14. unsigned int size;
    15. rx_buffer = igb_get_rx_buffer(rx_ring, size);
    16. igb_put_rx_buffer(rx_ring, rx_buffer);
    17. cleaned_count++;
    18. /* fetch next buffer in frame if non-eop */
    19. if (igb_is_non_eop(rx_ring, rx_desc))
    20. continue;
    21. /* verify the packet layout is correct */
    22. if (igb_cleanup_headers(rx_ring, rx_desc, skb)) {
    23. skb = NULL;
    24. continue;
    25. }
    26. /* probably a little skewed due to removing CRC */
    27. total_bytes += skb->len;
    28. /* populate checksum, timestamp, VLAN, and protocol */
    29. igb_process_skb_fields(rx_ring, rx_desc, skb);
    30. napi_gro_receive(&q_vector->napi, skb);
    31. /* update budget accounting */
    32. total_packets++;
    33. }
    34. return total_packets;
    35. }
    1. 从ringbuf中取出数据skb;
    2. 收取完数据以后,对其进⾏⼀些校验
    3. 设置 sbk 变量的 timestamp, VLAN id, protocol 
    1. gro_result_t napi_gro_receive(struct napi_struct *napi, struct sk_buff *skb)
    2. {
    3. skb_gro_reset_offset(skb);
    4. ret = napi_skb_finish(napi, skb, dev_gro_receive(napi, skb));
    5. trace_napi_gro_receive_exit(ret);
    6. }

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

    1. /* Pass the currently batched GRO_NORMAL SKBs up to the stack. */
    2. static void gro_normal_list(struct napi_struct *napi)
    3. {
    4. if (!napi->rx_count)
    5. return;
    6. netif_receive_skb_list_internal(&napi->rx_list);
    7. INIT_LIST_HEAD(&napi->rx_list);
    8. napi->rx_count = 0;
    9. }
    10. /* Queue one GRO_NORMAL SKB up for list processing. If batch size exceeded,
    11. * pass the whole batch up to the stack.
    12. */
    13. static void gro_normal_one(struct napi_struct *napi, struct sk_buff *skb)
    14. {
    15. list_add_tail(&skb->list, &napi->rx_list);
    16. if (++napi->rx_count >= gro_normal_batch)
    17. gro_normal_list(napi);
    18. }
    19. static gro_result_t napi_skb_finish(struct napi_struct *napi,
    20. struct sk_buff *skb,
    21. gro_result_t ret)
    22. {
    23. switch (ret) {
    24. case GRO_NORMAL:
    25. gro_normal_one(napi, skb);
    26. break;
    27. ...
    28. }

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

     参考

    零声教育


  • 相关阅读:
    nodejs 简介
    从零开始打造一款基于SpringBoot+SpringCloud的后台权限管理系统
    计算机毕业设计如何做
    初学python的感受
    RabbitMQ学习01
    数字集成电路设计(六、Verilog HDL高级程序设计举例)
    LVS+Keepalived高可用群集部署
    6月2(信息差)
    IDEA创建父子项目
    PG::SunsetDecoy
  • 原文地址:https://blog.csdn.net/WANGYONGZIXUE/article/details/124901341