Skip to content

Linux 内核 Virtio Ring 实现深入分析

目录

  1. 概述
  2. Split Virtqueue Ring (传统实现)
  3. Packed Ring (现代实现)
  4. 关键算法详解
  5. 中断与通知机制
  6. DMA 和内存屏障
  7. 数据结构关系图
  8. 总结与对比

1. 概述

Virtio Ring 是 Virtio 虚拟化框架的核心组件,负责在 guest 和 host 之间高效传输数据。本文基于 Linux 内核源码 /Users/sphinx/github/linux/drivers/virtio/virtio_ring.c 进行深入分析。

1.1 源码位置

文件说明
/Users/sphinx/github/linux/drivers/virtio/virtio_ring.c主要实现 (约 3600 行)
/Users/sphinx/github/linux/include/linux/virtio_ring.h内核内部接口
/Users/sphinx/github/linux/include/uapi/linux/virtio_ring.h用户空间 API 结构定义

1.2 Virtio Ring 类型

c
enum vq_layout {
    VQ_LAYOUT_SPLIT = 0,        // Split Ring
    VQ_LAYOUT_PACKED,           // Packed Ring
    VQ_LAYOUT_SPLIT_IN_ORDER,   // Split Ring (按顺序)
    VQ_LAYOUT_PACKED_IN_ORDER,  // Packed Ring (按顺序)
};

1.3 核心数据结构

c
// drivers/virtio/virtio_ring.c:192
struct vring_virtqueue {
    struct virtqueue vq;
    bool use_map_api;           // 是否使用 DMA API
    bool weak_barriers;         // 是否使用弱屏障
    bool broken;                // 队列是否损坏
    bool indirect;              // 支持间接描述符
    bool event;                 // 支持事件索引
    enum vq_layout layout;      // 环类型
    unsigned int free_head;      // 空闲描述符头
    // ...
    union {
        struct vring_virtqueue_split split;
        struct vring_virtqueue_packed packed;
    };
    bool (*notify)(struct virtqueue *vq);
    union virtio_map map;
};

2. Split Virtqueue Ring (传统实现)

2.1 核心数据结构

2.1.1 struct vring_desc (描述符)

定义于 include/uapi/linux/virtio_ring.h:104:

c
struct vring_desc {
    __virtio64 addr;      // 缓冲区地址 (guest 物理地址)
    __virtio32 len;       // 缓冲区长度
    __virtio16 flags;     // 描述符标志
    __virtio16 next;      // 链中下一个描述符索引
};
// 长度: 16 字节, 16 字节对齐

标志位 (定义于 include/uapi/linux/virtio_ring.h:37-42):

标志说明
VRING_DESC_F_NEXT1缓冲区通过 next 字段继续
VRING_DESC_F_WRITE2缓冲区是只写的 (否则只读)
VRING_DESC_F_INDIRECT4缓冲区包含描述符列表

2.1.2 struct vring_avail (可用环)

c
// include/uapi/linux/virtio_ring.h:111
struct vring_avail {
    __virtio16 flags;     // 标志 (如 VRING_AVAIL_F_NO_INTERRUPT)
    __virtio16 idx;       // 下一个可用环条目索引
    __virtio16 ring[];    // 可用描述符头数组
};

2.1.3 struct vring_used (已用环)

c
// include/uapi/linux/virtio_ring.h:118-132
struct vring_used_elem {
    __virtio32 id;        // 使用的描述符链起始索引
    __virtio32 len;       // 写入的总长度
};

struct vring_used {
    __virtio16 flags;     // 标志 (如 VRING_USED_F_NO_NOTIFY)
    __virtio16 idx;       // 下一个已用环条目索引
    vring_used_elem_t ring[];
};

2.1.4 struct vring (完整环)

c
// include/uapi/linux/virtio_ring.h:155
struct vring {
    unsigned int num;
    vring_desc_t *desc;       // 描述符数组
    vring_avail_t *avail;     // 可用环
    vring_used_t *used;       // 已用环
};

2.2 Split Ring 内存布局

内存布局 (Virtio 1.0 前传统布局):

struct vring {
    struct vring_desc desc[num];     // 描述符表

    struct vring_avail avail;        // 可用环
        - flags
        - idx
        - ring[num]                   // 可用描述符索引

    char pad[];                       // 对齐填充

    struct vring_used used;           // 已用环
        - flags
        - idx
        - ring[num]                   // 已用元素
        - avail_event_idx             // 事件索引 (在 used->ring[num] 位置)
};

