当前位置: 首页 > news >正文

qemu分析之 KVM_EXIT_MMIO


基于qemu-2.8.1

address_space_rw 函数作用

这是QEMU内存子系统的核心函数,负责分块处理跨MemoryRegion的内存读取操作。主要场景包括:

  1. 处理跨越多个MemoryRegion的连续内存读取
  2. 区分RAM直接访问与MMIO设备模拟
  3. 处理不同位宽(1/2/4/8字节)的访问

代码流程解析

/* 在RCU临界区内调用 */
MemTxResult address_space_read_continue(AddressSpace *as, hwaddr addr,
                                        MemTxAttrs attrs, uint8_t *buf,
                                        int len, hwaddr addr1, hwaddr l,
                                        MemoryRegion *mr)
{
    // 初始化变量...
    for (;;) {
        /* 判断是否为直接内存访问(RAM) */
        if (!memory_access_is_direct(mr, false)) {
            /* I/O设备处理分支 */
            // 准备MMIO访问(可能涉及锁操作)
            release_lock |= prepare_mmio_access(mr); 
            
            // 根据设备限制调整访问长度(对齐要求等)
            l = memory_access_size(mr, l, addr1); 
            
            // 分派不同位宽的读取操作
            switch (l) {
            case 8: // 64位读
                result |= memory_region_dispatch_read(mr, addr1, &val, 8, attrs);
                stq_p(buf, val); // 存储并处理字节序
                break;
            case 4: // 32位读
                result |= memory_region_dispatch_read(mr, addr1, &val, 4, attrs);
                stl_p(buf, val);
                break;
            case 2: // 16位读
                result |= memory_region_dispatch_read(mr, addr1, &val, 2, attrs);
                stw_p(buf, val);
                break;
            case 1: // 8位读
                result |= memory_region_dispatch_read(mr, addr1, &val, 1, attrs);
                stb_p(buf, val);
                break;
            default:
                abort(); // 非法访问长度
            }
        } else {
            /* RAM直接访问分支 */
            // 映射到主机虚拟地址
            ptr = qemu_map_ram_ptr(mr->ram_block, addr1);
            memcpy(buf, ptr, l); // 直接内存拷贝
        }

        // 必要时释放iothread锁
        if (release_lock) {
            qemu_mutex_unlock_iothread();
            release_lock = false;
        }

        // 更新剩余需处理的长度和位置
        len -= l;
        buf += l;
        addr += l;

        if (!len) break; // 完成所有读取

        // 转换到下一个内存区域
        l = len;
        mr = address_space_translate(as, addr, &addr1, &l, false);
    }
    return result;
}

关键机制详解

1. 内存区域类型判断
  • memory_access_is_direct(mr, false)
    • 返回true表示目标为RAM区域,可直接通过memcpy快速访问
    • 返回false表示目标为MMIO设备,需通过设备回调函数处理
2. MMIO访问准备
  • prepare_mmio_access(mr)
    • 必要时释放全局锁(qemu_mutex_unlock_iothread()),防止设备模拟时阻塞其他线程
    • 返回标记用于后续重新加锁
3. 设备访问分派
  • memory_region_dispatch_read()
    • 最终调用MemoryRegionOps中注册的.read回调函数
    • 示例设备操作:
      static const MemoryRegionOps mydev_ops = {
          .read = mydev_read,
          .write = mydev_write,
          .endianness = DEVICE_LITTLE_ENDIAN,
          //...
      };
      
4. 字节序处理
  • stq_p/stl_p/stw_p/stb_p宏:
    • 将主机端数值按目标架构字节序写入缓冲区
    • 例如:设备为Big-Endian时自动进行字节交换
5. 跨区域处理
  • 循环中的address_space_translate
    • 当读取操作跨越多个MemoryRegion时,自动分割为多次访问
    • 更新addr1为当前MemoryRegion内的偏移量

锁机制注意事项

  • RCU(Read-Copy-Update)保护:函数在RCU临界区内调用,确保内存拓扑结构不会在访问期间变化
  • IOThread锁释放:MMIO访问可能涉及长时间设备模拟,临时释放锁避免阻塞其他线程

典型调用链

address_space_read_full()
  → address_space_translate()        // 初始地址转换
  → address_space_read_continue()    // 本函数
    → memory_region_dispatch_read()  // 设备访问
      → mr->ops->read()              // 设备具体实现

错误处理

  • MemTxResult的按位或操作(|=):
    • 累积多个子操作的错误状态
    • 常见错误类型:
    typedef enum MemTxResult {
        MEMTX_OK = 0,         // 成功
        MEMTX_DECODE_ERROR,   // 地址无对应设备
        MEMTX_ACCESS_ERROR,   // 权限错误
        MEMTX_BUS_ERROR       // 设备返回错误
    } MemTxResult;
    

性能优化点

  1. RAM快速路径:直接memcpy避免函数调用开销
  2. 访问长度对齐memory_access_size()确保符合设备要求
  3. 锁粒度控制:仅在必要时持有iothread锁

该函数体现了QEMU内存模拟的核心设计思想:在保证正确性的前提下,对RAM和MMIO进行差异化处理以平衡性能与灵活性

2. qemu_map_ram_ptr 函数目标

在保证线程安全(RCU临界区内)的前提下,获取RAMBlock对应的宿主机物理内存指针,专用于QEMU内部对内存的底层操作。

2.1. 参数说明
  • RAMBlock *ram_block:内存块描述符(可为NULL)
  • ram_addr_t addr:全局内存地址空间中的地址
