mm: Convert page_vma_mapped_walk to work on PFNs

page_mapped_in_vma() really just wants to walk one page, but as the
code stands, if passed the head page of a compound page, it will
walk every page in the compound page.  Extract pfn/nr_pages/pgoff
from the struct page early, so they can be overridden by
page_mapped_in_vma().
Signed-off-by: default avatarMatthew Wilcox (Oracle) <willy@infradead.org>
parent aef13dec
...@@ -970,6 +970,11 @@ static inline struct hstate *page_hstate(struct page *page) ...@@ -970,6 +970,11 @@ static inline struct hstate *page_hstate(struct page *page)
return NULL; return NULL;
} }
static inline struct hstate *size_to_hstate(unsigned long size)
{
return NULL;
}
static inline unsigned long huge_page_size(struct hstate *h) static inline unsigned long huge_page_size(struct hstate *h)
{ {
return PAGE_SIZE; return PAGE_SIZE;
......
...@@ -11,6 +11,7 @@ ...@@ -11,6 +11,7 @@
#include <linux/rwsem.h> #include <linux/rwsem.h>
#include <linux/memcontrol.h> #include <linux/memcontrol.h>
#include <linux/highmem.h> #include <linux/highmem.h>
#include <linux/pagemap.h>
/* /*
* The anon_vma heads a list of private "related" vmas, to scan if * The anon_vma heads a list of private "related" vmas, to scan if
...@@ -201,11 +202,13 @@ int make_device_exclusive_range(struct mm_struct *mm, unsigned long start, ...@@ -201,11 +202,13 @@ int make_device_exclusive_range(struct mm_struct *mm, unsigned long start,
/* Avoid racy checks */ /* Avoid racy checks */
#define PVMW_SYNC (1 << 0) #define PVMW_SYNC (1 << 0)
/* Look for migarion entries rather than present PTEs */ /* Look for migration entries rather than present PTEs */
#define PVMW_MIGRATION (1 << 1) #define PVMW_MIGRATION (1 << 1)
struct page_vma_mapped_walk { struct page_vma_mapped_walk {
struct page *page; unsigned long pfn;
unsigned long nr_pages;
pgoff_t pgoff;
struct vm_area_struct *vma; struct vm_area_struct *vma;
unsigned long address; unsigned long address;
pmd_t *pmd; pmd_t *pmd;
...@@ -216,7 +219,9 @@ struct page_vma_mapped_walk { ...@@ -216,7 +219,9 @@ struct page_vma_mapped_walk {
#define DEFINE_PAGE_VMA_WALK(name, _page, _vma, _address, _flags) \ #define DEFINE_PAGE_VMA_WALK(name, _page, _vma, _address, _flags) \
struct page_vma_mapped_walk name = { \ struct page_vma_mapped_walk name = { \
.page = _page, \ .pfn = page_to_pfn(_page), \
.nr_pages = compound_nr(page), \
.pgoff = page_to_pgoff(page), \
.vma = _vma, \ .vma = _vma, \
.address = _address, \ .address = _address, \
.flags = _flags, \ .flags = _flags, \
...@@ -224,7 +229,9 @@ struct page_vma_mapped_walk { ...@@ -224,7 +229,9 @@ struct page_vma_mapped_walk {
#define DEFINE_FOLIO_VMA_WALK(name, _folio, _vma, _address, _flags) \ #define DEFINE_FOLIO_VMA_WALK(name, _folio, _vma, _address, _flags) \
struct page_vma_mapped_walk name = { \ struct page_vma_mapped_walk name = { \
.page = &_folio->page, \ .pfn = folio_pfn(_folio), \
.nr_pages = folio_nr_pages(_folio), \
.pgoff = folio_pgoff(_folio), \
.vma = _vma, \ .vma = _vma, \
.address = _address, \ .address = _address, \
.flags = _flags, \ .flags = _flags, \
...@@ -233,7 +240,7 @@ struct page_vma_mapped_walk { ...@@ -233,7 +240,7 @@ struct page_vma_mapped_walk {
static inline void page_vma_mapped_walk_done(struct page_vma_mapped_walk *pvmw) static inline void page_vma_mapped_walk_done(struct page_vma_mapped_walk *pvmw)
{ {
/* HugeTLB pte is set to the relevant page table entry without pte_mapped. */ /* HugeTLB pte is set to the relevant page table entry without pte_mapped. */
if (pvmw->pte && !PageHuge(pvmw->page)) if (pvmw->pte && !is_vm_hugetlb_page(pvmw->vma))
pte_unmap(pvmw->pte); pte_unmap(pvmw->pte);
if (pvmw->ptl) if (pvmw->ptl)
spin_unlock(pvmw->ptl); spin_unlock(pvmw->ptl);
......
...@@ -10,6 +10,7 @@ ...@@ -10,6 +10,7 @@
#include <linux/fs.h> #include <linux/fs.h>
#include <linux/mm.h> #include <linux/mm.h>
#include <linux/pagemap.h> #include <linux/pagemap.h>
#include <linux/rmap.h>
#include <linux/tracepoint-defs.h> #include <linux/tracepoint-defs.h>
struct folio_batch; struct folio_batch;
...@@ -475,18 +476,20 @@ vma_address(struct page *page, struct vm_area_struct *vma) ...@@ -475,18 +476,20 @@ vma_address(struct page *page, struct vm_area_struct *vma)
} }
/* /*
* Then at what user virtual address will none of the page be found in vma? * Then at what user virtual address will none of the range be found in vma?
* Assumes that vma_address() already returned a good starting address. * Assumes that vma_address() already returned a good starting address.
* If page is a compound head, the entire compound page is considered.
*/ */
static inline unsigned long static inline unsigned long vma_address_end(struct page_vma_mapped_walk *pvmw)
vma_address_end(struct page *page, struct vm_area_struct *vma)
{ {
struct vm_area_struct *vma = pvmw->vma;
pgoff_t pgoff; pgoff_t pgoff;
unsigned long address; unsigned long address;
VM_BUG_ON_PAGE(PageKsm(page), page); /* KSM page->index unusable */ /* Common case, plus ->pgoff is invalid for KSM */
pgoff = page_to_pgoff(page) + compound_nr(page); if (pvmw->nr_pages == 1)
return pvmw->address + PAGE_SIZE;
pgoff = pvmw->pgoff + pvmw->nr_pages;
address = vma->vm_start + ((pgoff - vma->vm_pgoff) << PAGE_SHIFT); address = vma->vm_start + ((pgoff - vma->vm_pgoff) << PAGE_SHIFT);
/* Check for address beyond vma (or wrapped through 0?) */ /* Check for address beyond vma (or wrapped through 0?) */
if (address < vma->vm_start || address > vma->vm_end) if (address < vma->vm_start || address > vma->vm_end)
......
...@@ -174,7 +174,8 @@ void putback_movable_pages(struct list_head *l) ...@@ -174,7 +174,8 @@ void putback_movable_pages(struct list_head *l)
static bool remove_migration_pte(struct page *page, struct vm_area_struct *vma, static bool remove_migration_pte(struct page *page, struct vm_area_struct *vma,
unsigned long addr, void *old) unsigned long addr, void *old)
{ {
DEFINE_PAGE_VMA_WALK(pvmw, old, vma, addr, PVMW_SYNC | PVMW_MIGRATION); DEFINE_PAGE_VMA_WALK(pvmw, (struct page *)old, vma, addr,
PVMW_SYNC | PVMW_MIGRATION);
struct page *new; struct page *new;
pte_t pte; pte_t pte;
swp_entry_t entry; swp_entry_t entry;
...@@ -184,7 +185,7 @@ static bool remove_migration_pte(struct page *page, struct vm_area_struct *vma, ...@@ -184,7 +185,7 @@ static bool remove_migration_pte(struct page *page, struct vm_area_struct *vma,
if (PageKsm(page)) if (PageKsm(page))
new = page; new = page;
else else
new = page - pvmw.page->index + new = page - pvmw.pgoff +
linear_page_index(vma, pvmw.address); linear_page_index(vma, pvmw.address);
#ifdef CONFIG_ARCH_ENABLE_THP_MIGRATION #ifdef CONFIG_ARCH_ENABLE_THP_MIGRATION
......
...@@ -53,18 +53,6 @@ static bool map_pte(struct page_vma_mapped_walk *pvmw) ...@@ -53,18 +53,6 @@ static bool map_pte(struct page_vma_mapped_walk *pvmw)
return true; return true;
} }
static inline bool pfn_is_match(struct page *page, unsigned long pfn)
{
unsigned long page_pfn = page_to_pfn(page);
/* normal page and hugetlbfs page */
if (!PageTransCompound(page) || PageHuge(page))
return page_pfn == pfn;
/* THP can be referenced by any subpage */
return pfn >= page_pfn && pfn - page_pfn < thp_nr_pages(page);
}
/** /**
* check_pte - check if @pvmw->page is mapped at the @pvmw->pte * check_pte - check if @pvmw->page is mapped at the @pvmw->pte
* @pvmw: page_vma_mapped_walk struct, includes a pair pte and page for checking * @pvmw: page_vma_mapped_walk struct, includes a pair pte and page for checking
...@@ -116,7 +104,17 @@ static bool check_pte(struct page_vma_mapped_walk *pvmw) ...@@ -116,7 +104,17 @@ static bool check_pte(struct page_vma_mapped_walk *pvmw)
pfn = pte_pfn(*pvmw->pte); pfn = pte_pfn(*pvmw->pte);
} }
return pfn_is_match(pvmw->page, pfn); return (pfn - pvmw->pfn) < pvmw->nr_pages;
}
/* Returns true if the two ranges overlap. Careful to not overflow. */
static bool check_pmd(unsigned long pfn, struct page_vma_mapped_walk *pvmw)
{
if ((pfn + HPAGE_PMD_NR - 1) < pvmw->pfn)
return false;
if (pfn > pvmw->pfn + pvmw->nr_pages - 1)
return false;
return true;
} }
static void step_forward(struct page_vma_mapped_walk *pvmw, unsigned long size) static void step_forward(struct page_vma_mapped_walk *pvmw, unsigned long size)
...@@ -127,7 +125,7 @@ static void step_forward(struct page_vma_mapped_walk *pvmw, unsigned long size) ...@@ -127,7 +125,7 @@ static void step_forward(struct page_vma_mapped_walk *pvmw, unsigned long size)
} }
/** /**
* page_vma_mapped_walk - check if @pvmw->page is mapped in @pvmw->vma at * page_vma_mapped_walk - check if @pvmw->pfn is mapped in @pvmw->vma at
* @pvmw->address * @pvmw->address
* @pvmw: pointer to struct page_vma_mapped_walk. page, vma, address and flags * @pvmw: pointer to struct page_vma_mapped_walk. page, vma, address and flags
* must be set. pmd, pte and ptl must be NULL. * must be set. pmd, pte and ptl must be NULL.
...@@ -152,8 +150,8 @@ static void step_forward(struct page_vma_mapped_walk *pvmw, unsigned long size) ...@@ -152,8 +150,8 @@ static void step_forward(struct page_vma_mapped_walk *pvmw, unsigned long size)
*/ */
bool page_vma_mapped_walk(struct page_vma_mapped_walk *pvmw) bool page_vma_mapped_walk(struct page_vma_mapped_walk *pvmw)
{ {
struct mm_struct *mm = pvmw->vma->vm_mm; struct vm_area_struct *vma = pvmw->vma;
struct page *page = pvmw->page; struct mm_struct *mm = vma->vm_mm;
unsigned long end; unsigned long end;
pgd_t *pgd; pgd_t *pgd;
p4d_t *p4d; p4d_t *p4d;
...@@ -164,32 +162,26 @@ bool page_vma_mapped_walk(struct page_vma_mapped_walk *pvmw) ...@@ -164,32 +162,26 @@ bool page_vma_mapped_walk(struct page_vma_mapped_walk *pvmw)
if (pvmw->pmd && !pvmw->pte) if (pvmw->pmd && !pvmw->pte)
return not_found(pvmw); return not_found(pvmw);
if (unlikely(PageHuge(page))) { if (unlikely(is_vm_hugetlb_page(vma))) {
unsigned long size = pvmw->nr_pages * PAGE_SIZE;
/* The only possible mapping was handled on last iteration */ /* The only possible mapping was handled on last iteration */
if (pvmw->pte) if (pvmw->pte)
return not_found(pvmw); return not_found(pvmw);
/* when pud is not present, pte will be NULL */ /* when pud is not present, pte will be NULL */
pvmw->pte = huge_pte_offset(mm, pvmw->address, page_size(page)); pvmw->pte = huge_pte_offset(mm, pvmw->address, size);
if (!pvmw->pte) if (!pvmw->pte)
return false; return false;
pvmw->ptl = huge_pte_lockptr(page_hstate(page), mm, pvmw->pte); pvmw->ptl = huge_pte_lockptr(size_to_hstate(size), mm,
pvmw->pte);
spin_lock(pvmw->ptl); spin_lock(pvmw->ptl);
if (!check_pte(pvmw)) if (!check_pte(pvmw))
return not_found(pvmw); return not_found(pvmw);
return true; return true;
} }
/* end = vma_address_end(pvmw);
* Seek to next pte only makes sense for THP.
* But more important than that optimization, is to filter out
* any PageKsm page: whose page->index misleads vma_address()
* and vma_address_end() to disaster.
*/
end = PageTransCompound(page) ?
vma_address_end(page, pvmw->vma) :
pvmw->address + PAGE_SIZE;
if (pvmw->pte) if (pvmw->pte)
goto next_pte; goto next_pte;
restart: restart:
...@@ -224,7 +216,7 @@ bool page_vma_mapped_walk(struct page_vma_mapped_walk *pvmw) ...@@ -224,7 +216,7 @@ bool page_vma_mapped_walk(struct page_vma_mapped_walk *pvmw)
if (likely(pmd_trans_huge(pmde))) { if (likely(pmd_trans_huge(pmde))) {
if (pvmw->flags & PVMW_MIGRATION) if (pvmw->flags & PVMW_MIGRATION)
return not_found(pvmw); return not_found(pvmw);
if (pmd_page(pmde) != page) if (!check_pmd(pmd_pfn(pmde), pvmw))
return not_found(pvmw); return not_found(pvmw);
return true; return true;
} }
...@@ -236,7 +228,7 @@ bool page_vma_mapped_walk(struct page_vma_mapped_walk *pvmw) ...@@ -236,7 +228,7 @@ bool page_vma_mapped_walk(struct page_vma_mapped_walk *pvmw)
return not_found(pvmw); return not_found(pvmw);
entry = pmd_to_swp_entry(pmde); entry = pmd_to_swp_entry(pmde);
if (!is_migration_entry(entry) || if (!is_migration_entry(entry) ||
pfn_swap_entry_to_page(entry) != page) !check_pmd(swp_offset(entry), pvmw))
return not_found(pvmw); return not_found(pvmw);
return true; return true;
} }
...@@ -250,7 +242,8 @@ bool page_vma_mapped_walk(struct page_vma_mapped_walk *pvmw) ...@@ -250,7 +242,8 @@ bool page_vma_mapped_walk(struct page_vma_mapped_walk *pvmw)
* cleared *pmd but not decremented compound_mapcount(). * cleared *pmd but not decremented compound_mapcount().
*/ */
if ((pvmw->flags & PVMW_SYNC) && if ((pvmw->flags & PVMW_SYNC) &&
PageTransCompound(page)) { transparent_hugepage_active(vma) &&
(pvmw->nr_pages >= HPAGE_PMD_NR)) {
spinlock_t *ptl = pmd_lock(mm, pvmw->pmd); spinlock_t *ptl = pmd_lock(mm, pvmw->pmd);
spin_unlock(ptl); spin_unlock(ptl);
...@@ -307,7 +300,8 @@ bool page_vma_mapped_walk(struct page_vma_mapped_walk *pvmw) ...@@ -307,7 +300,8 @@ bool page_vma_mapped_walk(struct page_vma_mapped_walk *pvmw)
int page_mapped_in_vma(struct page *page, struct vm_area_struct *vma) int page_mapped_in_vma(struct page *page, struct vm_area_struct *vma)
{ {
struct page_vma_mapped_walk pvmw = { struct page_vma_mapped_walk pvmw = {
.page = page, .pfn = page_to_pfn(page),
.nr_pages = 1,
.vma = vma, .vma = vma,
.flags = PVMW_SYNC, .flags = PVMW_SYNC,
}; };
......
...@@ -940,7 +940,7 @@ static bool page_mkclean_one(struct page *page, struct vm_area_struct *vma, ...@@ -940,7 +940,7 @@ static bool page_mkclean_one(struct page *page, struct vm_area_struct *vma,
*/ */
mmu_notifier_range_init(&range, MMU_NOTIFY_PROTECTION_PAGE, mmu_notifier_range_init(&range, MMU_NOTIFY_PROTECTION_PAGE,
0, vma, vma->vm_mm, address, 0, vma, vma->vm_mm, address,
vma_address_end(page, vma)); vma_address_end(&pvmw));
mmu_notifier_invalidate_range_start(&range); mmu_notifier_invalidate_range_start(&range);
while (page_vma_mapped_walk(&pvmw)) { while (page_vma_mapped_walk(&pvmw)) {
...@@ -1437,8 +1437,7 @@ static bool try_to_unmap_one(struct page *page, struct vm_area_struct *vma, ...@@ -1437,8 +1437,7 @@ static bool try_to_unmap_one(struct page *page, struct vm_area_struct *vma,
* Note that the page can not be free in this function as call of * Note that the page can not be free in this function as call of
* try_to_unmap() must hold a reference on the page. * try_to_unmap() must hold a reference on the page.
*/ */
range.end = PageKsm(page) ? range.end = vma_address_end(&pvmw);
address + PAGE_SIZE : vma_address_end(page, vma);
mmu_notifier_range_init(&range, MMU_NOTIFY_CLEAR, 0, vma, vma->vm_mm, mmu_notifier_range_init(&range, MMU_NOTIFY_CLEAR, 0, vma, vma->vm_mm,
address, range.end); address, range.end);
if (PageHuge(page)) { if (PageHuge(page)) {
...@@ -1732,8 +1731,7 @@ static bool try_to_migrate_one(struct page *page, struct vm_area_struct *vma, ...@@ -1732,8 +1731,7 @@ static bool try_to_migrate_one(struct page *page, struct vm_area_struct *vma,
* Note that the page can not be free in this function as call of * Note that the page can not be free in this function as call of
* try_to_unmap() must hold a reference on the page. * try_to_unmap() must hold a reference on the page.
*/ */
range.end = PageKsm(page) ? range.end = vma_address_end(&pvmw);
address + PAGE_SIZE : vma_address_end(page, vma);
mmu_notifier_range_init(&range, MMU_NOTIFY_CLEAR, 0, vma, vma->vm_mm, mmu_notifier_range_init(&range, MMU_NOTIFY_CLEAR, 0, vma, vma->vm_mm,
address, range.end); address, range.end);
if (PageHuge(page)) { if (PageHuge(page)) {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment