2355 字
12 分钟
ITS表保存操作触发时机
2025-09-01
无标签

Cursor

不对!这是一个很重要的误解。让我澄清一下:

ITS 表保存操作不是每次中断时触发的#

1. 实际触发时机#

ITS 表保存操作只在 VM 状态变化时触发,具体是:

// hw/intc/arm_gicv3_its_kvm.c:75-90
static void vm_change_state_handler(void *opaque, bool running, RunState state)
{
    GICv3ITSState *s = (GICv3ITSState *)opaque;
    Error *err = NULL;

    if (running) {
        return;  // VM运行时什么都不做
    }

    // 只有当VM停止时才保存表
    kvm_device_access(s->dev_fd, KVM_DEV_ARM_VGIC_GRP_CTRL,
                      KVM_DEV_ARM_ITS_SAVE_TABLES, NULL, true, &err);
}

关键判断:if (running) - 只有 VM 从运行状态变为停止状态时才执行保存。

2. 中断处理 vs 表保存的区别#

正常中断处理流程(每次中断都会发生):#

// 中断到达 -> ITS处理 -> 更新内核中的ITS表 -> 投递给vCPU
1. 设备发送MSI中断到ITS
2. ITS查找Device Table找到设备
3. ITS查找ITT(Interrupt Translation Table)找到中断映射
4. ITS查找Collection Table找到目标CPU
5. 投递中断给目标vCPU

表保存操作(只在 VM 停止时发生):#

// VM停止 -> 保存所有ITS表到客户机内存 -> 用于迁移
1. VM状态变为停止
2. 触发vm_change_state_handler
3. 调用KVM_DEV_ARM_ITS_SAVE_TABLES
4. 内核将所有ITS表保存到客户机RAM

3. 为什么不是每次中断都保存#

性能考虑:#

  • ITS 表保存是一个重量级操作,需要遍历所有表并写入内存
  • 如果每次中断都保存,性能开销巨大
  • 正常运行时,ITS 表数据在内核中动态维护就足够了

设计原理:#

  • 运行时:ITS 表在内核中实时更新,支持中断路由
  • 迁移时:才需要将内核中的表数据”快照”到客户机内存

4. 实际的时序对比#

正常运行时的中断处理(高频):#

设备中断 -> ITS内核处理 -> vCPU接收中断
(每秒可能数千次,无表保存操作)

迁移时的表保存(低频):#

用户发起迁移 -> VM停止 -> 保存ITS表 -> 迁移RAM -> 迁移完成
(整个VM生命周期可能只发生几次)

5. 代码证据#

在 ITS 的中断处理相关代码中,你不会找到表保存操作:

// hw/intc/arm_gicv3_its_kvm.c:75-90
static void vm_change_state_handler(void *opaque, bool running, RunState state)
{
    GICv3ITSState *s = (GICv3ITSState *)opaque;
    Error *err = NULL;

    if (running) {
        return;  // VM运行时什么都不做
    }

    // 只有当VM停止时才保存表
    kvm_device_access(s->dev_fd, KVM_DEV_ARM_VGIC_GRP_CTRL,
                      KVM_DEV_ARM_ITS_SAVE_TABLES, NULL, true, &err);
}
// 中断到达 -> ITS处理 -> 更新内核中的ITS表 -> 投递给vCPU
1. 设备发送MSI中断到ITS
2. ITS查找Device Table找到设备
3. ITS查找ITT(Interrupt Translation Table)找到中断映射
4. ITS查找Collection Table找到目标CPU
5. 投递中断给目标vCPU
// VM停止 -> 保存所有ITS表到客户机内存 -> 用于迁移
1. VM状态变为停止
2. 触发vm_change_state_handler
3. 调用KVM_DEV_ARM_ITS_SAVE_TABLES
4. 内核将所有ITS表保存到客户机RAM
设备中断 -> ITS内核处理 -> vCPU接收中断
(每秒可能数千次,无表保存操作)
用户发起迁移 -> VM停止 -> 保存ITS表 -> 迁移RAM -> 迁移完成
(整个VM生命周期可能只发生几次)

可以看到,KVM_DEV_ARM_ITS_SAVE_TABLES 只在 vm_change_state_handler 中被调用,这个函数只在 VM 状态变化时触发。

6. 总结#

  • 错误理解:每次 ITS 中断都会保存表 ❌
  • 正确理解:只有 VM 停止时才保存表 ✅

具体区别:

  • 中断处理:实时、高频、只更新内核中的 ITS 表
  • 表保存:低频、只在迁移/停机时、将表数据复制到客户机 RAM

这种设计既保证了运行时的性能,又满足了迁移时的状态保存需求。


User

ITS 表客户机 os 里面本身不就有一张吗


Cursor

非常好的问题!你说得对,这涉及到 ITS 表的多层架构。让我详细解释:

ITS 表的多层存在#

1. 客户机 OS 中的 ITS 表(Guest OS 视角)#

客户机 OS 确实维护着自己的 ITS 表,包括:

// 客户机OS中的ITS驱动维护的表:
- Device Table: 客户机认为的设备到ITT的映射
- Collection Table: 客户机认为的中断集合到CPU的映射  
- Interrupt Translation Tables (ITT): 客户机认为的EventID到INTID的映射

2. KVM 内核中的 ITS 表(Hypervisor 视角)#

但是,KVM 还需要维护虚拟化版本的这些表:

// KVM内核中维护的虚拟ITS表:
- 虚拟Device Table: 实际的设备映射,可能与客户机不同
- 虚拟Collection Table: 实际的CPU分配,需要映射到物理CPU
- 虚拟ITT: 实际的中断路由,需要考虑虚拟化开销

