Skip to content

Conversation

kaleidoscope416
Copy link
Contributor

No description provided.

@github-actions github-actions bot added the enhancement New feature or request label Aug 15, 2025
@kaleidoscope416 kaleidoscope416 force-pushed the sys_mincore branch 4 times, most recently from d45967d to d18c8cc Compare August 15, 2025 14:57
Comment on lines 46 to 49
let mut writer = UserBufferWriter::new(vec as *mut u8, page_count, true)?;
let mut tmp: Vec<u8> = vec![0; page_count];
let page_count = PageFrameCount::new(page_count);
current_address_space
Copy link
Member

Choose a reason for hiding this comment

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

这里没必要多动态申请内存,导致多拷贝一次。有userbuffer writer就能安全访问了。

Copy link
Member

Choose a reason for hiding this comment

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

测试程序放到c_unitest目录下,并且要有判断成功/失败的条件,具体让ai写个测试用例的demo就知道了。这个测试程序没法看出成功或失败。

Comment on lines 11 to 29
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.is_anonymous()) {
//todo: 当进程是否拥有文件写权限或是文件所有者,才允许对映射了文件的vma调用mincore,否则将对应地址范围的位图置为0
}
//todo: 处理大页
self.mincore_walk_page_range(mapper, start_addr, end_addr, 3, vec, offset);
Ok(())
}
Copy link
Member

Choose a reason for hiding this comment

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

Comment on lines 79 to 81
for i in 0..nr {
vec[vec_offset + i] = 0;
}
Copy link
Member

Choose a reason for hiding this comment

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

这里有两个问题:

  1. 批量赋值为的不应该这样写,而是应该用数组切片的fill方法。
  2. 没有校验offset+i是否会溢出下标导致panic

Copy link
Contributor Author

Choose a reason for hiding this comment

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

缓冲区的容量在一开始就开好了,检查固定页数

Comment on lines +86 to +87
let page_cache = guard.vm_file().unwrap().inode().page_cache();
match page_cache {
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为空。

Comment on lines 90 to 94
if page_cache.lock_irqsave().get_page(pgoff + i).is_some() {
vec[vec_offset + i] = 1;
} else {
vec[vec_offset + i] = 0;
}
Copy link
Member

Choose a reason for hiding this comment

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

这里每个操作都很轻量,没必要多次加锁。因为加锁本身的开销大于一次get_page.

Comment on lines 98 to 100
for i in 0..nr {
vec[vec_offset + i] = 0;
}
Copy link
Member

Choose a reason for hiding this comment

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

如上,创建新的切片然后使用fill()

@kaleidoscope416
Copy link
Contributor Author

@fslongjin

kaleidoscope416 and others added 3 commits September 4, 2025 16:49
- 添加大页支持,在遇到大页时按4K粒度填充
- 修复地址对齐检查,将assert改为返回EINVAL错误
- 严格验证用户缓冲区映射与写权限
- 修复VMA查找和排序逻辑,确保地址连续性判断正确
- 添加覆盖完整性校验
- 重写mincore测试用例,增加边界条件和文件映射测试

Signed-off-by: longjin <[email protected]>
@fslongjin
Copy link
Member

b1cf6bf 这个commit里面修复了几个bug,并且完善了测试用例。以下是GPT-5对问题的解析。

PageFault 中 find_nearest 导致的 "can not find nearest vma" 问题解析与修复

本文解释 test_mincore_filemap 触发的缺页错误日志:

  • 报错:can not find nearest vma, address: 0x...
  • 场景:对 mmap 返回的 addr[0] 进行读取触发 page fault(或随后 mincore),期望命中该文件映射的 VMA。
  • 结果:未找到 VMA,触发 SIGSEGV。

一、调用链概览(读触发缺页)

用户态:
  fd = open(...)
  addr = mmap(NULL, len, PROT_READ, MAP_PRIVATE, fd, 0)
  c = addr[0]           # 触发缺页

内核态:
  do_page_fault()
    -> PageFaultHandler::handle_page_fault()
      -> AddressSpace::current().{read,write}_irqsave()
      -> space_guard.mappings.find_nearest(fault_addr)  # 定位VMA
      -> 若找不到 VMA:打印 "can not find nearest vma" 并 SIGSEGV
      -> 若找到 VMA:继续各类 fault(匿名/文件/写时复制/...)

二、问题根因

1) find_nearest 方向错误(向上找最近)

当 fault 地址 a 落在某 VMA 区间内部或其上方空洞内时,find_nearest(a) 正确策略应是:

  • “向下取整”:在所有 start <= a 的 VMA 中选 start 最大的一项(floor)。
  • 一旦包含 a,直接返回该 VMA。

而问题版本采用了“向上取最近”(找 start >= a 的最小 start),导致如下反例:

地址轴:

   [VMA-1: s1 ---------------- e1)   gap   [VMA-2: s2 ---------- e2)
                 ^ a(缺页地址位于 VMA-1 内或其上方)

