鸿蒙轻内核A核源码分析系列五(3) 虚实映射
5、虚实映射解除函数LOS_ArchMmuUnmap
虚实映射解除函数LOS_ArchMmuUnmap
解除进程空间虚拟地址区间与物理地址区间的映射关系。 ⑴处函数OsGetPte1
用于获取指定虚拟地址对应的L1页表项地址。⑵处计算需要解除的无效映射的数量。如果页表描述符映射类型为Section,并且映射的数量超过256,则执行⑶解除映射Section。如果页表描述符映射类型为Page Table,则执行⑷先解除二级页表映射,然后解除一级页表映射,涉及的2个函数后文详细分析。⑹处函数使TLB失效,涉及些cp15寄存器和汇编,后续再分析。
STATUS_T LOS_ArchMmuUnmap(LosArchMmu *archMmu, VADDR_T vaddr, size_t count)
{
PTE_T l1Entry;
INT32 unmapped = 0;
UINT32 unmapCount = 0;
while (count > 0) {
⑴ l1Entry = OsGetPte1(archMmu->virtTtb, vaddr);
if (OsIsPte1Invalid(l1Entry)) {
⑵ unmapCount = OsUnmapL1Invalid(&vaddr, &count);
} else if (OsIsPte1Section(l1Entry)) {
if (MMU_DESCRIPTOR_IS_L1_SIZE_ALIGNED(vaddr) && count >= MMU_DESCRIPTOR_L2_NUMBERS_PER_L1) {
⑶ unmapCount = OsUnmapSection(archMmu, &vaddr, &count);
} else {
LOS_Panic("%s %d, unimplemented\n", __FUNCTION__, __LINE__);
}
} else if (OsIsPte1PageTable(l1Entry)) {
⑷ unmapCount = OsUnmapL2PTE(archMmu, vaddr, &count);
OsTryUnmapL1PTE(archMmu, vaddr, OsGetPte2Index(vaddr) + unmapCount,
MMU_DESCRIPTOR_L2_NUMBERS_PER_L1 - unmapCount);
⑸ vaddr += unmapCount << MMU_DESCRIPTOR_L2_SMALL_SHIFT;
} else {
LOS_Panic("%s %d, unimplemented\n", __FUNCTION__, __LINE__);
}
unmapped += unmapCount;
}
⑹ OsArmInvalidateTlbBarrier();
return unmapped;
}
5.1 函数OsUnmapL1Invalid
函数OsUnmapL1Invalid
用于解除无效的映射,会把虚拟地址增加,映射的数量减少。⑴处的MMU_DESCRIPTOR_L1_SMALL_SIZE
表示1MiB大小,*vaddr % MMU_DESCRIPTOR_L1_SMALL_SIZE
对1MiB取余,向右偏移12位>>MMU_DESCRIPTOR_L2_SMALL_SHIFT
表示大小转换为内存页数量。(为啥相减TODO?)。⑵处把解除映射的内存页数量左移12位转换为地址长度,然后更新虚拟地址。⑶处减去已经解除映射的数量。
STATIC INLINE UINT32 OsUnmapL1Invalid(vaddr_t *vaddr, UINT32 *count)
{
UINT32 unmapCount;
⑴ unmapCount = MIN2((MMU_DESCRIPTOR_L1_SMALL_SIZE - (*vaddr % MMU_DESCRIPTOR_L1_SMALL_SIZE)) >>
MMU_DESCRIPTOR_L2_SMALL_SHIFT, *count);
⑵ *vaddr += unmapCount << MMU_DESCRIPTOR_L2_SMALL_SHIFT;
⑶ *count -= unmapCount;
return unmapCount;
}
5.2 函数OsUnmapSection
函数OsUnmapSection
用于接触一级页表的Section映射。⑴处把虚拟地址对应的页表项基地址设置为0。⑵处使TLB寄存器失效,⑶更新虚拟地址和映射数量。
STATIC UINT32 OsUnmapSection(LosArchMmu *archMmu, vaddr_t *vaddr, UINT32 *count)
{
⑴ OsClearPte1(OsGetPte1Ptr((PTE_T *)archMmu->virtTtb, *vaddr));
⑵ OsArmInvalidateTlbMvaNoBarrier(*vaddr);
⑶ *vaddr += MMU_DESCRIPTOR_L1_SMALL_SIZE;
*count -= MMU_DESCRIPTOR_L2_NUMBERS_PER_L1;
return MMU_DESCRIPTOR_L2_NUMBERS_PER_L1;
}
5.3 函数OsUnmapL2PTE
函数OsUnmapL2PTE
用于。⑴处先调用函数OsGetPte1
计算虚拟地址对应页表项,然后调用函数OsGetPte2BasePtr
计算二级页表基地址。⑵处获取虚拟地址的二级页表项索引。⑶计算需要解除映射的数量(为啥取最小值 TODO)。⑷处依次解除各个二级页表的映射。⑸使TLB失效。
STATIC UINT32 OsUnmapL2PTE(const LosArchMmu *archMmu, vaddr_t vaddr, UINT32 *count)
{
UINT32 unmapCount;
UINT32 pte2Index;
PTE_T *pte2BasePtr = NULL;
⑴ pte2BasePtr = OsGetPte2BasePtr(OsGetPte1((PTE_T *)archMmu->virtTtb, vaddr));
if (pte2BasePtr == NULL) {
LOS_Panic("%s %d, pte2 base ptr is NULL\n", __FUNCTION__, __LINE__);
}
⑵ pte2Index = OsGetPte2Index(vaddr);
⑶ unmapCount = MIN2(MMU_DESCRIPTOR_L2_NUMBERS_PER_L1 - pte2Index, *count);
/* unmap page run */
⑷ OsClearPte2Continuous(&pte2BasePtr[pte2Index], unmapCount);
/* invalidate tlb */
⑸ OsArmInvalidateTlbMvaRangeNoBarrier(vaddr, unmapCount);
*count -= unmapCount;
return unmapCount;
}
6 其他函数
6.1 映射属性修改函数LOS_ArchMmuChangeProt
函数LOS_ArchMmuChangeProt
用于修改进程空间虚拟地址区间的映射属性,其中参数archMmu
为进程空间的MMU信息,vaddr
为虚拟地址,count
为映射的页数,flags
为映射使用的新标签属性信息。⑴处对参数进行校验,⑵处查询虚拟地址映射的物理地址,如果没有映射则执行⑶把虚拟地址增加1个内存页大小继续修改下一个内存页的属性。⑷处先解除当前内存页的映射,然后执行⑸使用新的映射属性重新映射,⑹处虚拟地址增加1个内存页大小继续修改下一个内存页的属性。
STATUS_T LOS_ArchMmuChangeProt(LosArchMmu *archMmu, VADDR_T vaddr, size_t count, UINT32 flags)
{
STATUS_T status;
PADDR_T paddr = 0;
⑴ if ((archMmu == NULL) || (vaddr == 0) || (count == 0)) {
VM_ERR("invalid args: archMmu %p, vaddr %p, count %d", archMmu, vaddr, count);
return LOS_NOK;
}
while (count > 0) {
⑵ count--;
status = LOS_ArchMmuQuery(archMmu, vaddr, &paddr, NULL);
if (status != LOS_OK) {
⑶ vaddr += MMU_DESCRIPTOR_L2_SMALL_SIZE;
continue;
}
⑷ status = LOS_ArchMmuUnmap(archMmu, vaddr, 1);
if (status < 0) {
VM_ERR("invalid args:aspace %p, vaddr %p, count %d", archMmu, vaddr, count);
return LOS_NOK;
}
⑸ status = LOS_ArchMmuMap(archMmu, vaddr, paddr, 1, flags);
if (status < 0) {
VM_ERR("invalid args:aspace %p, vaddr %p, count %d",
archMmu, vaddr, count);
return LOS_NOK;
}
⑹ vaddr += MMU_DESCRIPTOR_L2_SMALL_SIZE;
}
return LOS_OK;
}
6.2 映射转移函数LOS_ArchMmuMove
函数LOS_ArchMmuMove
用于将进程空间一个虚拟地址区间的映射关系转移至另一块未使用的虚拟地址区间重新做映射,其中参数oldVaddr
为老的虚拟地址,newVaddr
为新的虚拟内存地址,flags
在重新映射时可以更改映射属性信息。⑴处先查询老的虚拟地址映射的物理内存。如果没有映射关系,把新旧虚拟内存都增加一个内存页的大小,⑵处取消老的虚拟地址的映射,⑶处使用新的虚拟内存重新映射到查询到的物理内存地址。⑷把新旧虚拟内存都增加一个内存页的大小,继续处理下一个内存页。
STATUS_T LOS_ArchMmuMove(LosArchMmu *archMmu, VADDR_T oldVaddr, VADDR_T newVaddr, size_t count, UINT32 flags)
{
STATUS_T status;
PADDR_T paddr = 0;
if ((archMmu == NULL) || (oldVaddr == 0) || (newVaddr == 0) || (count == 0)) {
VM_ERR("invalid args: archMmu %p, oldVaddr %p, newVddr %p, count %d",
archMmu, oldVaddr, newVaddr, count);
return LOS_NOK;
}
while (count > 0) {
count--;
⑴ status = LOS_ArchMmuQuery(archMmu, oldVaddr, &paddr, NULL);
if (status != LOS_OK) {
oldVaddr += MMU_DESCRIPTOR_L2_SMALL_SIZE;
newVaddr += MMU_DESCRIPTOR_L2_SMALL_SIZE;
continue;
}
// we need to clear the mapping here and remain the phy page.
⑵ status = LOS_ArchMmuUnmap(archMmu, oldVaddr, 1);
if (status < 0) {
VM_ERR("invalid args: archMmu %p, vaddr %p, count %d",
archMmu, oldVaddr, count);
return LOS_NOK;
}
⑶ status = LOS_ArchMmuMap(archMmu, newVaddr, paddr, 1, flags);
if (status < 0) {
VM_ERR("invalid args:archMmu %p, old_vaddr %p, new_addr %p, count %d",
archMmu, oldVaddr, newVaddr, count);
return LOS_NOK;
}
⑷ oldVaddr += MMU_DESCRIPTOR_L2_SMALL_SIZE;
newVaddr += MMU_DESCRIPTOR_L2_SMALL_SIZE;
}
return LOS_OK;
}
小结
本文介绍了MMU虚实映射的基本概念,运行机制,分析了映射初始化、映射查询、映射虚拟内存和物理内存,解除虚实映射,更改映射属性,重新映射等常用接口的代码。感谢阅读,有什么问题,请留言。
- 点赞
- 收藏
- 关注作者
评论(0)