当前位置: 首页 > article >正文

接收网络包的过程——从硬件网卡解析到IP层

当一些网络包到来触发了中断,内核处理完这些网络包之后,我们可以先进入主动轮询 poll 网卡的方式,主动去接收到来的网络包。如果一直有,就一直处理,等处理告一段落,就返回干其他的事情。当再有下一批网络包到来的时候,再中断,再轮询 poll。这样就会大大减少中断的数量,提升网络处理的效率,这种处理方式我们称为 NAPI。

在网卡驱动程序初始化的时候,我们会调用 ixgb_init_module,注册一个驱动 ixgb_driver,并且调用它的 probe 函数 ixgb_probe。

在 ixgb_probe 中,我们会创建一个 struct net_device 表示这个网络设备,并且 netif_napi_add 函数为这个网络设备注册一个轮询 poll 函数 ixgb_clean,将来一旦出现网络包的时候,就是要通过它来轮询了。

当一个网卡被激活的时候,我们会调用函数 ixgb_open->ixgb_up,在这里面注册一个硬件的中断处理函数。

如果一个网络包到来,触发了硬件中断,就会调用 ixgb_intr,这里面会调用 __napi_schedule。

__napi_schedule 是处于中断处理的关键部分,在他被调用的时候,中断是暂时关闭的,但是处理网络包是个复杂的过程,需要到延迟处理部分,所以 ____napi_schedule 将当前设备放到 struct softnet_data 结构的 poll_list 里面,说明在延迟处理部分可以接着处理这个 poll_list 里面的网络设备。

然后 ____napi_schedule 触发一个软中断 NET_RX_SOFTIRQ,通过软中断触发中断处理的延迟处理部分,也是常用的手段。

在 net_rx_action 中,会得到 struct softnet_data 结构,这个结构在发送的时候我们也遇到过。当时它的 output_queue 用于网络包的发送,这里的 poll_list 用于网络包的接收。

在 net_rx_action 中,接下来是一个循环,在 poll_list 里面取出网络包到达的设备,然后调用 napi_poll 来轮询这些设备,napi_poll 会调用最初设备初始化的时候,注册的 poll 函数,对于 ixgb_driver,对应的函数是 ixgb_clean。

在网络设备的驱动层,有一个用于接收网络包的 rx_ring。它是一个环,从网卡硬件接收的包会放在这个环里面。这个环里面的 buffer_info[]是一个数组,存放的是网络包的内容。i 和 j 是这个数组的下标,在 ixgb_clean_rx_irq 里面的 while 循环中,依次处理环里面的数据。在这里面,我们看到了 i 和 j 加一之后,如果超过了数组的大小,就跳回下标 0,就说明这是一个环。

ixgb_check_copybreak 函数将 buffer_info 里面的内容,拷贝到 struct sk_buff *skb,从而可以作为一个网络包进行后续的处理,然后调用 netif_receive_skb。

从 netif_receive_skb 函数开始,我们就进入了内核的网络协议栈。

接下来的调用链为:netif_receive_skb->netif_receive_skb_internal->__netif_receive_skb->__netif_receive_skb_core。

在 __netif_receive_skb_core 中,我们先是处理了二层的一些逻辑。例如,对于 VLAN 的处理,接下来要想办法交给第三层。

在网络包 struct sk_buff 里面,二层的头里面有一个 protocol,表示里面一层,也即三层是什么协议。deliver_ptype_list_skb 在一个协议列表中逐个匹配。如果能够匹配到,就返回。

网络协议栈的 IP 层,从 ip_rcv 函数开始,我们的处理逻辑就从二层到了三层,IP 层。

在 ip_rcv 中,得到 IP 头,然后又遇到了我们见过多次的 NF_HOOK,这次因为是接收网络包,第一个 hook 点是 NF_INET_PRE_ROUTING,也就是 iptables 的 PREROUTING 链。如果里面有规则,则执行规则,然后调用 ip_rcv_finish。

ip_rcv_finish 得到网络包对应的路由表,然后调用 dst_input,在 dst_input 中,调用的是 struct rtable 的成员的 dst 的 input 函数。在 rt_dst_alloc 中,我们可以看到,input 函数指向的是 ip_local_deliver。