错误策略(向上找)会尝试找 start >= a:
  - 若 a 位于 VMA-1 内部:s1 < a < e1,则没有 start >= a 的更小 VMA 起点(VMA-1 被错过),返回 None
  - 若 a 位于 VMA-1 与 VMA-2 的 gap 中:s2 >= a,可返回 VMA-2,但它不包含 a,后续访问也会失败

正确策略(向下找):
  - 选择所有 start <= a 的 VMA 中起点最大的 VMA(floor),可命中 VMA-1

问题代码(历史实现,伪化展示要点):

// 旧:向上取最近
if guard.region.start >= vaddr && guard.region.start < nearest.start {
    nearest = v
}

修复后(向下取最近):

// 新:向下取最近
if guard.region.start <= vaddr && guard.region.start > nearest.start {
    nearest = v
}

对应实现位置:kernel/src/mm/ucontext.rsUserMappings::find_nearest()


2) 文件映射回调 inode.mmap() 使用了 hint 而非实际映射地址

mmap() 时,内核可能不使用用户的 hint(start_vaddr),而是选择一个合适的空闲区域并返回实际映射起点 start_page.virt_address()

问题版本中,文件侧回调使用了 hint:

// 旧:传入 hint
file.inode().mmap(start_vaddr.data(), len, offset);
``;

当 hint 与实际起点不同,文件/页缓存侧元数据会记录错误的基地址,随后的访问/预取/缺页处理可能与 VMA 不一致,叠加(1)的“向上取最近”问题,更容易走到找不到 VMA 的路径。

修复:使用实际映射起点:

```text
// 新:传入真实起点
file.inode().mmap(start_page.virt_address().data(), len, offset);

对应实现位置:kernel/src/mm/ucontext.rsInnerAddressSpace::file_mapping()


三、可视化示例(错误 vs 正确)

场景 1:fault 地址在 VMA-1 内部

地址: ─────────────────────────────────────────────────────────────→
        s1                a               e1      s2            e2
        |-----------------|---------------|       |--------------|
              VMA-1(文件映射)                  VMA-2(其他)

旧 find_nearest(向上找):尝试找 start >= a,VMA-1 被错过 → None → SIGSEGV
新 find_nearest(向下找):选 start <= a 最大的 s1 → 命中 VMA-1 → 继续缺页流程

场景 2:fault 地址在 VMA-1 与 VMA-2 之间的 gap

地址: ─────────────────────────────────────────────────────────────→
        s1                e1      a        s2            e2
        |-----------------|       |        |--------------|
              VMA-1(文件映射)           VMA-2(其他)

旧 find_nearest:返回 s2(向上最近),但 region 不包含 a → 失败
新 find_nearest:返回 s1(向下最近),region 不包含 a → 继续 stack grow 或报错逻辑更合理

四、修复点代码(简)

  1. find_nearest 向下取最近:
// 文件:kernel/src/mm/ucontext.rs
// 逻辑:若包含 a 则直接返回;否则在 start <= a 的 VMA 中取 start 最大者
if guard.region.contains(vaddr) {
    return Some(v.clone());
}
if guard.region.start <= vaddr
   && (nearest.is_none() || guard.region.start > nearest.start) {
    nearest = Some(v.clone());
}
  1. 文件映射回调使用真实起点:
// 文件:kernel/src/mm/ucontext.rs(file_mapping)
file.inode().mmap(start_page.virt_address().data(), len, offset);

五、联动修复与配套测试

  • sys_mincore

    • 未对齐返回 EINVALvec 不可写返回 EFAULT,只读锁访问地址空间;
    • 用户缓冲区使用 UserBufferWriter::new_checked + buffer_checked 确保权限校验。
  • AddressSpace::mincore

    • 对冲突 VMA 排序,按地址连续性处理;
    • 尾部覆盖性校验,缺失返回 ENOMEM
  • mincore 巨页:

    • 若上层条目标记为大页,按 4K 子页粒度批量填充驻留位。
  • C 单测补充

    • test_mincore_unaligned:未对齐 → EINVAL
    • test_mincore_len0:长度 0 → EINVAL
    • test_mincore_hole:跨空洞 → ENOMEM
    • test_mincore_vec_efaultvec 不可写 → EFAULT
    • test_mincore_filemap:文件映射后读取一页,第二次 mincore 低位变 1;使用 mkstemp 创建临时文件,避免依赖 /tmp

六、结论与收益

  • 将 VMA 查找策略修正为“向下取最近”,与缺页处理在实际系统中的通用做法一致;
  • 确保文件映射的文件侧回调使用真实的映射起点,避免 VMA 与 FS 元数据不一致;
  • 结合 mincore 的边界条件处理与单测补全,整体行为与 Linux 语义更加贴近且稳定。

@fslongjin fslongjin merged commit be0c86d into DragonOS-Community:master Sep 24, 2025
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants