Skip to content

Linux 内核 KVM 内存管理机制分析

本文档详细分析 Linux Kernel KVM (Kernel-based Virtual Machine) 的内存管理机制,包括 Dirty Ring、PFN Cache、Guest Memfd、Memory Slot 等核心组件。

目录

  1. Dirty Ring 机制
  2. PFN Cache
  3. Guest Memfd
  4. Memory Slot
  5. 内存分配流程
  6. 架构图

源码位置

文件说明
virt/kvm/kvm_main.cKVM 核心实现,VM 创建/销毁,memory slot 管理
virt/kvm/dirty_ring.cDirty ring 实现,脏页追踪
virt/kvm/pfncache.cGFN-to-PFN cache 实现
virt/kvm/guest_memfd.cGuest memfd 文件系统实现
include/linux/kvm_host.hKVM 主机端结构体定义
include/linux/kvm_dirty_ring.hDirty ring 接口定义
include/linux/kvm_types.hKVM 类型定义

1. Dirty Ring 机制

1.1 概述

Dirty Ring 是 KVM 中用于追踪虚拟机脏页的新一代机制,相比传统的 dirty bitmap 方式具有更好的可扩展性和更低的锁竞争。

源码位置: virt/kvm/dirty_ring.c, include/linux/kvm_dirty_ring.h

1.2 结构定义

struct kvm_dirty_ring

c
// include/linux/kvm_dirty_ring.h:21-28
struct kvm_dirty_ring {
    u32 dirty_index;    // 下一个可用的脏页槽位(生产者指针)
    u32 reset_index;     // 下一个待重置的槽位(消费者指针)
    u32 size;            // ring 大小(条目数量)
    u32 soft_limit;      // 软限制,达到此值时 VCPU 退出到用户空间
    struct kvm_dirty_gfn *dirty_gfns;  // 脏页数组
    int index;           // ring 索引
};

关键字段说明:

  • dirty_indexreset_index 形成生产者-消费者模式
  • size 表示 ring 容量,通常为 CPU 相关的值
  • soft_limit 用于触发 VCPU 退出,让用户空间有机会收集脏页
  • dirty_gfns 数组存储脏页的 GFN 信息

struct kvm_dirty_gfn

c
// include/linux/kvm.h (用户空间可见)
struct kvm_dirty_gfn {
    __u32 slot;    // 内存槽 ID
    __u32 flags;   // 标志位
    __u64 offset;  // GFN 相对于 slot 起始地址的偏移
};

1.3 kvm_dirty_ring_alloc()

位置: virt/kvm/dirty_ring.c:74-88

c
int kvm_dirty_ring_alloc(struct kvm *kvm, struct kvm_dirty_ring *ring,
                         int index, u32 size)
{
    ring->dirty_gfns = vzalloc(size);  // 使用 vzalloc 分配内存
    if (!ring->dirty_gfns)
        return -ENOMEM;

    ring->size = size / sizeof(struct kvm_dirty_gfn);  // 计算条目数量
    ring->soft_limit = ring->size - kvm_dirty_ring_get_rsvd_entries(kvm);
    ring->dirty_index = 0;
    ring->reset_index = 0;
    ring->index = index;

    return 0;
}

功能说明:

  • 使用 vzalloc() 分配零初始化的内存
  • soft_limit 预留 KVM_DIRTY_RING_RSVD_ENTRIES 个条目作为保护
  • kvm_dirty_ring_get_rsvd_entries() 计算预留 entries (dirty_ring.c:19-22)

1.4 kvm_dirty_ring_push()

位置: virt/kvm/dirty_ring.c:218-241

c
void kvm_dirty_ring_push(struct kvm_vcpu *vcpu, u32 slot, u64 offset)
{
    struct kvm_dirty_ring *ring = &vcpu->dirty_ring;
    struct kvm_dirty_gfn *entry;

    /* 环不应该满 */
    WARN_ON_ONCE(kvm_dirty_ring_full(ring));

    // 计算当前条目位置(使用掩码实现环形缓冲)
    entry = &ring->dirty_gfns[ring->dirty_index & (ring->size - 1)];

    entry->slot = slot;
    entry->offset = offset;
    /*
     * 确保数据先填充完成,再发布到用户空间
     */
    smp_wmb();
    kvm_dirty_gfn_set_dirtied(entry);  // 设置 KVM_DIRTY_GFN_F_DIRTY 标志
    ring->dirty_index++;
    trace_kvm_dirty_ring_push(ring, slot, offset);

    // 达到软限制时触发 VCPU 请求
    if (kvm_dirty_ring_soft_full(ring))
        kvm_make_request(KVM_REQ_DIRTY_RING_SOFT_FULL, vcpu);
}

关键点:

  • 使用 smp_wmb() 保证内存顺序
  • 达到 soft_limit 时发送 KVM_REQ_DIRTY_RING_SOFT_FULL 请求
  • kvm_dirty_gfn_set_dirtied() 设置 flags = KVM_DIRTY_GFN_F_DIRTY (dirty_ring.c:95-98)

1.5 kvm_dirty_ring_reset()

位置: virt/kvm/dirty_ring.c:105-216

c
int kvm_dirty_ring_reset(struct kvm *kvm, struct kvm_dirty_ring *ring,
                         int *nr_entries_reset)
{
    u32 cur_slot, next_slot;
    u64 cur_offset, next_offset;
    unsigned long mask = 0;
    struct kvm_dirty_gfn *entry;

    // ... 批处理重置逻辑

    while (likely((*nr_entries_reset) < INT_MAX)) {
        if (signal_pending(current))
            return -EINTR;

        entry = &ring->dirty_gfns[ring->reset_index & (ring->size - 1)];

        if (!kvm_dirty_gfn_harvested(entry))  // 检查 KVM_DIRTY_GFN_F_RESET
            break;

        next_slot = READ_ONCE(entry->slot);
        next_offset = READ_ONCE(entry->offset);

        /* 更新标志位 */
        kvm_dirty_gfn_set_invalid(entry);  // smp_store_release(&gfn->flags, 0)

        ring->reset_index++;
        (*nr_entries_reset)++;

        // 批处理优化:合并同一 slot 连续范围的 GFNs
        if (mask) {
            cond_resched();
            if (next_slot == cur_slot) {
                s64 delta = next_offset - cur_offset;
                if (delta >= 0 && delta < BITS_PER_LONG) {
                    mask |= 1ull << delta;
                    continue;
                }
                // 处理反向情况...
            }
            kvm_reset_dirty_gfn(kvm, cur_slot, cur_offset, mask);
        }
        // ...
    }
    // ...
}

