SEV内存加密位linux内核设置过程

1. KVM_MEMORY_ENCRYPT_OP初始化

c 复制代码
main()                     // QEMU 入口函数
    qemu_init()              // 初始化虚拟机
        configure_accelerators
            kvm_init
                sev_guest_init
                    KVM_SEV_INIT
                    sev_launch_start
                        KVM_SEV_LAUNCH_START
                    ram_block_notifier_add(&sev_ram_notifier);   //sev_ram_block_added KVM_MEMORY_ENCRYPT_REG_REGION
                kvm_state->memcrypt_encrypt_data = sev_encrypt_data;
                kvm_arch_init
                    kvm_vm_enable_cap
                kvm_memory_listener_register
                memory_listener_register
                cpus_register_accel
        machine_run_board_init()      //pc_init_isa
            pc_init1()    
                pc_memory_init
                pc_system_firmware_init
                    pc_system_flash_map
                        kvm_memcrypt_encrypt_data
                            kvm_state->memcrypt_encrypt_data

1.1、代码逻辑解析

此代码段属于 KVM 对 KVM_MEMORY_ENCRYPT_OP ioctl 的处理核心逻辑,其作用是为内存加密操作提供统一的入口,但实际功能由架构相关代码(如 AMD SVM)实现。以下为逐行分析:

c 复制代码
case KVM_MEMORY_ENCRYPT_OP: {
    r = -ENOTTY; // 默认返回"不支持该操作"
    if (kvm_x86_ops->mem_enc_op) // 检查架构是否实现内存加密操作
        r = kvm_x86_ops->mem_enc_op(kvm, argp); // 调用具体实现(如 AMD SEV)
    break;
}

1.2、关键行为解读

