函数开头的time_limit和budget是用来控制net_rx_action函数主动退出的,目的是保证网络包的接收不霸占CPU不放 。等下次网卡再有硬中断过来的时候再处理剩下的接收数据包 。其中budget可以通过内核参数调整 。这个函数中剩下的核心逻辑是获取到当前CPU变量softnet_data,对其poll_list进行遍历, 然后执行到网卡驱动注册到的poll函数 。对于igb网卡来说,就是igb驱动力的igb_poll函数了 。
static int igb_poll(struct napi_struct *napi, int budget){
... if (q_vector->tx.ring) clean_complete = igb_clean_tx_irq(q_vector); if (q_vector->rx.ring) clean_complete &= igb_clean_rx_irq(q_vector, budget); ...}
在读取操作中,igb_poll的重点工作是对igb_clean_rx_irq的调用 。
static bool igb_clean_rx_irq(struct igb_q_vector *q_vector, const int budget){ ... do { /* retrieve a buffer from the ring */ skb = igb_fetch_rx_buffer(rx_ring, rx_desc, skb); /* fetch next buffer in frame if non-eop */ if (igb_is_non_eop(rx_ring, rx_desc)) continue; } /* verify the packet layout is correct */ if (igb_cleanup_headers(rx_ring, rx_desc, skb)) { skb = NULL; continue; } /* populate checksum, timestamp, VLAN, and protocol */ igb_process_skb_fields(rx_ring, rx_desc, skb); napi_gro_receive(&q_vector->napi, skb);
}
igb_fetch_rx_buffer和igb_is_non_eop的作用就是把数据帧从RingBuffer上取下来 。为什么需要两个函数呢?因为有可能帧要占多多个RingBuffer,所以是在一个循环中获取的,直到帧尾部 。获取下来的一个数据帧用一个sk_buff来表示 。收取完数据以后,对其进行一些校验,然后开始设置sbk变量的timestamp, VLAN id, protocol等字段 。接下来进入到napi_gro_receive中:
//file: net/core/dev.cgro_result_t napi_gro_receive(struct napi_struct *napi, struct sk_buff *skb){ skb_gro_reset_offset(skb); return napi_skb_finish(dev_gro_receive(napi, skb), skb);}
dev_gro_receive这个函数代表的是网卡GRO特性,可以简单理解成把相关的小包合并成一个大包就行,目的是减少传送给网络栈的包数,这有助于减少 CPU 的使用量 。我们暂且忽略,直接看napi_skb_finish, 这个函数主要就是调用了netif_receive_skb 。
//file: net/core/dev.cstatic gro_result_t napi_skb_finish(gro_result_t ret, struct sk_buff *skb){ switch (ret) { case GRO_NORMAL: if (netif_receive_skb(skb)) ret = GRO_DROP; break; ......}
在netif_receive_skb中,数据包将被送到协议栈中 。声明,以下的3.3, 3.4, 3.5也都属于软中断的处理过程,只不过由于篇幅太长,单独拿出来成小节 。
3.3 网络协议栈处理netif_receive_skb函数会根据包的协议,假如是udp包,会将包依次送到ip_rcv(),udp_rcv()协议处理函数中进行处理 。
文章插图
图10 网络协议栈处理
//file: net/core/dev.cint netif_receive_skb(struct sk_buff *skb){ //RPS处理逻辑,先忽略 ...... return __netif_receive_skb(skb);}static int __netif_receive_skb(struct sk_buff *skb){ ...... ret = __netif_receive_skb_core(skb, false);}static int __netif_receive_skb_core(struct sk_buff *skb, bool pfmemalloc){ ...... //pcap逻辑,这里会将数据送入抓包点 。tcpdump就是从这个入口获取包的 list_for_each_entry_rcu(ptype, &ptype_all, list) { if (!ptype->dev || ptype->dev == skb->dev) { if (pt_prev) ret = deliver_skb(skb, pt_prev, orig_dev); pt_prev = ptype; } } ...... list_for_each_entry_rcu(ptype, &ptype_base[ntohs(type) & PTYPE_HASH_MASK], list) { if (ptype->type == type && (ptype->dev == null_or_dev || ptype->dev == skb->dev || ptype->dev == orig_dev)) { if (pt_prev) ret = deliver_skb(skb, pt_prev, orig_dev); pt_prev = ptype; } }}
推荐阅读
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 三种使用AI攻击网络安全的方法
- 理解了Linux I/O机制,才能真的明白“什么是多线程”
- Linux网络配置
- 带你重新认识Linux系统的inode
- 古剑奇谭网络版野外pvp 古剑奇谭ol剧情
- 从Linux源码看Socket的listen及连接队列
- linux网络编程常见API详解
- 了解神经网络和模型泛化
- TCP/IP 基础知识总结
- 如何在电脑上安装网络打印机?详细教程全部教给你