Re: [PATCH v9 07/10] mm/vmalloc: extend page table walk to support larger page_shift sizes and eliminate page table rewalk

From: Wen Jiang

Date: Thu Oct 01 2026 - 04:08:52 EST


On Wed, 30 Sept 2026 at 23:16, Uladzislau Rezki <urezki@xxxxxxxxx> wrote:
>
> On Wed, Sep 23, 2026 at 02:28:29PM +0800, Wen Jiang wrote:
> > From: "Barry Song (Xiaomi)" <baohua@xxxxxxxxxx>
> >
> > vmap_pages_range_noflush_walk() (formerly vmap_small_pages_range_noflush())
> > provides a clean interface by taking struct page **pages and mapping them
> > via direct PTE iteration. This avoids the page table rewalk seen when
> > using vmap_range_noflush() for page_shift values other than PAGE_SHIFT.
> >
> > Extend it to support larger page_shift values, and add PMD- and
> > contiguous-PTE mappings as well.
> >
> > Rename it to vmap_pages_range_noflush_walk() since it now handles
> > more than just small pages.
> >
> > For vmalloc() allocations with VM_ALLOW_HUGE_VMAP, we no longer need to
> > iterate over pages one by one via vmap_range_noflush(), which would
> > otherwise lead to page table rewalk. The code is now unified with the
> > PAGE_SHIFT case by simply calling vmap_pages_range_noflush_walk().
> >
> > Signed-off-by: Barry Song (Xiaomi) <baohua@xxxxxxxxxx>
> > Signed-off-by: Wen Jiang <jiangwen6@xxxxxxxxxx>
> > Tested-by: Xueyuan Chen <xueyuan.chen21@xxxxxxxxx>
> > Tested-by: Leo Yan <leo.yan@xxxxxxx>
> > Reviewed-by: Dev Jain <dev.jain@xxxxxxx>
> > ---
> > mm/vmalloc.c | 84 +++++++++++++++++++++++++++++++---------------------
> > 1 file changed, 50 insertions(+), 34 deletions(-)
> >
> > diff --git a/mm/vmalloc.c b/mm/vmalloc.c
> > index d475e52110e4a..6e0dec73107a7 100644
> > --- a/mm/vmalloc.c
> > +++ b/mm/vmalloc.c
> > @@ -558,8 +558,10 @@ void vunmap_range(unsigned long addr, unsigned long end)
> >
> > static int vmap_pages_pte_range(pmd_t *pmd, unsigned long addr,
> > unsigned long end, pgprot_t prot, struct page **pages, int *nr,
> > - pgtbl_mod_mask *mask)
> > + pgtbl_mod_mask *mask, unsigned int shift)
> > {
> > + unsigned long pfn, size;
> > + unsigned int steps;
> > int err = 0;
> > pte_t *pte;
> >
> > @@ -590,9 +592,10 @@ static int vmap_pages_pte_range(pmd_t *pmd, unsigned long addr,
> > break;
> > }
> >
> > - set_pte_at(&init_mm, addr, pte, mk_pte(page, prot));
> > - (*nr)++;
> > - } while (pte++, addr += PAGE_SIZE, addr != end);
> > + pfn = page_to_pfn(page);
> > + size = vmap_set_ptes(pte, addr, end, pfn, prot, shift);
> > + steps = PFN_DOWN(size);
> > + } while (pte += steps, *nr += steps, addr += size, addr != end);
> >
> nr_mapped_pages?
>

As noted in the patch 6 reply, I'll rename steps to nr_pages here as well.