1. 兼容性控制机制
  • -ENOTTY 的深层含义 :表示当前内核或硬件不支持内存加密功能,可能原因包括:

    • 未启用 CONFIG_KVM_AMD_SEV 内核编译选项
    • 硬件无 SEV 支持(如非 AMD EPYC CPU)
    • 内核版本过旧(<4.16)或过新(≥5.10,此接口已废弃)
  • kvm_x86_ops->mem_enc_op 的动态绑定

    • AMD 实现 :指向 svm_mem_enc_op()(定义于 arch/x86/kvm/svm/sev.c
    • Intel 实现 :始终为 NULL(Intel 无等效 SEV 功能)
2. 参数传递流程
c 复制代码
// 用户态(QEMU)调用示例:
struct kvm_sev_cmd cmd = {
    .id = KVM_SEV_INIT, // 子命令类型
    .data = (unsigned long)&init_params // 具体参数结构体
};
ioctl(kvm_fd, KVM_MEMORY_ENCRYPT_OP, &cmd);

// 内核态处理(svm_mem_enc_op):
static int svm_mem_enc_op(struct kvm *kvm, void __user *argp)
{
    struct kvm_sev_cmd cmd;
    copy_from_user(&cmd, argp, sizeof(cmd)); // 从用户空间复制命令
    return sev_handle_cmd(kvm, cmd.id, cmd.data); // 分发到 SEV 子处理函数
}

1.3、与新版接口的差异

旧版(KVM_MEMORY_ENCRYPT_OP) vs 新版(KVM_SEV_*)
特性 旧版接口 新版接口(≥5.10)
命令结构 统一入口 + 子命令 ID 独立 ioctl 命令(如 KVM_SEV_INIT
可维护性 参数解析耦合度高 类型安全,减少错误传递
功能扩展性 需修改共用结构体 独立命令互不影响
典型调用栈 ioctl(KVM_MEMORY_ENCRYPT_OP)svm_mem_enc_op()sev_handle_cmd() ioctl(KVM_SEV_INIT)sev_guest_init()
淘汰原因分析(内核 ≥5.10)
  1. 安全性 :旧接口允许任意 cmd.id 传递,存在非法命令注入风险
  2. 性能:新版省去多层间接跳转,调用路径更短
  3. 代码清晰度:每个 SEV 操作有独立入口,便于维护

1.4、实际应用场景验证

若在 5.4 内核 中执行以下 QEMU 命令:

bash 复制代码
qemu-system-x86_64 -machine confidential-guest-support=sev0...

内核日志将出现

log 复制代码
kvm_amd: SEV supported: 255 ASIDs
kvm_amd: SEV-ES supported: 255 ASIDs
svm_mem_enc_op: handling command KVM_SEV_INIT

此时旧接口仍有效,但 5.10+ 内核会拒绝此调用 ,需改用 KVM_SEV_INIT

1.5、调试技巧

  1. 动态追踪调用路径

    bash 复制代码
    echo 'p:svm_mem_enc_op arch/x86/kvm/svm/sev.c:1800 cmd_id=+0(%dx):u32' > /sys/kernel/debug/tracing/kprobe_events
    echo 1 > /sys/kernel/debug/tracing/events/kprobes/enable

    可捕获所有经过 svm_mem_enc_op 的命令 ID。

  2. 错误码解读

    • -ENOTTY → 检查硬件/Kconfig/内核版本
    • -EINVAL → 参数结构体不匹配(如 32/64 位模式混合)

1.6、迁移到新接口的代码示例

c 复制代码
// 旧版(废弃):
struct kvm_sev_cmd cmd = {.id = KVM_SEV_INIT};
ioctl(kvm_fd, KVM_MEMORY_ENCRYPT_OP, &cmd);

// 新版(推荐):
ioctl(kvm_fd, KVM_SEV_INIT, &launch_params); // 直接使用独立命令

该代码段是 KVM 内存加密功能演进的历史产物 ,理解其工作原理有助于调试旧版本兼容性问题,但在新开发中应遵循 AMD 官方推荐使用 KVM_SEV_* 系列专用命令。

2 qemu中设置逻辑

c 复制代码
kvm_init
    kvm_memory_listener_register
        memory_listener_register                              
            listener_add_address_space
                kvm_region_add        (kml->listener.region_add = kvm_region_add;)
                    kvm_set_phys_mem

3 KVM_SET_USER_MEMORY_REGION

3.1、函数调用链路解析

1. 内存槽操作核心流程

是 否 用户态 QEMU 调用 KVM_SET_USER_MEMORY_REGION 内核 kvm_vm_ioctl_set_memory_region __kvm_set_memory_region kvm_set_memslot? 调用 kvm_set_memslot 直接操作 memslots 触发 kvm_arch_commit_memory_region 调用 arch.encrypted_ops->region_add l

2. 关键函数映射关系
用户预期名称 内核实际名称/行为 所属文件
memslot_region_add kvm_set_memslot virt/kvm/kvm_main.c
__kvm_set_memory_region virt/kvm/kvm_main.c
sev_mem_enc_region_add arch/x86/kvm/svm/sev.c

3.2、调试与验证方法

1. 动态追踪技术验证
bash 复制代码
# 使用 ftrace 跟踪内存槽添加操作
echo 'p:kvm_set_memslot kvm_set_memslot' > /sys/kernel/debug/tracing/kprobe_events
echo 1 > /sys/kernel/debug/tracing/events/kprobes/enable

# 观察输出示例:
#   qemu-system-x86-3248  [002] ...1  5678.901234: kvm_set_memslot: (kvm_set_memslot+0x0/0x3c0)
2. 代码交叉引用检查
  • sev.c 中搜索 region_add 的引用:

    bash 复制代码
    git grep -n "region_add" arch/x86/kvm/svm/sev.c
    # 输出:sev.c:1873:    .region_add = sev_mem_enc_region_add,
  • 确认 sev_mem_enc_region_add 的实现细节:

    c 复制代码
    static int sev_mem_enc_region_add(struct kvm *kvm, u64 addr, u64 size) {
        // 具体实现:遍历 PFN 设置 C-bit
        for (pfn = addr >> PAGE_SHIFT; pfn < (addr + size) >> PAGE_SHIFT; pfn++)
            set_page_encryption_mask(pfn);
    }

3.3、版本兼容性说明

内核版本 关键变化点 影响分析
Linux 4.19 使用 kvm_set_memory_region 旧接口 直接操作 kvm_memory_slot
Linux 5.4+ 引入 kvm_set_memslot 统一接口 抽象化内存槽操作
Linux 5.15 加密回调通过 kvm_memory_encrypted_ops 明确分离加密相关操作

3.4、开发者操作建议

  1. 正确跟踪回调函数

    c 复制代码
    // 在 sev.c 中添加调试输出
    static int sev_mem_enc_region_add(struct kvm *kvm, u64 addr, u64 size) {
        pr_info("SEV: Encrypting memory region 0x%llx-0x%llx\n", addr, addr + size);
        // ...原有代码...
    }

    通过 dmesg 观察加密区域触发情况。

  2. API 变更应对策略

    diff 复制代码
    // 兼容多版本内核的代码示例
    #if LINUX_VERSION_CODE >= KERNEL_VERSION(5,4,0)
        ret = kvm_set_memslot(kvm, &old, &new, KVM_MR_MOVE);
    #else
        ret = kvm_set_memory_region(kvm, &mem);
    #endif
  3. 文档查阅指引

    • 官方内核文档:Documentation/virt/kvm/api.rst(搜索 KVM_SET_USER_MEMORY_REGION
    • AMD SEV 白皮书:AMD Secure Encrypted Virtualization API 第 4.2 节 "Memory Encryption"

4 KVM_MEMORY_ENCRYPT_REG_REGION

c 复制代码
memory_region_init_ram_from_file
	qemu_ram_alloc_from_file
	    mr->ram_block = qemu_ram_alloc_from_fd  (qemu)
	    qemu_ram_alloc_from_fd  (qemu)
	    qemu_ram_alloc_internal  (qemu)
	        ram_block_add  (qemu)
	            ram_block_notify_add  (qemu)
	                ram_block_added = sev_ram_block_added   (qemu)
	                sev_ram_block_added    (qemu)
	                    kvm_arch_vm_ioctl (KVM_MEMORY_ENCRYPT_REG_REGION)
	                        svm_x86_ops->mem_enc_reg_region 
	                            svm_register_enc_region

qemu_ram_alloc_internal 函数解析

qemu_ram_alloc_internal 是 QEMU 内存管理子系统中的核心内部函数,负责为虚拟机分配物理内存块(RAM Block)。它是 QEMU 内存初始化和动态扩展的关键环节,直接关联虚拟机的内存布局与性能优化。以下是其详细说明:


4.1. 函数定义与参数

函数原型
c 复制代码
RAMBlock *qemu_ram_alloc_internal(
    ram_addr_t size,        // 需要分配的内存大小(字节)
    ram_addr_t max_size,    // 最大可扩展大小(用于动态内存扩展)
    void (*resized)(const char*, uint64_t, void*), // 内存大小调整回调函数
    void *resized_opaque,   // 回调函数的用户参数
    uint32_t flags,         // 内存属性标志(如共享、只读等)
    const char *name,       // 内存块名称(调试用)
    Error **errp            // 错误处理指针
);
参数解析
  • size
    初始分配的物理内存大小(例如 4GB 的虚拟机内存)。
  • max_size
    允许动态扩展的最大内存大小。若无需扩展,通常与 size 相同。
  • resized 回调
    当内存块大小调整时触发(例如通过 balloon 驱动收缩内存)。
  • flags
    内存属性标志,常见值包括:
    • RAM_SHARED: 内存可共享(用于多进程或热迁移)。
    • RAM_PREALLOC: 预分配物理内存(避免惰性分配)。
    • RAM_NORESERVE: 不预留宿主内存(允许 overcommit)。
  • name
    唯一标识符,如 "pc.ram""virtio-mmio.region"

4.2. 核心功能

内存分配流程
  1. 创建 RAMBlock 结构体

    初始化 RAMBlock 对象,记录内存块的元数据(大小、宿主指针、映射关系等)。

    c 复制代码
    RAMBlock *block = g_new0(RAMBlock, 1);
    block->used_length = size;
    block->max_length = max_size;
    block->flags = flags;
    block->idstr = g_strdup(name);
  2. 宿主内存分配

    调用宿主系统的内存分配接口(如 mmapmemfd),根据 flags 选择策略:

    • 私有匿名映射 (默认):

      c 复制代码
      ptr = mmap(NULL, size, PROT_READ | PROT_WRITE,
                 MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
    • 大页内存 (若启用):

      c 复制代码
      ptr = qemu_memfd_alloc(name, size, F_SEAL_GROW | F_SEAL_SHRINK, errp);
    • 共享内存RAM_SHARED):

      c 复制代码
      ptr = shm_open(name, O_CREAT | O_RDWR, 0666);
      ftruncate(fd, size);
  3. 注册到全局链表

    将新分配的 RAMBlock 添加到 QEMU 的全局 RAM 列表 ram_list,供地址空间映射使用:

    c 复制代码
    QLIST_INSERT_HEAD_RCU(&ram_list.blocks, block, next);
  4. 通知内存监听器

    触发 MEMORY_DEVICE_PREPLUG 事件,通知设备模型(如 VGA、virtio-balloon)内存已更新。


4.3. 调用场景

场景 1:虚拟机启动初始化

在 QEMU 启动参数 -m 4G 时,主函数调用链为:

复制代码
main() → qemu_init() → pc_memory_init() → memory_region_allocate_system_memory()
                                       → qemu_ram_alloc_internal()
  • 作用 :分配虚拟机的主内存(pc.ram)。
场景 2:热插拔内存设备

通过 QMP 命令 memory-device-add 动态添加 DIMM:

复制代码
qmp_memory_device_add() → pc_dimm_plug() → pc_dimm_memory_plug()
                                        → qemu_ram_alloc_internal()
  • 作用:为热插拔的内存条分配新的 RAMBlock。
场景 3:内存动态调整

virtio-balloon 驱动收缩内存时:

复制代码
virtio_balloon_handle_output() → qemu_balloon() → ram_block_discard_range()
                                               → 触发 resized 回调
  • 作用 :释放部分内存并更新 RAMBlockused_length

4.4. 关键数据结构

RAMBlock 结构(部分)
c 复制代码
struct RAMBlock {
    struct rcu_head rcu;        // RCU 同步保护
    ram_addr_t offset;          // 在全局地址空间中的偏移
    ram_addr_t used_length;     // 当前使用的内存大小
    ram_addr_t max_length;      // 允许的最大内存大小
    void *host;                 // 宿主内存指针
    uint32_t flags;             // 内存属性标志
    char idstr[256];            // 唯一标识名称
    QLIST_ENTRY(RAMBlock) next; // 全局链表指针
    // ...(其他字段省略)
};
ram_list 全局变量
c 复制代码
RAMList ram_list = {
    .blocks = QLIST_HEAD_INITIALIZER(ram_list.blocks),
    .mru_block = NULL,
};

4.5. 性能优化机制

  1. 内存预分配RAM_PREALLOC

    避免宿主系统的惰性分配(lazy allocation),减少虚拟机运行时缺页中断的开销。

  2. 透明大页对齐

    若宿主支持 THP(Transparent Huge Pages),qemu_ram_alloc_internal 会确保分配的内存按大页对齐(如 2MB),提升 TLB 命中率。

  3. 内存锁(mlock)

    在实时性要求高的场景(如 vhost-user),调用 mlock 锁定物理内存,防止被换出。


4.6. 错误处理

  • 内存不足 :返回 NULL 并通过 error_setg(errp, "Cannot allocate memory") 设置错误信息。
  • 重复名称 :若 name 已存在,触发 assert(调试模式)或忽略(生产环境可能不同)。

4.7. 代码示例(简化版)

c 复制代码
// 分配 1GB 内存,允许扩展到 2GB
RAMBlock *ram = qemu_ram_alloc_internal(
    1 * GiB,
    2 * GiB,
    NULL,  // 无需 resized 回调
    NULL,
    RAM_SHARED, // 共享内存
    "guest.ram",
    &error_fatal
);

// 将 RAMBlock 映射到地址空间
MemoryRegion *mr = g_new(MemoryRegion, 1);
memory_region_init_ram_ptr(mr, NULL, "sysmem", ram->max_length, ram->host);
address_space_add_memory(&address_space_memory, mr, 0);

小结

qemu_ram_alloc_internal 是 QEMU 内存管理的底层基石,负责:

  • 物理内存分配:对接宿主系统的内存分配机制。
  • 元数据管理 :维护 RAMBlock 的全局状态。
  • 扩展性支持:为热插拔、动态调整提供基础设施。

5. KVM_MEMORY_ENCRYPT_OP和KVM_MEMORY_ENCRYPT_REG_REGION区别

5. 1、 KVM_MEMORY_ENCRYPT_OP

功能目标
  • 通用加密操作 :执行与内存加密相关的全局性操作,例如初始化加密上下文、密钥管理(如生成/注入密钥)、加密状态控制等。
  • 示例
    • AMD SEV 中的 KVM_SEV_INIT(初始化安全加密虚拟化环境)
    • KVM_SEV_LAUNCH_START(启动虚拟机加密流程)
参数处理
  • 直接传递指针 :通过 argp 直接传递操作类型和参数(通常是架构定义的联合体或结构体)。
  • 无显式数据拷贝 :依赖底层实现解析 argp 指针,操作可能直接修改内核或硬件状态。
操作层级
  • 架构相关实现 :通过 kvm_x86_ops->mem_enc_op 调用具体硬件(如 AMD SEV 或 Intel TDX)的加密操作接口。
  • 影响范围 :通常作用于整个虚拟机或加密会话。

5.2、 KVM_MEMORY_ENCRYPT_REG_REGION

功能目标
  • 内存区域注册 :将特定内存区域标记为加密/受保护区域,或从加密状态中解除注册。
  • 示例
    • AMD SEV 的 KVM_SEV_LAUNCH_UPDATE_DATA(标记某块内存需加密)
    • 解除内存区域的加密保护(如迁移完成后)。
参数处理
  • 结构化数据拷贝 :从用户空间复制 kvm_enc_region 结构体,明确包含内存区域的物理地址长度

    c 复制代码
    struct kvm_enc_region {
        __u64 addr;  // 内存区域起始地址
        __u64 size;  // 内存区域大小
    };
  • 安全性检查 :通过 copy_from_user 验证用户空间数据的合法性,避免非法内存访问。

操作层级
  • 内存粒度操作 :通过 kvm_x86_ops->mem_enc_reg_region 针对特定物理地址范围进行加密配置。
  • 影响范围 :仅作用于单个内存区域,用于精细化管理。

关键差异总结

维度 KVM_MEMORY_ENCRYPT_OP KVM_MEMORY_ENCRYPT_REG_REGION
操作类型 全局性加密控制(如初始化、密钥管理) 内存区域级加密配置(注册/解注册)
参数形式 可能为复合结构体(通过 argp 直接解析) 固定结构体(明确地址和大小)
数据传递 无显式用户空间拷贝 需从用户空间拷贝 kvm_enc_region
影响范围 虚拟机级别或加密会话级别 特定物理内存区域

典型应用场景

  1. 虚拟机启动加密

    • 调用 KVM_MEMORY_ENCRYPT_OP 初始化 SEV 环境。
    • 多次调用 KVM_MEMORY_ENCRYPT_REG_REGION 逐块注册内存到加密区域。
  2. 热迁移保护

    • 迁移前通过 KVM_MEMORY_ENCRYPT_OP 导出加密密钥。
    • 迁移后通过 KVM_MEMORY_ENCRYPT_REG_REGION 重新绑定内存区域。
  3. 动态内存加密

    • 运行时按需加密敏感内存区域(如 GPU 显存),通过 KVM_MEMORY_ENCRYPT_REG_REGION 动态注册。

底层实现依赖

  • 硬件支持 :两者最终通过 kvm_x86_ops 调用具体实现(如 AMD 的 sev_mem_enc_opsev_mem_enc_reg_region)。
  • 安全性约束:内存加密操作通常需要 Hypervisor 与安全处理器(如 AMD PSP)协同完成,确保密钥不泄露。

此设计体现了 KVM 对内存加密的灵活支持:既提供全局控制接口,又允许细粒度内存管理,适配不同硬件加密方案。

相关推荐
A小辣椒20 小时前
TShark:Wireshark CLI 功能
linux
A小辣椒1 天前
TShark:基础知识
linux
AlfredZhao1 天前
OCI 明明分配了 200G 系统盘,为什么 df 只看到 30G?
linux·oci
AlfredZhao2 天前
vi 删除指定范围的行,不用再反复按 dd
linux·vi
用户9718356334662 天前
银河麒麟 KY10 申威(SW64) 安装 nginx-1.16.1-2.p01.ky10.sw_64.rpm 详细步骤
linux
猪脚踏浪2 天前
linux 拷贝文件或目录到指定的位置
linux
大树883 天前
金刚石散热越强,管路越先见顶
大数据·运维·服务器·人工智能·ai
摇滚侠3 天前
Linux CentOS7 rpm 安装 MySQL 5.7
linux·运维·mysql
霸道流氓气质3 天前
领域驱动设计(DDD)在 Spring Boot 微服务中的实践指南
运维·spring boot·微服务
bush43 天前
嵌入式linux学习记录十四、术语
linux·嵌入式