linux内核对网卡驱动多队列的支持

陈伟堂 2018-11-15

linux的招牌就是它强大的网络功能,稳定,高效,能随着现实的日新月异而日趋完善。众所周知,linux的网卡由结构体net_device表示,一 个该结构体对应一个可以调度的数据包发送队列,注意,这里不谈数据包接收,数据包的实体在内核中以结构体sk_buff表示,这样的话,上述文字就可以用 以下图示来表示:

linux内核对网卡驱动多队列的支持

所谓的网卡对发送数据包的调度指的是多个数据包共享一个网卡的规则,当然就要拥有一系列的约定,比如区分数据包的优先级,区分数据包的类型,内核根据不同的调度策略来对不同的数据包进行排队,然后按照队列的顺序进行发送,关于这些细节不是本文要讨论的内容,可以参考linux内核对网络流量控制的支持方面的资料,需要明白的是,以往不支持多队列的内核中每个网卡拥有一个排队规则,然后根据排队规则的过滤器将数据包区分为一个一个的类,类中还可以嵌套新的更 细的排队规则,这个过程可以看到是以网卡为中心的,因为是每网卡一队列,这个队列指的是根队列,以网卡为中心实际上是一种过时的方式,还可以看成是以现在的观点来看偷懒的方式,毕竟网卡是内核和整个系统中的静态数据,是完全可以把握的,可是这已经不能适合当今的网络应用了,当今是一个必须适应大环境的世界 而不再是局部意义上的入乡随俗的世界了,丰富的数据没有必要入linux网卡的乡随网卡唯一的根队列的俗了,取而代之的是必须为丰富的网络数据提供属于它 们自己的舞台而不是逼迫它们强挤到网卡的根队列等待调度。我们来看一下具体的意义。

特别在无线设备上,几乎都是网络应用,有声音,有视频,有电子邮件,有实时数据等等。如果将这些数据都统一到一个队列中当然是可以的,因为2.6.27之 前的内核并没有因为没有实现多队列而出现问题,但是这么做是有弊端的,因为我们将必须用一种统一的方式管理它们,可实际上它们的区别很大很大,如果统一的队列有一些什么限制的话,这种限制将会限制到所有的数据,这将导致调度程序的复杂化。事实上真的需要将事情搅和在一起从而必须增加复杂性吗?我们在遇到困 难的时候必须想想现实是什么样子的,然后我们按照真实的世界的样子去做就会得到最好的解决方案,返璞归真真的是一条亘古不变的真理。实现了多队列,每个网卡将拥有很多的根队列,每个应用一个都有可能,这样的话我们就可以随心所欲的控制并且仅仅控制我们想控制的数据包了,比如视频数据强调实时性而对数据的准 确性要求不高,这样我们需要给视频数据包队列更高的发送优先级,为了不使视频效果变得很卡,我们缩短视频数据包队列的长度,而对那些要求准确性但是不要求实时的比如电子邮件的数据包队列,我们可以降低其优先级但是增加队列的长度,仅此而已,不需要变更已经很优美的调度机制就可以轻松实现。实现了多队列的网 卡的图示如下:

前面简要说明了多队列的意义,在对内核的更改上也很简单,就是将原先的每个net_device一个的队列改为多个就可以了:

static inline void qdisc_reset_all_tx(struct net_device *dev)

{

- qdisc_reset(dev->tx_queue.qdisc);

+ unsigned int i;

+ for (i = 0; i < dev->num_tx_queues; i++)

+ qdisc_reset(netdev_get_tx_queue(dev, i)->qdisc);

}

int dev_queue_xmit(struct sk_buff *skb)

{

struct net_device *dev = skb->dev;

@@ -1702,7 +1708,7 @@ int dev_queue_xmit(struct sk_buff *skb)

}

gso:

- txq = &dev->tx_queue;

+ txq = dev_pick_tx(dev, skb);

spin_lock_prefetch(&txq->lock);

/* Disable soft irqs for various locks below. Also

@@ -3788,8 +3794,9 @@ static void rollback_registered(struct net_device *dev)

dev_put(dev);

}

dev_pick_tx之后的一些代码就是:

...

txq = dev_pick_tx(dev, skb); //选出一个队列,不像以前那样的直接得到本网卡的队列。

q = rcu_dereference(txq->qdisc);

if (q->enqueue) { //这之后的q就和没有多队列支持的q是一样的了。

spinlock_t *root_lock = qdisc_lock(q);

spin_lock(root_lock);

if (unlikely(test_bit(__QDISC_STATE_DEACTIVATED, &q->state))) {

kfree_skb(skb);

rc = NET_XMIT_DROP;

} else {

rc = qdisc_enqueue_root(skb, q);

qdisc_run(q); //这应该很熟悉了

}

spin_unlock(root_lock);

goto out;

}

...

我们看一下dev_pick_tx:

static struct netdev_queue *dev_pick_tx(struct net_device *dev, struct sk_buff *skb)

{

u16 queue_index = 0;

if (dev->select_queue)

queue_index = dev->select_queue(dev, skb); //选择一个索引,这个策略可以设置,比如优先选择视频和音频队列,而哪个队列邦定哪个策略也是设定的。

else if (dev->real_num_tx_queues > 1)

queue_index = simple_tx_hash(dev, skb);

skb_set_queue_mapping(skb, queue_index);

return netdev_get_tx_queue(dev, queue_index);

}

另外,在网卡的hard_start_xmit中,驱动程序可以反馈给调度代码是否继续调度该队列或者下一个调度哪个队列,这些的具体策略全看场景怎么设置了,linux总是可以在机制和策略之间达到一个完美的平衡,比如利用一些标志位之类的小技巧。

linux内核对网卡驱动多队列的支持

相关推荐