二层(链路层)数据包发送过程分析

说明:本系列博文所涉及内核版本为2.6.32
当上层准备好一个包之后,交给链路层,链路层数据包发送主要通过dev_queue_xmit函数处理。数据包的发送可分为两种,一种是正常的传输流程,即通过网卡驱动,另一种是通过软中断(见注3)。为了理解方便,首先看一下dev_queue_xmi函数的整体调用关系图。28541347_14560547847AAd

  • dev_queue_xmit

本函数用来将带发送的skb加入一个dev的队列(Queue),调用这个函数前必须设置好skb的device和priority,本函数可以在中断上下文中被调用。

返回值:

返回非0(正数或负数)表示函数出错,返回0表示成功,但是并不表示数据包被成功发送出去,因为数据包可能因为限速等原因被丢掉。

函数执行后传入的skb将被释放,所以如果想控制数据包,实现对skb的重传时需要增加skb的引用计数。

当调用此函数时中断必须是打开的,因为BH enable必须要求IRQ enable,否则会造成死锁。

  • __dev_xmit_skb

__dev_xmit_skb函数主要做两件事情:

(1)如果流控对象为空的,试图直接发送数据包。

(2)如果流控对象不空,将数据包加入流控对象,并运行流控对象。

循环调用qdisc_restart发送数据,下面这个函数qdisc_restart是真正发送数据包的函数,它从队列上取下一个帧,然后尝试将它发送出去,若发送失败则一般是重新入队。

此函数返回值为:发送成功时返回剩余队列长度,发送失败时返回0(若发送成功且剩余队列长度为0也返回0)

  • qdisc_restart

__QDISC_STATE_RUNNING状态保证同一时刻只有一个cpu在处理这个qdisc,qdisc_lock(q)用来保证对这个队列的顺序访问。

通常netif_tx_lock用来确保本设备驱动的顺序(独占)访问的,qdisc_lock(q)用来保证qdisc的顺序访问,这两个是互斥的,获得其中一个必须释放另一个。

  • sch_direct_xmit

发送一个skb,将队列置为__QDISC_STATE_RUNNING状态,保证只有一个cpu运行这个函数,返回0表示队列为空或者发送受限,大于0表示队列非空。

  • dev_hard_start_xmit

  • dev_queue_xmit_nit

  • 环回设备

对于环回设备loopback,设备的ops->ndo_start_xmit被初始化为loopback_xmit函数。

  • drivers/net/loopback.c

  • 注:

1. CHECKSUM_PARTIAL表示使用硬件checksum ,L4层的伪头的校验已经完毕,并且已经加入uh->check字段中,此时只需要设备计算整个头4层头的校验值。

2. 整个数据包发送逻辑中会涉及到三个用于互斥访问的代码:

(1)spinlock_t *root_lock = qdisc_lock(q);

(2)test_and_set_bit(__QDISC_STATE_RUNNING, &q->state)

(3)__netif_tx_lockà spin_lock(&txq->_xmit_lock)

其中(1)(3)分别对应一个spinlock,(2)对应一个队列状态。在了解代码中如何使用这三个同步方法时,首先看一下相关数据结构的关系,如下。

28541347_14560553724420

图中绿色部分表示(1)(3)两处spinlock。首先看(1)处对应的代码:

所以root_lock是用于控制qdisc中skb队列访问的锁,当需要对skb队列进行enqueue、dequeue、requeue时,就需要加锁。

__QDISC_STATE_RUNNING标志用于保证一个流控对象(qdisc)不会同时被多个cpu访问。

而(3)处的spinlock,即struct netdev_queue中的_xmit_lock,则用于保证dev的注册函数的互斥访问,即deriver的同步。

另外,内核代码注释中写到,(1)和(3)是互斥的,获得(1)处的锁时必须先保证释放(3)处的锁,反之亦然,为什么要这样还没有想明白。。。。哪位大神知道还望指点

3. 已经有了dev_queue_xmit函数,为什么还需要软中断来发送呢?

我们可以看到在dev_queue_xmit中将skb进行了一些处理(比如合并成一个包,计算校验和等),处理完的skb是可以直接发送的了,这时dev_queue_xmit也会先将skb入队(skb一般都是在这个函数中入队的),并且调用qdisc_run尝试发送,但是有可能发送失败,这时就将skb重新入队,调度软中断,并且自己直接返回。

软中断只是发送队列中的skb以及释放已经发送的skb,它无需再对skb进行线性化或者校验和处理。另外在队列被停止的情况下,dev_queue_xmit仍然可以把包加入队列,但是不能发送,这样在队列被唤醒的时候就需要通过软中断来发送停止期间积压的包。简而言之,dev_queue_xmit是对skb做些最后的处理并且第一次尝试发送,软中断是将前者发送失败或者没发完的包发送出去。(其实发送软中断还有一个作用,就是释放已经发送的包,因为某些情况下发送是在硬件中断中完成的,为了提高硬件中断处理效率,内核提供一种方式将释放skb放到软中断中进行,这时只要调用dev_kfree_skb_irq,它将skb加入softnet_data的completion_queue中,然后开启发送软中断,net_tx_action会在软中断中将completion_queue中的skb全部释放掉)

打赏支持我写出更多好文章,谢谢!

打赏作者

打赏支持我写出更多好文章,谢谢!

1 2 收藏 评论

关于作者:lvyilong316

linux爱好者 个人主页 · 我的文章 · 5

相关文章

可能感兴趣的话题



直接登录
跳到底部
返回顶部