Virtio 驱动关键结构体与函数详解
Virtio 驱动关键结构体与函数详解
一、核心结构体(以 Linux virtio-net 驱动为例)
1. struct virtio_device
struct virtio_device {struct device dev; // 设备模型核心结构struct virtio_device_id id; // 设备标识符struct virtio_config_ops *config; // 配置操作函数集struct list_head vqs; // 挂载的 virtqueue 列表u64 features; // 协商后的特性位图void *priv; // 驱动私有数据 (指向 virtnet_info)
};
作用:代表一个 virtio 设备实例,包含设备通用属性和操作接口。
2. struct virtqueue
struct virtqueue {struct list_head list; // 挂载到 virtio_device 的链表void (*callback)(struct virtqueue *vq); // 中断回调函数const char *name; // 队列名称 (e.g., "input", "output")struct virtio_device *vdev; // 关联的 virtio 设备unsigned int index; // 队列索引void *priv; // 驱动私有数据 (指向 send_queue/receive_queue)
};
作用:核心通信队列,实现前后端数据传输的环形缓冲区。
3. struct virtnet_info (驱动私有结构)
struct virtnet_info {struct virtio_device *vdev; // 关联的 virtio 设备struct virtqueue *rvq, *svq, *cvq; // 接收/发送/控制 virtqueuestruct net_device *dev; // 关联的 net_devicestruct napi_struct napi; // NAPI 轮询结构struct receive_queue *rq; // 接收队列数组struct send_queue *sq; // 发送队列数组struct bpf_prog __rcu *xdp_prog; // XDP 程序指针u16 curr_queue_pairs; // 当前启用的队列对数量
};
作用:驱动核心上下文,管理设备状态和所有资源。
4. struct send_queue / receive_queue
struct send_queue {struct virtqueue *vq; // 关联的 virtqueuestruct napi_struct napi; // NAPI 上下文 (用于 TX NAPI)char name[40]; // 队列名称struct virtnet_info *vi; // 指向父结构struct sk_buff_head skb_queue; // SKB 缓冲队列
};struct receive_queue {struct virtqueue *vq; // 关联的 virtqueuestruct napi_struct napi; // NAPI 上下文struct virtnet_info *vi; // 指向父结构struct page *pages; // 接收页池struct xdp_rxq_info xdp_rxq; // XDP 接收队列
};
作用:管理发送/接收队列的专用数据结构。
5. VRing 相关结构
struct vring {unsigned int num; // 描述符数量struct vring_desc *desc; // 描述符表指针struct vring_avail *avail; // 可用环指针struct vring_used *used; // 已用环指针
};
内存布局:
+-------------------+
| Descriptor Table | -> [addr, len, flags, next]
+-------------------+
| Available Ring | -> [flags, idx, ring[]]
+-------------------+
| Used Ring | -> [flags, idx, ring[]]
+-------------------+
二、关键函数分析
1. 驱动初始化
// 驱动探测函数 (设备发现时调用)
static int virtnet_probe(struct virtio_device *vdev)
{// 1. 分配 virtnet_infostruct virtnet_info *vi = kzalloc(sizeof(*vi), GFP_KERNEL);// 2. 初始化设备virtio_cread_feature(vdev, ...); // 读取设备特性net = alloc_etherdev_mq(...); // 分配 net_device// 3. 设置 virtqueuesvi->vdev = vdev;vdev->priv = vi;virtnet_find_vqs(vi); // 查找并初始化 virtqueues// 4. 配置网络设备net->netdev_ops = &virtnet_netdev; // 设置操作函数集net->ethtool_ops = &virtnet_ethtool_ops;// 5. 注册设备register_netdev(net);
}// 初始化 virtqueues
static int virtnet_find_vqs(struct virtnet_info *vi)
{// 创建 virtqueue 回调函数callbacks[0] = skb_recv_done; // 接收完成回调callbacks[1] = skb_xmit_done; // 发送完成回调// 分配 virtqueuesvirtio_find_vqs(vdev, total_vqs, vqs, callbacks, names);// 关联到私有结构vi->rvq = vqs[0]; // 接收队列vi->svq = vqs[1]; // 发送队列
}
2. 数据发送路径
// 网络设备发送函数 (由内核协议栈调用)
static netdev_tx_t start_xmit(struct sk_buff *skb, struct net_device *dev)
{struct virtnet_info *vi = netdev_priv(dev);struct send_queue *sq = &vi->sq[queue_index];// 1. 准备分散/聚集列表struct scatterlist hdr, sg[VNET_MAX_SG];unsigned num_sg;sg_init_table(sg, VNET_MAX_SG);// 2. 填充数据包到 sg 列表skb_to_sgvec(skb, sg, offset, skb->len);// 3. 添加缓冲区到 virtqueueerr = virtqueue_add_outbuf(sq->vq, sg, num_sg, skb, GFP_ATOMIC);// 4. 通知设备if (likely(!err)) {virtqueue_kick(sq->vq);return NETDEV_TX_OK;}// 错误处理dev_kfree_skb_any(skb);return NETDEV_TX_OK;
}// 发送完成中断处理
static void skb_xmit_done(struct virtqueue *vq)
{struct send_queue *sq = vq->priv;struct virtnet_info *vi = sq->vi;// 禁用中断避免重入virtqueue_disable_cb(vq);// 处理完成的数据包while ((skb = virtqueue_get_buf(vq, &len)) {dev_consume_skb_any(skb); // 释放 skbpackets++;}// 重新启用中断virtqueue_enable_cb(vq);
}
3. 数据接收路径
// 接收队列初始化
static int virtnet_alloc_rx_buffers(struct virtnet_info *vi)
{for (i = 0; i < vi->curr_queue_pairs; i++) {struct receive_queue *rq = &vi->rq[i];while (rq->vq->num_free > 0) {// 1. 分配接收缓冲区struct sk_buff *skb = virtnet_alloc_skb(rq);// 2. 准备 sg 列表struct scatterlist sg;sg_init_one(&sg, skb->data, PAGE_SIZE);// 3. 添加空缓冲区到 virtqueueerr = virtqueue_add_inbuf(rq->vq, &sg, 1, skb, GFP_KERNEL);if (err)break;}// 4. 通知设备缓冲区可用virtqueue_kick(rq->vq);}
}// 数据接收中断处理 (NAPI 模式)
static int virtnet_poll(struct napi_struct *napi, int budget)
{struct receive_queue *rq = container_of(napi, struct receive_queue, napi);unsigned int received = 0;// 处理接收队列while (received < budget) {// 1. 从已用环获取数据包void *buf;unsigned int len;buf = virtqueue_get_buf(rq->vq, &len);if (!buf) break;// 2. 处理数据包struct sk_buff *skb = buf;skb_put(skb, len); // 设置数据长度napi_gro_receive(&rq->napi, skb); // 提交给协议栈// 3. 重新填充缓冲区if (virtnet_add_inbuf(rq, skb) == 0)received++;}// 4. 检查是否完成处理if (received < budget) {napi_complete_done(napi, received);virtqueue_enable_cb(rq->vq); // 重新启用中断}return received;
}
4. 配置操作函数集
static const struct virtio_config_ops virtio_net_config_ops = {.get_features = virtnet_get_features, // 获取设备特性.finalize_features = virtnet_finalize_features, // 特性协商.get = virtnet_get_config, // 读取配置空间.set = virtnet_set_config, // 写入配置空间.find_vqs = virtnet_find_vqs, // 查找 virtqueues.del_vqs = virtnet_del_vqs, // 删除 virtqueues.reset = virtnet_reset, // 重置设备.set_vq_affinity = virtnet_set_vq_affinity, // 设置CPU亲和性
};
三、关键函数调用关系
发送流程:
协议栈调用 ndo_start_xmit()→ virtnet_start_xmit()→ virtqueue_add_outbuf() // 添加发送缓冲区→ virtqueue_kick() // 通知设备→ 设备处理数据→ 中断触发 skb_xmit_done()→ virtqueue_get_buf() // 回收缓冲区→ dev_consume_skb_any() // 释放skb
接收流程:
设备接收数据→ 中断触发 virtnet_interrupt()→ napi_schedule() // 调度NAPI→ virtnet_poll()→ virtqueue_get_buf() // 获取数据包→ napi_gro_receive() // 提交协议栈→ virtnet_add_inbuf() // 补充新缓冲区→ virtqueue_kick() // 通知设备
四、高级特性相关函数
- 多队列支持 (Multi-Queue):
// 启用多队列
static int virtnet_set_queues(struct virtnet_info *vi, u16 queue_pairs)
{// 配置设备支持多队列virtio_cread_feature(vi->vdev, VIRTIO_NET_F_MQ, ...);// 分配队列数组vi->rq = kcalloc(queue_pairs, sizeof(*vi->rq), GFP_KERNEL);vi->sq = kcalloc(queue_pairs, sizeof(*vi->sq), GFP_KERNEL);// 初始化每个队列对for (i = 0; i < queue_pairs; i++) {netif_set_real_num_tx_queues(dev, queue_pairs);netif_set_real_num_rx_queues(dev, queue_pairs);}
}
- XDP 支持:
// XDP 程序设置
static int virtnet_xdp_set(struct net_device *dev, struct bpf_prog *prog)
{struct virtnet_info *vi = netdev_priv(dev);// 设置 XDP 程序rcu_assign_pointer(vi->xdp_prog, prog);// 配置接收路径if (prog) {for (i = 0; i < vi->max_queue_pairs; i++) {struct receive_queue *rq = &vi->rq[i];xdp_rxq_info_reg(&rq->xdp_rxq, dev, i);}}
}// XDP 数据路径
static struct sk_buff *virtnet_run_xdp(struct receive_queue *rq,struct bpf_prog *xdp_prog,struct xdp_buff *xdp)
{// 执行 XDP 程序act = bpf_prog_run_xdp(xdp_prog, xdp);switch (act) {case XDP_PASS:return build_skb_from_xdp_buff(rq, xdp);case XDP_TX:xdpf = convert_to_xdp_frame(xdp);virtnet_xdp_xmit(dev, 1, &xdpf, 0);break;case XDP_REDIRECT:xdp_do_redirect(dev, xdp, xdp_prog);break;}
}
五、关键优化函数
- 批处理优化:
// 批量添加接收缓冲区
static bool virtnet_rx_refill(struct receive_queue *rq, gfp_t gfp)
{while ((num = virtqueue_get_batch(rq->vq, &skbs, &len)) {for (i = 0; i < num; i++) {if (!virtnet_add_inbuf(rq, skbs[i])) {added++;}}}if (added)virtqueue_kick(rq->vq); // 批量通知
}
- 中断合并:
// 使用 EVENT_IDX 优化中断
static bool virtqueue_enable_cb_delayed(struct virtqueue *vq)
{u16 bufs = virtqueue_get_vring_size(vq) / 2;return vq->vring.avail_flags_shadow & VRING_AVAIL_F_NO_INTERRUPT;
}
总结
Virtio 驱动的核心围绕以下组件构建:
- 设备抽象:
virtio_device
封装设备通用属性 - 队列机制:
virtqueue
实现高效数据传输 - 内存管理:分散/聚集列表 (scatterlist) 实现零拷贝
- 中断处理:NAPI 机制结合回调函数实现高效处理
- 特性协商:灵活的特性位管理机制
关键函数聚焦在:
- 队列管理 (
virtqueue_add_*buf
,virtqueue_kick
) - 缓冲区回收 (
virtqueue_get_buf
) - 中断控制 (
virtqueue_enable/disable_cb
) - 协议栈接口 (
ndo_start_xmit
,napi_gro_receive
)
通过这种分层设计,Virtio 在保持高性能的同时,实现了前后端的完全解耦,成为虚拟化 I/O 的标准解决方案。