这段时间在看Fuchsia的代码,发现有很多去自己去做虚拟地址与物理地址映射的代码,一直觉得很奇怪,之前一直觉得MMU做虚拟地址与物理地址的映射,只是构建完成相关的映射表格,在x86上把这个表格指给cr3寄存器,后面开启虚拟地址后,程序访问的都是虚拟地址空间,MMU把虚拟地址转成物理地址,然后进行相关的寻址操作就欧克,至于这个映射表格的建立,我一直以为是一次性的,或者产生缺页的时候由MMU去维护更新就好了,刚好趁着这个机会,边读fuchsia的代码边理解下怎么去维护MMU的虚拟地址与物理地址的映射关系
为了理解什么是MMU,可能先得捋清楚虚拟内存与物理内存的区别。
这个很好理解,比如你买了台电脑,里面装了2跟8G的内存条,那么这个物理内存的大小就是16G,而物理地址空间,则是用来描述物理地址的范围的,这个跟芯片平台有比较大的关系,如果目前在intel X86的芯片上,对物理内存大小的支持分别如下:
目前fuchsia只支持intel 64位的CPU,所以不用关注前面三种的物理地址类型,只需要关注x86-64下的长模式就行,在这种模式下采用的是64位物理地址,但是只要低48位是有效地址,高16位是相关的标志位。至于为啥是48位有效地址,这个咱们就不关注了,反正是intel就是这么设计的。
虚拟内存这个也好理解,意思就是这个内存所指向的数据不是真实可用的嘛,这个地址是需要一系列的转换才能转换成真实可用的数据(物理内存上的数据),这一系列的转换包括逻辑地址经过通过分段机制转成线性地址,线性地址再经过分页机制再转成了物理地址,而这个分页机制则是在我们这篇文章里需要重点探讨的。这里的逻辑地址与线性地址都可以认为是虚拟地址。
虚拟内存的存在的由来是,主要要两个原因:
还有一个关注点就是,比如x86 64位是机器上虚拟地址空间往往也是48位的有效地址,那么可以寻址的空间大小就是256TB,物理地址也是采用48位的有效地址,物理地址的寻址空间大小也是256TB,但是有个问题:谁的机器上会有256TB的内存?目前主流的机器基本上是16G内存。怎么把256TB的虚拟地址映射到16G的真实物理地址上?这也是MMU需要去做的。
正因为上面所讲的目前计算机体系中有物理内存与虚拟内存的存在,同是这就就引入了一个虚拟地址与物理地址的转换问题。如前面讲到的,经过分段机制之后的线性地址才会经过分页机制去转成最终的物理地址,这就是MMU所干的活:
虚拟地址------》物理地址
分段机制,就不在这里赘述了,重点讲讲分页机制。
简单用文字描述下:
在上面,大致介绍了下虚拟内存与物理内存,已经虚拟内存与物理内存映射的基本概念,接下来我们看看在X86平台上大致是怎样的一种操作方式。
如上面提到的在X86平台上这个映射表可以直接叫PML4表,另外如我们在上一遍文章Fuchsia X86 kernel启动代码分析中提到的,在Start.S中我们可以看到这样的代码:
/*
* Set PGE to enable global kernel pages
*/
mov %cr4, %rax
or $(X86_CR4_PGE), %rax
mov %rax, %cr4
//将pml4放进cr3中,物理内存与虚拟内存映射生效
/* load the physical pointer to the top level page table */
mov $PHYS(pml4), %rax
mov %rax, %cr3
————————————————
版权声明:本文为CSDN博主「影子LEON」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/ljp1205/article/details/106231332
至于整个PML4表的创建过程,依然可以参考Fuchsia X86 kernel启动代码分析,在这个里面重点介绍了下,在这个阶段重点做了fuchsia kernel的地址空间的虚拟地址与物理地址映射。
今天我们这里重点介绍下后续这个PML4表格的更新,建立真实可用的映射关系。
在进入介绍怎么维护更新映射表之前,可能需要先大致捋一下在Fuchsia当中关于内存的一些简单概念:
void VmAspace::KernelAspaceInitPreHeap() TA_NO_THREAD_SAFETY_ANALYSIS {
// the singleton kernel address space
static VmAspace _kernel_aspace(KERNEL_ASPACE_BASE, KERNEL_ASPACE_SIZE, VmAspace::TYPE_KERNEL,
"kernel");
// the singleton dummy root vmar (used to break a reference cycle in
// Destroy())
static VmAddressRegionDummy dummy_vmar;
#if LK_DEBUGLEVEL > 1
_kernel_aspace.Adopt();
dummy_vmar.Adopt();
#endif
dummy_root_vmar = &dummy_vmar;
static VmAddressRegion _kernel_root_vmar(_kernel_aspace);
_kernel_aspace.root_vmar_ = fbl::AdoptRef(&_kernel_root_vmar);
zx_status_t status = _kernel_aspace.Init();
ASSERT(status == ZX_OK);
// save a pointer to the singleton kernel address space
VmAspace::kernel_aspace_ = &_kernel_aspace;
aspaces.push_front(kernel_aspace_);
}
这个VmAspace的起始地址与大小分别是:
#define KERNEL_ASPACE_BASE 0xffffff8000000000UL // -512GB
#define KERNEL_ASPACE_SIZE 0x0000008000000000UL
也就是说起点是从512GB的位置开始,大小是64GB
zx_status_t status = aspace->RootVmar()->CreateSubVmar(
kernel_regions[0].base - aspace->RootVmar()->base(), kernel_region_size, 0,
VMAR_FLAG_CAN_MAP_SPECIFIC | VMAR_FLAG_SPECIFIC | VMAR_CAN_RWX_FLAGS, "kernel region vmar",
&kernel_region);
ASSERT(status == ZX_OK);
for (const auto& region : kernel_regions) {
ASSERT(IS_PAGE_ALIGNED(region.base));
dprintf(INFO,
"VM: reserving kernel region [%#" PRIxPTR ", %#" PRIxPTR ") flags %#x name '%s'\n",
region.base, region.base + region.size, region.arch_mmu_flags, region.name);
status =
kernel_region->ReserveSpace(region.name, region.base, region.size, region.arch_mmu_flags);
ASSERT(status == ZX_OK);
}
在Kernel VmAddressRegion预留了这么些部分的空间地址长度:
const ktl::array _kernel_regions = {
kernel_region{
.name = "kernel_code",
.base = (vaddr_t)__code_start,
.size = ROUNDUP((uintptr_t)__code_end - (uintptr_t)__code_start, PAGE_SIZE),
.arch_mmu_flags = ARCH_MMU_FLAG_PERM_READ | ARCH_MMU_FLAG_PERM_EXECUTE,
},
kernel_region{
.name = "kernel_rodata",
.base = (vaddr_t)__rodata_start,
.size = ROUNDUP((uintptr_t)__rodata_end - (uintptr_t)__rodata_start, PAGE_SIZE),
.arch_mmu_flags = ARCH_MMU_FLAG_PERM_READ,
},
kernel_region{
.name = "kernel_data",
.base = (vaddr_t)__data_start,
.size = ROUNDUP((uintptr_t)__data_end - (uintptr_t)__data_start, PAGE_SIZE),
.arch_mmu_flags = ARCH_MMU_FLAG_PERM_READ | ARCH_MMU_FLAG_PERM_WRITE,
},
kernel_region{
.name = "kernel_bss",
.base = (vaddr_t)__bss_start,
.size = ROUNDUP((uintptr_t)_end - (uintptr_t)__bss_start, PAGE_SIZE),
.arch_mmu_flags = ARCH_MMU_FLAG_PERM_READ | ARCH_MMU_FLAG_PERM_WRITE,
},
};
Kernel VmAspace 与Kernel VmAddressRegion的区别是,Kernel VmAspace描述的是整个kernel内存空间,而Kernel VmAddressRegion描述的是kernel image被装载进内存的空间与大小。
. = KERNEL_BASE;
PROVIDE_HIDDEN(__code_start = .);
/kernel/BUILD.gn:95: "KERNEL_BASE=$kernel_base"
if (current_cpu == "arm64") {
kernel_base = "0xffffffff00000000"
} else if (current_cpu == "x64") {
kernel_base = "0xffffffff80100000" # Has KERNEL_LOAD_OFFSET baked into it.
}
可以看到Kernel VmAddressRegion的base地址是0xffffffff80100000,而Kernel VmAspace的base地址是0xffffffff80000000
zx_status_t VmAddressRegion::ReserveSpace(const char* name, vaddr_t base, size_t size,
uint arch_mmu_flags) {
canary_.Assert();
if (!is_in_range(base, size)) {
return ZX_ERR_INVALID_ARGS;
}
size_t offset = base - base_;
// We need a zero-length VMO to pass into CreateVmMapping so that a VmMapping would be created.
// The VmMapping is already mapped to physical pages in start.S.
// We would never call MapRange on the VmMapping, thus the VMO would never actually allocate any
// physical pages and we would never modify the PTE except for the permission change bellow
// caused by Protect.
fbl::RefPtr<VmObject> vmo;
zx_status_t status = VmObjectPaged::Create(PMM_ALLOC_FLAG_ANY, 0u, 0, &vmo);
if (status != ZX_OK) {
return status;
}
vmo->set_name(name, strlen(name));
// allocate a region and put it in the aspace list
fbl::RefPtr<VmMapping> r(nullptr);
// Here we use permissive arch_mmu_flags so that the following Protect call would actually
// call arch_aspace().Protect to change the mmu_flags in PTE.
status = CreateVmMapping(
offset, size, 0, VMAR_FLAG_SPECIFIC, vmo, 0,
ARCH_MMU_FLAG_PERM_READ | ARCH_MMU_FLAG_PERM_WRITE | ARCH_MMU_FLAG_PERM_EXECUTE, name, &r);
if (status != ZX_OK) {
return status;
}
return r->Protect(base, size, arch_mmu_flags);
zx_status_t VmAddressRegion::CreateVmMapping(size_t mapping_offset, size_t size, uint8_t align_pow2,
uint32_t vmar_flags, fbl::RefPtr<VmObject> vmo,
uint64_t vmo_offset, uint arch_mmu_flags,
const char* name, fbl::RefPtr<VmMapping>* out) {
DEBUG_ASSERT(out);
LTRACEF("%p %#zx %#zx %x\n", this, mapping_offset, size, vmar_flags);
// Check that only allowed flags have been set
if (vmar_flags & ~(VMAR_FLAG_SPECIFIC | VMAR_FLAG_SPECIFIC_OVERWRITE | VMAR_CAN_RWX_FLAGS)) {
return ZX_ERR_INVALID_ARGS;
}
// Validate that arch_mmu_flags does not contain any prohibited flags
if (!is_valid_mapping_flags(arch_mmu_flags)) {
return ZX_ERR_ACCESS_DENIED;
}
// If size overflows, it'll become 0 and get rejected in
// CreateSubVmarInternal.
size = ROUNDUP(size, PAGE_SIZE);
// Make sure that vmo_offset is aligned and that a mapping of this size
// wouldn't overflow the vmo offset.
if (!IS_PAGE_ALIGNED(vmo_offset) || vmo_offset + size < vmo_offset) {
return ZX_ERR_INVALID_ARGS;
}
// If we're mapping it with a specific permission, we should allow
// future Protect() calls on the mapping to keep that permission.
if (arch_mmu_flags & ARCH_MMU_FLAG_PERM_READ) {
vmar_flags |= VMAR_FLAG_CAN_MAP_READ;
}
if (arch_mmu_flags & ARCH_MMU_FLAG_PERM_WRITE) {
vmar_flags |= VMAR_FLAG_CAN_MAP_WRITE;
}
if (arch_mmu_flags & ARCH_MMU_FLAG_PERM_EXECUTE) {
vmar_flags |= VMAR_FLAG_CAN_MAP_EXECUTE;
}
fbl::RefPtr<VmAddressRegionOrMapping> res;
zx_status_t status =
CreateSubVmarInternal(mapping_offset, size, align_pow2, vmar_flags, ktl::move(vmo),
vmo_offset, arch_mmu_flags, name, &res);
if (status != ZX_OK) {
return status;
}
// TODO(teisenbe): optimize this
*out = res->as_vm_mapping();
return ZX_OK;
}
4.X86PageTableBase:这个是X86平台上真正操作MMU的部分,里面有一堆的操作MMU 转换表的函数方法。
接下来我们通过追踪一下整个流程看看到底怎么更新MMU的PML4表格的:
前面讲到,当需要把虚拟地址映射到物理地址上的时候,会去创建VmMapping对象,而VmMapping会利用VmObject对象去做真实的物理内存分配。在这里我们不管具体怎么分配的内存,只关心分配到的物理内存如何与虚拟内存建立映射关系。
我们从VmMapping 对象的MapRange函数入手:
zx_status_t VmMapping::MapRange(size_t offset, size_t len, bool commit) {
canary_.Assert();
len = ROUNDUP(len, PAGE_SIZE);
if (len == 0) {
return ZX_ERR_INVALID_ARGS;
}
Guard<Mutex> aspace_guard{aspace_->lock()};
if (state_ != LifeCycleState::ALIVE) {
return ZX_ERR_BAD_STATE;
}
LTRACEF("region %p, offset %#zx, size %#zx, commit %d\n", this, offset, len, commit);
DEBUG_ASSERT(object_);
if (!IS_PAGE_ALIGNED(offset) || !is_in_range(base_ + offset, len)) {
return ZX_ERR_INVALID_ARGS;
}
// precompute the flags we'll pass GetPageLocked
// if committing, then tell it to soft fault in a page
uint pf_flags = VMM_PF_FLAG_WRITE;
if (commit) {
pf_flags |= VMM_PF_FLAG_SW_FAULT;
}
// grab the lock for the vmo
Guard<Mutex> object_guard{object_->lock()};
// set the currently faulting flag for any recursive calls the vmo may make back into us.
DEBUG_ASSERT(!currently_faulting_);
currently_faulting_ = true;
auto ac = fbl::MakeAutoCall([&]() { currently_faulting_ = false; });
// iterate through the range, grabbing a page from the underlying object and
// mapping it in
size_t o;
VmMappingCoalescer coalescer(this, base_ + offset);
for (o = offset; o < offset + len; o += PAGE_SIZE) {
uint64_t vmo_offset = object_offset_ + o;
zx_status_t status;
paddr_t pa;
status = object_->GetPageLocked(vmo_offset, pf_flags, nullptr, nullptr, nullptr, &pa);
if (status != ZX_OK) {
// no page to map
if (commit) {
// fail when we can't commit every requested page
coalescer.Abort();
return status;
}
// skip ahead
continue;
}
vaddr_t va = base_ + o;
LTRACEF_LEVEL(2, "mapping pa %#" PRIxPTR " to va %#" PRIxPTR "\n", pa, va);
status = coalescer.Append(va, pa);
if (status != ZX_OK) {
return status;
}
}
return coalescer.Flush();
}
这个函数是去map(也就是去分配)size大小的物理内存。里面关键的几行代码:
VmMappingCoalescer coalescer(this, base_ + offset);
for (o = offset; o < offset + len; o += PAGE_SIZE) {
uint64_t vmo_offset = object_offset_ + o;
zx_status_t status;
paddr_t pa;
status = object_->GetPageLocked(vmo_offset, pf_flags, nullptr, nullptr, nullptr, &pa);
if (status != ZX_OK) {
// no page to map
if (commit) {
// fail when we can't commit every requested page
coalescer.Abort();
return status;
}
// skip ahead
continue;
}
vaddr_t va = base_ + o;
LTRACEF_LEVEL(2, "mapping pa %#" PRIxPTR " to va %#" PRIxPTR "\n", pa, va);
status = coalescer.Append(va, pa);
if (status != ZX_OK) {
return status;
}
status = object_->GetPageLocked(vmo_offset, pf_flags, nullptr, nullptr, nullptr, &pa)
每次通过VmObject对象分配到一个物理页,pa保存的是物理页的地址,也就是物理地址,然后将虚拟地址与对应的物理地址统一放在coalescer中:
status = coalescer.Append(va, pa);
然后,虚拟地址加上一个页面大小,再去分配对应的物理页,直到size大小。这里要注意一下,虚拟地址是肯定连续的,但是物理地址是不一定连续的。
接下来就需要把虚拟内存与对应的物理内存的对应关系加到MMU的映射表中:
zx_status_t VmMappingCoalescer::Flush() {
if (count_ == 0) {
return ZX_OK;
}
uint flags = mapping_->arch_mmu_flags();
if (flags & ARCH_MMU_FLAG_PERM_RWX_MASK) {
size_t mapped;
zx_status_t ret = mapping_->aspace()->arch_aspace().Map(base_, phys_, count_, flags, &mapped);
if (ret != ZX_OK) {
TRACEF("error %d mapping %zu pages starting at va %#" PRIxPTR "\n", ret, count_, base_);
aborted_ = true;
return ret;
}
DEBUG_ASSERT(mapped == count_);
}
base_ += count_ * PAGE_SIZE;
count_ = 0;
return ZX_OK;
}
zx_status_t X86ArchVmAspace::Map(vaddr_t vaddr, paddr_t* phys, size_t count, uint mmu_flags,
size_t* mapped) {
if (!IsValidVaddr(vaddr))
return ZX_ERR_INVALID_ARGS;
return pt_->MapPages(vaddr, phys, count, mmu_flags, mapped);
}
然后直接到X86PageTableBase中:
zx_status_t X86PageTableBase::MapPages(vaddr_t vaddr, paddr_t* phys, size_t count, uint mmu_flags,
size_t* mapped) {
canary_.Assert();
LTRACEF("aspace %p, vaddr %#" PRIxPTR " count %#zx mmu_flags 0x%x\n", this, vaddr, count,
mmu_flags);
if (!check_vaddr(vaddr))
return ZX_ERR_INVALID_ARGS;
for (size_t i = 0; i < count; ++i) {
if (!check_paddr(phys[i]))
return ZX_ERR_INVALID_ARGS;
}
if (count == 0)
return ZX_OK;
if (!allowed_flags(mmu_flags))
return ZX_ERR_INVALID_ARGS;
PageTableLevel top = top_level();
ConsistencyManager cm(this);
{
Guard<Mutex> a{&lock_};
DEBUG_ASSERT(virt_);
// TODO(teisenbe): Improve performance of this function by integrating deeper into
// the algorithm (e.g. make the cursors aware of the page array).
size_t idx = 0;
auto undo = fbl::MakeAutoCall([&]() {
AssertHeld(lock_);
if (idx > 0) {
MappingCursor start = {
.paddr = 0,
.vaddr = vaddr,
.size = idx * PAGE_SIZE,
};
MappingCursor result;
RemoveMapping(virt_, top, start, &result, &cm);
DEBUG_ASSERT(result.size == 0);
}
cm.Finish();
});
vaddr_t v = vaddr;
for (; idx < count; ++idx) {
MappingCursor start = {
.paddr = phys[idx],
.vaddr = v,
.size = PAGE_SIZE,
};
MappingCursor result;
zx_status_t status = AddMapping(virt_, mmu_flags, top, start, &result, &cm);
if (status != ZX_OK) {
dprintf(SPEW, "Add mapping failed with err=%d\n", status);
return status;
}
DEBUG_ASSERT(result.size == 0);
v += PAGE_SIZE;
}
undo.cancel();
cm.Finish();
}
if (mapped) {
*mapped = count;
}
return ZX_OK;
}
这个函数的几个参数分别是
vaddr_t vaddr 虚拟地址
paddr_t* phys 分配到的物理页地址数组。
size_t count 分别到的物理页数目
这个函数主要逻辑是以物理页为单元,为每个页面构建一个MappingCursor对象
MappingCursor start = {
.paddr = phys[idx],
.vaddr = v,
.size = PAGE_SIZE,
};
然后为每个物理页的映射关系更新到MMU的映射表当中:
zx_status_t X86PageTableBase::AddMapping(volatile pt_entry_t* table, uint mmu_flags,
PageTableLevel level, const MappingCursor& start_cursor,
MappingCursor* new_cursor, ConsistencyManager* cm) {
DEBUG_ASSERT(table);
DEBUG_ASSERT(check_vaddr(start_cursor.vaddr));
DEBUG_ASSERT(check_paddr(start_cursor.paddr));
zx_status_t ret = ZX_OK;
*new_cursor = start_cursor;
if (level == PT_L) {
return AddMappingL0(table, mmu_flags, start_cursor, new_cursor, cm);
}
auto abort = fbl::MakeAutoCall([&]() {
AssertHeld(lock_);
if (level == top_level()) {
MappingCursor cursor = start_cursor;
MappingCursor result;
// new_cursor->size should be how much is left to be mapped still
cursor.size -= new_cursor->size;
if (cursor.size > 0) {
RemoveMapping(table, level, cursor, &result, cm);
DEBUG_ASSERT(result.size == 0);
}
}
});
IntermediatePtFlags interm_flags = intermediate_flags();
PtFlags term_flags = terminal_flags(level, mmu_flags);
size_t ps = page_size(level);
bool level_supports_large_pages = supports_page_size(level);
uint index = vaddr_to_index(level, new_cursor->vaddr);
for (; index != NO_OF_PT_ENTRIES && new_cursor->size != 0; ++index) {
volatile pt_entry_t* e = table + index;
pt_entry_t pt_val = *e;
// See if there's a large page in our way
if (IS_PAGE_PRESENT(pt_val) && IS_LARGE_PAGE(pt_val)) {
return ZX_ERR_ALREADY_EXISTS;
}
// Check if this is a candidate for a new large page
bool level_valigned = page_aligned(level, new_cursor->vaddr);
bool level_paligned = page_aligned(level, new_cursor->paddr);
if (level_supports_large_pages && !IS_PAGE_PRESENT(pt_val) && level_valigned &&
level_paligned && new_cursor->size >= ps) {
UpdateEntry(cm, level, new_cursor->vaddr, table + index, new_cursor->paddr,
term_flags | X86_MMU_PG_PS, false /* was_terminal */);
new_cursor->paddr += ps;
new_cursor->vaddr += ps;
new_cursor->size -= ps;
DEBUG_ASSERT(new_cursor->size <= start_cursor.size);
} else {
// See if we need to create a new table
if (!IS_PAGE_PRESENT(pt_val)) {
volatile pt_entry_t* m = AllocatePageTable();
if (m == nullptr) {
// The mapping wasn't fully updated, but there is work here
// that might need to be undone.
size_t partial_update = fbl::min(ps, new_cursor->size);
new_cursor->paddr += partial_update;
new_cursor->vaddr += partial_update;
new_cursor->size -= partial_update;
return ZX_ERR_NO_MEMORY;
}
LTRACEF_LEVEL(2, "new table %p at level %d\n", m, level);
UpdateEntry(cm, level, new_cursor->vaddr, e, X86_VIRT_TO_PHYS(m), interm_flags,
false /* was_terminal */);
pt_val = *e;
pages_++;
}
MappingCursor cursor;
ret = AddMapping(get_next_table_from_entry(pt_val), mmu_flags, lower_level(level),
*new_cursor, &cursor, cm);
*new_cursor = cursor;
DEBUG_ASSERT(new_cursor->size <= start_cursor.size);
if (ret != ZX_OK) {
return ret;
}
}
}
abort.cancel();
return ZX_OK;
}
zx_status_t X86PageTableBase::AddMappingL0(volatile pt_entry_t* table, uint mmu_flags,
const MappingCursor& start_cursor,
MappingCursor* new_cursor, ConsistencyManager* cm) {
DEBUG_ASSERT(IS_PAGE_ALIGNED(start_cursor.size));
*new_cursor = start_cursor;
PtFlags term_flags = terminal_flags(PT_L, mmu_flags);
uint index = vaddr_to_index(PT_L, new_cursor->vaddr);
for (; index != NO_OF_PT_ENTRIES && new_cursor->size != 0; ++index) {
volatile pt_entry_t* e = table + index;
if (IS_PAGE_PRESENT(*e)) {
return ZX_ERR_ALREADY_EXISTS;
}
UpdateEntry(cm, PT_L, new_cursor->vaddr, e, new_cursor->paddr, term_flags,
false /* was_terminal */);
new_cursor->paddr += PAGE_SIZE;
new_cursor->vaddr += PAGE_SIZE;
new_cursor->size -= PAGE_SIZE;
DEBUG_ASSERT(new_cursor->size <= start_cursor.size);
}
return ZX_OK;
}
上面是两个核心函数。如前面所介绍,MMU的映射表一共有4级,最后一级是叫PT表。
PML4表,PDP表(Page directory point),PD表(Page directory),PT表(Page table)这四级表,每个表都占用了一个物理页面(4K大小),每个页面都存放了512个64位的指针,每个指针都指向的是下一级的页表,PML4表可以指向512个PDP表,每个PDP表中的每个指针可以指向一个PD表,也就是每个PDP表可以指向512个PD表,依次类推。最终PT表的每个64位指针指向的就是真实的物理页面。这个四级映射表的创建是在前面提到的上一篇文章中完成的。
上面的两个函数做的事情就是在找到合适的PT表的合适指针数组,把相应的指针值修改成对应的物理页地址。
简单说起来就是PML4表,PDP表(Page directory point),PD表(Page directory),PT表(Page table)这四个中表项(512个64指针数组中的哪一个成员)是由虚拟地址中有效48位中的从高开始的9位、9位,9位,9位决定的,比如0x000fffffffff000这个地址中,PML4表项是0x1ff,9位全是1,代表的是第511个表项中的指针值指向的PD页表,PD页表项也是0x1ff,代表的是这个表中的第511个表项中的指针值指向的PT页表,PT页表项也是0x1f,代表的是PT页表种第511个表项中指向的物理页地址。这样就完成了一次虚拟地址到物理地址的转换,而上面的两个函数则是建立这样的一个映射关系,可以自己按照前面的介绍去仔细理解逻辑。
好吧,这个就先写到这里了,通过阅读相关代码,发现MMU的映射表是需要OS自己去创建更新的,MMU硬件只在使用的时候会自动按照确定好的映射关系访问相应的物理地址。