mirror of
https://mirrors.bfsu.edu.cn/git/linux.git
synced 2024-12-18 08:35:08 +08:00
766b59e876
Patch series "arch: allow pte_offset_map[_lock]() to fail", v2. What is it all about? Some mmap_lock avoidance i.e. latency reduction. Initially just for the case of collapsing shmem or file pages to THPs; but likely to be relied upon later in other contexts e.g. freeing of empty page tables (but that's not work I'm doing). mmap_write_lock avoidance when collapsing to anon THPs? Perhaps, but again that's not work I've done: a quick attempt was not as easy as the shmem/file case. I would much prefer not to have to make these small but wide-ranging changes for such a niche case; but failed to find another way, and have heard that shmem MADV_COLLAPSE's usefulness is being limited by that mmap_write_lock it currently requires. These changes (though of course not these exact patches, and not all of these architectures!) have been in Google's data centre kernel for three years now: we do rely upon them. What are the per-arch changes about? Generally, two things. One: the current mmap locking may not be enough to guard against that tricky transition between pmd entry pointing to page table, and empty pmd entry, and pmd entry pointing to huge page: pte_offset_map() will have to validate the pmd entry for itself, returning NULL if no page table is there. What to do about that varies: often the nearby error handling indicates just to skip it; but in some cases a "goto again" looks appropriate (and if that risks an infinite loop, then there must have been an oops, or pfn 0 mistaken for page table, before). Deeper study of each site might show that 90% of them here in arch code could only fail if there's corruption e.g. a transition to THP would be surprising on an arch without HAVE_ARCH_TRANSPARENT_HUGEPAGE. But given the likely extension to freeing empty page tables, I have not limited this set of changes to THP; and it has been easier, and sets a better example, if each site is given appropriate handling. Two: pte_offset_map() will need to do an rcu_read_lock(), with the corresponding rcu_read_unlock() in pte_unmap(). But most architectures never supported CONFIG_HIGHPTE, so some don't always call pte_unmap() after pte_offset_map(), or have used userspace pte_offset_map() where pte_offset_kernel() is more correct. No problem in the current tree, but a problem once an rcu_read_unlock() will be needed to keep balance. A common special case of that comes in arch/*/mm/hugetlbpage.c, if the architecture supports hugetlb pages down at the lowest PTE level. huge_pte_alloc() uses pte_alloc_map(), but generic hugetlb code does no corresponding pte_unmap(); similarly for huge_pte_offset(). In rare transient cases, not yet made possible, pte_offset_map() and pte_offset_map_lock() may not find a page table: handle appropriately. Link: https://lkml.kernel.org/r/a4963be9-7aa6-350-66d0-2ba843e1af44@google.com Link: https://lkml.kernel.org/r/813429a1-204a-1844-eeae-7fd72826c28@google.com Signed-off-by: Hugh Dickins <hughd@google.com> Cc: Alexander Gordeev <agordeev@linux.ibm.com> Cc: Alexandre Ghiti <alexghiti@rivosinc.com> Cc: Aneesh Kumar K.V <aneesh.kumar@linux.ibm.com> Cc: Catalin Marinas <catalin.marinas@arm.com> Cc: Christian Borntraeger <borntraeger@linux.ibm.com> Cc: Chris Zankel <chris@zankel.net> Cc: Claudio Imbrenda <imbrenda@linux.ibm.com> Cc: David Hildenbrand <david@redhat.com> Cc: "David S. Miller" <davem@davemloft.net> Cc: Geert Uytterhoeven <geert@linux-m68k.org> Cc: Greg Ungerer <gerg@linux-m68k.org> Cc: Heiko Carstens <hca@linux.ibm.com> Cc: Helge Deller <deller@gmx.de> Cc: John David Anglin <dave.anglin@bell.net> Cc: John Paul Adrian Glaubitz <glaubitz@physik.fu-berlin.de> Cc: Kirill A. Shutemov <kirill.shutemov@linux.intel.com> Cc: Matthew Wilcox (Oracle) <willy@infradead.org> Cc: Max Filippov <jcmvbkbc@gmail.com> Cc: Michael Ellerman <mpe@ellerman.id.au> Cc: Michal Simek <monstr@monstr.eu> Cc: Mike Kravetz <mike.kravetz@oracle.com> Cc: Mike Rapoport (IBM) <rppt@kernel.org> Cc: Palmer Dabbelt <palmer@dabbelt.com> Cc: Peter Zijlstra <peterz@infradead.org> Cc: Qi Zheng <zhengqi.arch@bytedance.com> Cc: Russell King <linux@armlinux.org.uk> Cc: Suren Baghdasaryan <surenb@google.com> Cc: Thomas Bogendoerfer <tsbogend@alpha.franken.de> Cc: Will Deacon <will@kernel.org> Cc: Ingo Molnar <mingo@kernel.org> Cc: "H. Peter Anvin" <hpa@zytor.com> Cc: Thomas Gleixner <tglx@linutronix.de> Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
283 lines
6.2 KiB
C
283 lines
6.2 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* linux/arch/arm/lib/uaccess_with_memcpy.c
|
|
*
|
|
* Written by: Lennert Buytenhek and Nicolas Pitre
|
|
* Copyright (C) 2009 Marvell Semiconductor
|
|
*/
|
|
|
|
#include <linux/kernel.h>
|
|
#include <linux/ctype.h>
|
|
#include <linux/uaccess.h>
|
|
#include <linux/rwsem.h>
|
|
#include <linux/mm.h>
|
|
#include <linux/sched.h>
|
|
#include <linux/hardirq.h> /* for in_atomic() */
|
|
#include <linux/gfp.h>
|
|
#include <linux/highmem.h>
|
|
#include <linux/hugetlb.h>
|
|
#include <asm/current.h>
|
|
#include <asm/page.h>
|
|
|
|
static int
|
|
pin_page_for_write(const void __user *_addr, pte_t **ptep, spinlock_t **ptlp)
|
|
{
|
|
unsigned long addr = (unsigned long)_addr;
|
|
pgd_t *pgd;
|
|
p4d_t *p4d;
|
|
pmd_t *pmd;
|
|
pte_t *pte;
|
|
pud_t *pud;
|
|
spinlock_t *ptl;
|
|
|
|
pgd = pgd_offset(current->mm, addr);
|
|
if (unlikely(pgd_none(*pgd) || pgd_bad(*pgd)))
|
|
return 0;
|
|
|
|
p4d = p4d_offset(pgd, addr);
|
|
if (unlikely(p4d_none(*p4d) || p4d_bad(*p4d)))
|
|
return 0;
|
|
|
|
pud = pud_offset(p4d, addr);
|
|
if (unlikely(pud_none(*pud) || pud_bad(*pud)))
|
|
return 0;
|
|
|
|
pmd = pmd_offset(pud, addr);
|
|
if (unlikely(pmd_none(*pmd)))
|
|
return 0;
|
|
|
|
/*
|
|
* A pmd can be bad if it refers to a HugeTLB or THP page.
|
|
*
|
|
* Both THP and HugeTLB pages have the same pmd layout
|
|
* and should not be manipulated by the pte functions.
|
|
*
|
|
* Lock the page table for the destination and check
|
|
* to see that it's still huge and whether or not we will
|
|
* need to fault on write.
|
|
*/
|
|
if (unlikely(pmd_thp_or_huge(*pmd))) {
|
|
ptl = ¤t->mm->page_table_lock;
|
|
spin_lock(ptl);
|
|
if (unlikely(!pmd_thp_or_huge(*pmd)
|
|
|| pmd_hugewillfault(*pmd))) {
|
|
spin_unlock(ptl);
|
|
return 0;
|
|
}
|
|
|
|
*ptep = NULL;
|
|
*ptlp = ptl;
|
|
return 1;
|
|
}
|
|
|
|
if (unlikely(pmd_bad(*pmd)))
|
|
return 0;
|
|
|
|
pte = pte_offset_map_lock(current->mm, pmd, addr, &ptl);
|
|
if (unlikely(!pte))
|
|
return 0;
|
|
|
|
if (unlikely(!pte_present(*pte) || !pte_young(*pte) ||
|
|
!pte_write(*pte) || !pte_dirty(*pte))) {
|
|
pte_unmap_unlock(pte, ptl);
|
|
return 0;
|
|
}
|
|
|
|
*ptep = pte;
|
|
*ptlp = ptl;
|
|
|
|
return 1;
|
|
}
|
|
|
|
static unsigned long noinline
|
|
__copy_to_user_memcpy(void __user *to, const void *from, unsigned long n)
|
|
{
|
|
unsigned long ua_flags;
|
|
int atomic;
|
|
|
|
/* the mmap semaphore is taken only if not in an atomic context */
|
|
atomic = faulthandler_disabled();
|
|
|
|
if (!atomic)
|
|
mmap_read_lock(current->mm);
|
|
while (n) {
|
|
pte_t *pte;
|
|
spinlock_t *ptl;
|
|
int tocopy;
|
|
|
|
while (!pin_page_for_write(to, &pte, &ptl)) {
|
|
if (!atomic)
|
|
mmap_read_unlock(current->mm);
|
|
if (__put_user(0, (char __user *)to))
|
|
goto out;
|
|
if (!atomic)
|
|
mmap_read_lock(current->mm);
|
|
}
|
|
|
|
tocopy = (~(unsigned long)to & ~PAGE_MASK) + 1;
|
|
if (tocopy > n)
|
|
tocopy = n;
|
|
|
|
ua_flags = uaccess_save_and_enable();
|
|
__memcpy((void *)to, from, tocopy);
|
|
uaccess_restore(ua_flags);
|
|
to += tocopy;
|
|
from += tocopy;
|
|
n -= tocopy;
|
|
|
|
if (pte)
|
|
pte_unmap_unlock(pte, ptl);
|
|
else
|
|
spin_unlock(ptl);
|
|
}
|
|
if (!atomic)
|
|
mmap_read_unlock(current->mm);
|
|
|
|
out:
|
|
return n;
|
|
}
|
|
|
|
unsigned long
|
|
arm_copy_to_user(void __user *to, const void *from, unsigned long n)
|
|
{
|
|
/*
|
|
* This test is stubbed out of the main function above to keep
|
|
* the overhead for small copies low by avoiding a large
|
|
* register dump on the stack just to reload them right away.
|
|
* With frame pointer disabled, tail call optimization kicks in
|
|
* as well making this test almost invisible.
|
|
*/
|
|
if (n < 64) {
|
|
unsigned long ua_flags = uaccess_save_and_enable();
|
|
n = __copy_to_user_std(to, from, n);
|
|
uaccess_restore(ua_flags);
|
|
} else {
|
|
n = __copy_to_user_memcpy(uaccess_mask_range_ptr(to, n),
|
|
from, n);
|
|
}
|
|
return n;
|
|
}
|
|
|
|
static unsigned long noinline
|
|
__clear_user_memset(void __user *addr, unsigned long n)
|
|
{
|
|
unsigned long ua_flags;
|
|
|
|
mmap_read_lock(current->mm);
|
|
while (n) {
|
|
pte_t *pte;
|
|
spinlock_t *ptl;
|
|
int tocopy;
|
|
|
|
while (!pin_page_for_write(addr, &pte, &ptl)) {
|
|
mmap_read_unlock(current->mm);
|
|
if (__put_user(0, (char __user *)addr))
|
|
goto out;
|
|
mmap_read_lock(current->mm);
|
|
}
|
|
|
|
tocopy = (~(unsigned long)addr & ~PAGE_MASK) + 1;
|
|
if (tocopy > n)
|
|
tocopy = n;
|
|
|
|
ua_flags = uaccess_save_and_enable();
|
|
__memset((void *)addr, 0, tocopy);
|
|
uaccess_restore(ua_flags);
|
|
addr += tocopy;
|
|
n -= tocopy;
|
|
|
|
if (pte)
|
|
pte_unmap_unlock(pte, ptl);
|
|
else
|
|
spin_unlock(ptl);
|
|
}
|
|
mmap_read_unlock(current->mm);
|
|
|
|
out:
|
|
return n;
|
|
}
|
|
|
|
unsigned long arm_clear_user(void __user *addr, unsigned long n)
|
|
{
|
|
/* See rational for this in __copy_to_user() above. */
|
|
if (n < 64) {
|
|
unsigned long ua_flags = uaccess_save_and_enable();
|
|
n = __clear_user_std(addr, n);
|
|
uaccess_restore(ua_flags);
|
|
} else {
|
|
n = __clear_user_memset(addr, n);
|
|
}
|
|
return n;
|
|
}
|
|
|
|
#if 0
|
|
|
|
/*
|
|
* This code is disabled by default, but kept around in case the chosen
|
|
* thresholds need to be revalidated. Some overhead (small but still)
|
|
* would be implied by a runtime determined variable threshold, and
|
|
* so far the measurement on concerned targets didn't show a worthwhile
|
|
* variation.
|
|
*
|
|
* Note that a fairly precise sched_clock() implementation is needed
|
|
* for results to make some sense.
|
|
*/
|
|
|
|
#include <linux/vmalloc.h>
|
|
|
|
static int __init test_size_treshold(void)
|
|
{
|
|
struct page *src_page, *dst_page;
|
|
void *user_ptr, *kernel_ptr;
|
|
unsigned long long t0, t1, t2;
|
|
int size, ret;
|
|
|
|
ret = -ENOMEM;
|
|
src_page = alloc_page(GFP_KERNEL);
|
|
if (!src_page)
|
|
goto no_src;
|
|
dst_page = alloc_page(GFP_KERNEL);
|
|
if (!dst_page)
|
|
goto no_dst;
|
|
kernel_ptr = page_address(src_page);
|
|
user_ptr = vmap(&dst_page, 1, VM_IOREMAP, __pgprot(__PAGE_COPY));
|
|
if (!user_ptr)
|
|
goto no_vmap;
|
|
|
|
/* warm up the src page dcache */
|
|
ret = __copy_to_user_memcpy(user_ptr, kernel_ptr, PAGE_SIZE);
|
|
|
|
for (size = PAGE_SIZE; size >= 4; size /= 2) {
|
|
t0 = sched_clock();
|
|
ret |= __copy_to_user_memcpy(user_ptr, kernel_ptr, size);
|
|
t1 = sched_clock();
|
|
ret |= __copy_to_user_std(user_ptr, kernel_ptr, size);
|
|
t2 = sched_clock();
|
|
printk("copy_to_user: %d %llu %llu\n", size, t1 - t0, t2 - t1);
|
|
}
|
|
|
|
for (size = PAGE_SIZE; size >= 4; size /= 2) {
|
|
t0 = sched_clock();
|
|
ret |= __clear_user_memset(user_ptr, size);
|
|
t1 = sched_clock();
|
|
ret |= __clear_user_std(user_ptr, size);
|
|
t2 = sched_clock();
|
|
printk("clear_user: %d %llu %llu\n", size, t1 - t0, t2 - t1);
|
|
}
|
|
|
|
if (ret)
|
|
ret = -EFAULT;
|
|
|
|
vunmap(user_ptr);
|
|
no_vmap:
|
|
put_page(dst_page);
|
|
no_dst:
|
|
put_page(src_page);
|
|
no_src:
|
|
return ret;
|
|
}
|
|
|
|
subsys_initcall(test_size_treshold);
|
|
|
|
#endif
|