核心优化:

  • 批量处理相邻 GFN 的重置操作
  • 使用位掩码一次性重置多个连续页面
  • 调用 cond_resched() 避免锁竞争
  • kvm_dirty_gfn_harvested() 检查 flags & KVM_DIRTY_GFN_F_RESET (dirty_ring.c:100-103)

1.6 Dirty Ring vs Dirty Bitmap

特性Dirty RingDirty Bitmap
内存占用O(n) n=VCPU数O(2*pages_per_slot)
锁竞争低(每 VCPU 独立 ring)高(全局 bitmap)
扩展性
实现复杂度中等简单
用户空间交互轮询/事件驱动ioctl 同步

2. PFN Cache

2.1 概述

PFN Cache(Page Frame Number Cache)是 KVM 中用于缓存 GFN 到 PFN 转换结果的机制,减少重复的页表查找开销。

源码位置: virt/kvm/pfncache.c

2.2 结构定义

struct gfn_to_pfn_cache

PFN 缓存结构维护 GFN 到 PFN 的映射关系,核心字段包括:

c
// include/linux/kvm_types.h:84-97
struct gfn_to_pfn_cache {
    u64 generation;           // memslot generation 号
    gpa_t gpa;               // Guest Physical Address
    unsigned long uhva;      // Userspace HVA (用户空间虚拟地址)
    struct kvm_memory_slot *memslot;  // 关联的 memory slot
    struct kvm *kvm;         // KVM 实例
    struct list_head list;    // 链接到 kvm->gpc_list
    rwlock_t lock;           // 读写锁保护
    struct mutex refresh_lock;  // 刷新时的互斥锁
    void *khva;              // Kernel HVA (内核虚拟地址)
    kvm_pfn_t pfn;           // 缓存的 PFN
    bool active;              // 是否激活
    bool valid;               // 缓存是否有效
};

2.3 pfncache_init()

位置: virt/kvm/pfncache.c:385-395

c
void kvm_gpc_init(struct gfn_to_pfn_cache *gpc, struct kvm *kvm)
{
    rwlock_init(&gpc->lock);
    mutex_init(&gpc->refresh_lock);

    gpc->kvm = kvm;
    gpc->pfn = KVM_PFN_ERR_FAULT;    // 初始化为错误 PFN
    gpc->gpa = INVALID_GPA;
    gpc->uhva = KVM_HVA_ERR_BAD;
    gpc->active = gpc->valid = false;
}

2.4 kvm_pfncache_invalidate()

位置: virt/kvm/pfncache.c:25-58

c
void gfn_to_pfn_cache_invalidate_start(struct kvm *kvm, unsigned long start,
                                       unsigned long end)
{
    struct gfn_to_pfn_cache *gpc;

    spin_lock(&kvm->gpc_lock);
    list_for_each_entry(gpc, &kvm->gpc_list, list) {
        read_lock_irq(&gpc->lock);

        /* 只检查单页,长度检查由调用者负责 */
        if (gpc->valid && !is_error_noslot_pfn(gpc->pfn) &&
            gpc->uhva >= start && gpc->uhva < end) {
            read_unlock_irq(&gpc->lock);

            /*
             * 刷新过程中可能有修改,需重新检查
             */
            write_lock_irq(&gpc->lock);
            if (gpc->valid && !is_error_noslot_pfn(gpc->pfn) &&
                gpc->uhva >= start && gpc->uhva < end)
                gpc->valid = false;  // 标记为无效
            write_unlock_irq(&gpc->lock);
            continue;
        }

        read_unlock_irq(&gpc->lock);
    }
    spin_unlock(&kvm->gpc_lock);
}

功能说明:

  • 作为 MMU notifier 的回调函数
  • 当发生页面回收或迁移时,使相关缓存失效
  • 使用两阶段锁(读锁→写锁)减少锁竞争

2.5 kvm_gpc_refresh()

位置: virt/kvm/pfncache.c:366-383

c
int kvm_gpc_refresh(struct gfn_to_pfn_cache *gpc, unsigned long len)
{
    unsigned long uhva;

    guard(mutex)(&gpc->refresh_lock);

    if (!kvm_gpc_is_valid_len(gpc->gpa, gpc->uhva, len))
        return -EINVAL;

    /* 如果 GPA 有效则忽略 HVA */
    uhva = kvm_is_error_gpa(gpc->gpa) ? gpc->uhva : KVM_HVA_ERR_BAD;

    return __kvm_gpc_refresh(gpc, gpc->gpa, uhva);
}

2.6 GUP 机制

GUP(Get User Pages)是 Linux 内核中用于获取用户空间页面引用的机制,在 KVM 中用于:

c
// pfncache.c 中的 hva_to_pfn 流程
new_pfn = hva_to_pfn(&kfp);
if (is_error_noslot_pfn(new_pfn))
    goto out_error;

// 获取内核映射
if (new_pfn == gpc->pfn)
    new_khva = old_khva;  // 复用已有映射
else
    new_khva = gpc_map(new_pfn);  // 创建新映射

关键点:

  • 使用 FOLL_WRITE 标志获取写权限
  • 支持直接映射和高内存映射
  • 错误处理包括 KVM_PFN_ERR_FAULT 等

3. Guest Memfd

3.1 概述

Guest Memfd 是 KVM 实现基于 memfd 的 guest 内存管理机制,允许虚拟机使用匿名内存作为后端存储。

源码位置: virt/kvm/guest_memfd.c

3.2 核心结构

struct gmem_file

c
// virt/kvm/guest_memfd.c:24-28
struct gmem_file {
    struct kvm *kvm;              // 关联的 KVM 实例
    struct xarray bindings;       // slot => gmem 绑定关系 (xa_for_each 遍历)
    struct list_head entry;       // 链接到 inode 的 i_private_list
};

struct gmem_inode

c
// virt/kvm/guest_memfd.c:30-35
struct gmem_inode {
    struct shared_policy policy;  // NUMA 内存策略
    struct inode vfs_inode;       // 底层文件系统 inode

    u64 flags;                    // GUEST_MEMFD_FLAG_* 标志
};

关键宏 (guest_memfd.c:37-43):

c
#define GMEM_I(inode) \
    container_of(inode, struct gmem_inode, vfs_inode)

#define kvm_gmem_for_each_file(f, mapping) \
    list_for_each_entry(f, &(mapping)->i_private_list, entry)

3.3 kvm_gmem_create()

位置: virt/kvm/guest_memfd.c:628-640

c
int kvm_gmem_create(struct kvm *kvm, struct kvm_create_guest_memfd *args)
{
    loff_t size = args->size;
    u64 flags = args->flags;

    if (flags & ~kvm_gmem_get_supported_flags(kvm))
        return -EINVAL;

    if (size <= 0 || !PAGE_ALIGNED(size))
        return -EINVAL;

    return __kvm_gmem_create(kvm, size, flags);
}

内部实现 __kvm_gmem_create() (guest_memfd.c:559-626):

c
static int __kvm_gmem_create(struct kvm *kvm, loff_t size, u64 flags)
{
    // 1. 获取未使用的 fd
    fd = get_unused_fd_flags(0);
    if (fd < 0)
        return fd;

    // 2. 分配 gmem_file 结构
    f = kzalloc_obj(*f);
    if (!f) {
        err = -ENOMEM;
        goto err_fd;
    }

    // 3. 创建匿名 inode
    inode = anon_inode_make_secure_inode(kvm_gmem_mnt->mnt_sb, name, NULL);
    if (IS_ERR(inode)) {
        err = PTR_ERR(inode);
        goto err_fops;
    }

    // 4. 初始化 inode
    inode->i_op = &kvm_gmem_iops;
    inode->i_mapping->a_ops = &kvm_gmem_aops;
    inode->i_size = size;
    mapping_set_gfp_mask(inode->i_mapping, GFP_HIGHUSER);
    mapping_set_inaccessible(inode->i_mapping);

    GMEM_I(inode)->flags = flags;

    // 5. 创建文件描述符
    file = alloc_file_pseudo(inode, kvm_gmem_mnt, name, O_RDWR, &kvm_gmem_fops);
    file->f_flags |= O_LARGEFILE;
    file->private_data = f;

    kvm_get_kvm(kvm);
    f->kvm = kvm;
    xa_init(&f->bindings);
    list_add(&f->entry, &inode->i_mapping->i_private_list);

    fd_install(fd, file);
    return fd;
}

3.4 kvm_gmem_bind()

位置: virt/kvm/guest_memfd.c:642-704

c
int kvm_gmem_bind(struct kvm *kvm, struct kvm_memory_slot *slot,
                  unsigned int fd, loff_t offset)
{
    loff_t size = slot->npages << PAGE_SHIFT;
    unsigned long start, end;
    struct gmem_file *f;
    struct inode *inode;
    struct file *file;
    int r = -EINVAL;

    file = fget(fd);
    if (!file)
        return -EBADF;

    if (file->f_op != &kvm_gmem_fops)
        goto err;

    f = file->private_data;
    if (f->kvm != kvm)
        goto err;

    inode = file_inode(file);

    // 验证偏移和大小
    if (offset < 0 || !PAGE_ALIGNED(offset) ||
        offset + size > i_size_read(inode))
        goto err;

    filemap_invalidate_lock(inode->i_mapping);

    start = offset >> PAGE_SHIFT;
    end = start + slot->npages;

    // 检查绑定冲突
    if (!xa_empty(&f->bindings) &&
        xa_find(&f->bindings, &start, end - 1, XA_PRESENT)) {
        filemap_invalidate_unlock(inode->i_mapping);
        goto err;
    }

    // 执行绑定
    WRITE_ONCE(slot->gmem.file, file);
    slot->gmem.pgoff = start;
    if (kvm_gmem_supports_mmap(inode))
        slot->flags |= KVM_MEMSLOT_GMEM_ONLY;

    xa_store_range(&f->bindings, start, end - 1, slot, GFP_KERNEL);
    filemap_invalidate_unlock(inode->i_mapping);

    r = 0;
err:
    fput(file);
    return r;
}

3.5 kvm_gmem_get_pfn()

位置: virt/kvm/guest_memfd.c:788-819

c
int kvm_gmem_get_pfn(struct kvm *kvm, struct kvm_memory_slot *slot,
                     gfn_t gfn, kvm_pfn_t *pfn, struct page **page,
                     int *max_order)
{
    pgoff_t index = kvm_gmem_get_index(slot, gfn);
    struct folio *folio;
    int r = 0;

    CLASS(gmem_get_file, file)(slot);
    if (!file)
        return -EFAULT;

    folio = __kvm_gmem_get_pfn(file, slot, index, pfn, max_order);
    if (IS_ERR(folio))
        return PTR_ERR(folio);

    if (!folio_test_uptodate(folio)) {
        clear_highpage(folio_page(folio, 0));
        folio_mark_uptodate(folio);
    }

    r = kvm_gmem_prepare_folio(kvm, slot, gfn, folio);

    folio_unlock(folio);

    if (!r)
        *page = folio_file_page(folio, index);
    else
        folio_put(folio);

    return r;
}

3.6 Notifier 机制

位置: guest_memfd.c:160-228

c
static void __kvm_gmem_invalidate_begin(struct gmem_file *f, pgoff_t start,
                                        pgoff_t end,
                                        enum kvm_gfn_range_filter attr_filter)
{
    bool flush = false, found_memslot = false;
    struct kvm_memory_slot *slot;
    struct kvm *kvm = f->kvm;
    unsigned long index;

    xa_for_each_range(&f->bindings, index, slot, start, end - 1) {
        pgoff_t pgoff = slot->gmem.pgoff;

        struct kvm_gfn_range gfn_range = {
            .start = slot->base_gfn + max(pgoff, start) - pgoff,
            .end = slot->base_gfn + min(pgoff + slot->npages, end) - pgoff,
            .slot = slot,
            .may_block = true,
            .attr_filter = attr_filter,
        };

        if (!found_memslot) {
            found_memslot = true;
            KVM_MMU_LOCK(kvm);
            kvm_mmu_invalidate_begin(kvm);
        }

        flush |= kvm_mmu_unmap_gfn_range(kvm, &gfn_range);
    }

