The do_mincore() uses walk_page_range() to walk the page table. Fortunately, the caller always passes start/end that falls within a single VMA, so it's safe to use the walk_page_range_vma() in do_mincore() to eliminate an unnecessary find_vma() lookup. Unlike walk_page_range(), walk_page_range_vma() does not call walk_page_test(), which handles VM_PFNMAP by invoking ->pte_hole() to skip the page table walk. Without this check, PFNMAP PTEs would be treated as present by mincore_pte_range(), changing the returned residency status. Handle VM_PFNMAP explicitly in do_mincore() to preserve the original behavior. Acked-by: Zi Yan Signed-off-by: Kefeng Wang --- mm/mincore.c | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/mm/mincore.c b/mm/mincore.c index 296f2e3922b5..0c6731ae6c4d 100644 --- a/mm/mincore.c +++ b/mm/mincore.c @@ -259,7 +259,21 @@ static long do_mincore(unsigned long addr, unsigned long pages, unsigned char *v memset(vec, 1, pages); return pages; } - err = walk_page_range(vma->vm_mm, addr, end, &mincore_walk_ops, vec); + + /* + * walk_page_range_vma() does not call walk_page_test(), which + * handles VM_PFNMAP VMA by invoking ->pte_hole() to skip the + * page table walk. Without this check, PFNMAP PTEs would be + * treated as present by mincore_pte_range(), changing the returned + * residency status from the historical "not resident" to "resident". + * Handle VM_PFNMAP explicitly to preserve the original behavior. + */ + if (vma->vm_flags & VM_PFNMAP) { + __mincore_unmapped_range(addr, end, vma, vec); + return (end - addr) >> PAGE_SHIFT; + } + + err = walk_page_range_vma(vma, addr, end, &mincore_walk_ops, vec); if (err < 0) return err; return (end - addr) >> PAGE_SHIFT; -- 2.27.0