mirror of
https://github.com/torvalds/linux.git
synced 2025-12-01 07:26:02 +07:00
Merge tag 'folio-5.18c' of git://git.infradead.org/users/willy/pagecache
Pull folio updates from Matthew Wilcox:
- Rewrite how munlock works to massively reduce the contention on
i_mmap_rwsem (Hugh Dickins):
https://lore.kernel.org/linux-mm/8e4356d-9622-a7f0-b2c-f116b5f2efea@google.com/
- Sort out the page refcount mess for ZONE_DEVICE pages (Christoph
Hellwig):
https://lore.kernel.org/linux-mm/20220210072828.2930359-1-hch@lst.de/
- Convert GUP to use folios and make pincount available for order-1
pages. (Matthew Wilcox)
- Convert a few more truncation functions to use folios (Matthew
Wilcox)
- Convert page_vma_mapped_walk to use PFNs instead of pages (Matthew
Wilcox)
- Convert rmap_walk to use folios (Matthew Wilcox)
- Convert most of shrink_page_list() to use a folio (Matthew Wilcox)
- Add support for creating large folios in readahead (Matthew Wilcox)
* tag 'folio-5.18c' of git://git.infradead.org/users/willy/pagecache: (114 commits)
mm/damon: minor cleanup for damon_pa_young
selftests/vm/transhuge-stress: Support file-backed PMD folios
mm/filemap: Support VM_HUGEPAGE for file mappings
mm/readahead: Switch to page_cache_ra_order
mm/readahead: Align file mappings for non-DAX
mm/readahead: Add large folio readahead
mm: Support arbitrary THP sizes
mm: Make large folios depend on THP
mm: Fix READ_ONLY_THP warning
mm/filemap: Allow large folios to be added to the page cache
mm: Turn can_split_huge_page() into can_split_folio()
mm/vmscan: Convert pageout() to take a folio
mm/vmscan: Turn page_check_references() into folio_check_references()
mm/vmscan: Account large folios correctly
mm/vmscan: Optimise shrink_page_list for non-PMD-sized folios
mm/vmscan: Free non-shmem folios without splitting them
mm/rmap: Constify the rmap_walk_control argument
mm/rmap: Convert rmap_walk() to take a folio
mm: Turn page_anon_vma() into folio_anon_vma()
mm/rmap: Turn page_lock_anon_vma_read() into folio_lock_anon_vma_read()
...
This commit is contained in:
670
mm/mlock.c
670
mm/mlock.c
@@ -14,6 +14,7 @@
|
||||
#include <linux/swapops.h>
|
||||
#include <linux/pagemap.h>
|
||||
#include <linux/pagevec.h>
|
||||
#include <linux/pagewalk.h>
|
||||
#include <linux/mempolicy.h>
|
||||
#include <linux/syscalls.h>
|
||||
#include <linux/sched.h>
|
||||
@@ -27,6 +28,8 @@
|
||||
|
||||
#include "internal.h"
|
||||
|
||||
static DEFINE_PER_CPU(struct pagevec, mlock_pvec);
|
||||
|
||||
bool can_do_mlock(void)
|
||||
{
|
||||
if (rlimit(RLIMIT_MEMLOCK) != 0)
|
||||
@@ -46,441 +49,320 @@ EXPORT_SYMBOL(can_do_mlock);
|
||||
* be placed on the LRU "unevictable" list, rather than the [in]active lists.
|
||||
* The unevictable list is an LRU sibling list to the [in]active lists.
|
||||
* PageUnevictable is set to indicate the unevictable state.
|
||||
*
|
||||
* When lazy mlocking via vmscan, it is important to ensure that the
|
||||
* vma's VM_LOCKED status is not concurrently being modified, otherwise we
|
||||
* may have mlocked a page that is being munlocked. So lazy mlock must take
|
||||
* the mmap_lock for read, and verify that the vma really is locked
|
||||
* (see mm/rmap.c).
|
||||
*/
|
||||
|
||||
/*
|
||||
* LRU accounting for clear_page_mlock()
|
||||
*/
|
||||
void clear_page_mlock(struct page *page)
|
||||
static struct lruvec *__mlock_page(struct page *page, struct lruvec *lruvec)
|
||||
{
|
||||
int nr_pages;
|
||||
/* There is nothing more we can do while it's off LRU */
|
||||
if (!TestClearPageLRU(page))
|
||||
return lruvec;
|
||||
|
||||
if (!TestClearPageMlocked(page))
|
||||
return;
|
||||
lruvec = folio_lruvec_relock_irq(page_folio(page), lruvec);
|
||||
|
||||
nr_pages = thp_nr_pages(page);
|
||||
mod_zone_page_state(page_zone(page), NR_MLOCK, -nr_pages);
|
||||
count_vm_events(UNEVICTABLE_PGCLEARED, nr_pages);
|
||||
/*
|
||||
* The previous TestClearPageMlocked() corresponds to the smp_mb()
|
||||
* in __pagevec_lru_add_fn().
|
||||
*
|
||||
* See __pagevec_lru_add_fn for more explanation.
|
||||
*/
|
||||
if (!isolate_lru_page(page)) {
|
||||
putback_lru_page(page);
|
||||
} else {
|
||||
if (unlikely(page_evictable(page))) {
|
||||
/*
|
||||
* We lost the race. the page already moved to evictable list.
|
||||
* This is a little surprising, but quite possible:
|
||||
* PageMlocked must have got cleared already by another CPU.
|
||||
* Could this page be on the Unevictable LRU? I'm not sure,
|
||||
* but move it now if so.
|
||||
*/
|
||||
if (PageUnevictable(page))
|
||||
count_vm_events(UNEVICTABLE_PGSTRANDED, nr_pages);
|
||||
if (PageUnevictable(page)) {
|
||||
del_page_from_lru_list(page, lruvec);
|
||||
ClearPageUnevictable(page);
|
||||
add_page_to_lru_list(page, lruvec);
|
||||
__count_vm_events(UNEVICTABLE_PGRESCUED,
|
||||
thp_nr_pages(page));
|
||||
}
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Mark page as mlocked if not already.
|
||||
* If page on LRU, isolate and putback to move to unevictable list.
|
||||
*/
|
||||
void mlock_vma_page(struct page *page)
|
||||
{
|
||||
/* Serialize with page migration */
|
||||
BUG_ON(!PageLocked(page));
|
||||
|
||||
VM_BUG_ON_PAGE(PageTail(page), page);
|
||||
VM_BUG_ON_PAGE(PageCompound(page) && PageDoubleMap(page), page);
|
||||
|
||||
if (!TestSetPageMlocked(page)) {
|
||||
int nr_pages = thp_nr_pages(page);
|
||||
|
||||
mod_zone_page_state(page_zone(page), NR_MLOCK, nr_pages);
|
||||
count_vm_events(UNEVICTABLE_PGMLOCKED, nr_pages);
|
||||
if (!isolate_lru_page(page))
|
||||
putback_lru_page(page);
|
||||
if (PageUnevictable(page)) {
|
||||
if (PageMlocked(page))
|
||||
page->mlock_count++;
|
||||
goto out;
|
||||
}
|
||||
|
||||
del_page_from_lru_list(page, lruvec);
|
||||
ClearPageActive(page);
|
||||
SetPageUnevictable(page);
|
||||
page->mlock_count = !!PageMlocked(page);
|
||||
add_page_to_lru_list(page, lruvec);
|
||||
__count_vm_events(UNEVICTABLE_PGCULLED, thp_nr_pages(page));
|
||||
out:
|
||||
SetPageLRU(page);
|
||||
return lruvec;
|
||||
}
|
||||
|
||||
/*
|
||||
* Finish munlock after successful page isolation
|
||||
*
|
||||
* Page must be locked. This is a wrapper for page_mlock()
|
||||
* and putback_lru_page() with munlock accounting.
|
||||
*/
|
||||
static void __munlock_isolated_page(struct page *page)
|
||||
static struct lruvec *__mlock_new_page(struct page *page, struct lruvec *lruvec)
|
||||
{
|
||||
/*
|
||||
* Optimization: if the page was mapped just once, that's our mapping
|
||||
* and we don't need to check all the other vmas.
|
||||
*/
|
||||
if (page_mapcount(page) > 1)
|
||||
page_mlock(page);
|
||||
VM_BUG_ON_PAGE(PageLRU(page), page);
|
||||
|
||||
/* Did try_to_unlock() succeed or punt? */
|
||||
if (!PageMlocked(page))
|
||||
count_vm_events(UNEVICTABLE_PGMUNLOCKED, thp_nr_pages(page));
|
||||
lruvec = folio_lruvec_relock_irq(page_folio(page), lruvec);
|
||||
|
||||
putback_lru_page(page);
|
||||
/* As above, this is a little surprising, but possible */
|
||||
if (unlikely(page_evictable(page)))
|
||||
goto out;
|
||||
|
||||
SetPageUnevictable(page);
|
||||
page->mlock_count = !!PageMlocked(page);
|
||||
__count_vm_events(UNEVICTABLE_PGCULLED, thp_nr_pages(page));
|
||||
out:
|
||||
add_page_to_lru_list(page, lruvec);
|
||||
SetPageLRU(page);
|
||||
return lruvec;
|
||||
}
|
||||
|
||||
/*
|
||||
* Accounting for page isolation fail during munlock
|
||||
*
|
||||
* Performs accounting when page isolation fails in munlock. There is nothing
|
||||
* else to do because it means some other task has already removed the page
|
||||
* from the LRU. putback_lru_page() will take care of removing the page from
|
||||
* the unevictable list, if necessary. vmscan [page_referenced()] will move
|
||||
* the page back to the unevictable list if some other vma has it mlocked.
|
||||
*/
|
||||
static void __munlock_isolation_failed(struct page *page)
|
||||
static struct lruvec *__munlock_page(struct page *page, struct lruvec *lruvec)
|
||||
{
|
||||
int nr_pages = thp_nr_pages(page);
|
||||
bool isolated = false;
|
||||
|
||||
if (PageUnevictable(page))
|
||||
__count_vm_events(UNEVICTABLE_PGSTRANDED, nr_pages);
|
||||
else
|
||||
__count_vm_events(UNEVICTABLE_PGMUNLOCKED, nr_pages);
|
||||
if (!TestClearPageLRU(page))
|
||||
goto munlock;
|
||||
|
||||
isolated = true;
|
||||
lruvec = folio_lruvec_relock_irq(page_folio(page), lruvec);
|
||||
|
||||
if (PageUnevictable(page)) {
|
||||
/* Then mlock_count is maintained, but might undercount */
|
||||
if (page->mlock_count)
|
||||
page->mlock_count--;
|
||||
if (page->mlock_count)
|
||||
goto out;
|
||||
}
|
||||
/* else assume that was the last mlock: reclaim will fix it if not */
|
||||
|
||||
munlock:
|
||||
if (TestClearPageMlocked(page)) {
|
||||
__mod_zone_page_state(page_zone(page), NR_MLOCK, -nr_pages);
|
||||
if (isolated || !PageUnevictable(page))
|
||||
__count_vm_events(UNEVICTABLE_PGMUNLOCKED, nr_pages);
|
||||
else
|
||||
__count_vm_events(UNEVICTABLE_PGSTRANDED, nr_pages);
|
||||
}
|
||||
|
||||
/* page_evictable() has to be checked *after* clearing Mlocked */
|
||||
if (isolated && PageUnevictable(page) && page_evictable(page)) {
|
||||
del_page_from_lru_list(page, lruvec);
|
||||
ClearPageUnevictable(page);
|
||||
add_page_to_lru_list(page, lruvec);
|
||||
__count_vm_events(UNEVICTABLE_PGRESCUED, nr_pages);
|
||||
}
|
||||
out:
|
||||
if (isolated)
|
||||
SetPageLRU(page);
|
||||
return lruvec;
|
||||
}
|
||||
|
||||
/*
|
||||
* Flags held in the low bits of a struct page pointer on the mlock_pvec.
|
||||
*/
|
||||
#define LRU_PAGE 0x1
|
||||
#define NEW_PAGE 0x2
|
||||
static inline struct page *mlock_lru(struct page *page)
|
||||
{
|
||||
return (struct page *)((unsigned long)page + LRU_PAGE);
|
||||
}
|
||||
|
||||
static inline struct page *mlock_new(struct page *page)
|
||||
{
|
||||
return (struct page *)((unsigned long)page + NEW_PAGE);
|
||||
}
|
||||
|
||||
/*
|
||||
* mlock_pagevec() is derived from pagevec_lru_move_fn():
|
||||
* perhaps that can make use of such page pointer flags in future,
|
||||
* but for now just keep it for mlock. We could use three separate
|
||||
* pagevecs instead, but one feels better (munlocking a full pagevec
|
||||
* does not need to drain mlocking pagevecs first).
|
||||
*/
|
||||
static void mlock_pagevec(struct pagevec *pvec)
|
||||
{
|
||||
struct lruvec *lruvec = NULL;
|
||||
unsigned long mlock;
|
||||
struct page *page;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < pagevec_count(pvec); i++) {
|
||||
page = pvec->pages[i];
|
||||
mlock = (unsigned long)page & (LRU_PAGE | NEW_PAGE);
|
||||
page = (struct page *)((unsigned long)page - mlock);
|
||||
pvec->pages[i] = page;
|
||||
|
||||
if (mlock & LRU_PAGE)
|
||||
lruvec = __mlock_page(page, lruvec);
|
||||
else if (mlock & NEW_PAGE)
|
||||
lruvec = __mlock_new_page(page, lruvec);
|
||||
else
|
||||
lruvec = __munlock_page(page, lruvec);
|
||||
}
|
||||
|
||||
if (lruvec)
|
||||
unlock_page_lruvec_irq(lruvec);
|
||||
release_pages(pvec->pages, pvec->nr);
|
||||
pagevec_reinit(pvec);
|
||||
}
|
||||
|
||||
void mlock_page_drain(int cpu)
|
||||
{
|
||||
struct pagevec *pvec;
|
||||
|
||||
pvec = &per_cpu(mlock_pvec, cpu);
|
||||
if (pagevec_count(pvec))
|
||||
mlock_pagevec(pvec);
|
||||
}
|
||||
|
||||
bool need_mlock_page_drain(int cpu)
|
||||
{
|
||||
return pagevec_count(&per_cpu(mlock_pvec, cpu));
|
||||
}
|
||||
|
||||
/**
|
||||
* munlock_vma_page - munlock a vma page
|
||||
* @page: page to be unlocked, either a normal page or THP page head
|
||||
*
|
||||
* returns the size of the page as a page mask (0 for normal page,
|
||||
* HPAGE_PMD_NR - 1 for THP head page)
|
||||
*
|
||||
* called from munlock()/munmap() path with page supposedly on the LRU.
|
||||
* When we munlock a page, because the vma where we found the page is being
|
||||
* munlock()ed or munmap()ed, we want to check whether other vmas hold the
|
||||
* page locked so that we can leave it on the unevictable lru list and not
|
||||
* bother vmscan with it. However, to walk the page's rmap list in
|
||||
* page_mlock() we must isolate the page from the LRU. If some other
|
||||
* task has removed the page from the LRU, we won't be able to do that.
|
||||
* So we clear the PageMlocked as we might not get another chance. If we
|
||||
* can't isolate the page, we leave it for putback_lru_page() and vmscan
|
||||
* [page_referenced()/try_to_unmap()] to deal with.
|
||||
* mlock_folio - mlock a folio already on (or temporarily off) LRU
|
||||
* @folio: folio to be mlocked.
|
||||
*/
|
||||
unsigned int munlock_vma_page(struct page *page)
|
||||
void mlock_folio(struct folio *folio)
|
||||
{
|
||||
int nr_pages;
|
||||
struct pagevec *pvec = &get_cpu_var(mlock_pvec);
|
||||
|
||||
/* For page_mlock() and to serialize with page migration */
|
||||
BUG_ON(!PageLocked(page));
|
||||
VM_BUG_ON_PAGE(PageTail(page), page);
|
||||
if (!folio_test_set_mlocked(folio)) {
|
||||
int nr_pages = folio_nr_pages(folio);
|
||||
|
||||
if (!TestClearPageMlocked(page)) {
|
||||
/* Potentially, PTE-mapped THP: do not skip the rest PTEs */
|
||||
return 0;
|
||||
zone_stat_mod_folio(folio, NR_MLOCK, nr_pages);
|
||||
__count_vm_events(UNEVICTABLE_PGMLOCKED, nr_pages);
|
||||
}
|
||||
|
||||
nr_pages = thp_nr_pages(page);
|
||||
mod_zone_page_state(page_zone(page), NR_MLOCK, -nr_pages);
|
||||
|
||||
if (!isolate_lru_page(page))
|
||||
__munlock_isolated_page(page);
|
||||
else
|
||||
__munlock_isolation_failed(page);
|
||||
|
||||
return nr_pages - 1;
|
||||
folio_get(folio);
|
||||
if (!pagevec_add(pvec, mlock_lru(&folio->page)) ||
|
||||
folio_test_large(folio) || lru_cache_disabled())
|
||||
mlock_pagevec(pvec);
|
||||
put_cpu_var(mlock_pvec);
|
||||
}
|
||||
|
||||
/*
|
||||
* convert get_user_pages() return value to posix mlock() error
|
||||
/**
|
||||
* mlock_new_page - mlock a newly allocated page not yet on LRU
|
||||
* @page: page to be mlocked, either a normal page or a THP head.
|
||||
*/
|
||||
static int __mlock_posix_error_return(long retval)
|
||||
void mlock_new_page(struct page *page)
|
||||
{
|
||||
if (retval == -EFAULT)
|
||||
retval = -ENOMEM;
|
||||
else if (retval == -ENOMEM)
|
||||
retval = -EAGAIN;
|
||||
return retval;
|
||||
struct pagevec *pvec = &get_cpu_var(mlock_pvec);
|
||||
int nr_pages = thp_nr_pages(page);
|
||||
|
||||
SetPageMlocked(page);
|
||||
mod_zone_page_state(page_zone(page), NR_MLOCK, nr_pages);
|
||||
__count_vm_events(UNEVICTABLE_PGMLOCKED, nr_pages);
|
||||
|
||||
get_page(page);
|
||||
if (!pagevec_add(pvec, mlock_new(page)) ||
|
||||
PageHead(page) || lru_cache_disabled())
|
||||
mlock_pagevec(pvec);
|
||||
put_cpu_var(mlock_pvec);
|
||||
}
|
||||
|
||||
/*
|
||||
* Prepare page for fast batched LRU putback via putback_lru_evictable_pagevec()
|
||||
*
|
||||
* The fast path is available only for evictable pages with single mapping.
|
||||
* Then we can bypass the per-cpu pvec and get better performance.
|
||||
* when mapcount > 1 we need page_mlock() which can fail.
|
||||
* when !page_evictable(), we need the full redo logic of putback_lru_page to
|
||||
* avoid leaving evictable page in unevictable list.
|
||||
*
|
||||
* In case of success, @page is added to @pvec and @pgrescued is incremented
|
||||
* in case that the page was previously unevictable. @page is also unlocked.
|
||||
/**
|
||||
* munlock_page - munlock a page
|
||||
* @page: page to be munlocked, either a normal page or a THP head.
|
||||
*/
|
||||
static bool __putback_lru_fast_prepare(struct page *page, struct pagevec *pvec,
|
||||
int *pgrescued)
|
||||
void munlock_page(struct page *page)
|
||||
{
|
||||
VM_BUG_ON_PAGE(PageLRU(page), page);
|
||||
VM_BUG_ON_PAGE(!PageLocked(page), page);
|
||||
|
||||
if (page_mapcount(page) <= 1 && page_evictable(page)) {
|
||||
pagevec_add(pvec, page);
|
||||
if (TestClearPageUnevictable(page))
|
||||
(*pgrescued)++;
|
||||
unlock_page(page);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* Putback multiple evictable pages to the LRU
|
||||
*
|
||||
* Batched putback of evictable pages that bypasses the per-cpu pvec. Some of
|
||||
* the pages might have meanwhile become unevictable but that is OK.
|
||||
*/
|
||||
static void __putback_lru_fast(struct pagevec *pvec, int pgrescued)
|
||||
{
|
||||
count_vm_events(UNEVICTABLE_PGMUNLOCKED, pagevec_count(pvec));
|
||||
/*
|
||||
*__pagevec_lru_add() calls release_pages() so we don't call
|
||||
* put_page() explicitly
|
||||
*/
|
||||
__pagevec_lru_add(pvec);
|
||||
count_vm_events(UNEVICTABLE_PGRESCUED, pgrescued);
|
||||
}
|
||||
|
||||
/*
|
||||
* Munlock a batch of pages from the same zone
|
||||
*
|
||||
* The work is split to two main phases. First phase clears the Mlocked flag
|
||||
* and attempts to isolate the pages, all under a single zone lru lock.
|
||||
* The second phase finishes the munlock only for pages where isolation
|
||||
* succeeded.
|
||||
*
|
||||
* Note that the pagevec may be modified during the process.
|
||||
*/
|
||||
static void __munlock_pagevec(struct pagevec *pvec, struct zone *zone)
|
||||
{
|
||||
int i;
|
||||
int nr = pagevec_count(pvec);
|
||||
int delta_munlocked = -nr;
|
||||
struct pagevec pvec_putback;
|
||||
struct lruvec *lruvec = NULL;
|
||||
int pgrescued = 0;
|
||||
|
||||
pagevec_init(&pvec_putback);
|
||||
|
||||
/* Phase 1: page isolation */
|
||||
for (i = 0; i < nr; i++) {
|
||||
struct page *page = pvec->pages[i];
|
||||
struct folio *folio = page_folio(page);
|
||||
|
||||
if (TestClearPageMlocked(page)) {
|
||||
/*
|
||||
* We already have pin from follow_page_mask()
|
||||
* so we can spare the get_page() here.
|
||||
*/
|
||||
if (TestClearPageLRU(page)) {
|
||||
lruvec = folio_lruvec_relock_irq(folio, lruvec);
|
||||
del_page_from_lru_list(page, lruvec);
|
||||
continue;
|
||||
} else
|
||||
__munlock_isolation_failed(page);
|
||||
} else {
|
||||
delta_munlocked++;
|
||||
}
|
||||
|
||||
/*
|
||||
* We won't be munlocking this page in the next phase
|
||||
* but we still need to release the follow_page_mask()
|
||||
* pin. We cannot do it under lru_lock however. If it's
|
||||
* the last pin, __page_cache_release() would deadlock.
|
||||
*/
|
||||
pagevec_add(&pvec_putback, pvec->pages[i]);
|
||||
pvec->pages[i] = NULL;
|
||||
}
|
||||
if (lruvec) {
|
||||
__mod_zone_page_state(zone, NR_MLOCK, delta_munlocked);
|
||||
unlock_page_lruvec_irq(lruvec);
|
||||
} else if (delta_munlocked) {
|
||||
mod_zone_page_state(zone, NR_MLOCK, delta_munlocked);
|
||||
}
|
||||
|
||||
/* Now we can release pins of pages that we are not munlocking */
|
||||
pagevec_release(&pvec_putback);
|
||||
|
||||
/* Phase 2: page munlock */
|
||||
for (i = 0; i < nr; i++) {
|
||||
struct page *page = pvec->pages[i];
|
||||
|
||||
if (page) {
|
||||
lock_page(page);
|
||||
if (!__putback_lru_fast_prepare(page, &pvec_putback,
|
||||
&pgrescued)) {
|
||||
/*
|
||||
* Slow path. We don't want to lose the last
|
||||
* pin before unlock_page()
|
||||
*/
|
||||
get_page(page); /* for putback_lru_page() */
|
||||
__munlock_isolated_page(page);
|
||||
unlock_page(page);
|
||||
put_page(page); /* from follow_page_mask() */
|
||||
}
|
||||
}
|
||||
}
|
||||
struct pagevec *pvec = &get_cpu_var(mlock_pvec);
|
||||
|
||||
/*
|
||||
* Phase 3: page putback for pages that qualified for the fast path
|
||||
* This will also call put_page() to return pin from follow_page_mask()
|
||||
* TestClearPageMlocked(page) must be left to __munlock_page(),
|
||||
* which will check whether the page is multiply mlocked.
|
||||
*/
|
||||
if (pagevec_count(&pvec_putback))
|
||||
__putback_lru_fast(&pvec_putback, pgrescued);
|
||||
|
||||
get_page(page);
|
||||
if (!pagevec_add(pvec, page) ||
|
||||
PageHead(page) || lru_cache_disabled())
|
||||
mlock_pagevec(pvec);
|
||||
put_cpu_var(mlock_pvec);
|
||||
}
|
||||
|
||||
/*
|
||||
* Fill up pagevec for __munlock_pagevec using pte walk
|
||||
*
|
||||
* The function expects that the struct page corresponding to @start address is
|
||||
* a non-TPH page already pinned and in the @pvec, and that it belongs to @zone.
|
||||
*
|
||||
* The rest of @pvec is filled by subsequent pages within the same pmd and same
|
||||
* zone, as long as the pte's are present and vm_normal_page() succeeds. These
|
||||
* pages also get pinned.
|
||||
*
|
||||
* Returns the address of the next page that should be scanned. This equals
|
||||
* @start + PAGE_SIZE when no page could be added by the pte walk.
|
||||
*/
|
||||
static unsigned long __munlock_pagevec_fill(struct pagevec *pvec,
|
||||
struct vm_area_struct *vma, struct zone *zone,
|
||||
unsigned long start, unsigned long end)
|
||||
static int mlock_pte_range(pmd_t *pmd, unsigned long addr,
|
||||
unsigned long end, struct mm_walk *walk)
|
||||
|
||||
{
|
||||
pte_t *pte;
|
||||
struct vm_area_struct *vma = walk->vma;
|
||||
spinlock_t *ptl;
|
||||
pte_t *start_pte, *pte;
|
||||
struct page *page;
|
||||
|
||||
/*
|
||||
* Initialize pte walk starting at the already pinned page where we
|
||||
* are sure that there is a pte, as it was pinned under the same
|
||||
* mmap_lock write op.
|
||||
*/
|
||||
pte = get_locked_pte(vma->vm_mm, start, &ptl);
|
||||
/* Make sure we do not cross the page table boundary */
|
||||
end = pgd_addr_end(start, end);
|
||||
end = p4d_addr_end(start, end);
|
||||
end = pud_addr_end(start, end);
|
||||
end = pmd_addr_end(start, end);
|
||||
|
||||
/* The page next to the pinned page is the first we will try to get */
|
||||
start += PAGE_SIZE;
|
||||
while (start < end) {
|
||||
struct page *page = NULL;
|
||||
pte++;
|
||||
if (pte_present(*pte))
|
||||
page = vm_normal_page(vma, start, *pte);
|
||||
/*
|
||||
* Break if page could not be obtained or the page's node+zone does not
|
||||
* match
|
||||
*/
|
||||
if (!page || page_zone(page) != zone)
|
||||
break;
|
||||
|
||||
/*
|
||||
* Do not use pagevec for PTE-mapped THP,
|
||||
* munlock_vma_pages_range() will handle them.
|
||||
*/
|
||||
if (PageTransCompound(page))
|
||||
break;
|
||||
|
||||
get_page(page);
|
||||
/*
|
||||
* Increase the address that will be returned *before* the
|
||||
* eventual break due to pvec becoming full by adding the page
|
||||
*/
|
||||
start += PAGE_SIZE;
|
||||
if (pagevec_add(pvec, page) == 0)
|
||||
break;
|
||||
ptl = pmd_trans_huge_lock(pmd, vma);
|
||||
if (ptl) {
|
||||
if (!pmd_present(*pmd))
|
||||
goto out;
|
||||
if (is_huge_zero_pmd(*pmd))
|
||||
goto out;
|
||||
page = pmd_page(*pmd);
|
||||
if (vma->vm_flags & VM_LOCKED)
|
||||
mlock_folio(page_folio(page));
|
||||
else
|
||||
munlock_page(page);
|
||||
goto out;
|
||||
}
|
||||
pte_unmap_unlock(pte, ptl);
|
||||
return start;
|
||||
|
||||
start_pte = pte_offset_map_lock(vma->vm_mm, pmd, addr, &ptl);
|
||||
for (pte = start_pte; addr != end; pte++, addr += PAGE_SIZE) {
|
||||
if (!pte_present(*pte))
|
||||
continue;
|
||||
page = vm_normal_page(vma, addr, *pte);
|
||||
if (!page)
|
||||
continue;
|
||||
if (PageTransCompound(page))
|
||||
continue;
|
||||
if (vma->vm_flags & VM_LOCKED)
|
||||
mlock_folio(page_folio(page));
|
||||
else
|
||||
munlock_page(page);
|
||||
}
|
||||
pte_unmap(start_pte);
|
||||
out:
|
||||
spin_unlock(ptl);
|
||||
cond_resched();
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* munlock_vma_pages_range() - munlock all pages in the vma range.'
|
||||
* @vma - vma containing range to be munlock()ed.
|
||||
* mlock_vma_pages_range() - mlock any pages already in the range,
|
||||
* or munlock all pages in the range.
|
||||
* @vma - vma containing range to be mlock()ed or munlock()ed
|
||||
* @start - start address in @vma of the range
|
||||
* @end - end of range in @vma.
|
||||
* @end - end of range in @vma
|
||||
* @newflags - the new set of flags for @vma.
|
||||
*
|
||||
* For mremap(), munmap() and exit().
|
||||
*
|
||||
* Called with @vma VM_LOCKED.
|
||||
*
|
||||
* Returns with VM_LOCKED cleared. Callers must be prepared to
|
||||
* deal with this.
|
||||
*
|
||||
* We don't save and restore VM_LOCKED here because pages are
|
||||
* still on lru. In unmap path, pages might be scanned by reclaim
|
||||
* and re-mlocked by page_mlock/try_to_unmap before we unmap and
|
||||
* free them. This will result in freeing mlocked pages.
|
||||
* Called for mlock(), mlock2() and mlockall(), to set @vma VM_LOCKED;
|
||||
* called for munlock() and munlockall(), to clear VM_LOCKED from @vma.
|
||||
*/
|
||||
void munlock_vma_pages_range(struct vm_area_struct *vma,
|
||||
unsigned long start, unsigned long end)
|
||||
static void mlock_vma_pages_range(struct vm_area_struct *vma,
|
||||
unsigned long start, unsigned long end, vm_flags_t newflags)
|
||||
{
|
||||
vma->vm_flags &= VM_LOCKED_CLEAR_MASK;
|
||||
static const struct mm_walk_ops mlock_walk_ops = {
|
||||
.pmd_entry = mlock_pte_range,
|
||||
};
|
||||
|
||||
while (start < end) {
|
||||
struct page *page;
|
||||
unsigned int page_mask = 0;
|
||||
unsigned long page_increm;
|
||||
struct pagevec pvec;
|
||||
struct zone *zone;
|
||||
/*
|
||||
* There is a slight chance that concurrent page migration,
|
||||
* or page reclaim finding a page of this now-VM_LOCKED vma,
|
||||
* will call mlock_vma_page() and raise page's mlock_count:
|
||||
* double counting, leaving the page unevictable indefinitely.
|
||||
* Communicate this danger to mlock_vma_page() with VM_IO,
|
||||
* which is a VM_SPECIAL flag not allowed on VM_LOCKED vmas.
|
||||
* mmap_lock is held in write mode here, so this weird
|
||||
* combination should not be visible to other mmap_lock users;
|
||||
* but WRITE_ONCE so rmap walkers must see VM_IO if VM_LOCKED.
|
||||
*/
|
||||
if (newflags & VM_LOCKED)
|
||||
newflags |= VM_IO;
|
||||
WRITE_ONCE(vma->vm_flags, newflags);
|
||||
|
||||
pagevec_init(&pvec);
|
||||
/*
|
||||
* Although FOLL_DUMP is intended for get_dump_page(),
|
||||
* it just so happens that its special treatment of the
|
||||
* ZERO_PAGE (returning an error instead of doing get_page)
|
||||
* suits munlock very well (and if somehow an abnormal page
|
||||
* has sneaked into the range, we won't oops here: great).
|
||||
*/
|
||||
page = follow_page(vma, start, FOLL_GET | FOLL_DUMP);
|
||||
lru_add_drain();
|
||||
walk_page_range(vma->vm_mm, start, end, &mlock_walk_ops, NULL);
|
||||
lru_add_drain();
|
||||
|
||||
if (page && !IS_ERR(page)) {
|
||||
if (PageTransTail(page)) {
|
||||
VM_BUG_ON_PAGE(PageMlocked(page), page);
|
||||
put_page(page); /* follow_page_mask() */
|
||||
} else if (PageTransHuge(page)) {
|
||||
lock_page(page);
|
||||
/*
|
||||
* Any THP page found by follow_page_mask() may
|
||||
* have gotten split before reaching
|
||||
* munlock_vma_page(), so we need to compute
|
||||
* the page_mask here instead.
|
||||
*/
|
||||
page_mask = munlock_vma_page(page);
|
||||
unlock_page(page);
|
||||
put_page(page); /* follow_page_mask() */
|
||||
} else {
|
||||
/*
|
||||
* Non-huge pages are handled in batches via
|
||||
* pagevec. The pin from follow_page_mask()
|
||||
* prevents them from collapsing by THP.
|
||||
*/
|
||||
pagevec_add(&pvec, page);
|
||||
zone = page_zone(page);
|
||||
|
||||
/*
|
||||
* Try to fill the rest of pagevec using fast
|
||||
* pte walk. This will also update start to
|
||||
* the next page to process. Then munlock the
|
||||
* pagevec.
|
||||
*/
|
||||
start = __munlock_pagevec_fill(&pvec, vma,
|
||||
zone, start, end);
|
||||
__munlock_pagevec(&pvec, zone);
|
||||
goto next;
|
||||
}
|
||||
}
|
||||
page_increm = 1 + page_mask;
|
||||
start += page_increm * PAGE_SIZE;
|
||||
next:
|
||||
cond_resched();
|
||||
if (newflags & VM_IO) {
|
||||
newflags &= ~VM_IO;
|
||||
WRITE_ONCE(vma->vm_flags, newflags);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -500,10 +382,9 @@ static int mlock_fixup(struct vm_area_struct *vma, struct vm_area_struct **prev,
|
||||
pgoff_t pgoff;
|
||||
int nr_pages;
|
||||
int ret = 0;
|
||||
int lock = !!(newflags & VM_LOCKED);
|
||||
vm_flags_t old_flags = vma->vm_flags;
|
||||
vm_flags_t oldflags = vma->vm_flags;
|
||||
|
||||
if (newflags == vma->vm_flags || (vma->vm_flags & VM_SPECIAL) ||
|
||||
if (newflags == oldflags || (oldflags & VM_SPECIAL) ||
|
||||
is_vm_hugetlb_page(vma) || vma == get_gate_vma(current->mm) ||
|
||||
vma_is_dax(vma) || vma_is_secretmem(vma))
|
||||
/* don't set VM_LOCKED or VM_LOCKONFAULT and don't count */
|
||||
@@ -535,9 +416,9 @@ success:
|
||||
* Keep track of amount of locked VM.
|
||||
*/
|
||||
nr_pages = (end - start) >> PAGE_SHIFT;
|
||||
if (!lock)
|
||||
if (!(newflags & VM_LOCKED))
|
||||
nr_pages = -nr_pages;
|
||||
else if (old_flags & VM_LOCKED)
|
||||
else if (oldflags & VM_LOCKED)
|
||||
nr_pages = 0;
|
||||
mm->locked_vm += nr_pages;
|
||||
|
||||
@@ -547,11 +428,12 @@ success:
|
||||
* set VM_LOCKED, populate_vma_page_range will bring it back.
|
||||
*/
|
||||
|
||||
if (lock)
|
||||
if ((newflags & VM_LOCKED) && (oldflags & VM_LOCKED)) {
|
||||
/* No work to do, and mlocking twice would be wrong */
|
||||
vma->vm_flags = newflags;
|
||||
else
|
||||
munlock_vma_pages_range(vma, start, end);
|
||||
|
||||
} else {
|
||||
mlock_vma_pages_range(vma, start, end, newflags);
|
||||
}
|
||||
out:
|
||||
*prev = vma;
|
||||
return ret;
|
||||
@@ -645,6 +527,18 @@ static unsigned long count_mm_mlocked_page_nr(struct mm_struct *mm,
|
||||
return count >> PAGE_SHIFT;
|
||||
}
|
||||
|
||||
/*
|
||||
* convert get_user_pages() return value to posix mlock() error
|
||||
*/
|
||||
static int __mlock_posix_error_return(long retval)
|
||||
{
|
||||
if (retval == -EFAULT)
|
||||
retval = -ENOMEM;
|
||||
else if (retval == -ENOMEM)
|
||||
retval = -EAGAIN;
|
||||
return retval;
|
||||
}
|
||||
|
||||
static __must_check int do_mlock(unsigned long start, size_t len, vm_flags_t flags)
|
||||
{
|
||||
unsigned long locked;
|
||||
|
||||
Reference in New Issue
Block a user