3. 为什么需要两套表?#

// 客户机OS中的ITS驱动维护的表:
- Device Table: 客户机认为的设备到ITT的映射
- Collection Table: 客户机认为的中断集合到CPU的映射  
- Interrupt Translation Tables (ITT): 客户机认为的EventID到INTID的映射
// KVM内核中维护的虚拟ITS表:
- 虚拟Device Table: 实际的设备映射,可能与客户机不同
- 虚拟Collection Table: 实际的CPU分配,需要映射到物理CPU
- 虚拟ITT: 实际的中断路由,需要考虑虚拟化开销

让我查看 ITS 虚拟化的相关代码来解释这个问题:

原因分析:#

A. 虚拟化层的需求

// 1. 地址转换:客户机物理地址 -> 宿主机物理地址
// 2. CPU映射:虚拟CPU ID -> 物理CPU ID  
// 3. 中断号映射:客户机中断号 -> 宿主机中断号
// 4. 安全隔离:防止客户机访问不属于它的资源

B. 迁移的需求

// 客户机OS的表:客户机自己管理,在客户机内存中
// KVM的表:需要在迁移时特殊处理,包含虚拟化映射信息

4. 两套表的关系#

客户机 OS 的 ITS 表:#

  • 位置:客户机内存中
  • 内容:客户机视角的设备和中断映射
  • 管理者:客户机 OS 的 ITS 驱动
  • 特点:客户机认为这是”真实”的硬件表

KVM 的虚拟 ITS 表:#

  • 位置:宿主机内核内存中
  • 内容:实际的虚拟化映射关系
  • 管理者:KVM 的 vGIC-ITS 模块
  • 特点:包含真实的物理资源映射

5. 举个具体例子#

假设客户机 OS 要配置一个中断:

// 客户机OS视角:
Device ID: 0x1000 
Event ID: 0x20
Target CPU: vCPU-2 (客户机的CPU-2)
LPI ID: 8192

// 客户机在自己的ITS表中记录:
Device Table[0x1000] -> 指向客户机内存中的ITT
ITT[Event 0x20] -> LPI 8192, Collection 2  
Collection Table[2] -> vCPU-2

// 但KVM需要进行转换:
KVM的虚拟ITS表中记录:
Device 0x1000 -> 实际对应宿主机的PCI设备X
Event 0x20 -> 需要路由到物理CPU-5(vCPU-2运行在物理CPU-5上)
LPI 8192 -> 可能映射到不同的物理中断号

6. 迁移时的特殊处理#

这就解释了为什么迁移时需要特殊的表保存操作:

// 迁移前:
vm_change_state_handler() {
    // 将KVM内核中的虚拟ITS表状态
    // 同步回客户机内存中的表
    KVM_DEV_ARM_ITS_SAVE_TABLES
}

// 目的:确保客户机内存中的ITS表反映了
// 当前KVM中实际的中断路由状态

7. 为什么不直接用客户机的表?#

安全性:

  • 客户机不能直接控制物理中断路由
  • 需要 KVM 进行地址和权限检查

隔离性:

  • 多个客户机不能互相干扰
  • 物理资源需要虚拟化层管理

性能:

  • KVM 可以优化中断路由路径
  • 避免客户机每次都陷入到 hypervisor

8. 总结#

你的理解是对的,客户机确实有自己的 ITS 表。但是:

  1. 客户机 ITS 表:客户机 OS 管理,存储客户机视角的映射
  2. KVM 虚拟 ITS 表:KVM 管理,存储实际的物理映射
  3. 迁移时同步:将 KVM 的状态同步回客户机表,确保迁移后状态一致

这种双层设计是虚拟化的典型模式:客户机看到的是虚拟视图,hypervisor 维护真实的物理映射。

// 1. 地址转换:客户机物理地址 -> 宿主机物理地址
// 2. CPU映射:虚拟CPU ID -> 物理CPU ID  
// 3. 中断号映射:客户机中断号 -> 宿主机中断号
// 4. 安全隔离:防止客户机访问不属于它的资源
// 客户机OS的表:客户机自己管理,在客户机内存中
// KVM的表:需要在迁移时特殊处理,包含虚拟化映射信息
// 1. 地址转换:客户机物理地址 -> 宿主机物理地址
// 2. CPU映射:虚拟CPU ID -> 物理CPU ID  
// 3. 中断号映射:客户机中断号 -> 宿主机中断号
// 4. 安全隔离:防止客户机访问不属于它的资源
// 客户机OS的表:客户机自己管理,在客户机内存中
// KVM的表:需要在迁移时特殊处理,包含虚拟化映射信息
// 客户机OS视角:
Device ID: 0x1000 
Event ID: 0x20
Target CPU: vCPU-2 (客户机的CPU-2)
LPI ID: 8192

// 客户机在自己的ITS表中记录:
Device Table[0x1000] -> 指向客户机内存中的ITT
ITT[Event 0x20] -> LPI 8192, Collection 2  
Collection Table[2] -> vCPU-2

// 但KVM需要进行转换:
KVM的虚拟ITS表中记录:
Device 0x1000 -> 实际对应宿主机的PCI设备X
Event 0x20 -> 需要路由到物理CPU-5(vCPU-2运行在物理CPU-5上)
LPI 8192 -> 可能映射到不同的物理中断号
ITS表保存操作触发时机
https://scudays.github.io/posts/icsas/its-表保存/its表保存操作触发时机/
作者
Days
发布于
2025-09-01
许可协议
CC BY-NC-SA 4.0