Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
124 changes: 124 additions & 0 deletions kernel/src/mm/mincore.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
use crate::arch::MMArch;
use crate::{
arch::mm::PageMapper,
mm::{ucontext::LockedVMA, MemoryManagementArch, VirtAddr},
};
use system_error::SystemError;

impl LockedVMA {
pub fn do_mincore(
&self,
mapper: &PageMapper,
vec: &mut [u8],
start_addr: VirtAddr,
end_addr: VirtAddr,
offset: usize,
) -> Result<(), SystemError> {
let total_pages = (end_addr - start_addr) >> MMArch::PAGE_SHIFT;
if vec.len() < total_pages + offset {
return Err(SystemError::EINVAL);
}

if !self.can_do_mincore() {
let pages = (end_addr - start_addr) >> MMArch::PAGE_SHIFT;
vec[offset..offset + pages].fill(0);
return Ok(());
}
// 支持多级页表遍历;在遇到大页时按4K粒度填充
self.mincore_walk_page_range(mapper, start_addr, end_addr, 3, vec, offset);
Ok(())
}

fn mincore_walk_page_range(
&self,
mapper: &PageMapper,
start_addr: VirtAddr,
end_addr: VirtAddr,
level: usize,
vec: &mut [u8],
vec_offset: usize,
) -> usize {
let mut page_count = 0;
let mut start = start_addr;
while start < end_addr {
let entry_size = MMArch::PAGE_SIZE << (level * MMArch::PAGE_ENTRY_SHIFT);
let next = core::cmp::min(end_addr, start + entry_size);
if let Some(entry) = mapper.get_entry(start, level) {
// 大页处理:当上层条目标记为大页时,按子页数量批量填充
if level > 0 && entry.flags().has_flag(MMArch::ENTRY_FLAG_HUGE_PAGE) {
let sub_pages = (next - start) >> MMArch::PAGE_SHIFT;
let val = if entry.present() { 1 } else { 0 };
vec[vec_offset + page_count..vec_offset + page_count + sub_pages].fill(val);
page_count += sub_pages;
} else if level > 0 {
let sub_pages = self.mincore_walk_page_range(
mapper,
start,
next,
level - 1,
vec,
vec_offset + page_count,
);
page_count += sub_pages;
} else {
vec[vec_offset + page_count] = if entry.present() { 1 } else { 0 };
page_count += 1;
}
} else {
let unmapped_pages =
self.mincore_unmapped_range(start, next, vec, vec_offset + page_count);
page_count += unmapped_pages;
}
start = next;
}
page_count
}

fn mincore_unmapped_range(
&self,
start_addr: VirtAddr,
end_addr: VirtAddr,
vec: &mut [u8],
vec_offset: usize,
) -> usize {
let nr = (end_addr - start_addr) >> MMArch::PAGE_SHIFT;
if self.is_anonymous() {
vec[vec_offset..vec_offset + nr].fill(0);
} else {
let guard = self.lock_irqsave();
let pgoff = ((start_addr - guard.region().start()) >> MMArch::PAGE_SHIFT)
+ guard.file_page_offset().unwrap();
if guard.vm_file().is_none() {
vec[vec_offset..vec_offset + nr].fill(0);
return nr;
}
let page_cache = guard.vm_file().unwrap().inode().page_cache();
match page_cache {
Comment on lines +95 to +96
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这里直接unwrap可能会panic. 比如inode刚好删掉了,然后page cache因为是延迟清除的所以导致vm_file为空。

Some(page_cache) => {
let cache_guard = page_cache.lock_irqsave();
for i in 0..nr {
if cache_guard.get_page(pgoff + i).is_some() {
vec[vec_offset + i] = 1;
} else {
vec[vec_offset + i] = 0;
}
}
}
None => {
vec[vec_offset..vec_offset + nr].fill(0);
}
}
}
nr
}

pub fn can_do_mincore(&self) -> bool {
//todo: 没有实现vm_ops,这里只能找到匿名映射和文件映射。对于设备映射和其他特殊映射(对应linux中vm_ops有值,但不是文件映射的vma),返回false
if self.is_accessible() {
return true;
} else {
//todo: 若文件不是当前用户所有,需要当前用户对文件有写权限,否则返回false
return true;
}
}
}
1 change: 1 addition & 0 deletions kernel/src/mm/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ pub mod init;
pub mod kernel_mapper;
pub mod madvise;
pub mod memblock;
pub mod mincore;
pub mod mmio_buddy;
pub mod no_init;
pub mod page;
Expand Down
43 changes: 40 additions & 3 deletions kernel/src/mm/syscall/sys_mincore.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
use crate::arch::interrupt::TrapFrame;
use crate::arch::syscall::nr::SYS_MINCORE;
use crate::arch::MMArch;
use crate::libs::align::page_align_up;
use crate::mm::allocator::page_frame::{PageFrameCount, VirtPageFrame};
use crate::mm::ucontext::AddressSpace;
use crate::mm::{verify_area, MemoryManagementArch};
use crate::syscall::table::{FormattedSyscallParam, Syscall};
use crate::syscall::user_access::UserBufferWriter;
use system_error::SystemError;

use crate::mm::VirtAddr;
use alloc::vec::Vec;

pub struct SysMincoreHandle;
Expand All @@ -14,9 +21,39 @@ impl Syscall for SysMincoreHandle {

/// ## mincore系统调用
///
/// todo: 参考 https://code.dragonos.org.cn/xref/linux-6.6.21/mm/mincore.c#232 实现mincore
fn handle(&self, _args: &[usize], _frame: &mut TrapFrame) -> Result<usize, SystemError> {
return Err(SystemError::ENOSYS);
/// ## 参数
///
/// - `start_vaddr`:起始地址(已经对齐到页)
/// - `len`:需要遍历的长度
/// - `vec`:用户空间的vec指针
fn handle(&self, args: &[usize], _frame: &mut TrapFrame) -> Result<usize, SystemError> {
let start_vaddr = VirtAddr::new(Self::start_vaddr(args));
let len = Self::len(args);
let vec = Self::vec(args);
// 未对齐返回 EINVAL,而不是触发 panic
if !start_vaddr.check_aligned(MMArch::PAGE_SIZE) {
return Err(SystemError::EINVAL);
}

if verify_area(start_vaddr, len).is_err() {
return Err(SystemError::ENOMEM);
}
if len == 0 {
return Err(SystemError::EINVAL);
}
let len = page_align_up(len);
let current_address_space = AddressSpace::current()?;
let start_frame = VirtPageFrame::new(start_vaddr);
let page_count = len >> MMArch::PAGE_SHIFT;

// 严格验证 vec 映射与写权限,失败返回 EFAULT
let mut writer = UserBufferWriter::new_checked(vec as *mut u8, page_count, true)?;
let buf: &mut [u8] = writer.buffer(0)?;
let page_count = PageFrameCount::new(page_count);
current_address_space
.read()
.mincore(start_frame, page_count, buf)?;
return Ok(0);
}

/// Formats the syscall arguments for display/debugging purposes.
Expand Down
53 changes: 48 additions & 5 deletions kernel/src/mm/ucontext.rs
Original file line number Diff line number Diff line change
Expand Up @@ -398,7 +398,7 @@ impl InnerAddressSpace {
drop(fd_table_guard);

// offset需要4K对齐
if !offset & (MMArch::PAGE_SIZE - 1) == 0 {
if (offset & (MMArch::PAGE_SIZE - 1)) != 0 {
return Err(SystemError::EINVAL);
}
let pgoff = offset >> MMArch::PAGE_SHIFT;
Expand Down Expand Up @@ -435,7 +435,10 @@ impl InnerAddressSpace {
// todo!(impl mmap for other file)
// https://github.com/DragonOS-Community/DragonOS/pull/912#discussion_r1765334272
let file = file.unwrap();
let _ = file.inode().mmap(start_vaddr.data(), len, offset);
// 传入实际映射后的起始虚拟地址,而非用户传入的 hint
let _ = file
.inode()
.mmap(start_page.virt_address().data(), len, offset);
return Ok(start_page);
}

Expand Down Expand Up @@ -704,6 +707,45 @@ impl InnerAddressSpace {
return Ok(());
}

pub fn mincore(
&self,
start_page: VirtPageFrame,
page_count: PageFrameCount,
vec: &mut [u8],
) -> Result<(), SystemError> {
let mapper = &self.user_mapper.utable;

if self.mappings.contains(start_page.virt_address()).is_none() {
return Err(SystemError::ENOMEM);
}

let mut last_vaddr = start_page.virt_address();
let region = VirtRegion::new(start_page.virt_address(), page_count.bytes());
let mut vmas = self.mappings.conflicts(region).collect::<Vec<_>>();
// 为保证与地址连续性的判断正确,这里按起始地址升序遍历
vmas.sort_by_key(|v| v.lock_irqsave().region().start().data());
let mut offset = 0;
for v in vmas {
let region = *v.lock_irqsave().region();
// 保证相邻的两个vma连续
if region.start() != last_vaddr && last_vaddr != start_page.virt_address() {
return Err(SystemError::ENOMEM);
}
let start_vaddr = last_vaddr;
let end_vaddr = core::cmp::min(region.end(), start_vaddr + page_count.bytes());
v.do_mincore(mapper, vec, start_vaddr, end_vaddr, offset)?;
let page_count_this_vma = (end_vaddr - start_vaddr) >> MMArch::PAGE_SHIFT;
offset += page_count_this_vma;
last_vaddr = end_vaddr;
}

// 校验覆盖完整性:若末尾未覆盖到请求范围,则返回 ENOMEM
if last_vaddr != region.end() {
return Err(SystemError::ENOMEM);
}

return Ok(());
}
pub fn madvise(
&mut self,
start_page: VirtPageFrame,
Expand Down Expand Up @@ -974,9 +1016,10 @@ impl UserMappings {
if guard.region.contains(vaddr) {
return Some(v.clone());
}
if guard.region.start >= vaddr
&& if let Some(ref nearest) = nearest {
guard.region.start < nearest.lock_irqsave().region.start
// 选择起始地址不大于 vaddr 的 VMA 中,起始地址最大的一个
if guard.region.start <= vaddr
&& if let Some(ref current) = nearest {
guard.region.start > current.lock_irqsave().region.start
} else {
true
}
Expand Down
Loading