mirror of
https://github.com/DragonOS-Community/DragonOS.git
synced 2025-06-22 11:13:22 +00:00
feat(mm): 实现缺页中断处理,支持页面延迟分配和写时拷贝,以及用户栈自动拓展 (#715)
* 实现缺页中断处理 * 完善页表拷贝逻辑 * 优化代码结构 * 完善缺页异常信息 * 修改大页映射逻辑 * 修正大页映射错误 * 添加缺页中断支持标志 * 实现用户栈自动拓展功能
This commit is contained in:
@ -13,12 +13,14 @@ use crate::{
|
||||
arch::{interrupt::ipi::send_ipi, MMArch},
|
||||
exception::ipi::{IpiKind, IpiTarget},
|
||||
ipc::shm::ShmId,
|
||||
kerror, kwarn,
|
||||
kerror,
|
||||
libs::spinlock::{SpinLock, SpinLockGuard},
|
||||
};
|
||||
|
||||
use super::{
|
||||
allocator::page_frame::FrameAllocator, syscall::ProtFlags, ucontext::LockedVMA,
|
||||
allocator::page_frame::{FrameAllocator, PageFrameCount},
|
||||
syscall::ProtFlags,
|
||||
ucontext::LockedVMA,
|
||||
MemoryManagementArch, PageTableKind, PhysAddr, VirtAddr,
|
||||
};
|
||||
|
||||
@ -70,7 +72,9 @@ impl PageManager {
|
||||
}
|
||||
|
||||
pub fn get_mut(&mut self, paddr: &PhysAddr) -> &mut Page {
|
||||
self.phys2page.get_mut(paddr).unwrap()
|
||||
self.phys2page
|
||||
.get_mut(paddr)
|
||||
.unwrap_or_else(|| panic!("{:?}", paddr))
|
||||
}
|
||||
|
||||
pub fn insert(&mut self, paddr: PhysAddr, page: Page) {
|
||||
@ -141,9 +145,15 @@ impl Page {
|
||||
self.free_when_zero = dealloc_when_zero;
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn anon_vma(&self) -> &HashSet<Arc<LockedVMA>> {
|
||||
&self.anon_vma
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn map_count(&self) -> usize {
|
||||
self.map_count
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@ -265,7 +275,7 @@ impl<Arch: MemoryManagementArch> PageTable<Arch> {
|
||||
/// ## 返回值
|
||||
///
|
||||
/// 页表项在页表中的下标。如果addr不在当前页表所表示的虚拟地址空间中,则返回None
|
||||
pub unsafe fn index_of(&self, addr: VirtAddr) -> Option<usize> {
|
||||
pub fn index_of(&self, addr: VirtAddr) -> Option<usize> {
|
||||
let addr = VirtAddr::new(addr.data() & Arch::PAGE_ADDRESS_MASK);
|
||||
let shift = self.level * Arch::PAGE_ENTRY_SHIFT + Arch::PAGE_SHIFT;
|
||||
|
||||
@ -290,6 +300,61 @@ impl<Arch: MemoryManagementArch> PageTable<Arch> {
|
||||
self.level - 1,
|
||||
));
|
||||
}
|
||||
|
||||
/// 拷贝页表
|
||||
/// ## 参数
|
||||
///
|
||||
/// - `allocator`: 物理页框分配器
|
||||
/// - `copy_on_write`: 是否写时复制
|
||||
pub unsafe fn clone(
|
||||
&self,
|
||||
allocator: &mut impl FrameAllocator,
|
||||
copy_on_write: bool,
|
||||
) -> Option<PageTable<Arch>> {
|
||||
// 分配新页面作为新的页表
|
||||
let phys = allocator.allocate_one()?;
|
||||
let frame = MMArch::phys_2_virt(phys).unwrap();
|
||||
MMArch::write_bytes(frame, 0, MMArch::PAGE_SIZE);
|
||||
let new_table = PageTable::new(self.base, phys, self.level);
|
||||
if self.level == 0 {
|
||||
for i in 0..Arch::PAGE_ENTRY_NUM {
|
||||
if let Some(mut entry) = self.entry(i) {
|
||||
if entry.present() {
|
||||
if copy_on_write {
|
||||
let mut new_flags = entry.flags().set_write(false);
|
||||
entry.set_flags(new_flags);
|
||||
self.set_entry(i, entry);
|
||||
new_flags = new_flags.set_dirty(false);
|
||||
entry.set_flags(new_flags);
|
||||
new_table.set_entry(i, entry);
|
||||
} else {
|
||||
let phys = allocator.allocate_one()?;
|
||||
let mut anon_vma_guard = page_manager_lock_irqsave();
|
||||
anon_vma_guard.insert(phys, Page::new(false));
|
||||
let old_phys = entry.address().unwrap();
|
||||
let frame = MMArch::phys_2_virt(phys).unwrap().data() as *mut u8;
|
||||
frame.copy_from_nonoverlapping(
|
||||
MMArch::phys_2_virt(old_phys).unwrap().data() as *mut u8,
|
||||
MMArch::PAGE_SIZE,
|
||||
);
|
||||
new_table.set_entry(i, PageEntry::new(phys, entry.flags()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 非一级页表拷贝时,对每个页表项对应的页表都进行拷贝
|
||||
for i in 0..MMArch::PAGE_ENTRY_NUM {
|
||||
if let Some(next_table) = self.next_level_table(i) {
|
||||
let table = next_table.clone(allocator, copy_on_write)?;
|
||||
let old_entry = self.entry(i).unwrap();
|
||||
let entry = PageEntry::new(table.phys(), old_entry.flags());
|
||||
new_table.set_entry(i, entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(new_table)
|
||||
}
|
||||
}
|
||||
|
||||
/// 页表项
|
||||
@ -368,6 +433,22 @@ impl<Arch: MemoryManagementArch> PageEntry<Arch> {
|
||||
pub fn present(&self) -> bool {
|
||||
return self.data & Arch::ENTRY_FLAG_PRESENT != 0;
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn empty(&self) -> bool {
|
||||
self.data & !(Arch::ENTRY_FLAG_DIRTY & Arch::ENTRY_FLAG_ACCESSED) == 0
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn protnone(&self) -> bool {
|
||||
return self.data & (Arch::ENTRY_FLAG_PRESENT | Arch::ENTRY_FLAG_GLOBAL)
|
||||
== Arch::ENTRY_FLAG_GLOBAL;
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn write(&self) -> bool {
|
||||
return self.data & Arch::ENTRY_FLAG_READWRITE != 0;
|
||||
}
|
||||
}
|
||||
|
||||
/// 页表项的标志位
|
||||
@ -605,6 +686,36 @@ impl<Arch: MemoryManagementArch> PageFlags<Arch> {
|
||||
return self.has_flag(Arch::ENTRY_FLAG_WRITE_THROUGH);
|
||||
}
|
||||
|
||||
/// 设置当前页表是否为脏页
|
||||
///
|
||||
/// ## 参数
|
||||
///
|
||||
/// - value: 如果为true,那么将当前页表项的写穿策略设置为写穿。
|
||||
#[inline(always)]
|
||||
pub fn set_dirty(self, value: bool) -> Self {
|
||||
return self.update_flags(Arch::ENTRY_FLAG_DIRTY, value);
|
||||
}
|
||||
|
||||
/// 设置当前页表被访问
|
||||
///
|
||||
/// ## 参数
|
||||
///
|
||||
/// - value: 如果为true,那么将当前页表项的访问标志设置为已访问。
|
||||
#[inline(always)]
|
||||
pub fn set_access(self, value: bool) -> Self {
|
||||
return self.update_flags(Arch::ENTRY_FLAG_ACCESSED, value);
|
||||
}
|
||||
|
||||
/// 设置指向的页是否为大页
|
||||
///
|
||||
/// ## 参数
|
||||
///
|
||||
/// - value: 如果为true,那么将当前页表项的访问标志设置为已访问。
|
||||
#[inline(always)]
|
||||
pub fn set_huge_page(self, value: bool) -> Self {
|
||||
return self.update_flags(Arch::ENTRY_FLAG_HUGE_PAGE, value);
|
||||
}
|
||||
|
||||
/// MMIO内存的页表项标志
|
||||
#[inline(always)]
|
||||
pub fn mmio_flags() -> Self {
|
||||
@ -758,12 +869,6 @@ impl<Arch: MemoryManagementArch, F: FrameAllocator> PageMapper<Arch, F> {
|
||||
let i = table.index_of(virt)?;
|
||||
assert!(i < Arch::PAGE_ENTRY_NUM);
|
||||
if table.level() == 0 {
|
||||
// todo: 检查是否已经映射
|
||||
// 现在不检查的原因是,刚刚启动系统时,内核会映射一些页。
|
||||
if table.entry_mapped(i)? {
|
||||
kwarn!("Page {:?} already mapped", virt);
|
||||
}
|
||||
|
||||
compiler_fence(Ordering::SeqCst);
|
||||
|
||||
table.set_entry(i, entry);
|
||||
@ -797,6 +902,173 @@ impl<Arch: MemoryManagementArch, F: FrameAllocator> PageMapper<Arch, F> {
|
||||
}
|
||||
}
|
||||
|
||||
/// 进行大页映射
|
||||
pub unsafe fn map_huge_page(
|
||||
&mut self,
|
||||
virt: VirtAddr,
|
||||
flags: PageFlags<Arch>,
|
||||
) -> Option<PageFlush<Arch>> {
|
||||
// 验证虚拟地址是否对齐
|
||||
if !(virt.check_aligned(Arch::PAGE_SIZE)) {
|
||||
kerror!("Try to map unaligned page: virt={:?}", virt);
|
||||
return None;
|
||||
}
|
||||
|
||||
let virt = VirtAddr::new(virt.data() & (!Arch::PAGE_NEGATIVE_MASK));
|
||||
|
||||
let mut table = self.table();
|
||||
loop {
|
||||
let i = table.index_of(virt)?;
|
||||
assert!(i < Arch::PAGE_ENTRY_NUM);
|
||||
let next_table = table.next_level_table(i);
|
||||
if let Some(next_table) = next_table {
|
||||
table = next_table;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 支持2M、1G大页,即页表层级为1、2级的页表可以映射大页
|
||||
if table.level == 0 || table.level > 2 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let (phys, count) = self.frame_allocator.allocate(PageFrameCount::new(
|
||||
Arch::PAGE_ENTRY_NUM.pow(table.level as u32),
|
||||
))?;
|
||||
|
||||
MMArch::write_bytes(
|
||||
MMArch::phys_2_virt(phys).unwrap(),
|
||||
0,
|
||||
MMArch::PAGE_SIZE * count.data(),
|
||||
);
|
||||
|
||||
table.set_entry(
|
||||
table.index_of(virt)?,
|
||||
PageEntry::new(phys, flags.set_huge_page(true)),
|
||||
)?;
|
||||
Some(PageFlush::new(virt))
|
||||
}
|
||||
|
||||
/// 为虚拟地址分配指定层级的页表
|
||||
/// ## 参数
|
||||
///
|
||||
/// - `virt`: 虚拟地址
|
||||
/// - `level`: 指定页表层级
|
||||
///
|
||||
/// ## 返回值
|
||||
/// - Some(PageTable<Arch>): 虚拟地址对应层级的页表
|
||||
/// - None: 对应页表不存在
|
||||
pub unsafe fn allocate_table(
|
||||
&mut self,
|
||||
virt: VirtAddr,
|
||||
level: usize,
|
||||
) -> Option<PageTable<Arch>> {
|
||||
let table = self.get_table(virt, level + 1)?;
|
||||
let i = table.index_of(virt)?;
|
||||
let frame = self.frame_allocator.allocate_one()?;
|
||||
|
||||
// 清空这个页帧
|
||||
MMArch::write_bytes(MMArch::phys_2_virt(frame).unwrap(), 0, MMArch::PAGE_SIZE);
|
||||
|
||||
// 设置页表项的flags
|
||||
let flags: PageFlags<Arch> = PageFlags::new_page_table(virt.kind() == PageTableKind::User);
|
||||
|
||||
table.set_entry(i, PageEntry::new(frame, flags));
|
||||
table.next_level_table(i)
|
||||
}
|
||||
|
||||
/// 获取虚拟地址的指定层级页表
|
||||
/// ## 参数
|
||||
///
|
||||
/// - `virt`: 虚拟地址
|
||||
/// - `level`: 指定页表层级
|
||||
///
|
||||
/// ## 返回值
|
||||
/// - Some(PageTable<Arch>): 虚拟地址对应层级的页表
|
||||
/// - None: 对应页表不存在
|
||||
pub fn get_table(&self, virt: VirtAddr, level: usize) -> Option<PageTable<Arch>> {
|
||||
let mut table = self.table();
|
||||
if level > Arch::PAGE_LEVELS - 1 {
|
||||
return None;
|
||||
}
|
||||
|
||||
unsafe {
|
||||
loop {
|
||||
if table.level == level {
|
||||
return Some(table);
|
||||
}
|
||||
let i = table.index_of(virt)?;
|
||||
assert!(i < Arch::PAGE_ENTRY_NUM);
|
||||
|
||||
table = table.next_level_table(i)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 获取虚拟地址在指定层级页表的PageEntry
|
||||
/// ## 参数
|
||||
///
|
||||
/// - `virt`: 虚拟地址
|
||||
/// - `level`: 指定页表层级
|
||||
///
|
||||
/// ## 返回值
|
||||
/// - Some(PageEntry<Arch>): 虚拟地址在指定层级的页表的有效PageEntry
|
||||
/// - None: 无对应的有效PageEntry
|
||||
pub fn get_entry(&self, virt: VirtAddr, level: usize) -> Option<PageEntry<Arch>> {
|
||||
let table = self.get_table(virt, level)?;
|
||||
let i = table.index_of(virt)?;
|
||||
let entry = unsafe { table.entry(i) }?;
|
||||
|
||||
if !entry.empty() {
|
||||
Some(entry)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
|
||||
// let mut table = self.table();
|
||||
// if level > Arch::PAGE_LEVELS - 1 {
|
||||
// return None;
|
||||
// }
|
||||
// unsafe {
|
||||
// loop {
|
||||
// let i = table.index_of(virt)?;
|
||||
// assert!(i < Arch::PAGE_ENTRY_NUM);
|
||||
|
||||
// if table.level == level {
|
||||
// let entry = table.entry(i)?;
|
||||
// if !entry.empty() {
|
||||
// return Some(entry);
|
||||
// } else {
|
||||
// return None;
|
||||
// }
|
||||
// }
|
||||
|
||||
// table = table.next_level_table(i)?;
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
/// 拷贝用户空间映射
|
||||
/// ## 参数
|
||||
///
|
||||
/// - `umapper`: 要拷贝的用户空间
|
||||
/// - `copy_on_write`: 是否写时复制
|
||||
pub unsafe fn clone_user_mapping(&mut self, umapper: &mut Self, copy_on_write: bool) {
|
||||
let old_table = umapper.table();
|
||||
let new_table = self.table();
|
||||
let allocator = self.allocator_mut();
|
||||
// 顶级页表的[0, PAGE_KERNEL_INDEX)项为用户空间映射
|
||||
for entry_index in 0..Arch::PAGE_KERNEL_INDEX {
|
||||
if let Some(next_table) = old_table.next_level_table(entry_index) {
|
||||
let table = next_table.clone(allocator, copy_on_write).unwrap();
|
||||
let old_entry = old_table.entry(entry_index).unwrap();
|
||||
let entry = PageEntry::new(table.phys(), old_entry.flags());
|
||||
new_table.set_entry(entry_index, entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 将物理地址映射到具有线性偏移量的虚拟地址
|
||||
#[allow(dead_code)]
|
||||
pub unsafe fn map_linearly(
|
||||
|
Reference in New Issue
Block a user