    if (flush)
        kvm_flush_remote_tlbs(kvm);

    if (found_memslot)
        KVM_MMU_UNLOCK(kvm);
}

4. Memory Slot

4.1 概述

Memory Slot 是 KVM 管理虚拟机物理内存的核心数据结构,每个 slot 代表一段连续的 GPA 空间。

源码位置: virt/kvm/kvm_main.c, include/linux/kvm_host.h

4.2 结构定义

struct kvm_memory_slot

c
// include/linux/kvm_host.h:592-616
struct kvm_memory_slot {
    struct hlist_node id_node[2];         // 用于 id_hash 链表 (双链表用于 RCU)
    struct interval_tree_node hva_node[2];  // 用于 HVA 区间树
    struct rb_node gfn_node[2];           // 用于 GFN 红黑树 (双链表用于 RCU)
    gfn_t base_gfn;                      // 起始 GFN (Guest Frame Number)
    unsigned long npages;                // 页数
    unsigned long *dirty_bitmap;         // 脏页位图 (双位图结构)
    struct kvm_arch_memory_slot arch;    // 架构特定数据
    unsigned long userspace_addr;         // 用户空间 HVA (Host Virtual Address)
    u32 flags;                           // KVM_MEM_* 标志 (如 KVM_MEM_LOG_DIRTY_PAGES)
    short id;                            // slot ID
    u16 as_id;                           // 地址空间 ID

#ifdef CONFIG_KVM_GUEST_MEMFD
    struct {
        /*
         * 由 kvm->slots_lock 保护
         */
        struct file *file;               // guest_memfd 文件
        pgoff_t pgoff;                  // 在文件中的页偏移
    } gmem;
#endif
};

主要字段说明:

  • id_node: 用于通过 slot ID 快速查找 (id_hash 哈希表)
  • hva_node: 用于通过用户空间虚拟地址区间查找 (interval_tree)
  • gfn_node: 用于通过 GFN 区间查找 (rb_tree,红黑树)
  • dirty_bitmap: 传统 dirty logging 使用的位图 (双位图用于手动保护模式)
  • gmem: guest_memfd 绑定信息

辅助函数 (kvm_host.h:618-638):

c
static inline bool kvm_slot_has_gmem(const struct kvm_memory_slot *slot)
{
    return slot && (slot->flags & KVM_MEM_GUEST_MEMFD);
}

static inline bool kvm_slot_dirty_track_enabled(const struct kvm_memory_slot *slot)
{
    return slot->flags & KVM_MEM_LOG_DIRTY_PAGES;
}

static inline unsigned long kvm_dirty_bitmap_bytes(struct kvm_memory_slot *memslot)
{
    return ALIGN(memslot->npages, BITS_PER_LONG) / 8;
}

4.3 struct kvm (VM 主结构)

c
// include/linux/kvm_host.h:769-878
struct kvm {
    // MMU 锁
#ifdef KVM_HAVE_MMU_RWLOCK
    rwlock_t mmu_lock;
#else
    spinlock_t mmu_lock;
#endif

    struct mutex slots_lock;         // 内存槽操作锁
    struct mutex slots_arch_lock;    // 架构特定槽锁
    struct mm_struct *mm;            // 用户空间 mm_struct
    unsigned long nr_memslot_pages;  // 所有 slot 的总页数

    /* 两个 memslot 集合 - 活跃和非活跃(每个地址空间)*/
    struct kvm_memslots __memslots[KVM_MAX_NR_ADDRESS_SPACES][2];
    struct kvm_memslots __rcu *memslots[KVM_MAX_NR_ADDRESS_SPACES];

    struct xarray vcpu_array;       // vCPU 数组
    atomic_t nr_memslots_dirty_logging;  // 正在 dirty logging 的 slot 数

    /* MMU notifier 相关 */
    spinlock_t mn_invalidate_lock;
    unsigned long mn_active_invalidate_count;
    struct rcuwait mn_memslots_update_rcuwait;

    /* PFN cache 管理 */
    spinlock_t gpc_lock;
    struct list_head gpc_list;

    /* ... 其他字段 ... */
    u32 dirty_ring_size;             // dirty ring 大小
    bool dirty_ring_with_bitmap;     // 是否同时使用 ring 和 bitmap
};

4.4 kvm_set_memory_region()

位置: virt/kvm/kvm_main.c:2001-2132

c
static int kvm_set_memory_region(struct kvm *kvm,
                                 const struct kvm_userspace_memory_region2 *mem)
{
    struct kvm_memory_slot *old, *new;
    struct kvm_memslots *slots;
    enum kvm_mr_change change;
    unsigned long npages;
    gfn_t base_gfn;
    int as_id, id;
    int r;

    lockdep_assert_held(&kvm->slots_lock);

    r = check_memory_region_flags(kvm, mem);
    if (r)
        return r;

    as_id = mem->slot >> 16;       // 高16位是地址空间ID
    id = (u16)mem->slot;            // 低16位是slot ID

    // 基本合法性检查
    if ((mem->memory_size & (PAGE_SIZE - 1)) ||
        (mem->memory_size != (unsigned long)mem->memory_size))
        return -EINVAL;
    if (mem->guest_phys_addr & (PAGE_SIZE - 1))
        return -EINVAL;
    if ((mem->userspace_addr & (PAGE_SIZE - 1)) ||
        (mem->userspace_addr != untagged_addr(mem->userspace_addr)) ||
        !access_ok((void __user *)(unsigned long)mem->userspace_addr,
            mem->memory_size))
        return -EINVAL;

    // guest_memfd 相关检查
    if (mem->flags & KVM_MEM_GUEST_MEMFD &&
        (mem->guest_memfd_offset & (PAGE_SIZE - 1) ||
         mem->guest_memfd_offset + mem->memory_size < mem->guest_memfd_offset))
        return -EINVAL;

    slots = __kvm_memslots(kvm, as_id);
    old = id_to_memslot(slots, id);

    if (!mem->memory_size) {
        // 删除 slot
        if (!old || !old->npages)
            return -EINVAL;
        return kvm_set_memslot(kvm, old, NULL, KVM_MR_DELETE);
    }

    base_gfn = (mem->guest_phys_addr >> PAGE_SHIFT);
    npages = (mem->memory_size >> PAGE_SHIFT);

    if (!old || !old->npages) {
        change = KVM_MR_CREATE;
    } else {
        // 修改现有 slot
        if (mem->flags & KVM_MEM_GUEST_MEMFD)
            return -EINVAL;  // private memslot 不可修改
        if ((mem->userspace_addr != old->userspace_addr) ||
            (npages != old->npages) ||
            ((mem->flags ^ old->flags) & (KVM_MEM_READONLY | KVM_MEM_GUEST_MEMFD)))
            return -EINVAL;

        if (base_gfn != old->base_gfn)
            change = KVM_MR_MOVE;
        else if (mem->flags != old->flags)
            change = KVM_MR_FLAGS_ONLY;
        else
            return 0;  // 无变化
    }

    // 检查与其他 slot 的重叠
    if ((change == KVM_MR_CREATE || change == KVM_MR_MOVE) &&
        kvm_check_memslot_overlap(slots, id, base_gfn, base_gfn + npages))
        return -EEXIST;

    // 分配新的 slot 结构
    new = kzalloc_obj(*new, GFP_KERNEL_ACCOUNT);
    if (!new)
        return -ENOMEM;

    new->as_id = as_id;
    new->id = id;
    new->base_gfn = base_gfn;
    new->npages = npages;
    new->flags = mem->flags;
    new->userspace_addr = mem->userspace_addr;

    // 如果是 guest_memfd,绑定到文件
    if (mem->flags & KVM_MEM_GUEST_MEMFD) {
        r = kvm_gmem_bind(kvm, new, mem->guest_memfd, mem->guest_memfd_offset);
        if (r)
            goto out;
    }

    r = kvm_set_memslot(kvm, old, new, change);
    if (r)
        goto out_unbind;

    return 0;

out_unbind:
    if (mem->flags & KVM_MEM_GUEST_MEMFD)
        kvm_gmem_unbind(new);
out:
    kfree(new);
    return r;
}

4.5 gfn_to_memslot()

位置: virt/kvm/kvm_main.c:2635-2638

c
struct kvm_memory_slot *gfn_to_memslot(struct kvm *kvm, gfn_t gfn)
{
    return __gfn_to_memslot(kvm_memslots(kvm), gfn);
}

这是一个关键的性能优化点,使用路径:

  1. 从当前活跃的 memslots 中查找
  2. 使用 __gfn_to_memslot 通过 GFN 红黑树进行 O(log n) 查找
  3. vCPU 有缓存 (last_used_slot) 进一步优化热点访问

4.6 kvm_alloc_dirty_bitmap()

位置: virt/kvm/kvm_main.c:1428-1437

c
static int kvm_alloc_dirty_bitmap(struct kvm_memory_slot *memslot)
{
    unsigned long dirty_bytes = kvm_dirty_bitmap_bytes(memslot);

    /* 大小是实际位图的两倍,用于支持手动保护模式 */
    memslot->dirty_bitmap = __vcalloc(2, dirty_bytes, GFP_KERNEL_ACCOUNT);
    if (!memslot->dirty_bitmap)
        return -ENOMEM;

    return 0;
}

5. 内存分配流程

5.1 kvm_create_vm() - VM 创建

位置: virt/kvm/kvm_main.c:1105-1238

c
static struct kvm *kvm_create_vm(unsigned long type, const char *fdname)
{
    struct kvm *kvm = kvm_arch_alloc_vm();  // 分配 VM 结构
    struct kvm_memslots *slots;
    int r, i, j;

    if (!kvm)
        return ERR_PTR(-ENOMEM);

    /* 初始化锁 */
    KVM_MMU_LOCK_INIT(kvm);
    mmgrab(current->mm);
    kvm->mm = current->mm;
    kvm_eventfd_init(kvm);
    mutex_init(&kvm->lock);
    mutex_init(&kvm->irq_lock);
    mutex_init(&kvm->slots_lock);
    mutex_init(&kvm->slots_arch_lock);
    spin_lock_init(&kvm->mn_invalidate_lock);
    rcuwait_init(&kvm->mn_memslots_update_rcuwait);
    xa_init(&kvm->vcpu_array);

#ifdef CONFIG_KVM_GENERIC_MEMORY_ATTRIBUTES
    xa_init(&kvm->mem_attr_array);
#endif

    /* 初始化 PFN cache 列表 (gfn_to_pfn_cache) */
    INIT_LIST_HEAD(&kvm->gpc_list);
    spin_lock_init(&kvm->gpc_lock);

    /* 初始化设备链表 */
    INIT_LIST_HEAD(&kvm->devices);
    kvm->max_vcpus = KVM_MAX_VCPUS;

    /* 初始化 memslots (双重缓冲 - 活跃/非活跃) */
    for (i = 0; i < kvm_arch_nr_memslot_as_ids(kvm); i++) {
        for (j = 0; j < 2; j++) {
            slots = &kvm->__memslots[i][j];

            atomic_long_set(&slots->last_used_slot, (unsigned long)NULL);
            slots->hva_tree = RB_ROOT_CACHED;    // HVA 区间树
            slots->gfn_tree = RB_ROOT;            // GFN 红黑树
            hash_init(slots->id_hash);            // ID 哈希表
            slots->node_idx = j;
            slots->generation = i;  // 每个地址空间 generation 不同
        }
        rcu_assign_pointer(kvm->memslots[i], &kvm->__memslots[i][0]);
    }

    /* 分配 IO 总线 (PCI, IO, MMIO) */
    for (i = 0; i < KVM_NR_BUSES; i++) {
        rcu_assign_pointer(kvm->buses[i],
            kzalloc_obj(struct kvm_io_bus, GFP_KERNEL_ACCOUNT));
        if (!kvm->buses[i])
            goto out_err_no_arch_destroy_vm;
    }

    /* 架构特定初始化 (x86, ARM, RISC-V 等) */
    r = kvm_arch_init_vm(kvm, type);
    if (r)
        goto out_err_no_arch_destroy_vm;

    r = kvm_enable_virtualization();  // 启用虚拟化 (VMX/SVM)
    if (r)
        goto out_err_no_disable;

#ifdef CONFIG_HAVE_KVM_IRQCHIP
    INIT_HLIST_HEAD(&kvm->irq_ack_notifier_list);
#endif

    /* 初始化 MMU notifier (页面回收/迁移通知) */
    r = kvm_init_mmu_notifier(kvm);
    if (r)
        goto out_err_no_mmu_notifier;

    /* 初始化合并 MMIO ring */
    r = kvm_coalesced_mmio_init(kvm);
    if (r < 0)
        goto out_no_coalesced_mmio;

    /* 创建 debugfs 目录 */
    r = kvm_create_vm_debugfs(kvm, fdname);
    if (r)
        goto out_err_no_debugfs;

    /* 添加到全局 VM 列表 */
    mutex_lock(&kvm_lock);
    list_add(&kvm->vm_list, &vm_list);
    mutex_unlock(&kvm_lock);

    preempt_notifier_inc();
    kvm_init_pm_notifier(kvm);

    return kvm;

// 错误处理路径 (跳转到对应 out_xxx 标签)...
}

VM 创建流程图:

kvm_create_vm()

    ├── kvm_arch_alloc_vm()              # 分配架构特定 VM 结构

    ├── 初始化基本结构
    │   ├── KVM_MMU_LOCK_INIT(kvm)
    │   ├── kvm->mm = current->mm
    │   ├── mutex_init(&kvm->slots_lock)
    │   └── xa_init(&kvm->vcpu_array)

    ├── 初始化 PFN cache 列表
    │   ├── INIT_LIST_HEAD(&kvm->gpc_list)
    │   └── spin_lock_init(&kvm->gpc_lock)

    ├── 初始化 memslots (双重缓冲)
    │   └── for each address_space:
    │       ├── __memslots[i][0] (active)
    │       └── __memslots[i][1] (inactive)

    ├── 分配 IO buses (KVM_NR_BUSES=3)

    ├── kvm_arch_init_vm()                # 架构特定 VM 初始化

    ├── kvm_enable_virtualization()      # 启用虚拟化 (VMX/SVM/SME)

    ├── kvm_init_mmu_notifier()          # 初始化 MMU notifier

    ├── kvm_coalesced_mmio_init()        # 初始化合并 MMIO

    ├── kvm_create_vm_debugfs()          # 创建 debugfs 条目

    └── list_add(&kvm->vm_list)          # 添加到全局 VM 列表

5.2 kvm_destroy_vm() - VM 销毁

位置: virt/kvm/kvm_main.c:1261-1317

c
static void kvm_destroy_vm(struct kvm *kvm)
{
    int i;
    struct mm_struct *mm = kvm->mm;

    kvm_destroy_pm_notifier(kvm);
    kvm_uevent_notify_change(KVM_EVENT_DESTROY_VM, kvm);
    kvm_destroy_vm_debugfs(kvm);

    /* 从全局列表移除 */
    mutex_lock(&kvm_lock);
    list_del(&kvm->vm_list);
    mutex_unlock(&kvm_lock);

    kvm_arch_pre_destroy_vm(kvm);

    /* 释放中断路由 */
    kvm_free_irq_routing(kvm);

    /* 释放 IO buses */
    for (i = 0; i < KVM_NR_BUSES; i++) {
        struct kvm_io_bus *bus = kvm_get_bus_for_destruction(kvm, i);
        if (bus)
            kvm_io_bus_destroy(bus);
        kvm->buses[i] = NULL;
    }

    kvm_coalesced_mmio_free(kvm);

    /* 注销 MMU notifier */
    mmu_notifier_unregister(&kvm->mmu_notifier, kvm->mm);

    /*
     * 处理 pending invalidation:
     * - mn_active_invalidate_count 非零表示有待处理的 invalidation
     * - 如果为零但 mmu_invalidate_in_progress 非零则发出警告
     */
    WARN_ON(rcuwait_active(&kvm->mn_memslots_update_rcuwait));
    if (kvm->mn_active_invalidate_count)
        kvm->mn_active_invalidate_count = 0;
    else
        WARN_ON(kvm->mmu_invalidate_in_progress);

    /* 架构特定销毁 */
    kvm_arch_destroy_vm(kvm);

    /* 销毁设备 */
    kvm_destroy_devices(kvm);

    /* 释放所有 memslots (活跃和非活跃集合) */
    for (i = 0; i < kvm_arch_nr_memslot_as_ids(kvm); i++) {
        kvm_free_memslots(kvm, &kvm->__memslots[i][0]);
        kvm_free_memslots(kvm, &kvm->__memslots[i][1]);
    }

    /* 清理 SRCU */
    cleanup_srcu_struct(&kvm->irq_srcu);
    srcu_barrier(&kvm->srcu);
    cleanup_srcu_struct(&kvm->srcu);

    /* 释放属性数组 */
#ifdef CONFIG_KVM_GENERIC_MEMORY_ATTRIBUTES
    xa_destroy(&kvm->mem_attr_array);
#endif

    kvm_arch_free_vm(kvm);
    preempt_notifier_dec();
    kvm_disable_virtualization();
    mmdrop(mm);
}

5.3 kvm_free_memslot()

位置: virt/kvm/kvm_main.c:945-955

c
static void kvm_free_memslot(struct kvm *kvm, struct kvm_memory_slot *slot)
{
    /* 如果是 guest_memfd,先解除绑定 */
    if (slot->flags & KVM_MEM_GUEST_MEMFD)
        kvm_gmem_unbind(slot);

    /* 释放 dirty bitmap */
    kvm_destroy_dirty_bitmap(slot);

    /* 释放架构特定数据 */
    kvm_arch_free_memslot(kvm, slot);

    kfree(slot);
}

/* 释放 dirty bitmap (kvm_main.c:935-942) */
static void kvm_destroy_dirty_bitmap(struct kvm_memory_slot *memslot)
{
    if (!memslot->dirty_bitmap)
        return;

    vfree(memslot->dirty_bitmap);
    memslot->dirty_bitmap = NULL;
}