对齐要求 (定义于 include/uapi/linux/virtio_ring.h:86-91):

元素对齐大小
VRING_DESC_ALIGN_SIZE16 字节
VRING_AVAIL_ALIGN_SIZE2 字节
VRING_USED_ALIGN_SIZE4 字节

2.3 描述符链机制

Split Ring 使用描述符链来描述分散的缓冲区。一个链由多个描述符组成,每个描述符包含:

  1. addr: 缓冲区物理地址
  2. len: 缓冲区长度
  3. flags: 标志 (F_NEXT, F_WRITE, F_INDIRECT)
  4. next: 链中下一个描述符的索引

示例 - 散列表描述符链:

desc[0]: {addr=0x1000, len=100, flags=NEXT, next=1}
desc[1]: {addr=0x2000, len=200, flags=NEXT, next=2}
desc[2]: {addr=0x3000, len=300, flags=WRITE, next=0}

2.4 Split Ring 内部状态结构

c
// drivers/virtio/virtio_ring.c:77
struct vring_desc_state_split {
    void *data;               // 回调数据
    struct vring_desc *indir_desc;  // 间接描述符表
    u32 total_in_len;         // 总输入长度
};

// drivers/virtio/virtio_ring.c:99
struct vring_desc_extra {
    dma_addr_t addr;          // DMA 地址
    u32 len;                  // 长度
    u16 flags;                // 标志
    u16 next;                 // 下一个描述符索引
};

// drivers/virtio/virtio_ring.c:106
struct vring_virtqueue_split {
    struct vring vring;                   // 实际内存布局
    u16 avail_flags_shadow;               // avail->flags 的缓存值
    u16 avail_idx_shadow;                 // avail->idx 的缓存值
    struct vring_desc_state_split *desc_state;  // 每描述符状态
    struct vring_desc_extra *desc_extra;         // 每描述符额外信息
    dma_addr_t queue_dma_addr;            // DMA 地址
    size_t queue_size_in_bytes;           // 队列大小
    u32 vring_align;                      // 对齐要求
    bool may_reduce_num;                  // 是否可减少描述符数量
};

2.5 avail_event / used_event 通知机制

Split Ring 使用两个事件索引来协调通知:

  1. avail_event (vring_used_event): Guest 写入,通知 Host 它期望中断的 avail 索引
  2. used_event: Host 写入,通知 Guest 它期望中断的 used 索引
c
// include/uapi/linux/virtio_ring.h:193-194
#define vring_used_event(vr) ((vr)->avail->ring[(vr)->num])
#define vring_avail_event(vr) (*(__virtio16 *)&(vr)->used->ring[(vr)->num])

vring_need_event 函数 (include/uapi/linux/virtio_ring.h:219):

c
static inline int vring_need_event(__u16 event_idx, __u16 new_idx, __u16 old)
{
    // 判断是否需要触发事件
    return (__u16)(new_idx - event_idx - 1) < (__u16)(new_idx - old);
}

3. Packed Ring (现代实现)

3.1 Packed Ring vs Split Ring 区别

特性Split RingPacked Ring
描述符数量3 个独立数组单个描述符数组
内存布局分散在 3 个区域紧凑的单一区域
描述符大小16 字节16 字节
缓冲环avail 和 used 分开使用 flags 表示状态
Wrap Counter有 (15 位)
缓存效率较低较高
复杂度较低较高

3.2 核心数据结构

3.2.1 struct vring_packed_desc (压缩描述符)

定义于 include/uapi/linux/virtio_ring.h:236:

c
struct vring_packed_desc {
    __le64 addr;      // 缓冲区地址
    __le32 len;       // 缓冲区长度
    __le16 id;        // 缓冲区 ID
    __le16 flags;     // 标志
};
// 长度: 16 字节

标志位 (定义于 include/uapi/linux/virtio_ring.h:45-49):

标志说明
VRING_PACKED_DESC_F_AVAIL7可用标志位
VRING_PACKED_DESC_F_USED15已用标志位

3.2.2 struct vring_packed_desc_event (事件结构)

c
// include/uapi/linux/virtio_ring.h:229
struct vring_packed_desc_event {
    __le16 off_wrap;  // 描述符环改变事件偏移/换行计数器
    __le16 flags;     // 事件标志
};

事件标志 (定义于 include/uapi/linux/virtio_ring.h:60-69):

标志说明
VRING_PACKED_EVENT_FLAG_ENABLE0x0使能事件
VRING_PACKED_EVENT_FLAG_DISABLE0x1禁用事件
VRING_PACKED_EVENT_FLAG_DESC0x2针对特定描述符的事件

