当一些网络包到来触发中断内核处理完这些网络包之后,我们可以进入主动轮询 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 函数。

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

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

原文地址:https://blog.csdn.net/key_3_feng/article/details/134616557

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任

如若转载,请注明出处:http://www.7code.cn/show_18659.html

如若内容造成侵权/违法违规/事实不符,请联系代码007邮箱suwngjj01@126.com进行投诉反馈,一经查实,立即删除

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注