> >
> > lazy_mmu_mode_disable();
> > *mask |= PGTBL_PTE_MODIFIED;
> > @@ -602,60 +605,88 @@ static int vmap_pages_pte_range(pmd_t *pmd, unsigned long addr,
> >
> > static int vmap_pages_pmd_range(pud_t *pud, unsigned long addr,
> > unsigned long end, pgprot_t prot, struct page **pages, int *nr,
> > - pgtbl_mod_mask *mask)
> > + pgtbl_mod_mask *mask, unsigned int shift)
> > {
> > pmd_t *pmd;
> > unsigned long next;
> > + int err;
> >
> > pmd = pmd_alloc_track(&init_mm, pud, addr, mask);
> > if (!pmd)
> > return -ENOMEM;
> > do {
> > next = pmd_addr_end(addr, end);
> > - if (vmap_pages_pte_range(pmd, addr, next, prot, pages, nr, mask))
> > - return -ENOMEM;
> > +
> > + if (shift >= PMD_SHIFT) {
> > + struct page *page = pages[*nr];
> > + phys_addr_t phys_addr;
> > +
> > + if (WARN_ON(!page))
> > + return -ENOMEM;
> > + if (WARN_ON(!pfn_valid(page_to_pfn(page))))
> > + return -EINVAL;
> > +
> > + phys_addr = page_to_phys(page);
> > +
> > + if (vmap_try_huge_pmd(pmd, addr, next, phys_addr, prot, shift)) {
> > + *mask |= PGTBL_PMD_MODIFIED;
> > + *nr += 1 << (PMD_SHIFT - PAGE_SHIFT);
> >
> *nr += 1 << (PMD_SHIFT - PAGE_SHIFT); ---> *nr += 1U << (PMD_SHIFT - PAGE_SHIFT);
> as it used to be?
>

Agreed, I'll use 1U.

> > + continue;
> > + }
> > + }
> > + err = vmap_pages_pte_range(pmd, addr, next, prot, pages, nr, mask, shift);
> > + if (err)
> > + return err;
> > } while (pmd++, addr = next, addr != end);
> > return 0;
> > }
> >
> > static int vmap_pages_pud_range(p4d_t *p4d, unsigned long addr,
> > unsigned long end, pgprot_t prot, struct page **pages, int *nr,
> > - pgtbl_mod_mask *mask)
> > + pgtbl_mod_mask *mask, unsigned int shift)
> > {
> > pud_t *pud;
> > unsigned long next;
> > + int err;
> >
> > pud = pud_alloc_track(&init_mm, p4d, addr, mask);
> > if (!pud)
> > return -ENOMEM;
> > do {
> > next = pud_addr_end(addr, end);
> > - if (vmap_pages_pmd_range(pud, addr, next, prot, pages, nr, mask))
> > - return -ENOMEM;
> > + err = vmap_pages_pmd_range(pud, addr, next, prot, pages, nr, mask, shift);
> > + if (err)
> > + return err;
> > } while (pud++, addr = next, addr != end);
> > return 0;
> > }
> >
> > static int vmap_pages_p4d_range(pgd_t *pgd, unsigned long addr,
> > unsigned long end, pgprot_t prot, struct page **pages, int *nr,
> > - pgtbl_mod_mask *mask)
> > + pgtbl_mod_mask *mask, unsigned int shift)
> > {
> > p4d_t *p4d;
> > unsigned long next;
> > + int err;
> >
> > p4d = p4d_alloc_track(&init_mm, pgd, addr, mask);
> > if (!p4d)
> > return -ENOMEM;
> > do {
> > next = p4d_addr_end(addr, end);
> > - if (vmap_pages_pud_range(p4d, addr, next, prot, pages, nr, mask))
> > - return -ENOMEM;
> > + err = vmap_pages_pud_range(p4d, addr, next, prot, pages, nr, mask, shift);
> > + if (err)
> > + return err;
> > } while (p4d++, addr = next, addr != end);
> > return 0;
> > }
> >
> > -static int vmap_small_pages_range_noflush(unsigned long addr, unsigned long end,
> > - pgprot_t prot, struct page **pages)
> > +/*
> > + * It can take an array of pages which are not all contiguous, but it
> > + * may have contiguous chunks, as hinted by @shift.
> > + */
> > +static int vmap_pages_range_noflush_walk(unsigned long addr, unsigned long end,
> > + pgprot_t prot, struct page **pages, unsigned int shift)
> > {
> > unsigned long start = addr;
> > pgd_t *pgd;
> > @@ -670,7 +701,7 @@ static int vmap_small_pages_range_noflush(unsigned long addr, unsigned long end,
> > next = pgd_addr_end(addr, end);
> > if (pgd_bad(*pgd))
> > mask |= PGTBL_PGD_MODIFIED;
> > - err = vmap_pages_p4d_range(pgd, addr, next, prot, pages, &nr, &mask);
> > + err = vmap_pages_p4d_range(pgd, addr, next, prot, pages, &nr, &mask, shift);
> > if (err)
> > break;
> > } while (pgd++, addr = next, addr != end);
> > @@ -693,27 +724,12 @@ static int vmap_small_pages_range_noflush(unsigned long addr, unsigned long end,
> > int __vmap_pages_range_noflush(unsigned long addr, unsigned long end,
> > pgprot_t prot, struct page **pages, unsigned int page_shift)
> > {
> > - unsigned int i, nr = (end - addr) >> PAGE_SHIFT;
> > -
> > WARN_ON(page_shift < PAGE_SHIFT);
> >
> This is not about this patch but probably on that WARN_ON we should bail out
> early.
>

I'll send a separate cleanup patch changing it to:

if (WARN_ON_ONCE(page_shift < PAGE_SHIFT))
return -EINVAL;

Thanks,
Wen
> --
> Uladzislau Rezki