3.3 Packed Ring 内存布局

Packed Ring 内存布局:

+------------------+
| Descriptor Ring  |  num × struct vring_packed_desc (16 字节 each)
| (desc[])         |
+------------------+
| Driver Event     |  struct vring_packed_desc_event
| (driver)         |
+------------------+
| Device Event     |  struct vring_packed_desc_event
| (device)         |
+------------------+

3.4 包装描述符算法 (Wrap Counter)

Packed Ring 使用 wrap counter 来区分可用和已用描述符:

c
// drivers/virtio/virtio_ring.c:1428-1431
static bool packed_used_wrap_counter(u16 last_used_idx)
{
    return !!(last_used_idx & (1 << VRING_PACKED_EVENT_F_WRAP_CTR));
}

// VRING_PACKED_EVENT_F_WRAP_CTR = 15

描述符可用性判断 (drivers/virtio/virtio_ring.c:2036-2047):

c
static inline bool is_used_desc_packed(const struct vring_virtqueue *vq,
                                       u16 idx, bool used_wrap_counter)
{
    bool avail, used;
    u16 flags;

    flags = le16_to_cpu(vq->packed.vring.desc[idx].flags);
    avail = !!(flags & (1 << VRING_PACKED_DESC_F_AVAIL));
    used = !!(flags & (1 << VRING_PACKED_DESC_F_USED));

    // 描述符被认为是"已使用"当且仅当 avail == used == used_wrap_counter
    return avail == used && used == used_wrap_counter;
}

3.5 Packed Ring 内部状态结构

c
// drivers/virtio/virtio_ring.c:87
struct vring_desc_state_packed {
    void *data;                   // 回调数据
    struct vring_packed_desc *indir_desc;  // 间接描述符
    u16 num;                      // 描述符列表长度
    u16 last;                     // 列表中最后一个描述符状态
    u32 total_in_len;             // 总输入长度
};

// drivers/virtio/virtio_ring.c:135
struct vring_virtqueue_packed {
    struct {
        unsigned int num;
        struct vring_packed_desc *desc;
        struct vring_packed_desc_event *driver;
        struct vring_packed_desc_event *device;
    } vring;

    bool avail_wrap_counter;      // 可用换行计数器
    u16 avail_used_flags;         // 可用已用标志
    u16 next_avail_idx;          // 下一个可用描述符索引
    u16 event_flags_shadow;       // 事件标志缓存
    struct vring_desc_state_packed *desc_state;
    struct vring_desc_extra *desc_extra;
    dma_addr_t ring_dma_addr;
    dma_addr_t driver_event_dma_addr;
    dma_addr_t device_event_dma_addr;
    size_t ring_size_in_bytes;
    size_t event_size_in_bytes;
};

4. 关键算法详解

4.1 virtqueue_add_split() - 添加缓冲区 (Split)

位置: drivers/virtio/virtio_ring.c:599-792

函数签名:

c
static inline int virtqueue_add_split(struct vring_virtqueue *vq,
                                      struct scatterlist *sgs[],
                                      unsigned int total_sg,
                                      unsigned int out_sgs,
                                      unsigned int in_sgs,
                                      void *data,
                                      void *ctx,
                                      bool premapped,
                                      gfp_t gfp,
                                      unsigned long attr)

算法流程:

1. START_USE(vq) - 开始使用 virtqueue,检查重入

2. 检查错误条件:
   - data != NULL
   - ctx 为空或无 indirect 支持
   - vq 未损坏
   - total_sg > 0

3. 获取空闲描述符头:
   head = vq->free_head

4. 决定是否使用间接描述符:
   if (virtqueue_use_indirect(vq, total_sg))
       desc = alloc_indirect_split(vq, total_sg, gfp)
   else
       desc = vq->split.vring.desc

5. 遍历所有 scatter-gather 列表,填充描述符:
   for (每个 scatterlist sg) {
       - 映射 DMA 地址: vring_map_one_sg()
       - 设置标志: NEXT(如果不是最后一个), WRITE(如果是输入缓冲区)
       - 填充描述符: virtqueue_add_desc_split()
   }

6. 如果使用间接描述符:
   - 映射间接表 DMA
   - 在头描述符中设置 INDIRECT 标志

7. 更新空闲描述符计数:
   vq->vq.num_free -= descs_used

8. 更新 free_head:
   if (virtqueue_is_in_order(vq))
       vq->free_head += descs_used
   else if (indirect)
       vq->free_head = vq->split.desc_extra[head].next
   else
       vq->free_head = i

9. 保存状态:
   vq->split.desc_state[head].data = data
   vq->split.desc_state[head].indir_desc = ctx 或 desc

10. 添加到可用环:
    avail = vq->split.avail_idx_shadow & (vq->split.vring.num - 1)
    vq->split.vring.avail->ring[avail] = head

11. 内存屏障 + 更新索引:
    virtio_wmb(vq->weak_barriers)
    vq->split.avail_idx_shadow++
    vq->split.vring.avail->idx = avail_idx_shadow
    vq->num_added++

12. 如果添加太多,自动 kick:
    if (vq->num_added == (1 << 16) - 1)
        virtqueue_kick(&vq->vq)

4.2 virtqueue_add_packed() - 添加缓冲区 (Packed)

位置: drivers/virtio/virtio_ring.c:1615-1771

核心差异 (与 Split 对比):

c
// 1. 使用 avail_used_flags 而非独立的 avail 和 used
head_flags = flags;
desc[i].flags = flags;

// 2. Wrap counter 处理
if (++i >= vq->packed.vring.num) {
    i = 0;
    vq->packed.avail_used_flags ^=
        1 << VRING_PACKED_DESC_F_AVAIL |
        1 << VRING_PACKED_DESC_F_USED;
}

// 3. 换行处理
if (i <= head)
    vq->packed.avail_wrap_counter ^= 1;

// 4. 内存屏障后才设置头描述符标志
virtio_wmb(vq->weak_barriers)
vq->packed.vring.desc[head].flags = head_flags;

4.3 virtqueue_get_buf_ctx() - 获取完成缓冲区

Split 版本 (drivers/virtio/virtio_ring.c:917-972):

c
static void *virtqueue_get_buf_ctx_split(struct vring_virtqueue *vq,
                                         unsigned int *len,
                                         void **ctx)
{
    // 1. 检查是否有已用缓冲区
    if (!more_used_split(vq))
        return NULL;

    // 2. 读取内存屏障
    virtio_rmb(vq->weak_barriers);

    // 3. 获取已用环条目
    last_used = vq->last_used_idx & (vq->split.vring.num - 1);
    i = vq->split.vring.used->ring[last_used].id;
    *len = vq->split.vring.used->ring[last_used].len;

    // 4. 验证描述符
    if (i >= vq->split.vring.num || !vq->split.desc_state[i].data)
        return NULL;

    // 5. 保存返回值
    ret = vq->split.desc_state[i].data;

    // 6. 分离缓冲区
    detach_buf_split(vq, i, ctx);

    // 7. 更新 last_used_idx
    vq->last_used_idx++;

    // 8. 更新事件索引
    if (!(vq->split.avail_flags_shadow & VRING_AVAIL_F_NO_INTERRUPT))
        virtio_store_mb(vq->weak_barriers,
                        &vring_used_event(&vq->split.vring),
                        cpu_to_virtio16(vq->vq.vdev, vq->last_used_idx));

    return ret;
}

Packed 版本 (drivers/virtio/virtio_ring.c:2161-2211):

c
static void *virtqueue_get_buf_ctx_packed(struct vring_virtqueue *vq,
                                          unsigned int *len,
                                          void **ctx)
{
    // 1. 检查是否有已用缓冲区
    if (!more_used_packed(vq))
        return NULL;

    // 2. 读取内存屏障
    virtio_rmb(vq->weak_barriers);

    // 3. 解析 last_used_idx
    last_used_idx = READ_ONCE(vq->last_used_idx);
    used_wrap_counter = packed_used_wrap_counter(last_used_idx);
    last_used = packed_last_used(last_used_idx);

    // 4. 获取描述符
    id = le16_to_cpu(vq->packed.vring.desc[last_used].id);
    *len = le32_to_cpu(vq->packed.vring.desc[last_used].len);

    // 5. 验证
    if (id >= num || !vq->packed.desc_state[id].data)
        return NULL;

    // 6. 保存返回值
    ret = vq->packed.desc_state[id].data;

    // 7. 分离缓冲区
    detach_buf_packed(vq, id, ctx);

    // 8. 更新 last_used_idx
    update_last_used_idx_packed(vq, id, last_used, used_wrap_counter);

    return ret;
}

4.4 virtqueue_kick() - 通知前端

位置: drivers/virtio/virtio_ring.c:3002-3062

两阶段 kick 机制:

c
// 第一阶段: virtqueue_kick_prepare
bool virtqueue_kick_prepare(struct virtqueue *_vq)
{
    struct vring_virtqueue *vq = to_vvq(_vq);
    return VIRTQUEUE_CALL(vq, kick_prepare);
}

// virtqueue_kick_prepare_split (drivers/virtio/virtio_ring.c:794-822)
static bool virtqueue_kick_prepare_split(struct vring_virtqueue *vq)
{
    u16 new, old;
    bool needs_kick;

    virtio_mb(vq->weak_barriers);

    old = vq->split.avail_idx_shadow - vq->num_added;
    new = vq->split.avail_idx_shadow;
    vq->num_added = 0;

    if (vq->event) {
        // 使用事件索引,调用 vring_need_event
        needs_kick = vring_need_event(
            virtio16_to_cpu(vq->vq.vdev, vring_avail_event(&vq->split.vring)),
            new, old);
    } else {
        // 检查 NO_NOTIFY 标志
        needs_kick = !(vq->split.vring.used->flags &
            cpu_to_virtio16(vq->vq.vdev, VRING_USED_F_NO_NOTIFY));
    }
    return needs_kick;
}

// 第二阶段: virtqueue_notify
bool virtqueue_notify(struct virtqueue *_vq)
{
    struct vring_virtqueue *vq = to_vvq(_vq);

    if (unlikely(vq->broken))
        return false;

    if (!vq->notify(_vq)) {
        vq->broken = true;
        return false;
    }
    return true;
}

// 完整 kick
bool virtqueue_kick(struct virtqueue *vq)
{
    if (virtqueue_kick_prepare(vq))
        return virtqueue_notify(vq);
    return true;
}

4.5 virtqueue_detach_unused_buf() - 分离未用缓冲区

Split 版本 (drivers/virtio/virtio_ring.c:1125-1150):

c
static void *virtqueue_detach_unused_buf_split(struct vring_virtqueue *vq)
{
    unsigned int i;
    void *buf;

    for (i = 0; i < vq->split.vring.num; i++) {
        if (!vq->split.desc_state[i].data)
            continue;

        buf = vq->split.desc_state[i].data;

        if (virtqueue_is_in_order(vq))
            detach_buf_split_in_order(vq, i, NULL);
        else
            detach_buf_split(vq, i, NULL);

        vq->split.avail_idx_shadow--;
        vq->split.vring.avail->idx = cpu_to_virtio16(vq->vq.vdev,
                vq->split.avail_idx_shadow);
        return buf;
    }
    BUG_ON(vq->vq.num_free != vq->split.vring.num);
    return NULL;
}

Packed 版本 (drivers/virtio/virtio_ring.c:2321-2345):

c
static void *virtqueue_detach_unused_buf_packed(struct vring_virtqueue *vq)
{
    unsigned int i;
    void *buf;

    for (i = 0; i < vq->packed.vring.num; i++) {
        if (!vq->packed.desc_state[i].data)
            continue;

        buf = vq->packed.desc_state[i].data;

        if (virtqueue_is_in_order(vq))
            detach_buf_packed_in_order(vq, i, NULL);
        else
            detach_buf_packed(vq, i, NULL);
        return buf;
    }
    BUG_ON(vq->vq.num_free != vq->packed.vring.num);
    return NULL;
}

5. 中断与通知机制

5.1 vring_interrupt() - 中断处理

位置: drivers/virtio/virtio_ring.c:3229-3258

c
irqreturn_t vring_interrupt(int irq, void *_vq)
{
    struct vring_virtqueue *vq = to_vvq(_vq);

    // 1. 检查是否有更多已用缓冲区
    if (!more_used(vq)) {
        pr_debug("virtqueue interrupt with no work for %p\n", vq);
        return IRQ_NONE;
    }

    // 2. 检查队列是否损坏
    if (unlikely(vq->broken)) {
#ifdef CONFIG_VIRTIO_HARDEN_NOTIFICATION
        return IRQ_NONE;
#else
        return IRQ_HANDLED;
#endif
    }

    // 3. 设置事件触发标志
    if (vq->event)
        vq->event_triggered = true;

    // 4. 调用回调函数
    pr_debug("virtqueue callback for %p (%p)\n", vq, vq->vq.callback);
    if (vq->vq.callback)
        vq->vq.callback(&vq->vq);

    return IRQ_HANDLED;
}

5.2 通知机制流程