5.4 kvm_alloc_dirty_bitmap()

位置: virt/kvm/kvm_main.c:1428-1437

c
static int kvm_alloc_dirty_bitmap(struct kvm_memory_slot *memslot)
{
    unsigned long dirty_bytes = kvm_dirty_bitmap_bytes(memslot);

    /* 大小是实际位图的两倍,用于支持手动保护模式 */
    memslot->dirty_bitmap = __vcalloc(2, dirty_bytes, GFP_KERNEL_ACCOUNT);
    if (!memslot->dirty_bitmap)
        return -ENOMEM;

    return 0;
}

6. 架构图

6.1 KVM 内存管理整体架构

┌─────────────────────────────────────────────────────────────────────┐
│                         用户空间 (Userspace)                         │
│  ┌─────────────────┐  ┌─────────────────┐  ┌─────────────────┐    │
│  │   QEMU / CRIU   │  │   脏页采集线程   │  │  内存管理工具   │    │
│  └────────┬────────┘  └────────┬────────┘  └────────┬────────┘    │
└───────────┼───────────────────┼───────────────────┼───────────────┘
            │                   │                   │
            │ ioctl/KVM_SET_    │ KVM_GET_DIRTY_    │ KVM_CREATE_
            │   MEMORY_REGION   │   RING            │   GUEST_MEMFD
            ▼                   ▼                   ▼
┌─────────────────────────────────────────────────────────────────────┐
│                         KVM 内核模块                                 │
│  ┌─────────────────────────────────────────────────────────────┐   │
│  │                    kvm_main.c                                │   │
│  │  ┌───────────────┐  ┌───────────────┐  ┌───────────────┐     │   │
│  │  │ kvm_create_vm│  │kvm_set_memory │  │ gfn_to_memslot│     │   │
│  │  │ kvm_destroy_vm│ │_region        │  │               │     │   │
│  │  └───────┬───────┘  └───────┬───────┘  └───────┬───────┘     │   │
│  │          │                  │                  │             │   │
│  │  ┌───────┴──────────────────┴──────────────────┴───────┐     │   │
│  │  │              struct kvm_memory_slot                 │     │   │
│  │  │  base_gfn, npages, dirty_bitmap, gmem.file, ...     │     │   │
│  │  └─────────────────────────────────────────────────────┘     │   │
│  └─────────────────────────────────────────────────────────────┘   │
│                                                                     │
│  ┌─────────────────┐  ┌─────────────────┐  ┌─────────────────┐   │
│  │  dirty_ring.c   │  │  pfncache.c     │  │  guest_memfd.c  │   │
│  │                 │  │                 │  │                 │   │
│  │ kvm_dirty_ring  │  │ gfn_to_pfn_cache│  │ kvm_guest_memfd │   │
│  │  .dirty_index   │  │  .generation   │  │  .gmem_file     │   │
│  │  .reset_index   │  │  .pfn, .khva   │  │  .bindings     │   │
│  │  .dirty_gfns[]  │  │  .active,valid │  │                 │   │
│  └────────┬────────┘  └────────┬────────┘  └────────┬────────┘   │
└───────────┼─────────────────────┼───────────────────┼────────────┘
            │                     │                   │
            ▼                     ▼                   ▼
┌─────────────────────────────────────────────────────────────────────┐
│                      底层内核机制                                     │
│  ┌─────────────────┐  ┌─────────────────┐  ┌─────────────────┐   │
│  │   MMU Notifier  │  │   get_user_pages │  │   匿名 inode    │   │
│  │                 │  │   _fast (GUP)    │  │   (anon_inode)  │   │
│  │ invalidate_     │  │                 │  │                 │   │
│  │  range_start/   │  │ hva_to_pfn()    │  │ kvm_gmem_mnt    │   │
│  │  end            │  │                 │  │                 │   │
│  └─────────────────┘  └─────────────────┘  └─────────────────┘   │
└─────────────────────────────────────────────────────────────────────┘
            │                     │                   │
            ▼                     ▼                   ▼
┌─────────────────────────────────────────────────────────────────────┐
│                        物理内存层                                     │
│  ┌─────────────────┐  ┌─────────────────┐  ┌─────────────────┐   │
│  │   主机物理内存   │  │   Page Frame    │  │   Memfd 存储    │   │
│  │   (RAM)         │  │   Number (PFN)  │  │                 │   │
│  └─────────────────┘  └─────────────────┘  └─────────────────┘   │
└─────────────────────────────────────────────────────────────────────┘

6.2 Dirty Ring 工作流程

VCPU 执行 guest 写入                          用户空间采集
       │                                            │
       ▼                                            │
┌──────────────────┐                                │
│ 写入 guest 物理  │                                │
│     内存         │                                │
└────────┬─────────┘                                │
         │                                         │
         ▼                                         │
┌──────────────────┐     触发                 ┌──────────────────┐
│ KVM MMU 追踪     │──────────────────────────▶│ 检测 soft_limit  │
│ (标记 dirty)     │     KVM_REQ_              │ 达到,软限制触发  │
└────────┬─────────┘     DIRTY_RING_            └────────┬─────────┘
         │              SOFT_FULL                      │
         ▼                                             ▼
┌──────────────────┐                          ┌──────────────────┐
│ kvm_dirty_ring   │     VCPU 退出            │ VCPU 返回用户空间 │
│ _push()          │─────────────────────────▶│                  │
│                  │                          └────────┬─────────┘
│ [slot, offset]   │                                   │
│ 写入 dirty_gfns  │                                   │
└────────┬─────────┘                                   │
         │                                             │
         │◀──────────────────────────────────────────┘
         │            kvm_dirty_ring_reset()
         │                    │
         ▼                    ▼
┌──────────────────┐   ┌──────────────────┐
│ reset_index++    │   │ kvm_reset_dirty_ │
│                  │   │ gfn() 批量重置    │
│ bitmap[offset]   │   │ MMU 重新追踪      │
│ = 0              │   │                   │
└──────────────────┘   └──────────────────┘

6.3 Guest Memfd 架构

