從veth看虛擬網路裝置的qdisc
背景
前段時間在測試docker的網路效能的時候,發現了一個veth的效能問題,後來給docker官方提交了一個PR,參考set tx_queuelen to 0 when create veth device,引起了一些討論。再後來,RedHat的網路專家Jesper Brouer 出來詳細的討論了一下這個問題。
可以看到,veth裝置qdisc佇列,而環回裝置/橋接裝置是沒qdisc佇列的,參考br_dev_setup函式。
核心實現
在註冊(建立)裝置時,qdisc設定為noop_qdisc,
register_netdevice -> dev_init_scheduler
void dev_init_scheduler(struct net_device *dev)
{
dev->qdisc = &noop_qdisc;
netdev_for_each_tx_queue(dev, dev_init_scheduler_queue, &noop_qdisc);
dev_init_scheduler_queue(dev, &dev->rx_queue, &noop_qdisc);
setup_timer(&dev->watchdog_timer, dev_watchdog, (unsigned long)dev);
}
開啟裝置時,如果沒有配置qdisc時,就指定為預設的pfifo_fast佇列:
dev_open -> dev_activate,
void dev_activate(struct net_device *dev)
{
int need_watchdog;
/* No queueing discipline is attached to device;
create default one i.e. pfifo_fast for devices,
which need queueing and noqueue_qdisc for
virtual interfaces
*/
if (dev->qdisc == &noop_qdisc)
attach_default_qdiscs(dev);
...
}
static void attach_default_qdiscs(struct net_device *dev)
{
struct netdev_queue *txq;
struct Qdisc *qdisc;
txq = netdev_get_tx_queue(dev, 0);
if (!netif_is_multiqueue(dev) || dev->tx_queue_len == 0) {
netdev_for_each_tx_queue(dev, attach_one_default_qdisc, NULL);
dev->qdisc = txq->qdisc_sleeping;
atomic_inc(&dev->qdisc->refcnt);
} else {///multi queue
qdisc = qdisc_create_dflt(dev, txq, &mq_qdisc_ops, TC_H_ROOT);
if (qdisc) {
qdisc->ops->attach(qdisc);
dev->qdisc = qdisc;
}
}
}
static void attach_one_default_qdisc(struct net_device *dev,
struct netdev_queue *dev_queue,
void *_unused)
{
struct Qdisc *qdisc;
if (dev->tx_queue_len) {
qdisc = qdisc_create_dflt(dev, dev_queue,
&pfifo_fast_ops, TC_H_ROOT);
if (!qdisc) {
printk(KERN_INFO "%s: activation failed\n", dev->name);
return;
}
/* Can by-pass the queue discipline for default qdisc */
qdisc->flags |= TCQ_F_CAN_BYPASS;
} else {
qdisc = &noqueue_qdisc;
}
dev_queue->qdisc_sleeping = qdisc;
}
建立noqueue
開始嘗試直接刪除裝置預設的pfifo_fast佇列,發現會出錯:
# tc qdisc del dev vethd4ea root
RTNETLINK answers: No such file or directory
# tc -s qdisc ls dev vethd4ea
qdisc pfifo_fast 0: root refcnt 2 bands 3 priomap 1 2 2 2 1 2 0 0 1 1 1 1 1 1 1 1
Sent 29705382 bytes 441562 pkt (dropped 0, overlimits 0 requeues 0)
backlog 0b 0p requeues 0
後來看到Jesper Brouer給出一個替換預設佇列的方式,嘗試了一下,成功完成。
替換預設的qdisc佇列
# tc qdisc replace dev vethd4ea root pfifo limit 100
# tc -s qdisc ls dev vethd4ea
qdisc pfifo 8001: root refcnt 2 limit 100p
Sent 264 bytes 4 pkt (dropped 0, overlimits 0 requeues 0)
backlog 0b 0p requeues 0
# ip link show vethd4ea
9: vethd4ea: <BROADCAST,UP,LOWER_UP> mtu 1500 qdisc pfifo master docker0 state UP mode DEFAULT qlen 1000
link/ether 3a:15:3b:e1:d7:6d brd ff:ff:ff:ff:ff:ff
修改佇列長度
# ifconfig vethd4ea txqueuelen 0
刪除qdisc
# tc qdisc del dev vethd4ea root
# ip link show vethd4ea
9: vethd4ea: <BROADCAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP mode DEFAULT
link/ether 3a:15:3b:e1:d7:6d brd ff:ff:ff:ff:ff:ff
可以看到,UP的veth裝置成功修改成noqueue。
小結
總之,給虛擬網路裝置建立預設的qdisc,是不太合理的。這會讓虛擬機器(或者容器)的網路瓶頸過早的出現在qdisc,而不是真實的物理裝置(除非應用需要建立qdisc)。更多詳細參考這裡。