Guest 添加缓冲区到 virtqueue:
1. virtqueue_add_*()
2. 更新 avail ring (Split) 或 desc flags (Packed)
3. virtio_wmb() - 内存屏障
4. 更新 avail->idx (Split) 或 desc[head].flags (Packed)
5. virtqueue_kick()
6. virtqueue_kick_prepare_*() - 检查是否需要通知
7. virtqueue_notify() - 调用 vq->notify()

Host 处理并返回:
1. 读取 descriptor
2. 处理 I/O
3. 更新 used ring (Split) 或 desc flags (Packed)
4. 发送中断
5. Guest vring_interrupt() 被调用
6. virtqueue_get_buf_*() 获取结果

5.3 virtio_finalize_features() - 特征最终化

位置: drivers/virtio/virtio_ring.c:3505-3533

c
void vring_transport_features(struct virtio_device *vdev)
{
    unsigned int i;

    for (i = VIRTIO_TRANSPORT_F_START; i < VIRTIO_TRANSPORT_F_END; i++) {
        switch (i) {
        case VIRTIO_RING_F_INDIRECT_DESC:
            // 支持间接描述符
            break;
        case VIRTIO_RING_F_EVENT_IDX:
            // 支持事件索引
            break;
        case VIRTIO_F_VERSION_1:
            // Virtio 1.0
            break;
        case VIRTIO_F_ACCESS_PLATFORM:
            // 平台访问
            break;
        case VIRTIO_F_RING_PACKED:
            // Packed Ring
            break;
        case VIRTIO_F_ORDER_PLATFORM:
            // 平台排序
            break;
        case VIRTIO_F_NOTIFICATION_DATA:
            // 通知数据
            break;
        case VIRTIO_F_IN_ORDER:
            // 按顺序
            break;
        default:
            // 不支持的特征位被清除
            __virtio_clear_bit(vdev, i);
        }
    }
}

6. DMA 和内存屏障

6.1 内存屏障 (Memory Barrier)

定义于 include/linux/virtio_ring.h:26-58:

c
// 通用内存屏障
static inline void virtio_mb(bool weak_barriers)
{
    if (weak_barriers)
        virt_mb();
    else
        mb();
}

// 读取屏障
static inline void virtio_rmb(bool weak_barriers)
{
    if (weak_barriers)
        virt_rmb();
    else
        dma_rmb();
}

// 写入屏障
static inline void virtio_wmb(bool weak_barriers)
{
    if (weak_barriers)
        virt_wmb();
    else
        dma_wmb();
}

// 存储-加载屏障
#define virtio_store_mb(weak_barriers, p, v) \
do { \
    if (weak_barriers) { \
        virt_store_mb(*p, v); \
    } else { \
        WRITE_ONCE(*p, v); \
        mb(); \
    } \
} while (0)

屏障使用场景:

函数使用屏障目的
virtqueue_add_*()virtio_wmb()确保描述符在更新 avail->idx 之前可见
virtqueue_get_buf_*()virtio_rmb()确保在读取缓冲区数据前看到 used->idx 更新
virtqueue_kick_prepare_*()virtio_mb()确保暴露可用数组条目后再检查事件

6.2 DMA 映射

vring_map_one_sg() - 映射单个 scatterlist

位置: drivers/virtio/virtio_ring.c:446-482:

c
static int vring_map_one_sg(const struct vring_virtqueue *vq,
                            struct scatterlist *sg,
                            enum dma_data_direction direction,
                            dma_addr_t *addr, u32 *len,
                            bool premapped, unsigned long attr)
{
    if (premapped) {
        // 预映射: 直接使用 sg 中的 DMA 地址
        *addr = sg_dma_address(sg);
        *len = sg_dma_len(sg);
        return 0;
    }

    *len = sg->length;

    if (!vq->use_map_api) {
        // 不使用 DMA API: 使用物理地址
        kmsan_handle_dma(sg_phys(sg), sg->length, direction);
        *addr = (dma_addr_t)sg_phys(sg);
        return 0;
    }

    // 使用 DMA API 映射
    *addr = virtqueue_map_page_attrs(&vq->vq, sg_page(sg),
                                     sg->offset, sg->length,
                                     direction, attr);
    return vring_mapping_error(vq, *addr) ? -ENOMEM : 0;
}

vring_unmap_one_split() - 取消映射 (Split)

位置: drivers/virtio/virtio_ring.c:520-542:

c
static unsigned int vring_unmap_one_split(const struct vring_virtqueue *vq,
                                          struct vring_desc_extra *extra)
{
    u16 flags;

    flags = extra->flags;

    // 间接描述符或不需 unmap
    if (flags & VRING_DESC_F_INDIRECT) {
        if (!vq->use_map_api)
            goto out;
    } else if (!vring_need_unmap_buffer(vq, extra))
        goto out;

