【Asterinas】Asterinas 内存管理

Asterinas 内存管理
Asterinas 内存管理
研究Asterinas 对内存管理主要从以下几个方面展开:
•Asterinas对物理内存的管理方式
•Asterinas对进程虚拟地址空间的管理方式
•Asterinas虚拟地址空间与物理地址空间的映射管理方式

Asterinas对物理内存的管理方式
Asterinas kernel对于物理内存信息的获取
在操作系统的启动过程当中,物理内存等信息是bootloader传递给操作系统内核的,在X86上是由UEFI或者传统的BIOS,在UEFI与操作系统之间可以通过多种方式来传递这些信息,比如multiboot与multiboot2两个标准。
我们简单追踪一下物理内存信息如何传递到Asterinas kernel当中的,这样我们需要简单的追踪一下这个系统的启动流程。
a.linker.ld链接脚本直接指定整个操作系统编译生成镜像的入口:

Plain Text
ENTRY(__multiboot_boot)
OUTPUT_ARCH(i386:x86-64)
OUTPUT_FORMAT(elf64-x86-64)
b.通过__multiboot_boot 到__multiboot2_entry跳入RUST代码,然后进入init_memory_regions处理从multiboot传递过来的内存信息:
Rust
fn init_memory_regions(memory_regions: &'static Once<Vec>) {
let mut regions = Vec::::new();

let mb2_info: &BootInformation<'_> = MB2_INFO.get().unwrap(); //获取multiboot2头信息

// Add the regions returned by Grub.
let memory_regions_tag = mb2_info
    .memory_map_tag()
    .expect("Memory region not found from the Multiboot2 header!");
let num_memory_regions = memory_regions_tag.memory_areas().len();//获取multiboot2头中的内存信息
for i in 0..num_memory_regions {
    let start = memory_regions_tag.memory_areas()[i].start_address();
    let end = memory_regions_tag.memory_areas()[i].end_address();
    let area_typ: MemoryRegionType = memory_regions_tag.memory_areas()[i].typ().into();
    let region = MemoryRegion::new(
        start.try_into().unwrap(),
        (end - start).try_into().unwrap(),
        area_typ,
    );
    regions.push(region); //将物理内存信息保存起来,每块内存的首地址,长度与类型。
}

Asterinas kernel对于物理内存信息的管理
Asterinas kernel在获取到物理内存信息之后,是这样来管理这些物理内存的:
a.构建一个全局性质的FrameAllocator对象来负责管理这些物理内存:
Rust
pub(crate) fn init(regions: &[MemoryRegion]) {
let mut allocator = FrameAllocator::<32>::new();
for region in regions.iter() {
if region.typ() == MemoryRegionType::Usable {
// Make the memory region page-aligned, and skip if it is too small.
let start: usize = region.base().align_up(PAGE_SIZE) / PAGE_SIZE;
let end = (region.base() + region.len()).align_down(PAGE_SIZE) / PAGE_SIZE;
if end <= start {
continue;
}
allocator.add_frame(start, end);
info!(
“Found usable region, start:{:x}, end:{:x}”,
region.base(),
region.base() + region.len()
);
}
}
FRAME_ALLOCATOR.call_once(|| SpinLock::new(allocator));
}
这段代码将每块物理内存的地址与长度信息,转成了物理页编号,后续内核就是通过这个物理页号来管理物理内存页的分配:
Rust
let start: usize = region.base().align_up(PAGE_SIZE) / PAGE_SIZE;
let end = (region.base() + region.len()).align_down(PAGE_SIZE) / PAGE_SIZE;
b.在FrameAllocator中会把每个空的内存段拆成从1,2,4,8,16,32等等这样2的指数个连续物理内存页来管理物理内存。这段代码逻辑写得比较巧妙:
Rust
/// Add a range of frame number [start, end) to the allocator
pub fn add_frame(&mut self, start: usize, end: usize) {
assert!(start <= end);

    let mut total = 0;
    let mut current_start = start;

    while current_start < end {
        let lowbit = if current_start > 0 {
            current_start & (!current_start + 1)
        } else {
            32
        };
        let size: usize = min(
            min(lowbit, prev_power_of_two(end - current_start)),
            1 << (ORDER - 1),
        );
        total += size;

        self.free_list[size.trailing_zeros() as usize].insert(current_start);
        current_start += size;
    }

    self.total += total;
}

c.在FrameAllocator中分配物理内存也是会按照用户要求的分配物理页的个数向上按照2个指数取整,比如用户要求分配30个物理页,会实际分配32个物理页,即2的5次方。
Rust
/// Allocate a range of frames of the given size from the allocator. The size must be a power of
/// two. The allocated range will have alignment equal to the size.
fn alloc_power_of_two(&mut self, size: usize) -> Option {
let class: usize = size.trailing_zeros() as usize;
for i in class…self.free_list.len() {
// Find the first non-empty size class
if !self.free_list[i].is_empty() {
// Split buffers
for j in (class + 1…i + 1).rev() {
if let Some(block_ref) = self.free_list[j].iter().next() {
let block = *block_ref;
self.free_list[j - 1].insert(block + (1 << (j - 1)));
self.free_list[j - 1].insert(block);
self.free_list[j].remove(&block);
} else {
return None;
}
}

            let result = self.free_list[class].iter().next().clone();
            if let Some(result_ref) = result {
                let result = *result_ref;
                self.free_list[class].remove(&result);
                self.allocated += size;
                return Some(result);
            } else {
                return None;
            }
        }
    }
    None
}

Asterinas对进程虚拟地址空间的管理方式
在Asterinas系统中,对于应用进程的虚拟地址空间会有这么几个对象概念,分别是:
•VMAR:Virtual Memory Address,负责管理整个进程的虚拟地址空间。
•VmSpace:Virtual Memory Space, 这个会去负责做具体的映射工作,从虚拟地址映射到具体的物理内存。
•VMO:Virtual Memory Object,这个对象会去分配具体的物理内存,在Asterinas中定义是VmFrames。
简单描述下Asterinas管理分配内存方式,由VMAR来分配下虚拟地址空间,然后从FrameAllocator申请出满足要求的物理内存页,最后由VmSpace来执行具体的映射工作。
接下来我们来逐步分析下这些过程:
创建VMAR
在进程创建的时候,创建进程的VMAR:
Rust
pub fn new_root() -> Arc {
let mut free_regions = BTreeMap::new();
let root_region = FreeRegion::new(ROOT_VMAR_LOWEST_ADDR…ROOT_VMAR_HIGHEST_ADDR);
free_regions.insert(root_region.start(), root_region);
let vmar_inner = VmarInner {
is_destroyed: false,
child_vmar_s: BTreeMap::new(),
vm_mappings: BTreeMap::new(),
free_regions,
};
Vmar_::new(vmar_inner, VmSpace::new(), 0, ROOT_VMAR_HIGHEST_ADDR, None)
}
进程的虚拟地址空间中空余的部分是:
Rust
const ROOT_VMAR_LOWEST_ADDR: Vaddr = 0x0010_0000;
const ROOT_VMAR_HIGHEST_ADDR: Vaddr = 0x1000_0000_0000;
创建VmSpace:
在创建进程的VMAR的同时,创建VmSpace:
Rust
pub fn new() -> Self {
Self {
memory_set: Arc::new(Mutex::new(MemorySet::new())),
}
}
在VmSpace中创建MemorySet对象:
Rust
pub fn new() -> Self {
let mut page_table: PageTable = PageTable::<PageTableEntry, UserMode>::new(PageTableConfig {
address_width: super::page_table::AddressWidth::Level4,
});
let mapped_pte = crate::arch::mm::ALL_MAPPED_PTE.lock();
for (index, pte) in mapped_pte.iter() {
// Safety: These PTEs are all valid PTEs fetched from the initial page table during memory initialization.
unsafe {
page_table.add_root_mapping(*index, pte);
}
}
Self {
pt: page_table,
areas: BTreeMap::new(),
}
}

pub fn new(config: PageTableConfig) -> Self {
    let root_frame = VmAllocOptions::new(1).alloc_single().unwrap();
    Self {
        root_paddr: root_frame.start_paddr(),
        tables: vec![root_frame],
        config,
        _phantom: PhantomData,
    }
}

首先分配出一个物理页用来存放PT表。这个表是该进程的PML4表。这个详细的我们在后续的虚拟地址空间与物理地址空间的映射管理方式再来详细分析。
分配WMO对像
根据需要使用的物理内存数量去分配WMO对像。我们以进程heap堆分配为例子来分析下,在用户进程创建的时候,会默认创建一个用户堆:
Rust
pub const fn new() -> Self {
UserHeap {
heap_base: USER_HEAP_BASE,
heap_size_limit: USER_HEAP_SIZE_LIMIT,
current_heap_end: AtomicUsize::new(USER_HEAP_BASE),
}
}
用户堆的虚拟地址空间的起点与大小为:
Rust
pub const USER_HEAP_BASE: Vaddr = 0x0000_0000_1000_0000;
pub const USER_HEAP_SIZE_LIMIT: usize = PAGE_SIZE * 1000,
分配vmo,首先把需要分配的大小针对页帧大小向上对其,然后进行分配:
Rust
fn alloc_vmo_(size: usize, flags: VmoFlags, pager: Option<Arc>) -> Result<Vmo_> {
let size = size.align_up(PAGE_SIZE);
let committed_pages = committed_pages_if_continuous(flags, size)?;
let vmo_inner = VmoInner {
pager,
size,
committed_pages,
inherited_pages: None,
is_cow: false,
};
Ok(Vmo_ {
flags,
inner: Mutex::new(vmo_inner),
})
}
将需要分配的内存大小转换成需要分配的内存页的数量
Rust
fn committed_pages_if_continuous(flags: VmoFlags, size: usize) -> Result<BTreeMap<usize, VmFrame>> {
if flags.contains(VmoFlags::CONTIGUOUS) {
// if the vmo is continuous, we need to allocate frames for the vmo
let frames_num = size / PAGE_SIZE;
let frames = VmAllocOptions::new(frames_num)
.is_contiguous(true)
.alloc()?;
let mut committed_pages = BTreeMap::new();
for (idx, frame) in frames.into_iter().enumerate() {
committed_pages.insert(idx * PAGE_SIZE, frame);
}
Ok(committed_pages)
} else {
// otherwise, we wait for the page is read or write
Ok(BTreeMap::new())
}
}
根据具体的页面数量进行分配:
Rust

/// Allocate a range of frames from the allocator, returning the first frame of the allocated
/// range.
pub fn alloc(&mut self, count: usize) -> Option<usize> {
    let size = count.next_power_of_two();
    self.alloc_power_of_two(size)
}

具体分配算法,在前面关于asterinas对于物理内存的管理中提到过:Asterinas 内存管理,FrameAllocator会将内存页面数量按照1,2,4,8,64等这样的2的指数倍形式分类存放,在分配的时候也是首先找到需要分配物理页数量最接近的2的指数倍量。
Rust
/// Allocate a range of frames of the given size from the allocator. The size must be a power of
/// two. The allocated range will have alignment equal to the size.
fn alloc_power_of_two(&mut self, size: usize) -> Option {
let class = size.trailing_zeros() as usize;
for i in class…self.free_list.len() {
// Find the first non-empty size class
if !self.free_list[i].is_empty() {
// Split buffers
for j in (class + 1…i + 1).rev() {
if let Some(block_ref) = self.free_list[j].iter().next() {
let block = *block_ref;
self.free_list[j - 1].insert(block + (1 << (j - 1)));
self.free_list[j - 1].insert(block);
self.free_list[j].remove(&block);
} else {
return None;
}
}

            let result = self.free_list[class].iter().next().clone();
            if let Some(result_ref) = result {
                let result = *result_ref;
                self.free_list[class].remove(&result);
                self.allocated += size;
                return Some(result);
            } else {
                return None;
            }
        }
    }
    None
}

在WMO对象分配完成后,其实也就是分配了具体的物理页了,接下来需要做的就是需要将分配的物理页映射到虚拟地址空间当中去。
Asterinas虚拟地址空间与物理地址空间的映射管理方式
在这个部分里,我们来根据Asterinas系统来分析下在X86平台上如何进行物理内存地址与虚拟内存地址的映射工作。
X86平台的内存映射机制
在X86平台上有专门一套虚拟内存与物理内存的映射机制,如下图所示,简单描述下就是,在X86平台上,是采用了4级映射机制,虽然是64位系统,但是地址的低48位是用来寻址的,其中CR3寄存器用来存放PML4表的地址,PML4表存放在一个内存帧当中,默认是4K大小,PML4一共有512项,48位地址的高9位代表是在PML4表中的项数,每一项是一个8字节的指针值,指向的是对应的PDE表地址,同样的,一个PDE表也是4K大小,也有512项,48位地址中接下来的9位代表的是具体在PDE表中的某一项的,这一项也是一个8字节的指针,指向的是PTE表,类似的,PTE指向的是一个具体的物理页地址,这样就完成了虚拟地址到物理地址的映射转换,而48位中的低12位则是在4K页面内的偏移。(注:X86平台上内存映射还有其他种模式,这里只介绍最简单的模式)。

Asterinas系统在X86平台上对于页表的管理方式
我们首先从缺页中断开始跟踪,缺页中断是在当我们访问某个虚拟地址,但是这个虚拟地址没有具体的物理内存映射的时候会产生,而在Asterinas系统中由handle_page_fault来负责处理,在这里我们先忽略掉Asterinas异常处理流程,直接从handle_page_fault函数开始。
Rust
pub fn handle_page_fault(
&self,
page_fault_addr: Vaddr,
not_present: bool,
write: bool,
) -> Result<()> {
let vmo_offset = self.vmo_offset() + page_fault_addr - self.map_to_addr();
if vmo_offset >= self.vmo.size() {
return_errno_with_message!(Errno::EACCES, “page fault addr is not backed up by a vmo”);
}
let page_idx = vmo_offset / PAGE_SIZE;
if write {
self.vmo.check_rights(Rights::WRITE)?;
} else {
self.vmo.check_rights(Rights::READ)?;
}

    let required_perm = if write { VmPerm::W } else { VmPerm::R };
    self.check_perm(&page_idx, &required_perm)?;

    let frame = self.vmo.get_committed_frame(page_idx, write)?;

    // If read access to cow vmo triggers page fault, the map should be readonly.
    // If user next tries to write to the frame, another page fault will be triggered.
    let is_readonly = self.vmo.is_cow_child() && !write;
    self.map_one_page(page_idx, frame, is_readonly)
}

该函数的处理逻辑是这样的:
a.首先找到这个产生缺页中断的地址与某一个已经映射的虚拟地址区间首地址之间的差值:
Rust
let vmo_offset = self.vmo_offset() + page_fault_addr - self.map_to_addr();
b.将这个差值转成对应的内存帧页编号

Rust
let page_idx = vmo_offset / PAGE_SIZE;
c.分配一个新的物理内存帧
Rust
let frame = self.vmo.get_committed_frame(page_idx, write)?;
d.映射这个物理内存帧,page_idx为当前需要映射区间的虚拟内存页表号, VmFrame是需要映射的物理内存帧。
Rust
pub(super) fn map_one_page(
&self,
page_idx: usize,
frame: VmFrame,
is_readonly: bool,
) -> Result<()> {
let parent = self.parent.upgrade().unwrap();
let vm_space = parent.vm_space();
self.inner
.lock()
.map_one_page(&self.vmo, vm_space, page_idx, frame, is_readonly)
}
e.遍历一遍PML4表,寻找下需要映射的虚拟地址在这个四级映射表中有没有对应的映射项。
Rust
unsafe fn do_map(
&mut self,
vaddr: Vaddr,
paddr: Paddr,
flags: T::F,
) -> Result<(), PageTableError> {
let last_entry = self.page_walk(vaddr, true).unwrap();
trace!(
“Page Table: Map vaddr:{:x?}, paddr:{:x?}, flags:{:x?}”,
vaddr,
paddr,
flags
);
if last_entry.is_used() && last_entry.flags().is_present() {
return Err(PageTableError::InvalidModification);
}
last_entry.update(paddr, flags);
tlb_flush(vaddr);
Ok(())
}
f.page_walk的逻辑十分清晰而且易读,主要做了这几个动作:
Rust
fn page_walk(&mut self, vaddr: Vaddr, create: bool) -> Option<&mut T> {
let mut count = self.config.address_width as usize;
debug_assert!(size_of::() * (T::page_index(vaddr, count) + 1) <= PAGE_SIZE);
// Safety: The offset does not exceed the value of PAGE_SIZE.
// It only change the memory controlled by page table.
let mut current: &mut T = unsafe {
&mut *(paddr_to_vaddr(self.root_paddr + size_of::() * T::page_index(vaddr, count))
as *mut T)
};

    while count > 1 {
        if !current.flags().is_present() {
            if !create {
                return None;
            }
            // Create next table
            let frame = VmAllocOptions::new(1).alloc_single().unwrap();
            // Default flags: read, write, user, present
            let flags = T::F::new()
                .set_present(true)
                .set_accessible_by_user(true)
                .set_readable(true)
                .set_writable(true);
            current.update(frame.start_paddr(), flags);
            self.tables.push(frame);
        }
        if current.flags().is_huge() {
            break;
        }
        count -= 1;
        debug_assert!(size_of::<T>() * (T::page_index(vaddr, count) + 1) <= PAGE_SIZE);
        // Safety: The offset does not exceed the value of PAGE_SIZE.
        // It only change the memory controlled by page table.
        current = unsafe {
            &mut *(paddr_to_vaddr(
                current.paddr() + size_of::<T>() * T::page_index(vaddr, count),
            ) as *mut T)
        };
    }
    Some(current)
}

首先找出需要映射的虚拟地址在PML4表中的位置:
Rust
let mut current: &mut T = unsafe {
&mut *(paddr_to_vaddr(self.root_paddr + size_of::() * T::page_index(vaddr, count))
as *mut T)
};
其次如果PML4表项指向的PDPTE页表不存在,则重新分配一个物理内存页用来当成PDPTE页表,并且将新分配的PDPTE页的物理地址写入PML4页对应的位置中。
Rust
if !current.flags().is_present() {
if !create {
return None;
}
// Create next table
let frame = VmAllocOptions::new(1).alloc_single().unwrap();
// Default flags: read, write, user, present
let flags = T::F::new()
.set_present(true)
.set_accessible_by_user(true)
.set_readable(true)
.set_writable(true);
current.update(frame.start_paddr(), flags);
self.tables.push(frame);
}
类似的逻辑循环处理PDE与PTE页,最终找到在PTE页中的位置。
g.将新分配的物理页的地址更新进PTE页表中:
Rust
unsafe fn do_map(
&mut self,
vaddr: Vaddr,
paddr: Paddr,
flags: T::F,
) -> Result<(), PageTableError> {
let last_entry = self.page_walk(vaddr, true).unwrap();
trace!(
“Page Table: Map vaddr:{:x?}, paddr:{:x?}, flags:{:x?}”,
vaddr,
paddr,
flags
);
if last_entry.is_used() && last_entry.flags().is_present() {
return Err(PageTableError::InvalidModification);
}
last_entry.update(paddr, flags);
tlb_flush(vaddr);
Ok(())
}
由此完成虚拟地址与物理地址的映射关系。

Asterinas系统进程切换刷新PML4表
Asterinas在做进程调度的时候需要刷新CR3寄存器,使用本进程的PML4表:
Rust
/// Starts executing in the user mode. Make sure current task is the task in UserMode.
///
/// The method returns for one of three possible reasons indicated by UserEvent.
/// 1. The user invokes a system call;
/// 2. The user triggers an exception;
/// 3. The user triggers a fault.
///
/// After handling the user event and updating the user-mode CPU context,
/// this method can be invoked again to go back to the user space.
pub fn execute(&mut self) -> UserEvent {
unsafe {
self.user_space.vm_space().activate();
}
debug_assert!(Arc::ptr_eq(&self.current, &Task::current()));
self.context.execute()
}
实际刷新:
Rust
pub unsafe fn activate(&self) {
#[cfg(target_arch = “x86_64”)]
crate::arch::x86::mm::activate_page_table(
self.memory_set.lock().pt.root_paddr(),
x86_64::registers::control::Cr3Flags::PAGE_LEVEL_CACHE_DISABLE,
);
}
Asterinas kernel中虚拟地址空间与物理地址空间的映射
在Asterinas 启动阶段进入kernel之前会提前做好虚拟地址空间与物理地址空间的映射,简而言之则是维护自己的PML4表,这样我们才能从前面章节看到的,当分配好物理地址的时候,直接可以用paddr_to_vaddr函数将物理地址转成虚拟地址。我们来看下这个kernel的这个PML4表映射的建立过程:
在汇编代码中直接分配好内存,这会随着kernel的二进制镜像被装在进内存而直接分配(占用)好。
Rust
// The page tables and the stack
.align 4096

.global boot_page_table_start
boot_page_table_start:
boot_pml4:
.skip 4096
boot_pdpt:
.skip 4096
boot_pd:
boot_pd_0g_1g:
.skip 4096
boot_pd_1g_2g:
.skip 4096
boot_pd_2g_3g:
.skip 4096
boot_pd_3g_4g:
.skip 4096
boot_pt:
.skip 4096 * 512 * 4
boot_pd_32g:
.skip 4096
boot_pt_32g:
.skip 4096 * 512
boot_page_table_end:
建立PML4表映射:
Rust
// PML4: 0x00000000_00000000 ~ 0x00000000_3fffffff
// 0x00000000_40000000 ~ 0x00000000_7fffffff
// 0x00000000_80000000 ~ 0x00000000_bfffffff
// 0x00000000_c0000000 ~ 0x00000000_ffffffff
lea edi, [boot_pml4]
lea eax, [boot_pdpt + (PTE_PRESENT | PTE_WRITE | PTE_GLOBAL)]
mov dword ptr [edi], eax
mov dword ptr [edi + 4], 0

// PML4: 0xffff8000_00000000 ~ 0xffff8000_3fffffff
//       0xffff8000_40000000 ~ 0xffff8000_7fffffff
//       0xffff8000_80000000 ~ 0xffff8000_bfffffff
//       0xffff8000_c0000000 ~ 0xffff8000_ffffffff
//       0xffff8008_00000000 ~ 0xffff8008_3fffffff
lea edi, [boot_pml4 + 0x100 * 8]
lea eax, [boot_pdpt + (PTE_PRESENT | PTE_WRITE | PTE_GLOBAL)]
mov dword ptr [edi], eax
mov dword ptr [edi + 4], 0

// PML4: 0xffffffff_80000000 ~ 0xffffffff_bfffffff
//       0xffffffff_c0000000 ~ 0xffffffff_ffffffff
lea edi, [boot_pml4 + 0x1ff * 8]
lea eax, [boot_pdpt + (PTE_PRESENT | PTE_WRITE | PTE_GLOBAL)]
mov dword ptr [edi], eax
mov dword ptr [edi + 4], 0

// PDPT: 0x00000000_00000000 ~ 0x00000000_3fffffff
lea edi, [boot_pdpt]
lea eax, [boot_pd_0g_1g + (PTE_PRESENT | PTE_WRITE | PTE_GLOBAL)]
mov dword ptr [edi], eax
mov dword ptr [edi + 4], 0

// PDPT: 0x00000000_40000000 ~ 0x00000000_7fffffff
lea edi, [boot_pdpt + 0x1 * 8]
lea eax, [boot_pd_1g_2g + (PTE_PRESENT | PTE_WRITE | PTE_GLOBAL)]
mov dword ptr [edi], eax
mov dword ptr [edi + 4], 0

// PDPT: 0x00000000_80000000 ~ 0x00000000_bfffffff
lea edi, [boot_pdpt + 0x2 * 8]
lea eax, [boot_pd_2g_3g + (PTE_PRESENT | PTE_WRITE | PTE_GLOBAL)]
mov dword ptr [edi], eax
mov dword ptr [edi + 4], 0

// PDPT: 0x00000000_c0000000 ~ 0x00000000_ffffffff
lea edi, [boot_pdpt + 0x3 * 8]
lea eax, [boot_pd_3g_4g + (PTE_PRESENT | PTE_WRITE | PTE_GLOBAL)]
mov dword ptr [edi], eax
mov dword ptr [edi + 4], 0

// 1000 00000|000  100000|00 0000000|0 00000000 000
// PDPT: 0xffff8008_00000000 ~ 0xffff8008_3fffffff
lea edi, [boot_pdpt + 0x20 * 8]
lea eax, [boot_pd_32g + (PTE_PRESENT | PTE_WRITE | PTE_GLOBAL)]
mov dword ptr [edi], eax
mov dword ptr [edi + 4], 0

// PDPT: 0xffffffff_80000000 ~ 0xffffffff_bfffffff
lea edi, [boot_pdpt + 0x1fe * 8]
lea eax, [boot_pd_0g_1g + (PTE_PRESENT | PTE_WRITE | PTE_GLOBAL)]
mov dword ptr [edi], eax
mov dword ptr [edi + 4], 0

// PDPT: 0xffffffff_c0000000 ~ 0xffffffff_ffffffff
lea edi, [boot_pdpt + 0x1ff * 8]
lea eax, [boot_pd_1g_2g + (PTE_PRESENT | PTE_WRITE | PTE_GLOBAL)]
mov dword ptr [edi], eax
mov dword ptr [edi + 4], 0

// Page Directory: map to low 1 GiB * 4 space
lea edi, [boot_pd]
lea eax, [boot_pt + (PTE_PRESENT | PTE_WRITE | PTE_GLOBAL)]
mov ecx, 512 * 4 // (of entries in PD) * (number of PD)

write_pd_entry:
mov dword ptr [edi], eax
mov dword ptr [edi + 4], 0
add eax, 0x1000 // +4KiB to next table
add edi, 8
loop write_pd_entry

// Page Directory: map to 1 GiB space offset 32GiB
lea edi, [boot_pd_32g]
lea eax, [boot_pt_32g + (PTE_PRESENT | PTE_WRITE | PTE_GLOBAL)]
mov ecx, 512 // (of entries in PD)

write_pd_32g_entry:
mov dword ptr [edi], eax
mov dword ptr [edi + 4], 0x0
add eax, 0x1000 // +4KiB to next table
add edi, 8
loop write_pd_32g_entry

// Page Table: map to low 1 GiB * 4 space
lea edi, [boot_pt]
mov eax, (PTE_PRESENT | PTE_WRITE | PTE_GLOBAL) // Offset 0
mov ecx, 512 * 512 * 4 // (of entries in PT) * (number of PT) * (number of PD)

write_pt_entry:
mov dword ptr [edi], eax
mov dword ptr [edi + 4], 0
add eax, 0x1000 // +4KiB
add edi, 8
loop write_pt_entry

// Page Table: map to 1 GiB space offset 32GiB
lea edi, [boot_pt_32g]
mov eax, (PTE_PRESENT | PTE_WRITE | PTE_GLOBAL) // Offset 0x8_00000000 but should write to high 32bits
mov ecx, 512 * 512 // (of entries in PT) * (number of PT)

write_pt_32g_entry:
mov dword ptr [edi], eax
mov dword ptr [edi + 4], 0x8 // Offset 0x8_00000000
add eax, 0x1000 // +4KiB
add edi, 8
loop write_pt_32g_entry

jmp enable_long_mode

简单描述下这个kernel中实现的PML4表,一共有一张PML4表,一张PDPT表,4张pd表,512*4张PT表,其他的我们暂时忽略一下。PML4表的512项中有3项被使用,分别是第0项,第256项根第511项。而这三项都是指向同一个PDPT页表。而PDPT页表中的512个列表项被使用了4个,分别指向了4张PD表。也就是说同一个物理地址被映射到三个不同的虚拟地址上去了,这三个虚拟地址的区别就是48位地址值中的高9位有区别。而这个区别就是:
Rust
pub const PHYS_OFFSET: usize = 0xFFFF800000000000;
其中48位地址的高9位是等于128,
Rust
1000 0000 0
整个PML4表大致如下图所示:

而整个虚拟地址与物理地址是值对应的,因为PML4是从第0项,PDPT表也是从第0项,对应到的物理地址也是从0地址开始。
Rust
// Page Directory: map to low 1 GiB * 4 space
lea edi, [boot_pd]
lea eax, [boot_pt + (PTE_PRESENT | PTE_WRITE | PTE_GLOBAL)]
mov ecx, 512 * 4 // (of entries in PD) * (number of PD)
write_pd_entry:
mov dword ptr [edi], eax
mov dword ptr [edi + 4], 0
add eax, 0x1000 // +4KiB to next table
add edi, 8
loop write_pd_entry
而在kernel启动过程中,kernel的PML4表中的三项会被保存在全局变量中:
Rust
pub fn init() {
let (page_directory_base, _) = x86_64::registers::control::Cr3::read();
let page_directory_base = page_directory_base.start_address().as_u64() as usize;

// Safety: page_directory_base is read from Cr3, the address is valid.
let p4: &mut [PageTableEntry] = unsafe { table_of::<PageTableEntry>(page_directory_base).unwrap() };
// Cancel mapping in lowest addresses.
p4[0].clear();
let mut map_pte = ALL_MAPPED_PTE.lock();
for (i, p4_i) in p4.iter().enumerate().take(512) {
    if p4_i.flags().contains(PageTableFlags::PRESENT) {
        map_pte.insert(i, *p4_i);
    }
}

}
而每个进程在创建自己的PML4表的时候,会将Kernel全局PML4表复制到进程的PML4表里:
Rust
pub fn new() -> Self {
let mut page_table = PageTable::<PageTableEntry, UserMode>::new(PageTableConfig {
address_width: super::page_table::AddressWidth::Level4,
});
let mapped_pte = crate::arch::mm::ALL_MAPPED_PTE.lock();
for (index, pte) in mapped_pte.iter() {
// Safety: These PTEs are all valid PTEs fetched from the initial page table during memory initialization.
unsafe {
page_table.add_root_mapping(*index, pte);
}
}
Self {
pt: page_table,
areas: BTreeMap::new(),
}
}
因此在kernel中paddr_to_vaddr:
Rust

/// Convert physical address to virtual address using offset, only available inside aster-frame
pub(crate) fn paddr_to_vaddr(pa: usize) -> usize {
pa + PHYS_OFFSET
}
对于Kernel而言,它在每个进程中的地址空间是一致的。kernel也没有自己单独的PML4表了,只会随着进程切换而切换进程的PML4表。但是进程的PML4表的部分会有kernel空间的映射,这样当进程陷入kernel中的时候,还是可以通过虚拟地址去访问物理地址。

至此大致完成Asterinas系统中对于内存的管理的分析了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/767165.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

鸿蒙认证值得考吗?

鸿蒙认证值得考吗&#xff1f; 鸿蒙认证&#xff08;HarmonyOS Certification&#xff09;是华为为了培养和认证开发者在鸿蒙操作系统&#xff08;HarmonyOS&#xff09;领域的专业技能而设立的一系列认证项目。这些认证旨在帮助开发者和企业工程师提升在鸿蒙生态中的专业技能…

小故事——半个世纪的爱情

半个世纪的爱情 故事的开端永远是在那个情窦初开的年纪&#xff0c;那富有蓬勃朝气的少年时代&#xff0c;眼神中青涩未尽&#xff0c;正是这个时间&#xff0c;才真正的让人难以忘怀。她不过是那班级里面普普通通的小孩&#xff0c;故事的男主角同样也是简简单单的存在&#…

激光SLAM如何动态管理关键帧和地图

Tip: 如果你在进行深度学习、自动驾驶、模型推理、微调或AI绘画出图等任务&#xff0c;并且需要GPU资源&#xff0c;可以考虑使用UCloud云计算旗下的Compshare的GPU算力云平台。他们提供高性价比的4090 GPU&#xff0c;按时收费每卡2.6元&#xff0c;月卡只需要1.7元每小时&…

Activity、Window、DecorView的关系

目录 一、Activity、Window、DecorView的层级关系如下图所示&#xff1a; 1、Activity 2、Window 3、DecorView 二、DecorView初始化相关源码 三、DecorView显示时机 前言&#xff1a; 不同的Android版本有差异&#xff0c;以下基于Android 11进行讲解。 一、Activi…

音乐发行平台无加密开源源码

适用于唱片公司&#xff0c;用于接收物料&#xff0c;下载物料功能&#xff1a;个人或机构认证&#xff0c;上传专辑和歌曲&#xff0c;版税结算环境要求php7.4Nginx 1、导入数据库 2、/inc/conn.php里填写数据库密码等后台路径/admin&#xff08;可自行修改任意入口名称&…

Meta 3D Gen:文生 3D 模型

是由 Meta 公布的一个利用 Meta AssetGen&#xff08;模型生成&#xff09;和 TextureGen&#xff08;贴图材质生成&#xff09;的组合 AI 系统&#xff0c;可以在分分钟内生成高质量 3D 模型和高分辨率贴图纹理。 视频演示的效果非常好&#xff0c;目前只有论文&#xff0c;期…

计算机网络--网络层

一、网络层的服务和功能 网络层主要为应用层提供端对端的数据传输服务 网络层接受运输层的报文段&#xff0c;添加自己的首部&#xff0c;形成网络层分组。分组是网络层的传输单元。网络层分组在各个站点的网络层之间传输&#xff0c;最终到达接收方的网络层。接收方网络层将运…

PLC_博图系列☞TP:生成脉冲

PLC_博图系列☞TP&#xff1a;生成脉冲 文章目录 PLC_博图系列☞TP&#xff1a;生成脉冲背景介绍TP&#xff1a; 生成脉冲说明参数脉冲时序图示例 关键字&#xff1a; PLC、 西门子、 博图、 Siemens 、 TP 背景介绍 这是一篇关于PLC编程的文章&#xff0c;特别是关于西门…

快速上手文心一言指令:解锁AI对话新纪元

快速上手文心一言指令 一、引言&#xff1a;文心一言的魅力所在二、准备工作&#xff1a;了解文心一言平台2.1 轻松注册&#xff0c;开启智能对话之旅2.2 深度探索&#xff0c;掌握界面布局奥秘2.2.1 输入框&#xff1a;智慧交流的起点2.2.2 回复区&#xff1a;即时反馈的窗口2…

Echarts-折线图

1.案例1 1.1代码 option {"tooltip": {"trigger": "axis","backgroundColor": "rgba(32, 33, 36,.7)","borderColor": "rgba(32, 33, 36,0.20)","borderWidth": 10,"textStyle"…

星辰资讯 | TiUP v1.16 发版,支持 PD 微服务

如果你对 TiDB 还不太了解&#xff0c;或者你对数据库安装部署的认知仍然停留在手动和脚本时代&#xff0c;那么&#xff0c;请先戳这里了解一下 TiUP 神器&#xff1a; 震惊&#xff01;数据库小白装国产数据库只需10分钟&#xff01; TiDB 7.x 源码编译之 TiUP 篇 TiUP&#…

基于改进高斯-拉普拉斯滤波器的一维时间序列平滑与降噪(MATLAB)

以图像处理为例&#xff0c;拉普拉斯算子是基于图像的二阶导数来找到边缘并搜索过零点&#xff0c;传统的拉普拉斯算子常产生双像素宽的边缘&#xff0c;对于较暗区域中的亮斑进行边缘检测时&#xff0c;拉普拉斯运算就会使其变得更亮。因此&#xff0c;与梯度算子一样&#xf…

基于tensorflow2的目标检测完整实现过程

序言 虽然tf1仍然在维护&#xff0c;但tf2毕竟是主流&#xff0c;如果不是项目有明确要求&#xff0c;建议直接选择tf2。本文以tf2为例展开&#xff0c;总结从环境准备到使用自己的数据和tensorflow预训练模型进行快速训练和调用。对tensorflow和目标检测算法有深入了解的&…

Seal^_^【送书活动第8期】——《ChatGLM3大模型本地化部署、应用开发与微调》

Seal^_^【送书活动第8期】——《ChatGLM3大模型本地化部署、应用开发与微调》 一、参与方式二、本期推荐图书2.1 作者建语2.2 编辑推建2.3 图书简介2.4 前 言2.5 目 录 三、正版购买 大模型领域 既是繁星点点的未知宇宙&#xff0c;也是蕴含无数可能的广阔天地&#xff0c; 正…

“不喝鸡汤 不诉离殇”华火电燃灶用实力引领烹饪灶具发展

在这个快节奏的时代&#xff0c;我们常常被各种厨房电器的鸡汤所包围&#xff0c;并悄悄的告诉我们厨房生活是美好与温暖的&#xff0c;但面对现实中的挑战与困难时&#xff0c;常常表现出选择性失明&#xff1b;那些隐藏在传统厨房烹饪环境下的危机&#xff0c;就像是慢性的毒…

参数污染漏洞(HPP)挖掘技巧及实战案例全汇总

目录 概念: 漏洞原理: 实战案例总结: 1. 逻辑漏洞(IDOR) 2. 绕过检测(WAF) 挖掘技巧: 修复方案: 概念: HTTP参数污染,也叫HPP(HTTP Parameter Pollution)。简单地讲就是给一个参数赋上两个或两个以上的值,由于现行的HTTP标准没有提及在遇到多个输入值给相…

设计IC行业SAP软件如何处理芯片成本计算

在集成电路(IC)设计与制造行业中&#xff0c;精确的成本计算对于维持健康的财务状况、优化生产流程以及保持市场竞争力至关重要。SAP软件&#xff0c;作为一种全面的企业资源规划(ERP)解决方案&#xff0c;为IC行业提供了强大且灵活的成本计算工具。以下是SAP软件如何处理芯片成…

【Linux】应用层创建XXX文件,文件系统调用可以查看到文件名

搞了台电脑&#xff0c;昨天把系统装了下&#xff0c;继续搞事&#xff1a; 上次基于内核代码openat的系统打印被操作的文件名&#xff0c;发现不成功&#xff0c;很奇怪&#xff0c;这种问题内核不可能会犯这种低级别的问题吧&#xff1f; 反过来想&#xff0c;那不是内核的问…

Vscode快捷键崩溃

Vscode快捷键崩溃 Linux虚拟机下使用vscode写代码【ctrlA&#xff0c;CtrlC&#xff0c;CtrlV】等快捷键都不能使用&#xff0c;还会出现“NO text insert“等抽象的指令&#xff0c;问题就是不知道什么时候装了一个VIM插件&#xff0c;让他滚出电脑》》》

[vue3+js]实现3d旋转效果

1. 实现效果图&#xff1a; 2.实现代码&#xff1a; css: <style lang"scss" scoped>.bottomContainer{width: 1200px;height: 400px;display: flex;justify-content: center;position: relative;margin:200px auto;align-items: center;// background-image…