这篇文档主要介绍了网络数据包在二层的发送流程。网络数据包在二层的发送主要包括了网络设备层和驱动层两个部分,所以下面将会从这两个方面讲述报文在二层的发送流程。
网络设备层在报文发送时的处理流程
当网络协议栈上层准备好了待发送的报文,即构造了一个管理着待发送报文数据的skb对象之后,便会调用网络设备层的主入口函数dev_queue_xmit()进行后续的发送处理。
当上层已经准备好的skb对象达到网络设备层之后,一般来说并不是直接交给网卡驱动的(在没有设置TCQ_F_CAN_BYPASS的情况下),而是会用类型为struct netdev_queue的发送队列先将skb对象缓存起来,接着依次处理发送队列中的skb对象,将其中的报文数据交给网卡发送出去。而在类型为struct netdev_queue的发送队列中,真正用来缓存skb对象的则是类型为struct Qdisc的实例,该类型的实例通常会实现一组出入队列的回调函数,来实现skb的缓存,重传和移除等操作。当发送队列中struct Qdisc的实例设置了TCQ_F_CAN_BYPASS标志的时候,会将上层下发的skb对象直接通过网卡驱动交给网卡进行发送。
下图是网络设备层的报文发送主要流程:
1 | void __qdisc_run(struct Qdisc *q) |
从qdisc_run()函数的实现我们可以看到会有如下两种情况发生:
1) 一次性将struct Qdisc队列实例中所有的skb对象通过网卡驱动交给网卡发送出去。
2) 在某次处理struct Qdisc队列实例中的skb对象时,由于某些原因中途停止了,队列实例中可能还有skb对象没有处理完。
当struct Qdisc队列实例中还有skb对象没有处理完时,就会调用netif_schedule()函数触发一次发送软中断(NET_TX_SOFTIRQ),并将struct Qdisc队列实例加入到cpu私有数据对象softnet_data的output_queue链表成员中,在软中断中会遍历output_queue,继续处理其中的struct Qdisc队列实例剩余的skb对象。发送软中断处理函数实现如下:
1 | static __latent_entropy void net_tx_action(struct softirq_action *h) |
在net_tx_action()函数的实现中可以看到,其间接又调用了__qdisc_run()函数,这说明只要struct Qdisc队列实例中有skb对象没有处理完,就会继续触发发送软中断直到所有的队列中所有的skb对象都被处理完。
ixgbe驱动中和数据发送相关的内容
在二层网络报文接收流程中,接收报文描述符承载了报文从网卡到主存的过程,与之相对应的,发送报文描述符则承载了报文从主存到网卡的过程。对于网卡驱动而言,当收到来自协议栈上层下发的网络报文时,网卡驱动会将存放着报文数据的地址写入到报文发送描述符中,并将填充了报文地址信息的描述符传递给网卡,而网卡则从报文发送描述符中存放的地址中读取报文数据。
报文发送描述符
对于82599网卡而言,其支持两种格式的报文发送描述符,即传统格式和高级格式。虽然有两种不同格式的报文发送描述符,但是两种格式的报文发送描述符所占用的内存大小是一样的(目前为16字节),只是对这块内存使用有所不同。对于两种不同格式的报文发送描述符,可以通过设置报文发送描述符中的TDESC.DEXT位进行区分,当该位设置为0的时候,表明使用的是传统格式;当该位设置为1的时候,表明使用的是高级格式。下面介绍高级格式的报文发送描述符。
相比于传统格式,高级格式的报文发送描述符可以用来支持更多的功能特性。高级格式的报文描述符由于需要支持更多的功能特性,所以分为了读格式和回写格式。
先来看下82599网卡中读格式的定义,如下图:
1 | static bool ixgbe_clean_tx_irq(struct ixgbe_q_vector *q_vector, |
到这里,报文在二层的发送流程就介绍完了。