    // 执行 unmap
    virtqueue_unmap_page_attrs(&vq->vq,
                              extra->addr, extra->len,
                              (flags & VRING_DESC_F_WRITE) ?
                                  DMA_FROM_DEVICE : DMA_TO_DEVICE,
                              0);
out:
    return extra->next;
}

vring_unmap_extra_packed() - 取消映射 (Packed)

位置: drivers/virtio/virtio_ring.c:1438-1456:

c
static void vring_unmap_extra_packed(const struct vring_virtqueue *vq,
                                     const struct vring_desc_extra *extra)
{
    u16 flags;

    flags = extra->flags;

    if (flags & VRING_DESC_F_INDIRECT) {
        if (!vq->use_map_api)
            return;
    } else if (!vring_need_unmap_buffer(vq, extra))
        return;

    virtqueue_unmap_page_attrs(&vq->vq,
                              extra->addr, extra->len,
                              (flags & VRING_DESC_F_WRITE) ?
                                  DMA_FROM_DEVICE : DMA_TO_DEVICE,
                              0);
}

6.3 DMA API 选择

位置: drivers/virtio/virtio_ring.c:333-351:

c
static bool vring_use_map_api(const struct virtio_device *vdev)
{
    // 如果没有 DMA quirk,使用 DMA API
    if (!virtio_has_dma_quirk(vdev))
        return true;

    // Xen domain 特殊处理
    if (xen_domain())
        return true;

    return false;
}

6.4 缓存一致性

直接映射模式 (vring_alloc_queue):

c
static void *vring_alloc_queue(struct virtio_device *vdev, size_t size,
                               dma_addr_t *map_handle, gfp_t flag,
                               union virtio_map map)
{
    if (vring_use_map_api(vdev)) {
        // 使用 DMA API 分配一致映射
        return virtqueue_map_alloc_coherent(vdev, map, size,
                                            map_handle, flag);
    } else {
        // 使用页分配器
        void *queue = alloc_pages_exact(PAGE_ALIGN(size), flag);
        if (queue) {
            phys_addr_t phys_addr = virt_to_phys(queue);
            *map_handle = (dma_addr_t)phys_addr;
        }
        return queue;
    }
}

7. 数据结构关系图

7.1 Split Ring 整体结构

Split Ring 数据结构关系:

                    vring_virtqueue
                           |
           +---------------+---------------+
           |                               |
    struct virtqueue                  vring_virtqueue_split
           |                               |
    - callback                       - vring vring
    - vdev                                |   +--- desc (vring_desc[])
    - name                                |   +--- avail (vring_avail)
    - index                               |   +--- used (vring_used)
           |                               |
           |                    - avail_flags_shadow
           |                    - avail_idx_shadow
           |                    - desc_state[]
           |                    - desc_extra[]
           |
    - use_map_api
    - weak_barriers
    - broken
    - indirect
    - event
    - layout
    - free_head
    - num_added
    - last_used_idx
    - notify()
    - map

7.2 Packed Ring 整体结构

Packed Ring 数据结构关系:

                    vring_virtqueue
                           |
           +---------------+---------------+
           |                               |
    struct virtqueue                  vring_virtqueue_packed
           |                               |
    - callback                       - vring
           |                                |   +--- num
    - vdev                                |   +--- desc (vring_packed_desc[])
           |                                |   +--- driver (event)
    - name                                |   +--- device (event)
           |                               |
    - index                        - avail_wrap_counter
           |                        - avail_used_flags
           |                        - next_avail_idx
           |                        - event_flags_shadow
           |                        - desc_state[]
           |                        - desc_extra[]
           |
    - use_map_api
    - weak_barriers
    - broken
    - indirect
    - event
    - layout
    - free_head
    - num_added
    - last_used_idx
    - notify()
    - map

7.3 描述符状态管理

Split Ring 描述符状态:

desc_state[] 和 desc_extra[] 与 desc[] 一一对应:

desc[i] <---> desc_state[i] <---> desc_extra[i]
   |              |                  |
   v              v                  v
描述符         用户数据           DMA/标志信息
   |              |                  |
   +---- next ----+---- next -------+

7.4 添加缓冲区到 Split Ring 流程

添加缓冲区 (Split):

