基本逻辑
Stage2页表使用一个通用walker框架+回调机制,通过kvm_pgtable_walk()递归遍历每一级
页表,在不同的访问点调用回调函数完成映射。kvm_pgtable_stage2_map是入口,它复用
stage2_map_walker作为回调。
我们这里重点分析页表映射的流程,其中核心就是使用page table walker,一定要注意哪里
是框架逻辑,哪里是stage2页表map的具体业务逻辑。ARM KVM里还有很多使用walker的业务
逻辑,比如,stage2页表unmap、改页表属性、配置页表young属性、stage2 flush、stage2 split、
stage2 destroy range,dump stage页表等,所有这些都是对给定IPA/size操作对应stage2
页表的行为。
关键数据结构
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| struct kvm_pgtable { u32 ia_bits; s8 start_level; kvm_pteref_t pgd; struct kvm_pgtable_mm_ops *mm_ops; enum kvm_pgtable_stage2_flags flags; kvm_pgtable_force_pte_cb_t force_pte_cb; struct kvm_s2_mmu *mmu; };
struct kvm_pgtable_walker { const kvm_pgtable_visitor_fn_t cb; void * const arg; const enum kvm_pgtable_walk_flags flags; };
struct kvm_pgtable_visit_ctx { kvm_pte_t *ptep; kvm_pte_t old; void *arg; struct kvm_pgtable_mm_ops *mm_ops; u64 start, addr, end; s8 level; enum kvm_pgtable_walk_flags flags; };
struct stage2_map_data { const u64 phys; kvm_pte_t attr; u8 owner_id; struct kvm_s2_mmu *mmu; void *memcache; bool force_pte; bool annotation; };
|
页表级数与block支持
页表的输入决定页表的级数,stage1上是VA,stage2上就是IPA。IPA是虚机的物理地址,所以
stage2上就是虚机物理地址位数决定S2页表级数。
例如,对于一个4KB基础页,48bit的IPA,页表结构大概如下:
1 2 3 4 5 6 7 8 9 10 11
| 63 48 47 39 38 30 29 21 20 12 11 0 ┌────────┬────────┬────────┬────────┬────────┬─────────┐ │ IGNORE │ L0[9] │ L1[9] │ L2[9] │ L3[9] │ OFF[12]│ └────────┴───┬────┴───┬────┴───┬────┴───┬────┴───┬─────┘ │ │ │ │ │ TTBR ──► PGD ───► PUD ───► PMD ───► PTE │ │ │ │ │ │ └────────┴────────┴────────┴────────┘ │ ▼ PA[47:0]
|
如图,它是一个4级页表,每一级页表项的索引来自对应的VA/IPA域段。ARM spec上对于一个
页表项,有三个概念page/block/table,page是指叶子节点,比如这里的PTE就是一个page,
block是指中间的页表项,但是block已经没有再下一级,所以block就是我们一般说的传统
大页(这里不包括contig hugetlb),table是中间的页表项,但是它还有下一级页表项。
PTE属性位(stage-2)
| 位域 |
宏 |
含义 |
| bit[0] |
KVM_PTE_VALID |
PTE有效位 |
| bit[1] |
KVM_PTE_TYPE |
0=BLOCK, 1=PAGE/TABLE |
| bit[5:2] |
KVM_PTE_LEAF_ATTR_LO_S2_MEMATTR |
内存属性 (Device/NC/Normal) |
| bit[6] |
KVM_PTE_LEAF_ATTR_LO_S2_S2AP_R |
读权限 |
| bit[7] |
KVM_PTE_LEAF_ATTR_LO_S2_S2AP_W |
写权限 |
| bit[9:8] |
KVM_PTE_LEAF_ATTR_LO_S2_SH |
可共享性 |
| bit[10] |
KVM_PTE_LEAF_ATTR_LO_S2_AF |
Access Flag |
| bit[52] |
KVM_PTE_LEAF_ATTR_CONT |
连续 PTE 提示 |
| bit[54:53] |
KVM_PTE_LEAF_ATTR_HI_S2_XN |
执行权限 |
| bit[58:55] |
KVM_PTE_LEAF_ATTR_HI_SW |
软件位(保存prot信息) |
| bit[10] |
KVM_INVALID_PTE_LOCKED |
BBM 锁标记(仅在valid=0时有效) |
完整调用链分析
以stage2缺页处理流程分析下stage2 page table walk的逻辑。user_mem_abort的结尾进行
stage2 map的建立,这个时候已经有IPA、PA、size,这里的逻辑只是建立对应的页表。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| kvm_pgtable_stage2_map(struct kvm_pgtable *pgt, ...)
+-> kvm_pgtable_walk(pgt, addr, size, &walker)
+-> _kvm_pgtable_walk(pgt, &walk_data)
+-> __kvm_pgtable_walk(data, pgt->mm_ops, pteref, pgt->start_level)
|
分析walk核心函数__kvm_pgtable_walk。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80
|
__kvm_pgtable_walk +-> for (idx = kvm_pgtable_idx(data, level); idx < PTRS_PER_PTE; ++idx) kvm_pteref_t pteref = &pgtable[idx];
if (data->addr >= data->end) break;
__kvm_pgtable_visit(data, mm_ops, pteref, level);
+-> ret = kvm_pgtable_visitor_cb(data, &ctx, KVM_PGTABLE_WALK_TABLE_PRE) ... +-> stage2_map_walk_table_pre +-> stage2_leaf_mapping_allowed +-> stage2_map_walker_try_leaf
+-> childp = mm_ops->zalloc_page(data->memcache) stage2_try_break_pte(ctx, data->mmu) new = kvm_init_table_pte(childp, mm_ops) stage2_make_pte(ctx, new) +-> ctx.old = READ_ONCE(*ptep) table = kvm_pte_table(ctx.old, level) +-> if (!kvm_pgtable_walk_continue(data->walker, ret)) goto out;
+-> if (!table) { data->addr = ALIGN_DOWN(data->addr, kvm_granule_size(level)); data->addr += kvm_granule_size(level); goto out; }
+-> childp = (kvm_pteref_t)kvm_pte_follow(ctx.old, mm_ops)
+-> ret = __kvm_pgtable_walk(data, mm_ops, childp, level + 1)
+-> if (!kvm_pgtable_walk_continue(data->walker, ret)) goto out;
+-> kvm_pgtable_visitor_cb(data, &ctx, KVM_PGTABLE_WALK_TABLE_POST)
|
Break-Before-Make机制
ARM架构要求修改PTE映射时遵守BBM序列。也就是改pte的顺序应该是:
锁页表-> invalid页表 -> TLBI -> 更新页表 -> 解锁页表。上面stage2_map_walker_try_leaf
以及stage2_map_walk_leaf涉及PTE修改的地方都需要遵守这个流程,下面具体看下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| // 原子写入KVM_INVALID_PTE_LOCKED(BIT(10)置1)替换旧PTE,实际上是一起做了前两步。 stage2_try_set_pte(ctx, KVM_INVALID_PTE_LOCKED)
// 有些场景是可以跳过BBM的。 if (!kvm_pgtable_walk_skip_bbm_tlbi(ctx)) // 如有修改的是一个table,那么这个table覆盖的IPA都要做TLBI。 if (kvm_pte_table(ctx->old, ctx->level)) kvm_tlb_flush_vmid_range(mmu, addr, size); // 如果修改的是page/block,只有对应的page/block做TLBI就好。 else if (kvm_pte_valid(ctx->old)) kvm_call_hyp(__kvm_tlb_flush_vmid_ipa, mmu, ctx->addr, ctx->level);
// 释放page的引用计数。 put_page()
// 构建新页表项。 new = kvm_init_table_pte(childp, mm_ops) // 实际上一起做了后面两步。 stage2_make_pte(ctx, new)
|