用户空间 (QEMU)                        KVM 内核                          物理层
   │                                  │                               │
   │ KVM_CREATE_GUEST_MEMFD         │                               │
   │ ─────────────────────────────────▶                               │
   │                                  │ anon_inode_make_secure_inode()│
   │                                  │ ─────────────────────────────▶│
   │                                  │        创建伪文件系统 inode    │
   │◀─────────────────────────────────│                               │
   │   返回 fd                        │                               │
   │                                  │                               │
   │ KVM_SET_MEMORY_REGION            │                               │
   │ (KVM_MEM_GUEST_MEMFD)           │                               │
   │ ─────────────────────────────────▶                               │
   │                                  │ kvm_gmem_bind()               │
   │                                  │  - 验证 fd                    │
   │                                  │  - xa_store(bindings)         │
   │                                  │  - 设置 slot->gmem.file       │
   │                                  │                               │
   │                                  │                               │
   │ VCPU 访问 guest 内存              │                               │
   │ ─────────────────────────────────▶                               │
   │                                  │ kvm_mmu_fault()               │
   │                                  │  │                            │
   │                                  │  ▼                            │
   │                                  │ kvm_gmem_get_pfn()            │
   │                                  │  │                            │
   │                                  │  ▼                            │
   │                                  │ folio = kvm_gmem_get_folio()  │
   │                                  │  │                            │
   │                                  │  ▼                            │
   │                                  │ __filemap_get_folio()         │
   │                                  │  │                            │
   │                                  │  ▼                            │
   │                                  │      (如果需要) 分配 folio     │
   │                                  │◀──────────────────────────────│
   │                                  │                               │
   │                                  │ pfn = folio_file_pfn()       │

6.4 PFN Cache 架构

┌─────────────────────────────────────────────────────────────────┐
│                    GFN to PFN Cache 生命周期                    │
└─────────────────────────────────────────────────────────────────┘

1. 初始化                     2. 激活                       3. 首次使用
┌──────────────────┐    ┌──────────────────┐    ┌──────────────────┐
│ kvm_gpc_init()   │───▶│ kvm_gpc_activate │───▶│ kvm_gpc_refresh │
│                  │    │                  │    │                  │
│ - 初始化锁        │    │ - 添加到 gpc_list│    │ - 查找 memslot  │
│ - 设置初始值     │    │ - 设置 active    │    │ - 调用 hva_to_  │
│                  │    │                  │    │   pfn_retry()   │
└──────────────────┘    └──────────────────┘    │ - 获取 PFN      │
                                                └────────┬─────────┘


4. 缓存命中                  5. MMU Notifier            6. 再次刷新
┌──────────────────┐    ┌──────────────────┐    ┌──────────────────┐
│ kvm_gpc_check()  │    │ invalidate_start │───▶│ kvm_gpc_refresh │
│                  │    │                  │    │                  │
│ - 检查 generation│    │ - 设置 valid=flase│    │ - 重新查找 PFN  │
│ - 检查 valid     │    │ - 清除缓存       │    │ - 更新 khva/pfn │
│ - 返回 pfn       │◀───┤                  │    │ - 设置 valid=1  │
└──────────────────┘    └──────────────────┘    └──────────────────┘

6.5 Memory Slot 查找优化

gfn_to_memslot(gfn)


┌──────────────────┐
│ 检查 vCPU 本地缓存│
│ last_used_slot   │
└────────┬─────────┘
         │ 命中

┌──────────────────┐
│ try_get_memslot()│
│ 返回缓存的 slot  │
└────────┬─────────┘
         │ 未命中

┌──────────────────┐
│ search_memslots()│
│ (O(log n) 红黑树)│
└────────┬─────────┘


┌──────────────────┐
│ 更新 last_used_slot│
└────────┬─────────┘


┌──────────────────┐
│ 返回 slot        │
└──────────────────┘

6.6 双 Mem slot 集合 (RCU 优化)

┌──────────────────────────────────────────────────────────────┐
│                    Mem slot 双重缓冲                          │
└──────────────────────────────────────────────────────────────┘

kvm->memslots[i] ──────▶  ┌─────────────────┐    ┌─────────────────┐
       (RCU 指针)         │  __memslots[i][0]│    │  __memslots[i][1]│
                         │    (active)      │    │   (inactive)     │
                         └────────┬─────────┘    └────────┬─────────┘
                                  │                       │
                                  │                       │
                           ┌──────┴───────┐        ┌──────┴───────┐
                           │ hva_tree      │        │ hva_tree      │
                           │ gfn_tree      │        │ gfn_tree      │
                           │ id_hash       │        │ id_hash       │
                           └───────────────┘        └───────────────┘

用户空间修改时:
1. 修改 __memslots[i][1] (inactive)
2. 调用 kvm_set_memslot() 完成修改
3. rcu_assign_pointer() 切换指针
4. 调用 rcu_barrier() 等待旧引用释放
5. 交换 active/inactive 角色

总结

KVM 的内存管理是一个复杂而精密的系统,包含以下核心组件:

  1. Dirty Ring: 高效的脏页追踪机制,通过环形缓冲区和批处理优化减少锁竞争
  2. PFN Cache: GFN 到 PFN 转换结果的缓存,减少重复页表查找
  3. Guest Memfd: 基于匿名内存的 guest 内存管理,支持 NUMA 策略和 mmap
  4. Memory Slot: 虚拟机物理内存的核心抽象,支持多种后端类型
  5. MMU Notifier: 与 Linux 内存管理子系统交互的桥梁,处理页面迁移和回收

这些组件协同工作,为 KVM 虚拟机提供了高效、可靠的内存管理能力。

关键设计模式

设计模式应用场景优势
Per-VCPU RingDirty Ring减少锁竞争
双重缓冲Memory SlotsRCU 无锁切换
缓存PFN Cache减少页表查找
批处理Dirty Ring Reset合并操作,减少 MMU lock
区间树Slot HVA 查找O(log n) 查找

安全性考虑

  • MMU Notifier: 确保页面回收/迁移时缓存失效
  • slots_lock: 保护 memslot 操作
  • refcount: KVM 引用计数管理生命周期
  • gmem bindings: xarray 确保原子性绑定

文档信息

  • 分析版本: Linux Kernel (基于 master 分支)
  • 分析日期: 2026-04-26
  • 源码路径: /Users/sphinx/github/linux/virt/kvm/

基于 VitePress 构建