1. virtqueue_add_split()
   |
   +-> 检查 num_free >= descs_used
   |
   +-> alloc_indirect_split() 或使用直接描述符
   |
   +-> 填充描述符表:
   |     desc[head] --> desc[head+1] --> ... --> desc[head+n-1]
   |
   +-> 更新 free_head
   |
   +-> avail->ring[avail_idx % num] = head
   |
   +-> virtio_wmb()
   |
   +-> avail->idx++

7.5 添加缓冲区到 Packed Ring 流程

添加缓冲区 (Packed):

1. virtqueue_add_packed()
   |
   +-> 检查 num_free >= descs_used
   |
   +-> 设置 desc[i]:
   |     desc[i].addr = buffer_addr
   |     desc[i].len = buffer_len
   |     desc[i].id = head (buffer id)
   |     desc[i].flags = avail_used_flags | NEXT | WRITE
   |
   +-> 更新 avail_used_flags (翻转 AVAIL/USED 位)
   |
   +-> i++ (循环)
   |
   +-> 如果 i >= num, 重置 i=0 并翻转 wrap counter
   |
   +-> 更新 wrap counter
   |
   +-> 更新 next_avail_idx = i
   |
   +-> virtio_wmb()
   |
   +-> desc[head].flags = head_flags (使能)

8. 总结与对比

8.1 Split vs Packed 特性对比

特性Split RingPacked Ring
引入版本Virtio 0.9 (2009)Virtio 1.0 (2015)
内存布局3 个独立区域单一紧凑区域
缓存效率较低 (需要访问 3 个缓存行)较高 (单一缓存行)
复杂度简单复杂 (wrap counter)
硬件需求较低需要较强内存序支持
间接描述符支持支持
事件索引支持支持
IN_ORDER支持支持

8.2 关键设计决策

  1. 描述符状态分离: desc_state[]desc_extra[] 与实际描述符数组分离,简化内存管理

  2. avail_idx_shadow: 缓存 avail->idx 避免每次都读取 MMIO

  3. weak_barriers: 根据平台特性选择合适的内存屏障

  4. 两阶段 kick: kick_prepare()notify() 分离,允许批处理通知

  5. event_triggered: 优化减少不必要的中断禁用

8.3 代码组织

c
// 操作函数指针表
static const struct virtqueue_ops split_ops = {
    .add = virtqueue_add_split,
    .get = virtqueue_get_buf_ctx_split,
    .kick_prepare = virtqueue_kick_prepare_split,
    .disable_cb = virtqueue_disable_cb_split,
    .enable_cb_delayed = virtqueue_enable_cb_delayed_split,
    .enable_cb_prepare = virtqueue_enable_cb_prepare_split,
    .poll = virtqueue_poll_split,
    .detach_unused_buf = virtqueue_detach_unused_buf_split,
    .more_used = more_used_split,
    .resize = virtqueue_resize_split,
    .reset = virtqueue_reset_split,
};

// VIRTQUEUE_CALL 宏根据 layout 选择操作集
#define VIRTQUEUE_CALL(vq, op, ...) \
    ({ \
        switch (vq->layout) { \
        case VQ_LAYOUT_SPLIT: \
            ret = split_ops.op(vq, ##__VA_ARGS__); \
            break; \
        case VQ_LAYOUT_PACKED: \
            ret = packed_ops.op(vq, ##__VA_ARGS__); \
            break; \
        ... \
        } \
        ret; \
    })

8.4 性能优化建议

  1. 使用 IN_ORDER: 如果设备支持,使用按顺序处理模式可减少状态管理开销

  2. 预映射缓冲区: 使用 virtqueue_add_*_premapped 减少 DMA 映射开销

  3. 批处理: 累积多个缓冲区后再 kick,减少通知次数

  4. 回调优化: 使用 virtqueue_enable_cb_delayed 延迟启用回调,减少中断


参考代码位置汇总

功能文件位置
Split Ring 添加drivers/virtio/virtio_ring.c:599
Packed Ring 添加drivers/virtio/virtio_ring.c:1615
Split Ring 获取drivers/virtio/virtio_ring.c:917
Packed Ring 获取drivers/virtio/virtio_ring.c:2161
中断处理drivers/virtio/virtio_ring.c:3229
DMA 映射drivers/virtio/virtio_ring.c:446
内存屏障include/linux/virtio_ring.h:26
环创建 (Split)drivers/virtio/virtio_ring.c:1357
环创建 (Packed)drivers/virtio/virtio_ring.c:2577
特征最终化drivers/virtio/virtio_ring.c:3505
用户空间 APIinclude/uapi/linux/virtio_ring.h

文档生成日期: 2026-04-26内核版本: Linux (master branch)

基于 VitePress 构建