结论:
- E1000网卡支持 ethtool -g xxx 和 ethtool -G xxx rx 4096 tx 4096 命令
- rtl8139网卡仅支持 ethtool -g xxx 命令
- virtio 模式:
- centos7 仅支持ethtool -g xxx 命令
- centos 6.5 两个命令都不支持
- virtio模式下执行ethtool -g xxx 命令获取的是vring队列Queue Size 的大小是256,是QEMU端代码写死的不能被修改。
分析原因:
E1000和rtl8139 是全虚拟化ethtool -g xxx 命令实际查询的是虚拟网卡的接收和发送队列的长度,而且e1000支持修改队列长度,理论上网卡的队列越长,网卡处理性能越高。
而virtio是半虚拟化,它的实现原理和传统网卡不一样,virtio中的核心部分是vring队列,这个队列是用于virtio前段driver和后端vhost通信的桥梁,在centos 7中支持ethtool -g xxx 命令
最终调用的函数为:
static void virtnet_get_ringparam(struct net_device *dev,
struct ethtool_ringparam *ring)
{
struct virtnet_info *vi = netdev_priv(dev);
ring->rx_max_pending = virtqueue_get_vring_size(vi->rq[0].vq);
ring->tx_max_pending = virtqueue_get_vring_size(vi->sq[0].vq);
ring->rx_pending = ring->rx_max_pending;
ring->tx_pending = ring->tx_max_pending;
}
此函数中获取rx 和tx 队列长度时,实际读取的是vring 队列的size。
centos 6.5 之所以不支持ethtool -g xxx 命令 是因为 2.6.32 的linux内核没有相关操作接口,virtnet支持的ethtool操作接口如下:
static const struct ethtool_ops virtnet_ethtool_ops = {
.set_tx_csum = virtnet_set_tx_csum,
.set_sg = ethtool_op_set_sg,
.set_tso = ethtool_op_set_tso,
.set_ufo = ethtool_op_set_ufo,
.get_link = ethtool_op_get_link,
.get_ringparam = virtnet_get_ringparam, /*此接口是我打了patch之后才支持的,原本不支持*/
};
virittio设备的配置空间如下:
使用PCI设备的BAR0来对PCI设备进行配置,从上表中可以看出Queue Size 对于前段virtio driver 来说是只读的,没有写权限。Queue Size 的大小只能由设备指定。
所以在virtio模式下不支持ethtool -G xxx rx 4096 tx 4096 命令,因为没有写权限。
初始化 virtqueue如下
该部分代码的实现在 virtio-pci.c 里 setup_vq()里面,具体为:
1.选择 virtqueue 的索引,写入 Queue Select 寄存器
2.读取 queue size 寄存器获得 virtqueue 的可用数目
3.分配并清零 4096 字节对齐的连续物理内存用于存放 virtqueue(调用 alloc_pages_exact()).把内存地址除以 4096 写入 Queue Address 寄存器(VIRTIO_PCI_QUEUE_ADDR_SHIFT)
4.可选情况下,如果 MSI-X 中断机制启用,选择一个向量用于 virtqueue 请求的中断,把对应向量的 MSI-X 表格入口号写入 Queue Vector 寄存器域,然后再次读取该域以确认返回正确值。
对应代码如下:
static struct virtqueue *setup_vq(struct virtio_device *vdev, unsigned index,
void (*callback)(struct virtqueue *vq),
const char *name,
u16 msix_vec)
{
struct virtio_pci_device *vp_dev = to_vp_device(vdev);
struct virtio_pci_vq_info *info;
struct virtqueue *vq;
unsigned long flags, size;
u16 num;
int err;
VIRTIO_PCI_QUEUE_ADDR_SHIFT
/* Select the queue we're interested in */
/*1.选择 virtqueue 的索引,写入 Queue Select 寄存器*/
iowrite16(index, vp_dev->ioaddr + VIRTIO_PCI_QUEUE_SEL);
/* Check if queue is either not available or already active. */
/*BAR0寄存器地址偏移12字节*/
/*是只读的不能修改*/
/*2.读取 queue size 寄存器获得 virtqueue 的可用数目*/
num = ioread16(vp_dev->ioaddr + VIRTIO_PCI_QUEUE_NUM);
if (!num || ioread32(vp_dev->ioaddr + VIRTIO_PCI_QUEUE_PFN))
return ERR_PTR(-ENOENT);
/* allocate and fill out our structure the represents an active
* queue */
info = kmalloc(sizeof(struct virtio_pci_vq_info), GFP_KERNEL);
if (!info)
return ERR_PTR(-ENOMEM);
info->queue_index = index;
info->num = num;
info->msix_vector = msix_vec;
size = PAGE_ALIGN(vring_size(num, VIRTIO_PCI_VRING_ALIGN));
info->queue = alloc_pages_exact(size, GFP_KERNEL|__GFP_ZERO);
if (info->queue == NULL) {
err = -ENOMEM;
goto out_info;
}
/* activate the queue */
/*分配并清零 4096 字节对齐的连续物理内存用于存放 virtqueue(调用 alloc_pages_exact()).
把内存地址除以 4096 写入 Queue Address 寄存器(VIRTIO_PCI_QUEUE_ADDR_SHIFT)*/
iowrite32(virt_to_phys(info->queue) >> VIRTIO_PCI_QUEUE_ADDR_SHIFT,
vp_dev->ioaddr + VIRTIO_PCI_QUEUE_PFN);
/* create the vring */
vq = vring_new_virtqueue(info->num, VIRTIO_PCI_VRING_ALIGN,
vdev, info->queue, vp_notify, callback, name);
if (!vq) {
err = -ENOMEM;
goto out_activate_queue;
}
vq->priv = info;
info->vq = vq;
if (msix_vec != VIRTIO_MSI_NO_VECTOR) {
iowrite16(msix_vec, vp_dev->ioaddr + VIRTIO_MSI_QUEUE_VECTOR);
msix_vec = ioread16(vp_dev->ioaddr + VIRTIO_MSI_QUEUE_VECTOR);
if (msix_vec == VIRTIO_MSI_NO_VECTOR) {
err = -EBUSY;
goto out_assign;
}
}
spin_lock_irqsave(&vp_dev->lock, flags);
list_add(&info->node, &vp_dev->virtqueues);
spin_unlock_irqrestore(&vp_dev->lock, flags);
return vq;
out_assign:
vring_del_virtqueue(vq);
out_activate_queue:
iowrite32(0, vp_dev->ioaddr + VIRTIO_PCI_QUEUE_PFN);
free_pages_exact(info->queue, size);
out_info:
kfree(info);
return ERR_PTR(err);
}
关于Queue Size 的初始化,在qemu初始化virtionet设备时进行了初始化,Queue Size 即kernel读取的num,被初始化为256。
相关代码如下:
static void virtio_net_device_realize(DeviceState *dev, Error **errp)会调用virtio_net_add_queue设置Queue Size 大小。
static void virtio_net_add_queue(VirtIONet *n, int index)
{
VirtIODevice *vdev = VIRTIO_DEVICE(n);
n->vqs[index].rx_vq = virtio_add_queue(vdev, 256, virtio_net_handle_rx); /*收包队列默认256*/
if (n->net_conf.tx && !strcmp(n->net_conf.tx, "timer")) {
n->vqs[index].tx_vq =
virtio_add_queue(vdev, 256, virtio_net_handle_tx_timer); /*发包队列默认256,定时器模式*/
n->vqs[index].tx_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL,
virtio_net_tx_timer,
&n->vqs[index]);
} else {
n->vqs[index].tx_vq =
virtio_add_queue(vdev, 256, virtio_net_handle_tx_bh);/*发包队列默认256,中断模式*/
n->vqs[index].tx_bh = qemu_bh_new(virtio_net_tx_bh, &n->vqs[index]);
}
n->vqs[index].tx_waiting = 0;
n->vqs[index].n = n;
}
通过以分析得出结论virtio模式下Queue Size 的大小是256,是QEMU设备端指定的不支持修改,且virtio driver端只有读权限。