2018-09-03 22:07:36 +08:00
|
|
|
#include <linux/gfp.h>
|
|
|
|
#include <linux/highmem.h>
|
|
|
|
#include <linux/kernel.h>
|
2022-09-05 20:24:27 +08:00
|
|
|
#include <linux/kmsan-checks.h>
|
2018-09-03 22:07:36 +08:00
|
|
|
#include <linux/mmdebug.h>
|
|
|
|
#include <linux/mm_types.h>
|
2022-01-15 06:06:10 +08:00
|
|
|
#include <linux/mm_inline.h>
|
2018-09-03 22:07:36 +08:00
|
|
|
#include <linux/pagemap.h>
|
|
|
|
#include <linux/rcupdate.h>
|
|
|
|
#include <linux/smp.h>
|
|
|
|
#include <linux/swap.h>
|
|
|
|
|
|
|
|
#include <asm/pgalloc.h>
|
|
|
|
#include <asm/tlb.h>
|
|
|
|
|
2020-02-04 09:37:08 +08:00
|
|
|
#ifndef CONFIG_MMU_GATHER_NO_GATHER
|
2018-09-18 20:51:50 +08:00
|
|
|
|
2018-09-03 22:07:36 +08:00
|
|
|
static bool tlb_next_batch(struct mmu_gather *tlb)
|
|
|
|
{
|
|
|
|
struct mmu_gather_batch *batch;
|
|
|
|
|
|
|
|
batch = tlb->active;
|
|
|
|
if (batch->next) {
|
|
|
|
tlb->active = batch->next;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (tlb->batch_count == MAX_GATHER_BATCH_COUNT)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
batch = (void *)__get_free_pages(GFP_NOWAIT | __GFP_NOWARN, 0);
|
|
|
|
if (!batch)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
tlb->batch_count++;
|
|
|
|
batch->next = NULL;
|
|
|
|
batch->nr = 0;
|
|
|
|
batch->max = MAX_GATHER_BATCH;
|
|
|
|
|
|
|
|
tlb->active->next = batch;
|
|
|
|
tlb->active = batch;
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2018-09-18 20:51:50 +08:00
|
|
|
static void tlb_batch_pages_flush(struct mmu_gather *tlb)
|
|
|
|
{
|
|
|
|
struct mmu_gather_batch *batch;
|
|
|
|
|
|
|
|
for (batch = &tlb->local; batch && batch->nr; batch = batch->next) {
|
mm/mmu_gather: limit free batch count and add schedule point in tlb_batch_pages_flush
free a large list of pages maybe cause rcu_sched starved on
non-preemptible kernels. howerver free_unref_page_list maybe can't
cond_resched as it maybe called in interrupt or atomic context, especially
can't detect atomic context in CONFIG_PREEMPTION=n.
The issue is detected in guest with kvm cpu 200% overcommit, however I
didn't see the warning in the host with the same application. I'm sure
that the patch is needed for guest kernel, but no sure for host.
To reproduce, set up two virtual machines in one host machine, per vm has
the same number cpu and half memory of host. the run ltpstress.sh in per
vm, then will see rcu stall warning.kernel is preempt disabled, append
kernel command 'preempt=none' if enable dynamic preempt . It could
detected in loongson machine(32 core, 128G mem) and ProLiant DL380
Gen9(x86 E5-2680, 28 core, 64G mem)
tlb flush batch count depends on PAGE_SIZE, it's too large if PAGE_SIZE >
4K, here limit free batch count with 512. And add schedule point in
tlb_batch_pages_flush.
rcu: rcu_sched kthread starved for 5359 jiffies! g454793 f0x0
RCU_GP_WAIT_FQS(5) ->state=0x0 ->cpu=19
[...]
Call Trace:
free_unref_page_list+0x19c/0x270
release_pages+0x3cc/0x498
tlb_flush_mmu_free+0x44/0x70
zap_pte_range+0x450/0x738
unmap_page_range+0x108/0x240
unmap_vmas+0x74/0xf0
unmap_region+0xb0/0x120
do_munmap+0x264/0x438
vm_munmap+0x58/0xa0
sys_munmap+0x10/0x20
syscall_common+0x24/0x38
Link: https://lkml.kernel.org/r/20220317072857.2635262-1-wangjianxing@loongson.cn
Signed-off-by: Jianxing Wang <wangjianxing@loongson.cn>
Signed-off-by: Peter Zijlstra <peterz@infradead.org>
Cc: Will Deacon <will@kernel.org>
Cc: Nicholas Piggin <npiggin@gmail.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
2022-04-29 14:16:12 +08:00
|
|
|
struct page **pages = batch->pages;
|
|
|
|
|
|
|
|
do {
|
|
|
|
/*
|
|
|
|
* limit free batch count when PAGE_SIZE > 4K
|
|
|
|
*/
|
|
|
|
unsigned int nr = min(512U, batch->nr);
|
|
|
|
|
|
|
|
free_pages_and_swap_cache(pages, nr);
|
|
|
|
pages += nr;
|
|
|
|
batch->nr -= nr;
|
|
|
|
|
|
|
|
cond_resched();
|
|
|
|
} while (batch->nr);
|
2018-09-18 20:51:50 +08:00
|
|
|
}
|
|
|
|
tlb->active = &tlb->local;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void tlb_batch_list_free(struct mmu_gather *tlb)
|
|
|
|
{
|
|
|
|
struct mmu_gather_batch *batch, *next;
|
|
|
|
|
|
|
|
for (batch = tlb->local.next; batch; batch = next) {
|
|
|
|
next = batch->next;
|
|
|
|
free_pages((unsigned long)batch, 0);
|
|
|
|
}
|
|
|
|
tlb->local.next = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool __tlb_remove_page_size(struct mmu_gather *tlb, struct page *page, int page_size)
|
|
|
|
{
|
|
|
|
struct mmu_gather_batch *batch;
|
|
|
|
|
|
|
|
VM_BUG_ON(!tlb->end);
|
|
|
|
|
2020-02-04 09:37:05 +08:00
|
|
|
#ifdef CONFIG_MMU_GATHER_PAGE_SIZE
|
2018-09-18 20:51:50 +08:00
|
|
|
VM_WARN_ON(tlb->page_size != page_size);
|
|
|
|
#endif
|
|
|
|
|
|
|
|
batch = tlb->active;
|
|
|
|
/*
|
|
|
|
* Add the page and check if we are full. If so
|
|
|
|
* force a flush.
|
|
|
|
*/
|
|
|
|
batch->pages[batch->nr++] = page;
|
|
|
|
if (batch->nr == batch->max) {
|
|
|
|
if (!tlb_next_batch(tlb))
|
|
|
|
return true;
|
|
|
|
batch = tlb->active;
|
|
|
|
}
|
|
|
|
VM_BUG_ON_PAGE(batch->nr > batch->max, page);
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2020-02-04 09:37:08 +08:00
|
|
|
#endif /* MMU_GATHER_NO_GATHER */
|
2018-09-18 20:51:50 +08:00
|
|
|
|
2020-02-04 09:37:11 +08:00
|
|
|
#ifdef CONFIG_MMU_GATHER_TABLE_FREE
|
2018-09-03 22:07:36 +08:00
|
|
|
|
2020-02-04 09:37:11 +08:00
|
|
|
static void __tlb_remove_table_free(struct mmu_table_batch *batch)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
|
|
|
|
for (i = 0; i < batch->nr; i++)
|
|
|
|
__tlb_remove_table(batch->tables[i]);
|
|
|
|
|
|
|
|
free_page((unsigned long)batch);
|
|
|
|
}
|
|
|
|
|
|
|
|
#ifdef CONFIG_MMU_GATHER_RCU_TABLE_FREE
|
2018-09-03 22:07:36 +08:00
|
|
|
|
|
|
|
/*
|
2020-02-04 09:37:11 +08:00
|
|
|
* Semi RCU freeing of the page directories.
|
|
|
|
*
|
|
|
|
* This is needed by some architectures to implement software pagetable walkers.
|
|
|
|
*
|
|
|
|
* gup_fast() and other software pagetable walkers do a lockless page-table
|
|
|
|
* walk and therefore needs some synchronization with the freeing of the page
|
|
|
|
* directories. The chosen means to accomplish that is by disabling IRQs over
|
|
|
|
* the walk.
|
|
|
|
*
|
|
|
|
* Architectures that use IPIs to flush TLBs will then automagically DTRT,
|
|
|
|
* since we unlink the page, flush TLBs, free the page. Since the disabling of
|
|
|
|
* IRQs delays the completion of the TLB flush we can never observe an already
|
|
|
|
* freed page.
|
|
|
|
*
|
|
|
|
* Architectures that do not have this (PPC) need to delay the freeing by some
|
|
|
|
* other means, this is that means.
|
|
|
|
*
|
|
|
|
* What we do is batch the freed directory pages (tables) and RCU free them.
|
|
|
|
* We use the sched RCU variant, as that guarantees that IRQ/preempt disabling
|
|
|
|
* holds off grace periods.
|
|
|
|
*
|
|
|
|
* However, in order to batch these pages we need to allocate storage, this
|
|
|
|
* allocation is deep inside the MM code and can thus easily fail on memory
|
|
|
|
* pressure. To guarantee progress we fall back to single table freeing, see
|
|
|
|
* the implementation of tlb_remove_table_one().
|
|
|
|
*
|
2018-09-03 22:07:36 +08:00
|
|
|
*/
|
|
|
|
|
|
|
|
static void tlb_remove_table_smp_sync(void *arg)
|
|
|
|
{
|
|
|
|
/* Simply deliver the interrupt */
|
|
|
|
}
|
|
|
|
|
2020-02-04 09:37:11 +08:00
|
|
|
static void tlb_remove_table_sync_one(void)
|
2018-09-03 22:07:36 +08:00
|
|
|
{
|
|
|
|
/*
|
|
|
|
* This isn't an RCU grace period and hence the page-tables cannot be
|
|
|
|
* assumed to be actually RCU-freed.
|
|
|
|
*
|
|
|
|
* It is however sufficient for software page-table walkers that rely on
|
2020-02-04 09:37:11 +08:00
|
|
|
* IRQ disabling.
|
2018-09-03 22:07:36 +08:00
|
|
|
*/
|
|
|
|
smp_call_function(tlb_remove_table_smp_sync, NULL, 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void tlb_remove_table_rcu(struct rcu_head *head)
|
|
|
|
{
|
2020-02-04 09:37:11 +08:00
|
|
|
__tlb_remove_table_free(container_of(head, struct mmu_table_batch, rcu));
|
|
|
|
}
|
2018-09-03 22:07:36 +08:00
|
|
|
|
2020-02-04 09:37:11 +08:00
|
|
|
static void tlb_remove_table_free(struct mmu_table_batch *batch)
|
|
|
|
{
|
|
|
|
call_rcu(&batch->rcu, tlb_remove_table_rcu);
|
|
|
|
}
|
2018-09-03 22:07:36 +08:00
|
|
|
|
2020-02-04 09:37:11 +08:00
|
|
|
#else /* !CONFIG_MMU_GATHER_RCU_TABLE_FREE */
|
2018-09-03 22:07:36 +08:00
|
|
|
|
2020-02-04 09:37:11 +08:00
|
|
|
static void tlb_remove_table_sync_one(void) { }
|
|
|
|
|
|
|
|
static void tlb_remove_table_free(struct mmu_table_batch *batch)
|
|
|
|
{
|
|
|
|
__tlb_remove_table_free(batch);
|
|
|
|
}
|
|
|
|
|
|
|
|
#endif /* CONFIG_MMU_GATHER_RCU_TABLE_FREE */
|
|
|
|
|
|
|
|
/*
|
|
|
|
* If we want tlb_remove_table() to imply TLB invalidates.
|
|
|
|
*/
|
|
|
|
static inline void tlb_table_invalidate(struct mmu_gather *tlb)
|
|
|
|
{
|
|
|
|
if (tlb_needs_table_invalidate()) {
|
|
|
|
/*
|
|
|
|
* Invalidate page-table caches used by hardware walkers. Then
|
|
|
|
* we still need to RCU-sched wait while freeing the pages
|
|
|
|
* because software walkers can still be in-flight.
|
|
|
|
*/
|
|
|
|
tlb_flush_mmu_tlbonly(tlb);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void tlb_remove_table_one(void *table)
|
|
|
|
{
|
|
|
|
tlb_remove_table_sync_one();
|
|
|
|
__tlb_remove_table(table);
|
2018-09-03 22:07:36 +08:00
|
|
|
}
|
|
|
|
|
2018-09-20 16:55:10 +08:00
|
|
|
static void tlb_table_flush(struct mmu_gather *tlb)
|
2018-09-03 22:07:36 +08:00
|
|
|
{
|
|
|
|
struct mmu_table_batch **batch = &tlb->batch;
|
|
|
|
|
|
|
|
if (*batch) {
|
|
|
|
tlb_table_invalidate(tlb);
|
2020-02-04 09:37:11 +08:00
|
|
|
tlb_remove_table_free(*batch);
|
2018-09-03 22:07:36 +08:00
|
|
|
*batch = NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void tlb_remove_table(struct mmu_gather *tlb, void *table)
|
|
|
|
{
|
|
|
|
struct mmu_table_batch **batch = &tlb->batch;
|
|
|
|
|
|
|
|
if (*batch == NULL) {
|
|
|
|
*batch = (struct mmu_table_batch *)__get_free_page(GFP_NOWAIT | __GFP_NOWARN);
|
|
|
|
if (*batch == NULL) {
|
|
|
|
tlb_table_invalidate(tlb);
|
|
|
|
tlb_remove_table_one(table);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
(*batch)->nr = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
(*batch)->tables[(*batch)->nr++] = table;
|
|
|
|
if ((*batch)->nr == MAX_TABLE_BATCH)
|
|
|
|
tlb_table_flush(tlb);
|
|
|
|
}
|
|
|
|
|
2020-02-04 09:37:11 +08:00
|
|
|
static inline void tlb_table_init(struct mmu_gather *tlb)
|
|
|
|
{
|
|
|
|
tlb->batch = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
#else /* !CONFIG_MMU_GATHER_TABLE_FREE */
|
|
|
|
|
|
|
|
static inline void tlb_table_flush(struct mmu_gather *tlb) { }
|
|
|
|
static inline void tlb_table_init(struct mmu_gather *tlb) { }
|
|
|
|
|
|
|
|
#endif /* CONFIG_MMU_GATHER_TABLE_FREE */
|
2018-09-03 22:07:36 +08:00
|
|
|
|
2018-09-20 16:55:10 +08:00
|
|
|
static void tlb_flush_mmu_free(struct mmu_gather *tlb)
|
|
|
|
{
|
|
|
|
tlb_table_flush(tlb);
|
2020-02-04 09:37:08 +08:00
|
|
|
#ifndef CONFIG_MMU_GATHER_NO_GATHER
|
2018-09-20 16:55:10 +08:00
|
|
|
tlb_batch_pages_flush(tlb);
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
void tlb_flush_mmu(struct mmu_gather *tlb)
|
|
|
|
{
|
|
|
|
tlb_flush_mmu_tlbonly(tlb);
|
|
|
|
tlb_flush_mmu_free(tlb);
|
|
|
|
}
|
|
|
|
|
2021-01-28 07:53:44 +08:00
|
|
|
static void __tlb_gather_mmu(struct mmu_gather *tlb, struct mm_struct *mm,
|
2021-01-28 07:53:45 +08:00
|
|
|
bool fullmm)
|
2018-09-03 22:07:36 +08:00
|
|
|
{
|
2022-09-05 20:24:27 +08:00
|
|
|
/*
|
|
|
|
* struct mmu_gather contains 7 1-bit fields packed into a 32-bit
|
|
|
|
* unsigned int value. The remaining 25 bits remain uninitialized
|
|
|
|
* and are never used, but KMSAN updates the origin for them in
|
|
|
|
* zap_pXX_range() in mm/memory.c, thus creating very long origin
|
|
|
|
* chains. This is technically correct, but consumes too much memory.
|
|
|
|
* Unpoisoning the whole structure will prevent creating such chains.
|
|
|
|
*/
|
|
|
|
kmsan_unpoison_memory(tlb, sizeof(*tlb));
|
2018-09-20 16:50:11 +08:00
|
|
|
tlb->mm = mm;
|
2021-01-28 07:53:45 +08:00
|
|
|
tlb->fullmm = fullmm;
|
2018-09-20 16:50:11 +08:00
|
|
|
|
2020-02-04 09:37:08 +08:00
|
|
|
#ifndef CONFIG_MMU_GATHER_NO_GATHER
|
2018-09-20 16:50:11 +08:00
|
|
|
tlb->need_flush_all = 0;
|
|
|
|
tlb->local.next = NULL;
|
|
|
|
tlb->local.nr = 0;
|
|
|
|
tlb->local.max = ARRAY_SIZE(tlb->__pages);
|
|
|
|
tlb->active = &tlb->local;
|
|
|
|
tlb->batch_count = 0;
|
|
|
|
#endif
|
|
|
|
|
2020-02-04 09:37:11 +08:00
|
|
|
tlb_table_init(tlb);
|
2020-02-04 09:37:05 +08:00
|
|
|
#ifdef CONFIG_MMU_GATHER_PAGE_SIZE
|
2018-09-20 16:50:11 +08:00
|
|
|
tlb->page_size = 0;
|
|
|
|
#endif
|
|
|
|
|
|
|
|
__tlb_reset_range(tlb);
|
2018-09-03 22:07:36 +08:00
|
|
|
inc_tlb_flush_pending(tlb->mm);
|
|
|
|
}
|
|
|
|
|
2021-04-17 06:45:54 +08:00
|
|
|
/**
|
|
|
|
* tlb_gather_mmu - initialize an mmu_gather structure for page-table tear-down
|
|
|
|
* @tlb: the mmu_gather structure to initialize
|
|
|
|
* @mm: the mm_struct of the target address space
|
|
|
|
*
|
|
|
|
* Called to initialize an (on-stack) mmu_gather structure for page-table
|
|
|
|
* tear-down from @mm.
|
|
|
|
*/
|
2021-01-28 07:53:45 +08:00
|
|
|
void tlb_gather_mmu(struct mmu_gather *tlb, struct mm_struct *mm)
|
2021-01-28 07:53:44 +08:00
|
|
|
{
|
2021-01-28 07:53:45 +08:00
|
|
|
__tlb_gather_mmu(tlb, mm, false);
|
2021-01-28 07:53:44 +08:00
|
|
|
}
|
|
|
|
|
2021-04-17 06:45:54 +08:00
|
|
|
/**
|
|
|
|
* tlb_gather_mmu_fullmm - initialize an mmu_gather structure for page-table tear-down
|
|
|
|
* @tlb: the mmu_gather structure to initialize
|
|
|
|
* @mm: the mm_struct of the target address space
|
|
|
|
*
|
|
|
|
* In this case, @mm is without users and we're going to destroy the
|
|
|
|
* full address space (exit/execve).
|
|
|
|
*
|
|
|
|
* Called to initialize an (on-stack) mmu_gather structure for page-table
|
|
|
|
* tear-down from @mm.
|
|
|
|
*/
|
2021-01-28 07:53:44 +08:00
|
|
|
void tlb_gather_mmu_fullmm(struct mmu_gather *tlb, struct mm_struct *mm)
|
|
|
|
{
|
2021-01-28 07:53:45 +08:00
|
|
|
__tlb_gather_mmu(tlb, mm, true);
|
2021-01-28 07:53:44 +08:00
|
|
|
}
|
|
|
|
|
2018-09-20 16:50:11 +08:00
|
|
|
/**
|
|
|
|
* tlb_finish_mmu - finish an mmu_gather structure
|
|
|
|
* @tlb: the mmu_gather structure to finish
|
|
|
|
*
|
|
|
|
* Called at the end of the shootdown operation to free up any resources that
|
|
|
|
* were required.
|
|
|
|
*/
|
2021-01-28 07:53:43 +08:00
|
|
|
void tlb_finish_mmu(struct mmu_gather *tlb)
|
2018-09-03 22:07:36 +08:00
|
|
|
{
|
|
|
|
/*
|
|
|
|
* If there are parallel threads are doing PTE changes on same range
|
2020-06-09 12:33:54 +08:00
|
|
|
* under non-exclusive lock (e.g., mmap_lock read-side) but defer TLB
|
mm: mmu_gather: remove __tlb_reset_range() for force flush
A few new fields were added to mmu_gather to make TLB flush smarter for
huge page by telling what level of page table is changed.
__tlb_reset_range() is used to reset all these page table state to
unchanged, which is called by TLB flush for parallel mapping changes for
the same range under non-exclusive lock (i.e. read mmap_sem).
Before commit dd2283f2605e ("mm: mmap: zap pages with read mmap_sem in
munmap"), the syscalls (e.g. MADV_DONTNEED, MADV_FREE) which may update
PTEs in parallel don't remove page tables. But, the forementioned
commit may do munmap() under read mmap_sem and free page tables. This
may result in program hang on aarch64 reported by Jan Stancek. The
problem could be reproduced by his test program with slightly modified
below.
---8<---
static int map_size = 4096;
static int num_iter = 500;
static long threads_total;
static void *distant_area;
void *map_write_unmap(void *ptr)
{
int *fd = ptr;
unsigned char *map_address;
int i, j = 0;
for (i = 0; i < num_iter; i++) {
map_address = mmap(distant_area, (size_t) map_size, PROT_WRITE | PROT_READ,
MAP_SHARED | MAP_ANONYMOUS, -1, 0);
if (map_address == MAP_FAILED) {
perror("mmap");
exit(1);
}
for (j = 0; j < map_size; j++)
map_address[j] = 'b';
if (munmap(map_address, map_size) == -1) {
perror("munmap");
exit(1);
}
}
return NULL;
}
void *dummy(void *ptr)
{
return NULL;
}
int main(void)
{
pthread_t thid[2];
/* hint for mmap in map_write_unmap() */
distant_area = mmap(0, DISTANT_MMAP_SIZE, PROT_WRITE | PROT_READ,
MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
munmap(distant_area, (size_t)DISTANT_MMAP_SIZE);
distant_area += DISTANT_MMAP_SIZE / 2;
while (1) {
pthread_create(&thid[0], NULL, map_write_unmap, NULL);
pthread_create(&thid[1], NULL, dummy, NULL);
pthread_join(thid[0], NULL);
pthread_join(thid[1], NULL);
}
}
---8<---
The program may bring in parallel execution like below:
t1 t2
munmap(map_address)
downgrade_write(&mm->mmap_sem);
unmap_region()
tlb_gather_mmu()
inc_tlb_flush_pending(tlb->mm);
free_pgtables()
tlb->freed_tables = 1
tlb->cleared_pmds = 1
pthread_exit()
madvise(thread_stack, 8M, MADV_DONTNEED)
zap_page_range()
tlb_gather_mmu()
inc_tlb_flush_pending(tlb->mm);
tlb_finish_mmu()
if (mm_tlb_flush_nested(tlb->mm))
__tlb_reset_range()
__tlb_reset_range() would reset freed_tables and cleared_* bits, but this
may cause inconsistency for munmap() which do free page tables. Then it
may result in some architectures, e.g. aarch64, may not flush TLB
completely as expected to have stale TLB entries remained.
Use fullmm flush since it yields much better performance on aarch64 and
non-fullmm doesn't yields significant difference on x86.
The original proposed fix came from Jan Stancek who mainly debugged this
issue, I just wrapped up everything together.
Jan's testing results:
v5.2-rc2-24-gbec7550cca10
--------------------------
mean stddev
real 37.382 2.780
user 1.420 0.078
sys 54.658 1.855
v5.2-rc2-24-gbec7550cca10 + "mm: mmu_gather: remove __tlb_reset_range() for force flush"
---------------------------------------------------------------------------------------_
mean stddev
real 37.119 2.105
user 1.548 0.087
sys 55.698 1.357
[akpm@linux-foundation.org: coding-style fixes]
Link: http://lkml.kernel.org/r/1558322252-113575-1-git-send-email-yang.shi@linux.alibaba.com
Fixes: dd2283f2605e ("mm: mmap: zap pages with read mmap_sem in munmap")
Signed-off-by: Yang Shi <yang.shi@linux.alibaba.com>
Signed-off-by: Jan Stancek <jstancek@redhat.com>
Reported-by: Jan Stancek <jstancek@redhat.com>
Tested-by: Jan Stancek <jstancek@redhat.com>
Suggested-by: Will Deacon <will.deacon@arm.com>
Tested-by: Will Deacon <will.deacon@arm.com>
Acked-by: Will Deacon <will.deacon@arm.com>
Cc: Peter Zijlstra <peterz@infradead.org>
Cc: Nick Piggin <npiggin@gmail.com>
Cc: "Aneesh Kumar K.V" <aneesh.kumar@linux.ibm.com>
Cc: Nadav Amit <namit@vmware.com>
Cc: Minchan Kim <minchan@kernel.org>
Cc: Mel Gorman <mgorman@suse.de>
Cc: <stable@vger.kernel.org> [4.20+]
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2019-06-14 06:56:05 +08:00
|
|
|
* flush by batching, one thread may end up seeing inconsistent PTEs
|
|
|
|
* and result in having stale TLB entries. So flush TLB forcefully
|
|
|
|
* if we detect parallel PTE batching threads.
|
|
|
|
*
|
|
|
|
* However, some syscalls, e.g. munmap(), may free page tables, this
|
|
|
|
* needs force flush everything in the given range. Otherwise this
|
|
|
|
* may result in having stale TLB entries for some architectures,
|
|
|
|
* e.g. aarch64, that could specify flush what level TLB.
|
2018-09-03 22:07:36 +08:00
|
|
|
*/
|
2018-09-20 16:50:11 +08:00
|
|
|
if (mm_tlb_flush_nested(tlb->mm)) {
|
mm: mmu_gather: remove __tlb_reset_range() for force flush
A few new fields were added to mmu_gather to make TLB flush smarter for
huge page by telling what level of page table is changed.
__tlb_reset_range() is used to reset all these page table state to
unchanged, which is called by TLB flush for parallel mapping changes for
the same range under non-exclusive lock (i.e. read mmap_sem).
Before commit dd2283f2605e ("mm: mmap: zap pages with read mmap_sem in
munmap"), the syscalls (e.g. MADV_DONTNEED, MADV_FREE) which may update
PTEs in parallel don't remove page tables. But, the forementioned
commit may do munmap() under read mmap_sem and free page tables. This
may result in program hang on aarch64 reported by Jan Stancek. The
problem could be reproduced by his test program with slightly modified
below.
---8<---
static int map_size = 4096;
static int num_iter = 500;
static long threads_total;
static void *distant_area;
void *map_write_unmap(void *ptr)
{
int *fd = ptr;
unsigned char *map_address;
int i, j = 0;
for (i = 0; i < num_iter; i++) {
map_address = mmap(distant_area, (size_t) map_size, PROT_WRITE | PROT_READ,
MAP_SHARED | MAP_ANONYMOUS, -1, 0);
if (map_address == MAP_FAILED) {
perror("mmap");
exit(1);
}
for (j = 0; j < map_size; j++)
map_address[j] = 'b';
if (munmap(map_address, map_size) == -1) {
perror("munmap");
exit(1);
}
}
return NULL;
}
void *dummy(void *ptr)
{
return NULL;
}
int main(void)
{
pthread_t thid[2];
/* hint for mmap in map_write_unmap() */
distant_area = mmap(0, DISTANT_MMAP_SIZE, PROT_WRITE | PROT_READ,
MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
munmap(distant_area, (size_t)DISTANT_MMAP_SIZE);
distant_area += DISTANT_MMAP_SIZE / 2;
while (1) {
pthread_create(&thid[0], NULL, map_write_unmap, NULL);
pthread_create(&thid[1], NULL, dummy, NULL);
pthread_join(thid[0], NULL);
pthread_join(thid[1], NULL);
}
}
---8<---
The program may bring in parallel execution like below:
t1 t2
munmap(map_address)
downgrade_write(&mm->mmap_sem);
unmap_region()
tlb_gather_mmu()
inc_tlb_flush_pending(tlb->mm);
free_pgtables()
tlb->freed_tables = 1
tlb->cleared_pmds = 1
pthread_exit()
madvise(thread_stack, 8M, MADV_DONTNEED)
zap_page_range()
tlb_gather_mmu()
inc_tlb_flush_pending(tlb->mm);
tlb_finish_mmu()
if (mm_tlb_flush_nested(tlb->mm))
__tlb_reset_range()
__tlb_reset_range() would reset freed_tables and cleared_* bits, but this
may cause inconsistency for munmap() which do free page tables. Then it
may result in some architectures, e.g. aarch64, may not flush TLB
completely as expected to have stale TLB entries remained.
Use fullmm flush since it yields much better performance on aarch64 and
non-fullmm doesn't yields significant difference on x86.
The original proposed fix came from Jan Stancek who mainly debugged this
issue, I just wrapped up everything together.
Jan's testing results:
v5.2-rc2-24-gbec7550cca10
--------------------------
mean stddev
real 37.382 2.780
user 1.420 0.078
sys 54.658 1.855
v5.2-rc2-24-gbec7550cca10 + "mm: mmu_gather: remove __tlb_reset_range() for force flush"
---------------------------------------------------------------------------------------_
mean stddev
real 37.119 2.105
user 1.548 0.087
sys 55.698 1.357
[akpm@linux-foundation.org: coding-style fixes]
Link: http://lkml.kernel.org/r/1558322252-113575-1-git-send-email-yang.shi@linux.alibaba.com
Fixes: dd2283f2605e ("mm: mmap: zap pages with read mmap_sem in munmap")
Signed-off-by: Yang Shi <yang.shi@linux.alibaba.com>
Signed-off-by: Jan Stancek <jstancek@redhat.com>
Reported-by: Jan Stancek <jstancek@redhat.com>
Tested-by: Jan Stancek <jstancek@redhat.com>
Suggested-by: Will Deacon <will.deacon@arm.com>
Tested-by: Will Deacon <will.deacon@arm.com>
Acked-by: Will Deacon <will.deacon@arm.com>
Cc: Peter Zijlstra <peterz@infradead.org>
Cc: Nick Piggin <npiggin@gmail.com>
Cc: "Aneesh Kumar K.V" <aneesh.kumar@linux.ibm.com>
Cc: Nadav Amit <namit@vmware.com>
Cc: Minchan Kim <minchan@kernel.org>
Cc: Mel Gorman <mgorman@suse.de>
Cc: <stable@vger.kernel.org> [4.20+]
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2019-06-14 06:56:05 +08:00
|
|
|
/*
|
|
|
|
* The aarch64 yields better performance with fullmm by
|
|
|
|
* avoiding multiple CPUs spamming TLBI messages at the
|
|
|
|
* same time.
|
|
|
|
*
|
|
|
|
* On x86 non-fullmm doesn't yield significant difference
|
|
|
|
* against fullmm.
|
|
|
|
*/
|
|
|
|
tlb->fullmm = 1;
|
2018-09-20 16:50:11 +08:00
|
|
|
__tlb_reset_range(tlb);
|
mm: mmu_gather: remove __tlb_reset_range() for force flush
A few new fields were added to mmu_gather to make TLB flush smarter for
huge page by telling what level of page table is changed.
__tlb_reset_range() is used to reset all these page table state to
unchanged, which is called by TLB flush for parallel mapping changes for
the same range under non-exclusive lock (i.e. read mmap_sem).
Before commit dd2283f2605e ("mm: mmap: zap pages with read mmap_sem in
munmap"), the syscalls (e.g. MADV_DONTNEED, MADV_FREE) which may update
PTEs in parallel don't remove page tables. But, the forementioned
commit may do munmap() under read mmap_sem and free page tables. This
may result in program hang on aarch64 reported by Jan Stancek. The
problem could be reproduced by his test program with slightly modified
below.
---8<---
static int map_size = 4096;
static int num_iter = 500;
static long threads_total;
static void *distant_area;
void *map_write_unmap(void *ptr)
{
int *fd = ptr;
unsigned char *map_address;
int i, j = 0;
for (i = 0; i < num_iter; i++) {
map_address = mmap(distant_area, (size_t) map_size, PROT_WRITE | PROT_READ,
MAP_SHARED | MAP_ANONYMOUS, -1, 0);
if (map_address == MAP_FAILED) {
perror("mmap");
exit(1);
}
for (j = 0; j < map_size; j++)
map_address[j] = 'b';
if (munmap(map_address, map_size) == -1) {
perror("munmap");
exit(1);
}
}
return NULL;
}
void *dummy(void *ptr)
{
return NULL;
}
int main(void)
{
pthread_t thid[2];
/* hint for mmap in map_write_unmap() */
distant_area = mmap(0, DISTANT_MMAP_SIZE, PROT_WRITE | PROT_READ,
MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
munmap(distant_area, (size_t)DISTANT_MMAP_SIZE);
distant_area += DISTANT_MMAP_SIZE / 2;
while (1) {
pthread_create(&thid[0], NULL, map_write_unmap, NULL);
pthread_create(&thid[1], NULL, dummy, NULL);
pthread_join(thid[0], NULL);
pthread_join(thid[1], NULL);
}
}
---8<---
The program may bring in parallel execution like below:
t1 t2
munmap(map_address)
downgrade_write(&mm->mmap_sem);
unmap_region()
tlb_gather_mmu()
inc_tlb_flush_pending(tlb->mm);
free_pgtables()
tlb->freed_tables = 1
tlb->cleared_pmds = 1
pthread_exit()
madvise(thread_stack, 8M, MADV_DONTNEED)
zap_page_range()
tlb_gather_mmu()
inc_tlb_flush_pending(tlb->mm);
tlb_finish_mmu()
if (mm_tlb_flush_nested(tlb->mm))
__tlb_reset_range()
__tlb_reset_range() would reset freed_tables and cleared_* bits, but this
may cause inconsistency for munmap() which do free page tables. Then it
may result in some architectures, e.g. aarch64, may not flush TLB
completely as expected to have stale TLB entries remained.
Use fullmm flush since it yields much better performance on aarch64 and
non-fullmm doesn't yields significant difference on x86.
The original proposed fix came from Jan Stancek who mainly debugged this
issue, I just wrapped up everything together.
Jan's testing results:
v5.2-rc2-24-gbec7550cca10
--------------------------
mean stddev
real 37.382 2.780
user 1.420 0.078
sys 54.658 1.855
v5.2-rc2-24-gbec7550cca10 + "mm: mmu_gather: remove __tlb_reset_range() for force flush"
---------------------------------------------------------------------------------------_
mean stddev
real 37.119 2.105
user 1.548 0.087
sys 55.698 1.357
[akpm@linux-foundation.org: coding-style fixes]
Link: http://lkml.kernel.org/r/1558322252-113575-1-git-send-email-yang.shi@linux.alibaba.com
Fixes: dd2283f2605e ("mm: mmap: zap pages with read mmap_sem in munmap")
Signed-off-by: Yang Shi <yang.shi@linux.alibaba.com>
Signed-off-by: Jan Stancek <jstancek@redhat.com>
Reported-by: Jan Stancek <jstancek@redhat.com>
Tested-by: Jan Stancek <jstancek@redhat.com>
Suggested-by: Will Deacon <will.deacon@arm.com>
Tested-by: Will Deacon <will.deacon@arm.com>
Acked-by: Will Deacon <will.deacon@arm.com>
Cc: Peter Zijlstra <peterz@infradead.org>
Cc: Nick Piggin <npiggin@gmail.com>
Cc: "Aneesh Kumar K.V" <aneesh.kumar@linux.ibm.com>
Cc: Nadav Amit <namit@vmware.com>
Cc: Minchan Kim <minchan@kernel.org>
Cc: Mel Gorman <mgorman@suse.de>
Cc: <stable@vger.kernel.org> [4.20+]
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2019-06-14 06:56:05 +08:00
|
|
|
tlb->freed_tables = 1;
|
2018-09-20 16:50:11 +08:00
|
|
|
}
|
2018-09-03 22:07:36 +08:00
|
|
|
|
2018-09-20 16:50:11 +08:00
|
|
|
tlb_flush_mmu(tlb);
|
|
|
|
|
2020-02-04 09:37:08 +08:00
|
|
|
#ifndef CONFIG_MMU_GATHER_NO_GATHER
|
2018-09-20 16:50:11 +08:00
|
|
|
tlb_batch_list_free(tlb);
|
|
|
|
#endif
|
2018-09-03 22:07:36 +08:00
|
|
|
dec_tlb_flush_pending(tlb->mm);
|
|
|
|
}
|