linux网络报文接收发送浅析

  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

linux网络报文接收发送浅析

对于linux内核来说,网络报文由网络设备来进行接收。设备驱动程序从网络设备中读取报文,通过内核提供的网络接口函数,将报文传递到内核中的网络协议栈。报文经过协议栈的处理,或转发、或丢弃、或被传送给某个进程。

网络报文的发送与之相反,进程通过系统调用将数据送入网络协议栈,或者由网络协议栈自己发起报文的发送,然后协议栈通过调用网络接口函数来调度驱动程序,使其将报文传送给网络设备,从而发送出去。本文讨论的是网络接口层,它是网络设备驱动程序与网络协议栈交互的纽带。见下图中红色部分的netif。

报文的接收

网络报文的接收源自网络设备。网络设备在接收到一个报文之后,通过中断告知CPU。网卡驱动程序需要注册对该中断事件的处理函数(参见《linux中断处理浅析》),以处理接收到的报文。

在中断处理函数中,网络驱动程序有两种方法对报文进行处理(老式的方法,和新式的方法),我们先介绍老式的处理方式。在这种方式下,中断处理函数主要完成以下工作:

分配一个skb结构(该结构用于保存一个报文)。操作设备,将设备收到的数据拷贝到这个skb结构对应的缓冲区中。设置skb的协议类型skb->protocol,该类型表明了网络协议栈的上层协议(下面我们将会看到)。然后调用内核提供的网络接口函数netif_rx;

netif_rx(skb);

netif_rx函数对skb的如时间戳这样的附加信息进行初始化以后,将这个skb结构放入当前CPU的softdate_net结构的input_pkt_queue队列中。netif_rx会根据队列的长度,对设备的拥塞状况进行判断(队列过长则代表报文接收过快,以致于上层来不及处理)。如果设备已陷入拥塞,则收到的报文可能直接被丢弃。

如果一切正常,netif_rx会调用网络接口函数netif_rx_schedule,以触发对接收报文的进一步处理;

netif_rx_schedule(dev);

netif_rx使用softdate_net结构中内嵌的backlog_dev作为dev来调用netif_rx_schedule,后者将其加入到softdate_net结构的poll_list队列中(如果这个dev不在队列中的话),以使其等待被调度。

相比老式的处理方式,新式的处理方式(称为NAPI)在中断处理函数中仅仅是以对应设备的dev结构为参数调用netif_rx_schedule函数即可。

最后netif_rx_schedule函数会触发NET_RX_SOFTIRQ软中断,于是接下来对应的软中断处理函数

net_rx_action将被调用;

net_rx_action();

对于当前CPU对应的softdate_net结构的poll_list队列中的所有dev,调用dev->poll方法。该方法是由对应dev的驱动程序实现的,用于接收及处理报文(前面提到的backlog_dev除外)。

net_rx_action每次运行都有一定的限度,并不一定要将所有报文都处理完。在处理完一定数量的报文配额、或处理过程超过一定时间后,net_rx_action便会返回。返回前触发一次NET_RX_SOFTIRQ软中断,等待下一次中断到来的时候继续被调度。

以上过程如图所示(摘自ULNI):

上面提到的softdate_net结构是用于进行报文收发调度的结构,内核为每个CPU维护一个这样的结构。在报文接收过程中用到了其中的三个成员:

1、poll_list,网络设备dev的队列。其中的设备接收到了报文,需要被处理;

2、input_pkt_queue,skb报文结构的队列,保存了已接收并需要被处理的报文;

3、backlog_dev,一个虚拟的网络设备dev结构;

后两个成员是专门为支持老式的处理方式而设置的,在这种方式下,接收到的skb被放入input_pkt_queue 队列,然后backlog_dev被加入poll_list。而最后,自然backlog_dev->poll函数将对input_pkt_queue队列中的skb进行处理。backlog_dev->poll等于process_backlog函数;

process_backlog(backlog_dev, budget);

既然net_rx_action每次运行都有一个配额,它在调用dev->poll时也会传递当前剩余的配额值,即budget。process_backlog会遍历input_pkt_queue队列中的skb,调用netif_receive_skb函数对其进行处理。process_backlog函数有两种结局,一个是配额到或时间到,直接返回;另一个是处理完input_pkt_queue 队列中的所有skb,此时需要将backlog_dev从poll_list中删除。

新式的NAPI处理方式所要做的事跟老的处理方式其实是很类似的。在其对应的dev->poll函数中,需要分配skb结构、从设备读取报文、调用netif_receive_skb让网络协议栈的上层来处理报文。

这种方式最大的好处是:在dev->poll函数中,不一定只处理一个报文。具体怎么处理可以由驱动程序灵活控制。比如说,假设现在网络负载非常大,如果网络设备每接收一个报文都通过一次中断来告知内核,

这样做效率并不理想。而此时dev->poll可以做一些轮询的工作,如果网络设备已经接收了多个报文,可

以一次性都处理了。并且,就算设备此刻所接收到的报文都已经处理完了,驱动程序也可以根据某种方式

预判设备在很短的一段时间内还将收到报文,于是依然将自己对应的dev结构留在poll_list中,等待下一

次继续被调度。

当dev仍结构留在poll_list中时,设备驱动程序可以关闭设备接收到报文时的中断通知,因为目前处于轮

询状态。而当驱动程序认为在将来的一段时间以内无报文可收时,则可以将其dev从poll_list中移除,然

后开启设备接收到报文时的中断通知。等待下一次报文接收的中断到来时,这个dev再重新被放入poll_list。netif_receive_skb(skb);

该函数会将skb提交给抓包程序进行处理、还会触发数据链路层的桥接功能(见《linux网桥浅析》)、然后将报文提交给网络协议栈的上层(网络层)进行处理。

网络层的协议有IP、ARP等等很多种,在这里怎么知道这个skb该提交给哪种协议呢?在报文的数据链路层报头中保存着三个重要信息,发送者和接收者的Mac地址、和上层协议标识。回想一下之前的流程,在skb接收完成之后我们就已经设置了skb->protocol(从报头中得到),上层协议就由它来指定。比如,0x0800代表IP协议、0x0806代表ARP协议,这是由协议规定的。

netif_receive_skb并不是用一个switch-case来匹配skb->protocol,以选择网络层处理函数的。系统中有

一个名为ptype_base的hash表,各种网络层的协议在其初始化时都会在这个hash表中注册一个类型为packet_type的表项(以协议类型为key),如下图所示(摘自ULNI):

netif_receive_skb要做的就是在这个hash表中遍历所有type与skb->protocol匹配的packet_type结构(packet_type结构的dev可用于限定skb->dev,NULL表示不限),然后调用其func回调函数。(可见,一个报文有可能被多种协议所处理。)

至此报文被提交到了网络层,在这里就不继续深入了。

报文的发送

报文的发送是由网络协议栈的上层发起的。网络协议栈上层构造一个需要发送的skb结构后(该skb已经

包含了数据链路层的报头),调用dev_queue_xmit函数进行发送;

dev_queue_xmit(skb);

该函数先会处理一些缓冲区重组、计算校验和之类的杂事,然后开始处理报文的发送。

发送报文有两种策略,有队列或无队列。这是由网络设备驱动程序在定义其对应的dev结构时指定的,一般的设备都会使用队列。

dev->qdisc指向一个队列的实例,里面包含了队列本身以及操作队列的方法(enqueue、dequeue、requeue)。这些方法的集合组成了一种队列规则(skb将以某种规则入队、以某种规则出队,并不一定是简单的先进

相关文档
最新文档