在64位Arm架构中,地址转换机制是内存管理单元(MMU)的核心功能,它通过多级页表将程序使用的虚拟地址(VA)转换为物理地址(PA)。AArch64架构特别设计了两阶段地址转换机制(Stage 1和Stage 2),为虚拟化环境提供了硬件级支持。让我们先看一个典型的转换流程:
c复制// 两阶段地址转换伪代码示例
AddressDescriptor FullTranslateWithTag(vaddress, acctype, iswrite, wasaligned, size, iswritevalidcap) {
// 第一阶段转换
S1 = FirstStageTranslateWithTag(vaddress, acctype, iswrite, wasaligned, size, iswritevalidcap);
// 如果启用了第二阶段转换且第一阶段未出错
if !IsFault(S1) && HasS2Translation() {
result = SecondStageTranslate(S1, vaddress, acctype, iswrite, wasaligned, s2fs1walk, size, hwupdatewalk, iswritevalidcap);
} else {
result = S1;
}
return result;
}
第一阶段转换(S1)由操作系统控制,将虚拟地址(VA)转换为中间物理地址(IPA)。在虚拟化场景中,第二阶段转换(S2)由hypervisor管理,将IPA转换为最终物理地址(PA)。这种设计实现了:
关键控制寄存器包括:
TranslationTableWalk函数是地址转换的核心,其处理流程如下:
c复制TLBRecord TranslationTableWalk(ipaddress, vaddress, acctype, iswrite, secondstage, s2fs1walk, size) {
// 设置初始参数
if !secondstage {
grainsize = Log2(页大小); // 4KB/16KB/64KB
stride = grainsize - 3; // 每级页表索引位数
level = 起始层级; // 通常为0或1
}
// 遍历页表层级
do {
// 计算当前层级的页表项索引
index = ExtractBits(inputaddr, addrselecttop, addrselectbottom);
descaddr = baseaddress | (index << 3); // 每个描述符8字节
// 读取描述符(可能触发S2转换)
desc = ReadDescriptor(descaddr);
if 是块或页描述符 {
提取输出地址和属性;
break;
} else { // 表描述符
baseaddress = 下一级表基地址;
level++;
}
} while (true);
// 设置返回结果
result.addrdesc.paddress = 输出地址;
result.addrdesc.memattrs = 内存属性;
return result;
}
在转换过程中会进行多项安全检查:
访问权限检查(AP[2:1]位):
AP=01表示只允许特权写执行权限控制(UXN/PXN位):
内存类型验证(MemType):
当访问标记为MemType_Device的内存区域时,硬件会执行额外检查:
c复制// 设备内存指令获取检查
if (!IsFault(S1.addrdesc) && S1.addrdesc.memattrs.memtype == MemType_Device && acctype == AccType_IFETCH) {
S1.addrdesc = InstructionDevice(S1.addrdesc, vaddress, ipaddress, S1.level, acctype, iswrite, secondstage, s2fs1walk);
}
设备内存的特殊性体现在:
权限检查在两个转换阶段分别进行:
S1权限检查:
S2权限检查:
CheckS2Permissionc复制// 第二阶段权限检查
if !IsFault(S2.addrdesc) {
S2.addrdesc.fault = CheckS2Permission(S2.perms, vaddress, ipaddress, S2.level, acctype, iswrite, s2fs1walk, hwupdatewalk);
}
现代Arm处理器支持自动更新页表描述符中的访问标志:
c复制// 访问标志检查与更新
if desc.AF == 0 { // Access Flag位为0
if !update_AF {
return AccessFlagFault(); // 触发异常
} else {
result.descupdate.AF = TRUE; // 需要硬件更新
}
}
硬件更新机制包括:
虽然伪代码中未显式描述TLB,但实际实现需要考虑:
TLB结构:
失效操作:
TLBI指令显式失效条目推测预取:
Hypervisor通过以下机制控制S2转换:
VTCR_EL2配置:
VTTBR_EL2:
c复制// 第二阶段转换启用检查
s2_enabled = HCR_EL2.VM == '1' || HCR_EL2.DC == '1';
if s2_enabled {
ipaddress = S1.paddress; // S1输出作为S2输入
S2 = TranslationTableWalk(ipaddress, vaddress, acctype, iswrite, TRUE, s2fs1walk, size);
}
当S1页表遍历本身需要地址转换时(即S1页表位于guest物理内存中),会触发嵌套转换:
c复制if secondstage || !HasS2Translation() {
descaddr2 = descaddr; // 直接使用物理地址
} else {
// 对S1页表地址进行S2转换
descaddr2 = SecondStageWalk(descaddr, vaddress, acctype, iswrite, 8, hwupdatewalk);
}
这种设计确保了:
AArch64实现了细粒度的权限控制:
特权级别:
权限位组合:
属性从各级页表继承而来:
c复制// 第一阶段属性继承
if !secondstage {
result.perms.xn = xn OR xn_table; // 合并当前和上级XN
result.perms.ap[2] = ap[2] OR ap_table[1]; // 合并AP
if !singlepriv {
result.perms.pxn = pxn OR pxn_table;
}
}
这种继承机制允许:
地址转换可能触发多种异常:
Translation Fault:
Permission Fault:
Alignment Fault:
错误处理采用分层机制:
c复制// 典型错误处理伪代码
if !basefound || disabled {
return TranslationFault(ipaddress, level, acctype, iswrite, secondstage, s2fs1walk);
}
if desc.AF == 0 && !update_AF {
return AccessFlagFault(ipaddress, level, acctype, iswrite, secondstage, s2fs1walk);
}
错误信息包含:
通过合理配置块描述符减少TLB压力:
| 页大小 | 颗粒度 | 最大块大小 | 适用场景 |
|---|---|---|---|
| 4KB | 12 | 1GB | 通用计算 |
| 16KB | 14 | 32MB | 移动设备 |
| 64KB | 16 | 512MB | 服务器 |
配置建议:
硬件特性利用:
TLBI RANGE:高效失效地址范围PRFM指令预取页表关键实现机制:
DMA访问处理流程:
关键防护点:
在分析这些机制时,我经常通过QEMU模拟器配合GDB单步调试观察转换过程。一个实用的技巧是在关键函数设置断点,比如在TranslationTableWalk入口处打印输入参数,在返回时检查输出描述符。这比单纯阅读手册更能深入理解硬件行为。