在 ip_local_deliver 函数中,如果 IP 层进行了分段,则进行重新的组合。接下来就是我们熟悉的 NF_HOOK。hook 点在 NF_INET_LOCAL_IN,对应 iptables 里面的 INPUT 链。在经过 iptables 规则处理完毕后,我们调用 ip_local_deliver_finish。

在 IP 头中,有一个字段 protocol 用于指定里面一层的协议,在这里应该是 TCP 协议。于是,从 inet_protos 数组中,找出 TCP 协议对应的处理函数。这个数组的定义如下,里面的内容是 struct net_protocol。

在系统初始化的时候,网络协议栈的初始化调用的是 inet_init,它会调用 inet_add_protocol,将 TCP 协议对应的处理函数 tcp_protocol、UDP 协议对应的处理函数 udp_protocol,放到 inet_protos 数组中。

在上面的网络包的接收过程中,会取出 TCP 协议对应的处理函数 tcp_protocol,然后调用 handler 函数,也即 tcp_v4_rcv 函数。

接收网络包的上半部分,分以下几个层次。

  • 硬件网卡接收到网络包之后,通过 DMA 技术,将网络包放入 Ring Buffer。
  • 硬件网卡通过中断通知 CPU 新的网络包的到来。
  • 网卡驱动程序会注册中断处理函数 ixgb_intr。
  • 中断处理函数处理完需要暂时屏蔽中断的核心流程之后,通过软中断 NET_RX_SOFTIRQ 触发接下来的处理过程。
  • NET_RX_SOFTIRQ 软中断处理函数 net_rx_action,net_rx_action 会调用 napi_poll,进而调用 ixgb_clean_rx_irq,从 Ring Buffer 中读取数据到内核 struct sk_buff。
  • 调用 netif_receive_skb 进入内核网络协议栈,进行一些关于 VLAN 的二层逻辑处理后,调用 ip_rcv 进入三层 IP 层。
  • 在 IP 层,会处理 iptables 规则,然后调用 ip_local_deliver,交给更上层 TCP 层。
  • 在 TCP 层调用 tcp_v4_rcv。

此文章为11月Day25学习笔记,内容来源于极客时间《趣谈Linux操作系统》,推荐该课程。


http://www.kler.cn/news/148024.html

相关文章:

  • jar包不挂断地运行命令
  • 【电路笔记】-电源电压
  • 时间复杂度和运算
  • lack——主页前后端开发优化(精华:java多线程实现数据插入)
  • 【docker】docker的基础命令
  • 消失的数字,旋转数组(leetcode 一题多解)
  • 力扣 hot100 最小覆盖子串 滑动窗口 字符计数
  • 【沁恒蓝牙mesh】CH58x 将RTC时钟切换为LSE外部低速时钟
  • 中年人怎么发展?持续发展?
  • 牛客 算法 HJ103 Redraiment的走法 golang语言实现
  • 【brpc学习实践九】mbvar及bvar可观测
  • Web语言基础课程期末代做
  • 【git】pip install git+https://github.com/xxx/xxx替换成本地下载编译安装解决网络超时问题
  • 【开源】基于Vue和SpringBoot的企业项目合同信息系统
  • 【实验】配置用户通过IPv6方式上网
  • uni-app图片上传
  • idea打开.class文件没有反编译
  • “SRP模型+”多技术融合在生态环境脆弱性评价模型构建、时空格局演变分析与RSEI 指数的生态质量评价及拓展应用
  • Redis 面试题——持久化
  • Leetcode 2939. Maximum Xor Product
  • 问答知识库快速构建技术解析及行业实践
  • springsecurity6配置三
  • [Java][单列集合+数组遍历方法]通过Lambda表达式简化匿名内部类遍历数组学习体会
  • Python常见基础数据结构
  • 门店管理系统小程序作用是什么
  • 【git】工作中常用的命令
  • linux复习笔记05(小滴课堂)
  • FPGA模块——AD高速转换模块(并行输出转换的数据)
  • SpringBootWeb案例_01
  • RBAC(Role-Based Access Control,基于角色的访问控制)