2.2. 关键逻辑流程
void *qemu_map_ram_ptr(RAMBlock *ram_block, ram_addr_t addr)
{
    // Step 1: 自动定位RAMBlock(当未显式指定时)
    RAMBlock *block = ram_block;
    if (block == NULL) {
        block = qemu_get_ram_block(addr); // 根据地址查找所属内存块
        addr -= block->offset;            // 转换为块内偏移量
    }

    // Step 2: Xen特殊内存映射处理
    if (xen_enabled() && block->host == NULL) {
        if (block->offset == 0) {
            // Case 1: 映射地址所在页(避免全量映射)
            return xen_map_cache(addr, 0, 0);
        } else {
            // Case 2: 首次映射时缓存整个块
            block->host = xen_map_cache(block->offset, block->max_length, 1);
        }
    }

    // Step 3: 计算最终指针
    return ramblock_ptr(block, addr);
}
2.3. 关键点说明
  • 自动块定位:当ram_block参数为NULL时,通过qemu_get_ram_block(addr)自动查找地址所属的内存块,并调整地址为块内偏移量
  • Xen支持
    • 若运行在Xen虚拟化环境且内存未映射时:
    • 零偏移块直接映射目标地址所在页(xen_map_cache第三个参数0表示不锁定)
    • 其他块首次映射时会缓存整个块(block->max_length指定长度,第三个参数1表示锁定内存)
  • 指针计算:最终通过ramblock_ptr宏(通常定义为block->host + addr)计算实际指针
2.4. 使用限制
  • ✖️ 不适用于通用DMA操作(建议用address_space_map
  • ✖️ 不适用于设备自有内存(建议用memory_region_get_ram_ptr
  • ✔️ 专用于QEMU内部需要直接访问客户机物理内存的场景
2.5. 典型应用场景
  • 虚拟设备模拟时快速访问特定内存区域
  • 调试工具需要直接读取内存内容
  • Xen等特殊虚拟化方案中的内存管理

建议结合QEMU内存管理文档《memory.txt》理解其完整上下文,使用时需严格遵守RCU锁规则和API约束条件。

3.0 memory_region_dispatch_read

memory_region_dispatch_read 是 QEMU 内存管理子系统中的关键函数,用于处理对模拟内存区域的读取操作。其核心作用如下:

3.1 核心功能
  1. 地址路由
    根据目标物理地址,查找对应的 MemoryRegion(可能递归遍历子区域),确定处理该地址的 MemoryRegionOps

  2. 回调触发
    调用目标 MemoryRegion 注册的 read 回调函数(通过 MemoryRegionOps),执行设备特定的读取逻辑(如访问模拟设备寄存器)。

  3. 数据转换
    处理字节序转换(Host与Guest的Endianness差异),确保数据格式正确。

  4. 访问控制
    检查内存区域的读写权限(如 ROM 不可写),触发异常(如访问未实现区域时的 unassigned_access)。

3.2 典型参数
  • addr: 目标地址(相对于MemoryRegion的偏移)
  • pv: 读取数据存储的指针
  • size: 操作大小(1/2/4/8字节)
  • attrs: 内存访问属性(如类型、特权级)
3.3 工作流程
  1. 地址解析
    通过 AddressSpace 和物理地址找到对应的 MemoryRegion
  2. 权限检查
    验证该区域是否支持读操作。
  3. 回调执行
    调用 MemoryRegionOps->read,由设备模型返回数据。
  4. 字节序调整
    根据区域配置转换数据字节序。
  5. 未实现处理
    若区域无回调,可能填充默认值或记录错误。
3.4 应用场景
  • CPU 访问物理内存时(如 ld 指令)
  • 设备模拟代码读取其管理的寄存器/内存
  • 虚拟化场景中处理客户机内存访问

例如,当Guest OS读取PCI设备寄存器时,此函数将路由到PCI设备的read回调,返回设备状态信息。

相关文章:

  • 【C++标准库类型】深入理解C++中的using声明:从基础到实践
  • 关于ModbusTCP/RTU协议对接Ethernet/IP(CIP)协议的方案
  • 大模型剪枝、量化、蒸馏的区别和联系
  • 自动化测试 | Python+PyCharm+Google Chrome+Selenium 环境安装记录
  • 【MySQL】MySQL服务器——mysqld
  • Java 大视界 -- 基于 Java 的大数据机器学习模型的迁移学习应用与实践(129)
  • Redis的缓存雪崩、缓存击穿、缓存穿透与缓存预热、缓存降级
  • 数据可信、隐私可控:CESS 如何打造波卡生态数据新基建?
  • Netty基础—4.NIO的使用简介一
  • laravel项目中使用FFMPeg 剪裁视频
  • 电子电气架构 --- 智能电动汽车概述
  • 【InterruptCallBack】- KRTS C++示例精讲(14)
  • java自带日志系统介绍(JUL)以及和Log4j 2、Logback、SLF4J不同日志工具的对比
  • 【解决方案】RAGFlow部分问题清单及解决方案备忘1
  • 专业视角:set 和 multiset的原理与应用解析
  • 星海智算:风月ComfyUI_SD3.5使用教程
  • 嵌入式开发工程师笔试面试指南-模电基础
  • uni-app vue2 记住密码功能
  • Python实现的用于处理协作车辆进入路口遮挡自车路径的情况
  • [Space Shooter_1] Environment | CMake | SDL