Missing READ_ONCE in core and arch-specific pgtable code leading to crashes
Subsystems (18)
cover Missing READ_ONCE in core and arch-specific pgtable code leading to crashes
Hi,
We recently had a crash report[1] on arm64 that involved a bad dereference in the page_vma_mapped code during ext4 writeback with THP active. I can reproduce this on -rc2:
[ 254.032812] PC is at check_pte+0x20/0x170 [ 254.032948] LR is at page_vma_mapped_walk+0x2e0/0x540 [...] [ 254.036114] Process doio (pid: 2463, stack limit = 0xffff00000f2e8000) [ 254.036361] Call trace: [ 254.038977] [<ffff000008233328>] check_pte+0x20/0x170 [ 254.039137] [<ffff000008233758>] page_vma_mapped_walk+0x2e0/0x540 [ 254.039332] [<ffff000008234adc>] page_mkclean_one+0xac/0x278 [ 254.039489] [<ffff000008234d98>] rmap_walk_file+0xf0/0x238 [ 254.039642] [<ffff000008236e74>] rmap_walk+0x64/0xa0 [ 254.039784] [<ffff0000082370c8>] page_mkclean+0x90/0xa8 [ 254.040029] [<ffff0000081f3c64>] clear_page_dirty_for_io+0x84/0x2a8 [ 254.040311] [<ffff00000832f984>] mpage_submit_page+0x34/0x98 [ 254.040518] [<ffff00000832fb4c>] mpage_process_page_bufs+0x164/0x170 [ 254.040743] [<ffff00000832fc8c>] mpage_prepare_extent_to_map+0x134/0x2b8 [ 254.040969] [<ffff00000833530c>] ext4_writepages+0x484/0xe30 [ 254.041175] [<ffff0000081f6ab4>] do_writepages+0x44/0xe8 [ 254.041372] [<ffff0000081e5bd4>] __filemap_fdatawrite_range+0xbc/0x110 [ 254.041568] [<ffff0000081e5e68>] file_write_and_wait_range+0x48/0xd8 [ 254.041739] [<ffff000008324310>] ext4_sync_file+0x80/0x4b8 [ 254.041907] [<ffff0000082bd434>] vfs_fsync_range+0x64/0xc0 [ 254.042106] [<ffff0000082332b4>] SyS_msync+0x194/0x1e8
After digging into the issue, I found that we appear to be racing with a concurrent pmd update in page_vma_mapped_walk, assumedly due a THP splitting operation. Looking at the code there:
pvmw->pmd = pmd_offset(pud, pvmw->address);
if (pmd_trans_huge(*pvmw->pmd) || is_pmd_migration_entry(*pvmw->pmd)) {
[...]
} else {
if (!check_pmd(pvmw))
return false;
}
if (!map_pte(pvmw))
goto next_pte;what happens in the crashing scenario is that we see all zeroes for the PMD in pmd_trans_huge(*pvmw->pmd), and so go to the 'else' case (migration isn't enabled, so the test is removed at compile-time). check_pmd then does:
pmde = READ_ONCE(*pvmw->pmd); return pmd_present(pmde) && !pmd_trans_huge(pmde);
and reads a valid table entry for the PMD because the splitting has completed (i.e. the first dereference reads from the pmdp_invalidate in the splitting code, whereas the second dereferenced reads from the following pmd_populate). It returns true because we should descend to the PTE level in map_pte. map_pte does:
pvmw->pte = pte_offset_map(pvmw->pmd, pvmw->address);
which on arm64 (and this appears to be the same on x86) ends up doing:
(pmd_page_paddr((*(pvmw->pmd))) + pte_index(pvmw->address) * sizeof(pte_t))
as part of its calculation. However, this is horribly broken because GCC inlines everything and reuses the register it loaded for the initial pmd_trans_huge check (when we loaded the value of zero) here, so we end up calculating a junk pointer and crashing when we dereference it. Disassembly at the end of the mail[2] for those who are curious.
The moral of the story is that read-after-read (same address) ordering *only* applies if READ_ONCE is used consistently. This means we need to fix page table dereferences in the core code as well as the arch code to avoid this problem. The two RFC patches in this series fix arm64 (which is a bigger fix that necessary since I clean things up too) and page_vma_mapped_walk.
Comments welcome.
Will
[1] http://lists.infradead.org/pipermail/linux-arm-kernel/2017-September/532786.html
[2]
// page_vma_mapped_walk
// pvmw->pmd = pmd_offset(pud, pvmw->address);
ldr x0, [x19, #24] // pvmw->pmd
// if (pmd_trans_huge(*pvmw->pmd) || is_pmd_migration_entry(*pvmw->pmd)) {
ldr x1, [x0] // *pvmw->pmd cbz x1, ffff0000082336a0 <page_vma_mapped_walk+0x228> tbz w1, #1, ffff000008233788 <page_vma_mapped_walk+0x310> // pmd_trans_huge?
// else if (!check_pmd(pvmw))
ldr x0, [x0] // READ_ONCE in check_pmd tst x0, x24 // pmd_present? b.eq ffff000008233538 <page_vma_mapped_walk+0xc0> // b.none tbz w0, #1, ffff000008233538 <page_vma_mapped_walk+0xc0> // pmd_trans_huge?
// if (!map_pte(pvmw))
ldr x0, [x19, #16] // pvmw->address
// pvmw->pte = pte_offset_map(pvmw->pmd, pvmw->address);
and x1, x1, #0xfffffffff000 // Reusing the old value of *pvmw->pmd!!!
[...]
--->8
Will Deacon (2):
arm64: mm: Use READ_ONCE/WRITE_ONCE when accessing page tables
mm: page_vma_mapped: Ensure pmd is loaded with READ_ONCE outside of
lock
arch/arm64/include/asm/hugetlb.h | 2 +- arch/arm64/include/asm/kvm_mmu.h | 18 +-- arch/arm64/include/asm/mmu_context.h | 4 +- arch/arm64/include/asm/pgalloc.h | 42 +++--- arch/arm64/include/asm/pgtable.h | 29 ++-- arch/arm64/kernel/hibernate.c | 148 +++++++++--------- arch/arm64/mm/dump.c | 54 ++++--- arch/arm64/mm/fault.c | 44 +++--- arch/arm64/mm/hugetlbpage.c | 94 ++++++------ arch/arm64/mm/kasan_init.c | 62 ++++---- arch/arm64/mm/mmu.c | 281 ++++++++++++++++++----------------- arch/arm64/mm/pageattr.c | 30 ++-- mm/page_vma_mapped.c | 25 ++-- 13 files changed, 427 insertions(+), 406 deletions(-)
Signature · 1 line
2.1.4
RFC PATCH 1/2 arm64: mm: Use READ_ONCE/WRITE_ONCE when accessing page tables
In many cases, page tables can be accessed concurrently by either another CPU (due to things like fast gup) or by the hardware page table walker itself, which may set access/dirty bits. In such cases, it is important to use READ_ONCE/WRITE_ONCE when accessing page table entries so that entries cannot be torn, merged or subject to apparent loss of coherence.
Whilst there are some scenarios where this cannot happen (e.g. pinned kernel mappings for the linear region), the overhead of using READ_ONCE /WRITE_ONCE everywhere is minimal and makes the code an awful lot easier to reason about. This patch consistently uses these macros in the arch code, as well as explicitly namespacing pointers to page table entries from the entries themselves by using adopting a 'p' suffix for the former (as is sometimes used elsewhere in the kernel source).
--- arch/arm64/include/asm/hugetlb.h | 2 +- arch/arm64/include/asm/kvm_mmu.h | 18 +-- arch/arm64/include/asm/mmu_context.h | 4 +- arch/arm64/include/asm/pgalloc.h | 42 +++--- arch/arm64/include/asm/pgtable.h | 29 ++-- arch/arm64/kernel/hibernate.c | 148 +++++++++--------- arch/arm64/mm/dump.c | 54 ++++--- arch/arm64/mm/fault.c | 44 +++--- arch/arm64/mm/hugetlbpage.c | 94 ++++++------ arch/arm64/mm/kasan_init.c | 62 ++++---- arch/arm64/mm/mmu.c | 281 ++++++++++++++++++----------------- arch/arm64/mm/pageattr.c | 30 ++-- 12 files changed, 417 insertions(+), 391 deletions(-)
arch/arm64/include/asm/hugetlb.h +1 -1
@@ -22,7 +22,7 @@static inline pte_t huge_ptep_get (pte_t *ptep) { -return *ptep; +return READ_ONCE (*ptep); }
arch/arm64/include/asm/kvm_mmu.h +9 -9
@@ -173,32 +173,32 @@ static inline pmd_t kvm_s2pmd_mkwrite(pmd_t pmd)return pmd; } -static inline void kvm_set_s2pte_readonly (pte_t *pte) +static inline void kvm_set_s2pte_readonly (pte_t *ptep) {pteval_t old_pteval, pteval; - pteval =READ_ONCE (pte_val (*pte)); + pteval =READ_ONCE (pte_val (*ptep));do { old_pteval = pteval; pteval &= ~PTE_S2_RDWR ; pteval |=PTE_S2_RDONLY ; - pteval =cmpxchg_relaxed (&pte_val (*pte), old_pteval, pteval); + pteval =cmpxchg_relaxed (&pte_val (*ptep), old_pteval, pteval); }while (pteval != old_pteval); } -static inline bool kvm_s2pte_readonly (pte_t *pte) +static inline bool kvm_s2pte_readonly (pte_t *ptep) { -return (pte_val (*pte) &PTE_S2_RDWR ) ==PTE_S2_RDONLY ; +return (READ_ONCE (pte_val (*ptep)) &PTE_S2_RDWR ) ==PTE_S2_RDONLY ; } -static inline void kvm_set_s2pmd_readonly (pmd_t *pmd) +static inline void kvm_set_s2pmd_readonly (pmd_t *pmdp) { -kvm_set_s2pte_readonly ((pte_t *)pmd); +kvm_set_s2pte_readonly ((pte_t *)pmdp); } -static inline bool kvm_s2pmd_readonly (pmd_t *pmd) +static inline bool kvm_s2pmd_readonly (pmd_t *pmdp) { -return kvm_s2pte_readonly ((pte_t *)pmd); +return kvm_s2pte_readonly ((pte_t *)pmdp); }static inline bool kvm_page_empty (void *ptr)
arch/arm64/include/asm/mmu_context.h +2 -2
@@ -127,13 +127,13 @@ static inline void cpu_install_idmap(void)* Atomically replaces the active TTBR1_EL1 PGD with a new VA-compatible PGD, * avoiding the possibility of conflicting TLB entries being allocated. */ -static inline void cpu_replace_ttbr1 (pgd_t *pgd) +static inline void cpu_replace_ttbr1 (pgd_t *pgdp) {typedef void (ttbr_replace_func )(phys_addr_t );extern ttbr_replace_func idmap_cpu_replace_ttbr1;ttbr_replace_func *replace_phys; -phys_addr_t pgd_phys =virt_to_phys (pgd); +phys_addr_t pgd_phys =virt_to_phys (pgdp); replace_phys = (void *)__pa_symbol (idmap_cpu_replace_ttbr1);
arch/arm64/include/asm/pgalloc.h +21 -21
91 lines not on this page · raw message
arch/arm64/include/asm/pgtable.h +16 -13
@@ -181,7 +181,7 @@ static inline pmd_t pmd_mkcont(pmd_t pmd)static inline void set_pte (pte_t *ptep,pte_t pte) { - *ptep = pte; +WRITE_ONCE (*ptep, pte);/* * Only if the new pte is valid and kernel, otherwise TLB maintenance
@@ -216,6 +216,8 @@ extern void __sync_icache_dcache(pte_t pteval, unsigned long addr);static inline void set_pte_at (struct mm_struct *mm,unsigned long addr,pte_t *ptep,pte_t pte) { +pte_t old_pte; +if (pte_present (pte) &&pte_user_exec (pte) && !pte_special (pte))__sync_icache_dcache (pte, addr);
@@ -224,13 +226,14 @@ static inline void set_pte_at(struct mm_struct *mm, unsigned long addr,* hardware updates of the pte (ptep_set_access_flags safely changes * valid ptes without going through an invalid entry). */ -if (pte_valid (*ptep) &&pte_valid (pte)) { + old_pte =READ_ONCE (*ptep); +if (pte_valid (old_pte) &&pte_valid (pte)) {VM_WARN_ONCE (!pte_young (pte),"%s: racy access flag clearing: 0x%016llx -> 0x%016llx" , - __func__,pte_val (*ptep),pte_val (pte)); -VM_WARN_ONCE (pte_write (*ptep) && !pte_dirty (pte), + __func__,pte_val (old_pte),pte_val (pte)); +VM_WARN_ONCE (pte_write (old_pte) && !pte_dirty (pte),"%s: racy dirty state clearing: 0x%016llx -> 0x%016llx" , - __func__,pte_val (*ptep),pte_val (pte)); + __func__,pte_val (old_pte),pte_val (pte)); }set_pte (ptep, pte);
@@ -383,7 +386,7 @@ extern pgprot_t phys_mem_access_prot(struct file *file, unsigned long pfn,static inline void set_pmd (pmd_t *pmdp,pmd_t pmd) { - *pmdp = pmd; +WRITE_ONCE (*pmdp, pmd);dsb (ishst);isb (); }
@@ -401,7 +404,7 @@ static inline phys_addr_t pmd_page_paddr(pmd_t pmd)/* Find an entry in the third-level page table. */ #define pte_index (addr) (((addr) >> PAGE_SHIFT) & (PTRS_PER_PTE - 1)) -#define pte_offset_phys (dir,addr) (pmd_page_paddr(*(dir)) + pte_index(addr) * sizeof(pte_t)) +#define pte_offset_phys (dir,addr) (pmd_page_paddr(READ_ONCE(*(dir))) + pte_index(addr) * sizeof(pte_t))#define pte_offset_kernel (dir,addr) ((pte_t *)__va(pte_offset_phys((dir), (addr))))#define pte_offset_map (dir,addr) pte_offset_kernel((dir), (addr))
@@ -434,7 +437,7 @@ static inline phys_addr_t pmd_page_paddr(pmd_t pmd)static inline void set_pud (pud_t *pudp,pud_t pud) { - *pudp = pud; +WRITE_ONCE (*pudp, pud);dsb (ishst);isb (); }
@@ -452,7 +455,7 @@ static inline phys_addr_t pud_page_paddr(pud_t pud)/* Find an entry in the second-level page table. */ #define pmd_index (addr) (((addr) >> PMD_SHIFT) & (PTRS_PER_PMD - 1)) -#define pmd_offset_phys (dir, addr) (pud_page_paddr(*(dir)) + pmd_index(addr) * sizeof(pmd_t)) +#define pmd_offset_phys (dir, addr) (pud_page_paddr(READ_ONCE(*(dir))) + pmd_index(addr) * sizeof(pmd_t))#define pmd_offset (dir, addr) ((pmd_t *)__va(pmd_offset_phys((dir), (addr))))#define pmd_set_fixmap (addr) ((pmd_t *)set_fixmap_offset(FIX_PMD, addr))
@@ -487,7 +490,7 @@ static inline phys_addr_t pud_page_paddr(pud_t pud)static inline void set_pgd (pgd_t *pgdp,pgd_t pgd) { - *pgdp = pgd; +WRITE_ONCE (*pgdp, pgd);dsb (ishst); }
@@ -504,7 +507,7 @@ static inline phys_addr_t pgd_page_paddr(pgd_t pgd)/* Find an entry in the frst-level page table. */ #define pud_index (addr) (((addr) >> PUD_SHIFT) & (PTRS_PER_PUD - 1)) -#define pud_offset_phys (dir, addr) (pgd_page_paddr(*(dir)) + pud_index(addr) * sizeof(pud_t)) +#define pud_offset_phys (dir, addr) (pgd_page_paddr(READ_ONCE(*(dir))) + pud_index(addr) * sizeof(pud_t))#define pud_offset (dir, addr) ((pud_t *)__va(pud_offset_phys((dir), (addr))))#define pud_set_fixmap (addr) ((pud_t *)set_fixmap_offset(FIX_PUD, addr))
@@ -645,9 +648,9 @@ static inline void ptep_set_wrprotect(struct mm_struct *mm, unsigned long addres* dirty state: clearing of PTE_RDONLY when PTE_WRITE (a.k.a. PTE_DBM) * is set. */ -VM_WARN_ONCE (pte_write (*ptep) && !pte_dirty (*ptep), -"%s: potential race with hardware DBM" , __func__); pte =READ_ONCE (*ptep); +VM_WARN_ONCE (pte_write (pte) && !pte_dirty (pte), +"%s: potential race with hardware DBM" , __func__); do { old_pte = pte; pte = pte_wrprotect(pte);
arch/arm64/kernel/hibernate.c +76 -72
259 lines not on this page · raw message
arch/arm64/mm/dump.c +30 -24
95 lines not on this page · raw message
arch/arm64/mm/fault.c +25 -19
80 lines not on this page · raw message
arch/arm64/mm/hugetlbpage.c +43 -51
162 lines not on this page · raw message
arch/arm64/mm/kasan_init.c +31 -31
113 lines not on this page · raw message
arch/arm64/mm/mmu.c +147 -134
623 lines not on this page · raw message
arch/arm64/mm/pageattr.c +16 -14
@@ -156,30 +156,32 @@ void __kernel_map_pages(struct page *page, int numpages, int enable)*/ bool kernel_page_present (struct page *page) { -pgd_t *pgd; -pud_t *pud; -pmd_t *pmd; -pte_t *pte; +pgd_t *pgdp; +pud_t *pudp, pud; +pmd_t *pmdp, pmd; +pte_t *ptep;unsigned long addr = (unsigned long )page_address (page); - pgd =pgd_offset_k (addr); -if (pgd_none (*pgd)) + pgdp =pgd_offset_k (addr); +if (pgd_none (READ_ONCE (*pgdp)))return false; - pud =pud_offset (pgd, addr); -if (pud_none (*pud)) + pudp =pud_offset (pgdp, addr); + pud =READ_ONCE (*pudp); +if (pud_none (pud))return false; -if (pud_sect (*pud)) +if (pud_sect (pud))return true; - pmd =pmd_offset (pud, addr); -if (pmd_none (*pmd)) + pmdp =pmd_offset (pudp, addr); + pmd =READ_ONCE (*pmdp); +if (pmd_none (pmd))return false; -if (pmd_sect (*pmd)) +if (pmd_sect (pmd))return true; - pte =pte_offset_kernel (pmd, addr); -return pte_valid (*pte); + ptep =pte_offset_kernel (pmdp, addr); +return pte_valid (READ_ONCE (*ptep)); }#endif /* CONFIG_HIBERNATION */ #endif /* CONFIG_DEBUG_PAGEALLOC */
Signature · 1 line
2.1.4
Peter Zijlstra
maintainerRe: [RFC PATCH 1/2] arm64: mm: Use READ_ONCE/WRITE_ONCE when accessing page tables
Quoted 5 lines · Will Deacon
In many cases, page tables can be accessed concurrently by either another CPU (due to things like fast gup) or by the hardware page table walker itself, which may set access/dirty bits. In such cases, it is important to use READ_ONCE/WRITE_ONCE when accessing page table entries so that entries cannot be torn, merged or subject to apparent loss of coherence.
In fact, we should use lockless_dereference() for many of them. Yes Alpha is the only one that cares about the difference between that and READ_ONCE() and they do have the extra barrier, but if we're going to do this, we might as well do it 'right' :-)
Also, a very long standing item on my TODO list is to see how much of it we can unify across the various architectures, because there's a giant amount of boiler plate involved with all this.
Re: [RFC PATCH 1/2] arm64: mm: Use READ_ONCE/WRITE_ONCE when accessing page tables
Quoted 11 lines · Peter Zijlstra
Quoted 5 lines · Will Deacon
In many cases, page tables can be accessed concurrently by either another CPU (due to things like fast gup) or by the hardware page table walker itself, which may set access/dirty bits. In such cases, it is important to use READ_ONCE/WRITE_ONCE when accessing page table entries so that entries cannot be torn, merged or subject to apparent loss of coherence.
In fact, we should use
lockless_dereference()for many of them. Yes Alpha is the only one that cares about the difference between that andREAD_ONCE()and they do have the extra barrier, but if we're going to do this, we might as well do it 'right' :-)
I know this sounds daft, but I think one of the big reasons why lockless_dereference() doesn't get an awful lot of use is because it's such a mouthful! Why don't we just move the smp_read_barrier_depends() into READ_ONCE? Would anybody actually care about the potential impact on Alpha (which, frankly, is treading on thin ice given the low adoption of lockless_dereference())?
Quoted 3 lines
Also, a very long standing item on my TODO list is to see how much of it we can unify across the various architectures, because there's a giant amount of boiler plate involved with all this.
Yeah, I'd be happy to help with that as a separate series. I already tripped over 5 or 6 page table walkers in arch/arm64/ alone :(
Will
Re: [RFC PATCH 1/2] arm64: mm: Use READ_ONCE/WRITE_ONCE when accessing page tables
Quoted 13 lines · Will Deacon
Quoted 11 lines · Peter Zijlstra
Quoted 5 lines · Will Deacon
In many cases, page tables can be accessed concurrently by either another CPU (due to things like fast gup) or by the hardware page table walker itself, which may set access/dirty bits. In such cases, it is important to use READ_ONCE/WRITE_ONCE when accessing page table entries so that entries cannot be torn, merged or subject to apparent loss of coherence.
In fact, we should use
lockless_dereference()for many of them. Yes Alpha is the only one that cares about the difference between that andREAD_ONCE()and they do have the extra barrier, but if we're going to do this, we might as well do it 'right' :-)
Quoted 6 lines
I know this sounds daft, but I think one of the big reasons why
lockless_dereference()doesn't get an awful lot of use is because it's such a mouthful! Why don't we just move thesmp_read_barrier_depends()intoREAD_ONCE? Would anybody actually care about the potential impact on Alpha (which, frankly, is treading on thin ice given the low adoption oflockless_dereference())?
This is my cue to ask my usual question... ;-)
Are people still running mainline kernels on Alpha? (Added Alpha folks.)
As always, if anyone is, we must continue to support Alpha, but sounds like time to check again.
Thanx, Paul
Re: [RFC PATCH 1/2] arm64: mm: Use READ_ONCE/WRITE_ONCE when accessing page tables
Quoted 21 lines · Paul E. McKenney
Quoted 13 lines · Will Deacon
Quoted 11 lines · Peter Zijlstra
Quoted 5 lines · Will Deacon
In many cases, page tables can be accessed concurrently by either another CPU (due to things like fast gup) or by the hardware page table walker itself, which may set access/dirty bits. In such cases, it is important to use READ_ONCE/WRITE_ONCE when accessing page table entries so that entries cannot be torn, merged or subject to apparent loss of coherence.
In fact, we should use
lockless_dereference()for many of them. Yes Alpha is the only one that cares about the difference between that andREAD_ONCE()and they do have the extra barrier, but if we're going to do this, we might as well do it 'right' :-)Quoted 6 lines
I know this sounds daft, but I think one of the big reasons why
lockless_dereference()doesn't get an awful lot of use is because it's such a mouthful! Why don't we just move thesmp_read_barrier_depends()intoREAD_ONCE? Would anybody actually care about the potential impact on Alpha (which, frankly, is treading on thin ice given the low adoption oflockless_dereference())?
Quoted 6 lines
This is my cue to ask my usual question... ;-)
Are people still running mainline kernels on Alpha? (Added Alpha folks.)
As always, if anyone is, we must continue to support Alpha, but sounds like time to check again.
I'll be honest and say that I haven't updated mine for a while, but I do have a soft spot for those machines :(
Will
Re: [RFC PATCH 1/2] arm64: mm: Use READ_ONCE/WRITE_ONCE when accessing page tables
Quoted 24 lines · Will Deacon
Quoted 23 lines · Paul E. McKenney
Quoted 13 lines · Will Deacon
Quoted 11 lines · Peter Zijlstra
Quoted 5 lines · Will Deacon
In many cases, page tables can be accessed concurrently by either another CPU (due to things like fast gup) or by the hardware page table walker itself, which may set access/dirty bits. In such cases, it is important to use READ_ONCE/WRITE_ONCE when accessing page table entries so that entries cannot be torn, merged or subject to apparent loss of coherence.
In fact, we should use
lockless_dereference()for many of them. Yes Alpha is the only one that cares about the difference between that andREAD_ONCE()and they do have the extra barrier, but if we're going to do this, we might as well do it 'right' :-)Quoted 6 lines
I know this sounds daft, but I think one of the big reasons why
lockless_dereference()doesn't get an awful lot of use is because it's such a mouthful! Why don't we just move thesmp_read_barrier_depends()intoREAD_ONCE? Would anybody actually care about the potential impact on Alpha (which, frankly, is treading on thin ice given the low adoption oflockless_dereference())?This is my cue to ask my usual question... ;-)
Quoted 7 lines
Quoted 4 lines
Are people still running mainline kernels on Alpha? (Added Alpha folks.)
As always, if anyone is, we must continue to support Alpha, but sounds like time to check again.
I'll be honest and say that I haven't updated mine for a while, but I do have a soft spot for those machines :(
Let's see what the Alpha folks say. I myself have had a close relationship with Alpha for almost 20 years, but I suspect that in my case it is more a hard spot on my head rather than a soft spot in my heart. ;-)
Thanx, Paul
Re: [RFC PATCH 1/2] arm64: mm: Use READ_ONCE/WRITE_ONCE when accessing page tables
In reply to Paul E. McKenneyQuoted 21 lines · Paul E. McKenney
Quoted 13 lines · Will Deacon
Quoted 11 lines · Peter Zijlstra
Quoted 5 lines · Will Deacon
In many cases, page tables can be accessed concurrently by either another CPU (due to things like fast gup) or by the hardware page table walker itself, which may set access/dirty bits. In such cases, it is important to use READ_ONCE/WRITE_ONCE when accessing page table entries so that entries cannot be torn, merged or subject to apparent loss of coherence.
In fact, we should use
lockless_dereference()for many of them. Yes Alpha is the only one that cares about the difference between that andREAD_ONCE()and they do have the extra barrier, but if we're going to do this, we might as well do it 'right' :-)Quoted 6 lines
I know this sounds daft, but I think one of the big reasons why
lockless_dereference()doesn't get an awful lot of use is because it's such a mouthful! Why don't we just move thesmp_read_barrier_depends()intoREAD_ONCE? Would anybody actually care about the potential impact on Alpha (which, frankly, is treading on thin ice given the low adoption oflockless_dereference())?
Quoted 3 lines
This is my cue to ask my usual question... ;-)
Are people still running mainline kernels on Alpha? (Added Alpha folks.)
Yes. I run two Alpha build daemons that build the unofficial debian-alpha port. Debian popcon reports nine machines running Alpha, which are likely to be running the 4.12.y kernel which is currently in debian-alpha, (and presumably soon to be 4.13.y which is now built on Alpha in experimental).
Cheers
Michael
Re: [RFC PATCH 1/2] arm64: mm: Use READ_ONCE/WRITE_ONCE when accessing page tables
Quoted 24 lines · Michael Cree
Quoted 23 lines · Paul E. McKenney
Quoted 13 lines · Will Deacon
Quoted 11 lines · Peter Zijlstra
Quoted 5 lines · Will Deacon
In many cases, page tables can be accessed concurrently by either another CPU (due to things like fast gup) or by the hardware page table walker itself, which may set access/dirty bits. In such cases, it is important to use READ_ONCE/WRITE_ONCE when accessing page table entries so that entries cannot be torn, merged or subject to apparent loss of coherence.
In fact, we should use
lockless_dereference()for many of them. Yes Alpha is the only one that cares about the difference between that andREAD_ONCE()and they do have the extra barrier, but if we're going to do this, we might as well do it 'right' :-)Quoted 6 lines
I know this sounds daft, but I think one of the big reasons why
lockless_dereference()doesn't get an awful lot of use is because it's such a mouthful! Why don't we just move thesmp_read_barrier_depends()intoREAD_ONCE? Would anybody actually care about the potential impact on Alpha (which, frankly, is treading on thin ice given the low adoption oflockless_dereference())?This is my cue to ask my usual question... ;-)
Quoted 7 lines
Quoted 1 line
Are people still running mainline kernels on Alpha? (Added Alpha folks.)
Yes. I run two Alpha build daemons that build the unofficial debian-alpha port. Debian popcon reports nine machines running Alpha, which are likely to be running the 4.12.y kernel which is currently in debian-alpha, (and presumably soon to be 4.13.y which is now built on Alpha in experimental).
I salute your dedication to Alpha! ;-)
Thanx, Paul
Re: [RFC PATCH 1/2] arm64: mm: Use READ_ONCE/WRITE_ONCE when accessing page tables
Quoted 27 lines · Paul E. McKenney
Quoted 26 lines · Michael Cree
Quoted 24 lines · Paul E. McKenney
Quoted 13 lines · Will Deacon
Quoted 11 lines · Peter Zijlstra
Quoted 5 lines · Will Deacon
In many cases, page tables can be accessed concurrently by either another CPU (due to things like fast gup) or by the hardware page table walker itself, which may set access/dirty bits. In such cases, it is important to use READ_ONCE/WRITE_ONCE when accessing page table entries so that entries cannot be torn, merged or subject to apparent loss of coherence.
In fact, we should use
lockless_dereference()for many of them. Yes Alpha is the only one that cares about the difference between that andREAD_ONCE()and they do have the extra barrier, but if we're going to do this, we might as well do it 'right' :-)Quoted 6 lines
I know this sounds daft, but I think one of the big reasons why
lockless_dereference()doesn't get an awful lot of use is because it's such a mouthful! Why don't we just move thesmp_read_barrier_depends()intoREAD_ONCE? Would anybody actually care about the potential impact on Alpha (which, frankly, is treading on thin ice given the low adoption oflockless_dereference())?This is my cue to ask my usual question... ;-)
Are people still running mainline kernels on Alpha? (Added Alpha folks.)
Quoted 7 lines
Quoted 5 lines
Yes. I run two Alpha build daemons that build the unofficial debian-alpha port. Debian popcon reports nine machines running Alpha, which are likely to be running the 4.12.y kernel which is currently in debian-alpha, (and presumably soon to be 4.13.y which is now built on Alpha in experimental).
I salute your dedication to Alpha! ;-)
Ok, but where does that leave us wrt my initial proposal of moving smp_read_barrier_depends() into READ_ONCE and getting rid of lockless_dereference?
Michael (or anybody else running mainline on SMP Alpha) -- would you be able to give the diff below a spin and see whether there's a measurable performance impact?
Cheers,
Will
--->8
include/linux/compiler.h +1 -0
@@ -340,6 +340,7 @@ static __always_inline void __write_once_size(volatile void *p, void *res, int s__read_once_size (&(x), __u.__c,sizeof (x)); \ else \ __read_once_size_nocheck(&(x), __u.__c, sizeof(x)); \ +smp_read_barrier_depends ();/* Enforce dependency ordering from x */ \ __u.__val; \ })#define READ_ONCE (x) __READ_ONCE(x, 1)
Re: [RFC PATCH 1/2] arm64: mm: Use READ_ONCE/WRITE_ONCE when accessing page tables
Quoted 36 lines · Will Deacon
Quoted 27 lines · Paul E. McKenney
Quoted 26 lines · Michael Cree
Quoted 24 lines · Paul E. McKenney
Quoted 13 lines · Will Deacon
Quoted 11 lines · Peter Zijlstra
Quoted 5 lines · Will Deacon
In many cases, page tables can be accessed concurrently by either another CPU (due to things like fast gup) or by the hardware page table walker itself, which may set access/dirty bits. In such cases, it is important to use READ_ONCE/WRITE_ONCE when accessing page table entries so that entries cannot be torn, merged or subject to apparent loss of coherence.
In fact, we should use
lockless_dereference()for many of them. Yes Alpha is the only one that cares about the difference between that andREAD_ONCE()and they do have the extra barrier, but if we're going to do this, we might as well do it 'right' :-)Quoted 6 lines
I know this sounds daft, but I think one of the big reasons why
lockless_dereference()doesn't get an awful lot of use is because it's such a mouthful! Why don't we just move thesmp_read_barrier_depends()intoREAD_ONCE? Would anybody actually care about the potential impact on Alpha (which, frankly, is treading on thin ice given the low adoption oflockless_dereference())?This is my cue to ask my usual question... ;-)
Are people still running mainline kernels on Alpha? (Added Alpha folks.)
Quoted 7 lines
Quoted 5 lines
Yes. I run two Alpha build daemons that build the unofficial debian-alpha port. Debian popcon reports nine machines running Alpha, which are likely to be running the 4.12.y kernel which is currently in debian-alpha, (and presumably soon to be 4.13.y which is now built on Alpha in experimental).
I salute your dedication to Alpha! ;-)
Quoted 7 lines
Ok, but where does that leave us wrt my initial proposal of moving
smp_read_barrier_depends()intoREAD_ONCEand getting rid oflockless_dereference?Michael (or anybody else running mainline on SMP Alpha) -- would you be able to give the diff below a spin and see whether there's a measurable performance impact?
This will be a sensitive test. The smp_read_barrier_depends() can be removed from lockless_dereference(). Without this removal Alpha will get two memory barriers from rcu_dereference() and friends.
Thanx, Paul
Quoted 19 lines
Cheers,
Will
--->8
include/linux/compiler.h +1
-0@@ -340,6 +340,7 @@ static __always_inline void __write_once_size(volatile void *p, void *res, int s__read_once_size (&(x), __u.__c,sizeof (x)); \ else \ __read_once_size_nocheck(&(x), __u.__c, sizeof(x)); \ +smp_read_barrier_depends ();/* Enforce dependency ordering from x */ \ __u.__val; \ })#define READ_ONCE (x) __READ_ONCE(x, 1)
Re: [RFC PATCH 1/2] arm64: mm: Use READ_ONCE/WRITE_ONCE when accessing page tables
Quoted 41 lines · Paul E. McKenney
Quoted 40 lines · Will Deacon
Quoted 27 lines · Paul E. McKenney
Quoted 26 lines · Michael Cree
Quoted 24 lines · Paul E. McKenney
Quoted 13 lines · Will Deacon
Quoted 11 lines · Peter Zijlstra
Quoted 5 lines · Will Deacon
In many cases, page tables can be accessed concurrently by either another CPU (due to things like fast gup) or by the hardware page table walker itself, which may set access/dirty bits. In such cases, it is important to use READ_ONCE/WRITE_ONCE when accessing page table entries so that entries cannot be torn, merged or subject to apparent loss of coherence.
In fact, we should use
lockless_dereference()for many of them. Yes Alpha is the only one that cares about the difference between that andREAD_ONCE()and they do have the extra barrier, but if we're going to do this, we might as well do it 'right' :-)Quoted 6 lines
I know this sounds daft, but I think one of the big reasons why
lockless_dereference()doesn't get an awful lot of use is because it's such a mouthful! Why don't we just move thesmp_read_barrier_depends()intoREAD_ONCE? Would anybody actually care about the potential impact on Alpha (which, frankly, is treading on thin ice given the low adoption oflockless_dereference())?This is my cue to ask my usual question... ;-)
Are people still running mainline kernels on Alpha? (Added Alpha folks.)
Quoted 7 lines
Quoted 5 lines
Yes. I run two Alpha build daemons that build the unofficial debian-alpha port. Debian popcon reports nine machines running Alpha, which are likely to be running the 4.12.y kernel which is currently in debian-alpha, (and presumably soon to be 4.13.y which is now built on Alpha in experimental).
I salute your dedication to Alpha! ;-)
Ok, but where does that leave us wrt my initial proposal of moving
smp_read_barrier_depends()intoREAD_ONCEand getting rid oflockless_dereference?
Quoted 7 lines
Quoted 3 lines
Michael (or anybody else running mainline on SMP Alpha) -- would you be able to give the diff below a spin and see whether there's a measurable performance impact?
This will be a sensitive test. The
smp_read_barrier_depends()can be removed fromlockless_dereference(). Without this removal Alpha will get two memory barriers fromrcu_dereference()and friends.
Oh yes, good point. I was trying to keep the diff simple, but you're right that this is packing too many barriers. Fixed diff below.
Thanks,
Will
--->8
include/linux/compiler.h +1 -1
@@ -340,6 +340,7 @@ static __always_inline void __write_once_size(volatile void *p, void *res, int s__read_once_size (&(x), __u.__c,sizeof (x)); \ else \ __read_once_size_nocheck(&(x), __u.__c, sizeof(x)); \ +smp_read_barrier_depends ();/* Enforce dependency ordering from x */ \ __u.__val; \ })#define READ_ONCE (x) __READ_ONCE(x, 1)
@@ -620,7 +621,6 @@ static __always_inline void __write_once_size(volatile void *p, void *res, int s ({ \typeof (p) _________p1 =READ_ONCE (p); \ typeof(*(p)) *___typecheck_p __maybe_unused; \ -smp_read_barrier_depends ();/* Dependency order vs. p above. */ \ (_________p1); \ })
Re: [RFC PATCH 1/2] arm64: mm: Use READ_ONCE/WRITE_ONCE when accessing page tables
Quoted 46 lines · Will Deacon
Quoted 45 lines · Paul E. McKenney
Quoted 43 lines · Will Deacon
Quoted 27 lines · Paul E. McKenney
Quoted 26 lines · Michael Cree
Quoted 24 lines · Paul E. McKenney
Quoted 13 lines · Will Deacon
Quoted 11 lines · Peter Zijlstra
Quoted 5 lines · Will Deacon
In many cases, page tables can be accessed concurrently by either another CPU (due to things like fast gup) or by the hardware page table walker itself, which may set access/dirty bits. In such cases, it is important to use READ_ONCE/WRITE_ONCE when accessing page table entries so that entries cannot be torn, merged or subject to apparent loss of coherence.
In fact, we should use
lockless_dereference()for many of them. Yes Alpha is the only one that cares about the difference between that andREAD_ONCE()and they do have the extra barrier, but if we're going to do this, we might as well do it 'right' :-)Quoted 6 lines
I know this sounds daft, but I think one of the big reasons why
lockless_dereference()doesn't get an awful lot of use is because it's such a mouthful! Why don't we just move thesmp_read_barrier_depends()intoREAD_ONCE? Would anybody actually care about the potential impact on Alpha (which, frankly, is treading on thin ice given the low adoption oflockless_dereference())?This is my cue to ask my usual question... ;-)
Are people still running mainline kernels on Alpha? (Added Alpha folks.)
Quoted 7 lines
Quoted 5 lines
Yes. I run two Alpha build daemons that build the unofficial debian-alpha port. Debian popcon reports nine machines running Alpha, which are likely to be running the 4.12.y kernel which is currently in debian-alpha, (and presumably soon to be 4.13.y which is now built on Alpha in experimental).
I salute your dedication to Alpha! ;-)
Ok, but where does that leave us wrt my initial proposal of moving
smp_read_barrier_depends()intoREAD_ONCEand getting rid oflockless_dereference?Michael (or anybody else running mainline on SMP Alpha) -- would you be able to give the diff below a spin and see whether there's a measurable performance impact?
Quoted 6 lines
Quoted 3 lines
This will be a sensitive test. The
smp_read_barrier_depends()can be removed fromlockless_dereference(). Without this removal Alpha will get two memory barriers fromrcu_dereference()and friends.Oh yes, good point. I was trying to keep the diff simple, but you're right that this is packing too many barriers. Fixed diff below.
Not seeing any objections thus far. If there are none by (say) the end of this week, I would be happy to queue a patch for the 4.16 merge window. That should give ample opportunity for further review and testing.
Thanx, Paul
Quoted 27 lines
Thanks,
Will
--->8
include/linux/compiler.h +1
-1@@ -340,6 +340,7 @@ static __always_inline void __write_once_size(volatile void *p, void *res, int s__read_once_size (&(x), __u.__c,sizeof (x)); \ else \ __read_once_size_nocheck(&(x), __u.__c, sizeof(x)); \ +smp_read_barrier_depends ();/* Enforce dependency ordering from x */ \ __u.__val; \ })#define READ_ONCE (x) __READ_ONCE(x, 1)@@ -620,7 +621,6 @@ static __always_inline void __write_once_size(volatile void *p, void *res, int s ({ \typeof (p) _________p1 =READ_ONCE (p); \ typeof(*(p)) *___typecheck_p __maybe_unused; \ -smp_read_barrier_depends ();/* Dependency order vs. p above. */ \ (_________p1); \ })
Re: [RFC PATCH 1/2] arm64: mm: Use READ_ONCE/WRITE_ONCE when accessing page tables
Hi Paul,
Quoted 15 lines · Paul E. McKenney
Quoted 14 lines · Will Deacon
Quoted 12 lines · Paul E. McKenney
Quoted 7 lines · Will Deacon
Ok, but where does that leave us wrt my initial proposal of moving
smp_read_barrier_depends()intoREAD_ONCEand getting rid oflockless_dereference?Michael (or anybody else running mainline on SMP Alpha) -- would you be able to give the diff below a spin and see whether there's a measurable performance impact?
This will be a sensitive test. The
smp_read_barrier_depends()can be removed fromlockless_dereference(). Without this removal Alpha will get two memory barriers fromrcu_dereference()and friends.
Quoted 7 lines
Quoted 2 lines
Oh yes, good point. I was trying to keep the diff simple, but you're right that this is packing too many barriers. Fixed diff below.
Not seeing any objections thus far. If there are none by (say) the end of this week, I would be happy to queue a patch for the 4.16 merge window. That should give ample opportunity for further review and testing.
Ok, full patch below.
Will
--->8
From 15956d0cc6b37208d8542b1858a8d8b64227acf4 Mon Sep 17 00:00:00 2001
From: Will Deacon <[email protected]>
Date: Thu, 5 Oct 2017 16:57:36 +0100
Subject: [PATCH] locking/barriers: Kill lockless_dereference
lockless_dereference is a nice idea, but it's gained little traction in kernel code since it's introduction three years ago. This is partly because it's a pain to type, but also because using READ_ONCE instead will work correctly on all architectures apart from Alpha, which is a fully supported but somewhat niche architecture these days.
This patch moves smp_read_barrier_depends() (a NOP on all architectures other than Alpha) from lockless_dereference into READ_ONCE, converts the few actual users over to READ_ONCE and then finally removes lockless_dereference altogether.
--- Documentation/memory-barriers.txt | 12 ------------ .../translations/ko_KR/memory-barriers.txt | 12 ------------ arch/x86/events/core.c | 2 +- arch/x86/include/asm/mmu_context.h | 4 ++-- arch/x86/kernel/ldt.c | 2 +- drivers/md/dm-mpath.c | 20 ++++++++++---------- fs/dcache.c | 4 ++-- fs/overlayfs/ovl_entry.h | 2 +- fs/overlayfs/readdir.c | 2 +- include/linux/compiler.h | 21 +-------------------- include/linux/rculist.h | 4 ++-- include/linux/rcupdate.h | 4 ++-- kernel/events/core.c | 4 ++-- kernel/seccomp.c | 2 +- kernel/task_work.c | 2 +- mm/slab.h | 2 +- 16 files changed, 28 insertions(+), 71 deletions(-)
Documentation/memory-barriers.txt +0 -12
@@ -1886,18 +1886,6 @@ There are some more advanced barrier functions: See Documentation/atomic_{t,bitops}.txt for more information. - (*) lockless_dereference(); - - This can be thought of as a pointer-fetch wrapper around the - smp_read_barrier_depends() data-dependency barrier. - - This is also similar to rcu_dereference(), but in cases where - object lifetime is handled by some mechanism other than RCU, for - example, when the objects removed only when the system goes down. - In addition, lockless_dereference() is used in some data structures - that can be used both with and without RCU. - - (*) dma_wmb(); (*) dma_rmb();
Documentation/translations/ko_KR/memory-barriers.txt +0 -12
@@ -1858,18 +1858,6 @@ Mandatory 배리어들은 SMP 시스템에서도 UP 시스템에서도 SMP 효 참고하세요. - (*) lockless_dereference(); - - 이 함수는 smp_read_barrier_depends() 데이터 의존성 배리어를 사용하는 - 포인터 읽어오기 래퍼(wrapper) 함수로 생각될 수 있습니다. - - 객체의 라이프타임이 RCU 외의 메커니즘으로 관리된다는 점을 제외하면 - rcu_dereference() 와도 유사한데, 예를 들면 객체가 시스템이 꺼질 때에만 - 제거되는 경우 등입니다. 또한, lockless_dereference() 은 RCU 와 함께 - 사용될수도, RCU 없이 사용될 수도 있는 일부 데이터 구조에 사용되고 - 있습니다. - - (*) dma_wmb(); (*) dma_rmb();
arch/x86/events/core.c +1 -1
@@ -2371,7 +2371,7 @@ static unsigned long get_segment_base(unsigned int segment)struct ldt_struct *ldt;/* IRQs are off, so this synchronizes with smp_store_release */ - ldt =lockless_dereference (current->active_mm->context.ldt); + ldt =READ_ONCE (current->active_mm->context.ldt);if (!ldt || idx >= ldt->nr_entries)return 0 ;
arch/x86/include/asm/mmu_context.h +2 -2
@@ -72,8 +72,8 @@ static inline void load_mm_ldt(struct mm_struct *mm)#ifdef CONFIG_MODIFY_LDT_SYSCALL struct ldt_struct *ldt; -/* lockless_dereference synchronizes with smp_store_release */ - ldt =lockless_dereference (mm->context.ldt); +/* READ_ONCE synchronizes with smp_store_release */ + ldt =READ_ONCE (mm->context.ldt);/* * Any change to mm->context.ldt is followed by an IPI to all
arch/x86/kernel/ldt.c +1 -1
@@ -101,7 +101,7 @@ static void finalize_ldt_struct(struct ldt_struct *ldt)static void install_ldt (struct mm_struct *current_mm,struct ldt_struct *ldt) { -/* Synchronizes with lockless_dereference in load_mm_ldt. */ +/* Synchronizes with READ_ONCE in load_mm_ldt. */ smp_store_release (¤t_mm->context.ldt, ldt);/* Activate the LDT for all CPUs using current_mm. */
drivers/md/dm-mpath.c +10 -10
@@ -366,7 +366,7 @@ static struct pgpath *choose_path_in_pg(struct multipath *m, pgpath =path_to_pgpath (path); -if (unlikely (lockless_dereference (m->current_pg) != pg)) { +if (unlikely (READ_ONCE (m->current_pg) != pg)) {/* Only update current_pgpath if pg changed */ spin_lock_irqsave (&m->lock, flags); m->current_pgpath = pgpath;
@@ -390,7 +390,7 @@ static struct pgpath *choose_pgpath(struct multipath *m, size_t nr_bytes) }/* Were we instructed to switch PG? */ -if (lockless_dereference (m->next_pg)) { +if (READ_ONCE (m->next_pg)) {spin_lock_irqsave (&m->lock, flags); pg = m->next_pg;if (!pg) {
@@ -406,7 +406,7 @@ static struct pgpath *choose_pgpath(struct multipath *m, size_t nr_bytes)/* Don't change PG until it has no remaining paths */ check_current_pg: - pg =lockless_dereference (m->current_pg); + pg =READ_ONCE (m->current_pg);if (pg) { pgpath =choose_path_in_pg (m, pg, nr_bytes);if (!IS_ERR_OR_NULL (pgpath))
@@ -473,7 +473,7 @@ static int multipath_clone_and_map(struct dm_target *ti, struct request *rq,struct request *clone;/* Do we need to select a new pgpath? */ - pgpath =lockless_dereference (m->current_pgpath); + pgpath =READ_ONCE (m->current_pgpath);if (!pgpath || !test_bit (MPATHF_QUEUE_IO , &m->flags)) pgpath =choose_pgpath (m, nr_bytes);
@@ -535,7 +535,7 @@ static int __multipath_map_bio(struct multipath *m, struct bio *bio, struct dm_mbool queue_io;/* Do we need to select a new pgpath? */ - pgpath =lockless_dereference (m->current_pgpath); + pgpath =READ_ONCE (m->current_pgpath); queue_io =test_bit (MPATHF_QUEUE_IO , &m->flags);if (!pgpath || !queue_io) pgpath =choose_pgpath (m, nr_bytes);
@@ -1804,7 +1804,7 @@ static int multipath_prepare_ioctl(struct dm_target *ti,struct pgpath *current_pgpath;int r; - current_pgpath =lockless_dereference (m->current_pgpath); + current_pgpath =READ_ONCE (m->current_pgpath);if (!current_pgpath) current_pgpath =choose_pgpath (m,0 );
@@ -1826,7 +1826,7 @@ static int multipath_prepare_ioctl(struct dm_target *ti, }if (r == -ENOTCONN ) { -if (!lockless_dereference (m->current_pg)) { +if (!READ_ONCE (m->current_pg)) {/* Path status changed, redo selection */ (void )choose_pgpath (m,0 ); }
@@ -1895,9 +1895,9 @@ static int multipath_busy(struct dm_target *ti)return (m->queue_mode !=DM_TYPE_MQ_REQUEST_BASED );/* Guess which priority_group will be used at next mapping time */ - pg =lockless_dereference (m->current_pg); - next_pg =lockless_dereference (m->next_pg); -if (unlikely (!lockless_dereference (m->current_pgpath) && next_pg)) + pg =READ_ONCE (m->current_pg); + next_pg =READ_ONCE (m->next_pg); +if (unlikely (!READ_ONCE (m->current_pgpath) && next_pg)) pg = next_pg;if (!pg) {
fs/dcache.c +2 -2
@@ -231,7 +231,7 @@ static inline int dentry_cmp(const struct dentry *dentry, const unsigned char *c {/* * Be careful about RCU walk racing with rename: -* use 'lockless_dereference' to fetch the name pointer. +* use 'READ_ONCE' to fetch the name pointer. * * NOTE! Even if a rename will mean that the length * was not loaded atomically, we don't care. The
@@ -245,7 +245,7 @@ static inline int dentry_cmp(const struct dentry *dentry, const unsigned char *c* early because the data cannot match (there can * be no NUL in the ct/tcount data) */ -const unsigned char *cs =lockless_dereference (dentry->d_name.name); +const unsigned char *cs =READ_ONCE (dentry->d_name.name);return dentry_string_cmp (cs, ct, tcount); }
fs/overlayfs/ovl_entry.h +1 -1
@@ -74,5 +74,5 @@ static inline struct ovl_inode *OVL_I(struct inode *inode)static inline struct dentry *ovl_upperdentry_dereference (struct ovl_inode *oi) { -return lockless_dereference (oi->__upperdentry); +return READ_ONCE (oi->__upperdentry); }
fs/overlayfs/readdir.c +1 -1
@@ -754,7 +754,7 @@ static int ovl_dir_fsync(struct file *file, loff_t start, loff_t end,if (!od->is_upper &&OVL_TYPE_UPPER (ovl_path_type (dentry))) {struct inode *inode =file_inode (file); - realfile =lockless_dereference (od->upperfile); + realfile =READ_ONCE (od->upperfile);if (!realfile) {struct path upperpath;
include/linux/compiler.h +1 -20
@@ -340,6 +340,7 @@ static __always_inline void __write_once_size(volatile void *p, void *res, int s__read_once_size (&(x), __u.__c,sizeof (x)); \ else \ __read_once_size_nocheck(&(x), __u.__c, sizeof(x)); \ +smp_read_barrier_depends ();/* Enforce dependency ordering from x */ \ __u.__val; \ })#define READ_ONCE (x) __READ_ONCE(x, 1)
@@ -604,24 +605,4 @@ static __always_inline void __write_once_size(volatile void *p, void *res, int s (volatile typeof(x) *)&(x); })#define ACCESS_ONCE (x) (*__ACCESS_ONCE(x)) -/** -* lockless_dereference() - safely load a pointer for later dereference -* @p: The pointer to load -* -* Similar to rcu_dereference(), but for situations where the pointed-to -* object's lifetime is managed by something other than RCU. That -* "something other" might be reference counting or simple immortality. -* -* The seemingly unused variable ___typecheck_p validates that @p is -* indeed a pointer type by using a pointer to typeof(*p) as the type. -* Taking a pointer to typeof(*p) again is needed in case p is void *. -*/ -#define lockless_dereference (p) \ -({ \ - typeof(p) _________p1 = READ_ONCE(p); \ - typeof(*(p)) *___typecheck_p __maybe_unused; \ - smp_read_barrier_depends();/* Dependency order vs. p above. */ \ - (_________p1); \ -}) -#endif /* __LINUX_COMPILER_H */
include/linux/rculist.h +2 -2
@@ -274,7 +274,7 @@ static inline void list_splice_tail_init_rcu(struct list_head *list,* primitives such as list_add_rcu() as long as it's guarded by rcu_read_lock(). */ #define list_entry_rcu (ptr, type, member) \ - container_of(lockless_dereference(ptr), type, member) + container_of(READ_ONCE(ptr), type, member)/** * Where are list_empty_rcu() and list_first_entry_rcu()?
@@ -367,7 +367,7 @@ static inline void list_splice_tail_init_rcu(struct list_head *list,* example is when items are added to the list, but never deleted. */ #define list_entry_lockless (ptr, type, member) \ - container_of((typeof(ptr))lockless_dereference(ptr), type, member) + container_of((typeof(ptr))READ_ONCE(ptr), type, member)/** * list_for_each_entry_lockless - iterate over rcu list of given type
include/linux/rcupdate.h +2 -2
@@ -346,7 +346,7 @@ static inline void rcu_preempt_sleep_check(void) { }#define __rcu_dereference_check (p, c, space) \ ({ \/* Dependency order vs. p above. */ \ - typeof(*p) *________p1 = (typeof(*p) *__force)lockless_dereference(p); \ + typeof(*p) *________p1 = (typeof(*p) *__force)READ_ONCE(p); \RCU_LOCKDEP_WARN (!(c),"suspicious rcu_dereference_check() usage" ); \rcu_dereference_sparse (p, space); \ ((typeof(*p) __force __kernel *)(________p1)); \
@@ -360,7 +360,7 @@ static inline void rcu_preempt_sleep_check(void) { }#define rcu_dereference_raw (p) \ ({ \/* Dependency order vs. p above. */ \ -typeof (p) ________p1 =lockless_dereference (p); \ +typeof (p) ________p1 =READ_ONCE (p); \ ((typeof(*p) __force __kernel *)(________p1)); \ })
kernel/events/core.c +2 -2
@@ -4231,7 +4231,7 @@ static void perf_remove_from_owner(struct perf_event *event)* indeed free this event, otherwise we need to serialize on * owner->perf_event_mutex. */ - owner =lockless_dereference (event->owner); + owner =READ_ONCE (event->owner);if (owner) {/* * Since delayed_put_task_struct() also drops the last
@@ -4328,7 +4328,7 @@ int perf_event_release_kernel(struct perf_event *event)* Cannot change, child events are not migrated, see the * comment with perf_event_ctx_lock_nested(). */ - ctx =lockless_dereference (child->ctx); + ctx =READ_ONCE (child->ctx);/* * Since child_mutex nests inside ctx::mutex, we must jump * through hoops. We start by grabbing a reference on the ctx.
kernel/seccomp.c +1 -1
@@ -189,7 +189,7 @@ static u32 seccomp_run_filters(const struct seccomp_data *sd,u32 ret =SECCOMP_RET_ALLOW ;/* Make sure cross-thread synced filter points somewhere sane. */ struct seccomp_filter *f = -lockless_dereference (current->seccomp.filter); +READ_ONCE (current->seccomp.filter);/* Ensure unexpected behavior doesn't result in failing open. */ if (unlikely (WARN_ON (f ==NULL )))
kernel/task_work.c +1 -1
@@ -67,7 +67,7 @@ task_work_cancel(struct task_struct *task, task_work_func_t func)* we raced with task_work_run(), *pprev == NULL/exited. */ raw_spin_lock_irqsave (&task->pi_lock, flags); -while ((work =lockless_dereference (*pprev))) { +while ((work =READ_ONCE (*pprev))) {if (work->func != func) pprev = &work->next;else if (cmpxchg (pprev, work, work->next) == work)
mm/slab.h +1 -1
@@ -258,7 +258,7 @@ cache_from_memcg_idx(struct kmem_cache *s, int idx)* memcg_caches issues a write barrier to match this (see * memcg_create_kmem_cache()). */ - cachep =lockless_dereference (arr->entries[idx]); + cachep =READ_ONCE (arr->entries[idx]);rcu_read_unlock ();return cachep;
Signature · 1 line
2.1.4
Re: [RFC PATCH 1/2] arm64: mm: Use READ_ONCE/WRITE_ONCE when accessing page tables
Quoted 21 lines · Will Deacon
Hi Paul,
Quoted 18 lines · Paul E. McKenney
Quoted 16 lines · Will Deacon
Quoted 5 lines · Paul E. McKenney
Quoted 4 lines · Will Deacon
Ok, but where does that leave us wrt my initial proposal of moving
smp_read_barrier_depends()intoREAD_ONCEand getting rid oflockless_dereference?Quoted 7 lines
Quoted 3 lines
Michael (or anybody else running mainline on SMP Alpha) -- would you be able to give the diff below a spin and see whether there's a measurable performance impact?
This will be a sensitive test. The
smp_read_barrier_depends()can be removed fromlockless_dereference(). Without this removal Alpha will get two memory barriers fromrcu_dereference()and friends.Oh yes, good point. I was trying to keep the diff simple, but you're right that this is packing too many barriers. Fixed diff below.
Quoted 6 lines
Quoted 4 lines
Not seeing any objections thus far. If there are none by (say) the end of this week, I would be happy to queue a patch for the 4.16 merge window. That should give ample opportunity for further review and testing.
Ok, full patch below.
Very good, applied for testing and review. I did have to adjust context a bit for other -rcu commits, and the result is below. (Removed a single "*" in a comment.)
Thanx, Paul
------------------------------------------------------------------------
commit 7e3675cc18bbf4d84f60bfc02ff563ae3764ad35
Author: Will Deacon <[email protected]> Date: Thu Oct 5 17:31:54 2017 +0100
locking/barriers: Kill lockless_dereference
lockless_dereference is a nice idea, but it's gained little traction in kernel code since it's introduction three years ago. This is partly because it's a pain to type, but also because using READ_ONCE instead will work correctly on all architectures apart from Alpha, which is a fully supported but somewhat niche architecture these days.
This patch moves smp_read_barrier_depends() (a NOP on all architectures other than Alpha) from lockless_dereference into READ_ONCE, converts the few actual users over to READ_ONCE and then finally removes lockless_dereference altogether.
Documentation/memory-barriers.txt +0 -12
@@ -1880,18 +1880,6 @@ There are some more advanced barrier functions: See Documentation/atomic_{t,bitops}.txt for more information. - (*) lockless_dereference(); - - This can be thought of as a pointer-fetch wrapper around the - smp_read_barrier_depends() data-dependency barrier. - - This is also similar to rcu_dereference(), but in cases where - object lifetime is handled by some mechanism other than RCU, for - example, when the objects removed only when the system goes down. - In addition, lockless_dereference() is used in some data structures - that can be used both with and without RCU. - - (*) dma_wmb(); (*) dma_rmb();
Documentation/translations/ko_KR/memory-barriers.txt +0 -12
@@ -1858,18 +1858,6 @@ Mandatory 배리어들은 SMP 시스템에서도 UP 시스템에서도 SMP 효 참고하세요. - (*) lockless_dereference(); - - 이 함수는 smp_read_barrier_depends() 데이터 의존성 배리어를 사용하는 - 포인터 읽어오기 래퍼(wrapper) 함수로 생각될 수 있습니다. - - 객체의 라이프타임이 RCU 외의 메커니즘으로 관리된다는 점을 제외하면 - rcu_dereference() 와도 유사한데, 예를 들면 객체가 시스템이 꺼질 때에만 - 제거되는 경우 등입니다. 또한, lockless_dereference() 은 RCU 와 함께 - 사용될수도, RCU 없이 사용될 수도 있는 일부 데이터 구조에 사용되고 - 있습니다. - - (*) dma_wmb(); (*) dma_rmb();
arch/x86/events/core.c +1 -1
@@ -2371,7 +2371,7 @@ static unsigned long get_segment_base(unsigned int segment)struct ldt_struct *ldt;/* IRQs are off, so this synchronizes with smp_store_release */ - ldt =lockless_dereference (current->active_mm->context.ldt); + ldt =READ_ONCE (current->active_mm->context.ldt);if (!ldt || idx >= ldt->nr_entries)return 0 ;
arch/x86/include/asm/mmu_context.h +2 -2
@@ -72,8 +72,8 @@ static inline void load_mm_ldt(struct mm_struct *mm)#ifdef CONFIG_MODIFY_LDT_SYSCALL struct ldt_struct *ldt; -/* lockless_dereference synchronizes with smp_store_release */ - ldt =lockless_dereference (mm->context.ldt); +/* READ_ONCE synchronizes with smp_store_release */ + ldt =READ_ONCE (mm->context.ldt);/* * Any change to mm->context.ldt is followed by an IPI to all
arch/x86/kernel/ldt.c +1 -1
@@ -101,7 +101,7 @@ static void finalize_ldt_struct(struct ldt_struct *ldt)static void install_ldt (struct mm_struct *current_mm,struct ldt_struct *ldt) { -/* Synchronizes with lockless_dereference in load_mm_ldt. */ +/* Synchronizes with READ_ONCE in load_mm_ldt. */ smp_store_release (¤t_mm->context.ldt, ldt);/* Activate the LDT for all CPUs using current_mm. */
drivers/md/dm-mpath.c +10 -10
@@ -366,7 +366,7 @@ static struct pgpath *choose_path_in_pg(struct multipath *m, pgpath =path_to_pgpath (path); -if (unlikely (lockless_dereference (m->current_pg) != pg)) { +if (unlikely (READ_ONCE (m->current_pg) != pg)) {/* Only update current_pgpath if pg changed */ spin_lock_irqsave (&m->lock, flags); m->current_pgpath = pgpath;
@@ -390,7 +390,7 @@ static struct pgpath *choose_pgpath(struct multipath *m, size_t nr_bytes) }/* Were we instructed to switch PG? */ -if (lockless_dereference (m->next_pg)) { +if (READ_ONCE (m->next_pg)) {spin_lock_irqsave (&m->lock, flags); pg = m->next_pg;if (!pg) {
@@ -406,7 +406,7 @@ static struct pgpath *choose_pgpath(struct multipath *m, size_t nr_bytes)/* Don't change PG until it has no remaining paths */ check_current_pg: - pg =lockless_dereference (m->current_pg); + pg =READ_ONCE (m->current_pg);if (pg) { pgpath =choose_path_in_pg (m, pg, nr_bytes);if (!IS_ERR_OR_NULL (pgpath))
@@ -473,7 +473,7 @@ static int multipath_clone_and_map(struct dm_target *ti, struct request *rq,struct request *clone;/* Do we need to select a new pgpath? */ - pgpath =lockless_dereference (m->current_pgpath); + pgpath =READ_ONCE (m->current_pgpath);if (!pgpath || !test_bit (MPATHF_QUEUE_IO , &m->flags)) pgpath =choose_pgpath (m, nr_bytes);
@@ -535,7 +535,7 @@ static int __multipath_map_bio(struct multipath *m, struct bio *bio, struct dm_mbool queue_io;/* Do we need to select a new pgpath? */ - pgpath =lockless_dereference (m->current_pgpath); + pgpath =READ_ONCE (m->current_pgpath); queue_io =test_bit (MPATHF_QUEUE_IO , &m->flags);if (!pgpath || !queue_io) pgpath =choose_pgpath (m, nr_bytes);
@@ -1804,7 +1804,7 @@ static int multipath_prepare_ioctl(struct dm_target *ti,struct pgpath *current_pgpath;int r; - current_pgpath =lockless_dereference (m->current_pgpath); + current_pgpath =READ_ONCE (m->current_pgpath);if (!current_pgpath) current_pgpath =choose_pgpath (m,0 );
@@ -1826,7 +1826,7 @@ static int multipath_prepare_ioctl(struct dm_target *ti, }if (r == -ENOTCONN ) { -if (!lockless_dereference (m->current_pg)) { +if (!READ_ONCE (m->current_pg)) {/* Path status changed, redo selection */ (void )choose_pgpath (m,0 ); }
@@ -1895,9 +1895,9 @@ static int multipath_busy(struct dm_target *ti)return (m->queue_mode !=DM_TYPE_MQ_REQUEST_BASED );/* Guess which priority_group will be used at next mapping time */ - pg =lockless_dereference (m->current_pg); - next_pg =lockless_dereference (m->next_pg); -if (unlikely (!lockless_dereference (m->current_pgpath) && next_pg)) + pg =READ_ONCE (m->current_pg); + next_pg =READ_ONCE (m->next_pg); +if (unlikely (!READ_ONCE (m->current_pgpath) && next_pg)) pg = next_pg;if (!pg) {
fs/dcache.c +2 -2
@@ -231,7 +231,7 @@ static inline int dentry_cmp(const struct dentry *dentry, const unsigned char *c {/* * Be careful about RCU walk racing with rename: -* use 'lockless_dereference' to fetch the name pointer. +* use 'READ_ONCE' to fetch the name pointer. * * NOTE! Even if a rename will mean that the length * was not loaded atomically, we don't care. The
@@ -245,7 +245,7 @@ static inline int dentry_cmp(const struct dentry *dentry, const unsigned char *c* early because the data cannot match (there can * be no NUL in the ct/tcount data) */ -const unsigned char *cs =lockless_dereference (dentry->d_name.name); +const unsigned char *cs =READ_ONCE (dentry->d_name.name);return dentry_string_cmp (cs, ct, tcount); }
fs/overlayfs/ovl_entry.h +1 -1
@@ -74,5 +74,5 @@ static inline struct ovl_inode *OVL_I(struct inode *inode)static inline struct dentry *ovl_upperdentry_dereference (struct ovl_inode *oi) { -return lockless_dereference (oi->__upperdentry); +return READ_ONCE (oi->__upperdentry); }
fs/overlayfs/readdir.c +1 -1
@@ -754,7 +754,7 @@ static int ovl_dir_fsync(struct file *file, loff_t start, loff_t end,if (!od->is_upper &&OVL_TYPE_UPPER (ovl_path_type (dentry))) {struct inode *inode =file_inode (file); - realfile =lockless_dereference (od->upperfile); + realfile =READ_ONCE (od->upperfile);if (!realfile) {struct path upperpath;
include/linux/compiler.h +1 -20
@@ -340,6 +340,7 @@ static __always_inline void __write_once_size(volatile void *p, void *res, int s__read_once_size (&(x), __u.__c,sizeof (x)); \ else \ __read_once_size_nocheck(&(x), __u.__c, sizeof(x)); \ +smp_read_barrier_depends ();/* Enforce dependency ordering from x */ \ __u.__val; \ })#define READ_ONCE (x) __READ_ONCE(x, 1)
@@ -604,24 +605,4 @@ static __always_inline void __write_once_size(volatile void *p, void *res, int s (volatile typeof(x) *)&(x); })#define ACCESS_ONCE (x) (*__ACCESS_ONCE(x)) -/** -* lockless_dereference() - safely load a pointer for later dereference -* @p: The pointer to load -* -* Similar to rcu_dereference(), but for situations where the pointed-to -* object's lifetime is managed by something other than RCU. That -* "something other" might be reference counting or simple immortality. -* -* The seemingly unused variable ___typecheck_p validates that @p is -* indeed a pointer type by using a pointer to typeof(*p) as the type. -* Taking a pointer to typeof(*p) again is needed in case p is void *. -*/ -#define lockless_dereference (p) \ -({ \ - typeof(p) _________p1 = READ_ONCE(p); \ - typeof(*(p)) *___typecheck_p __maybe_unused; \ - smp_read_barrier_depends();/* Dependency order vs. p above. */ \ - (_________p1); \ -}) -#endif /* __LINUX_COMPILER_H */
include/linux/rculist.h +2 -2
@@ -274,7 +274,7 @@ static inline void list_splice_tail_init_rcu(struct list_head *list,* primitives such as list_add_rcu() as long as it's guarded by rcu_read_lock(). */ #define list_entry_rcu (ptr, type, member) \ - container_of(lockless_dereference(ptr), type, member) + container_of(READ_ONCE(ptr), type, member)/* * Where are list_empty_rcu() and list_first_entry_rcu()?
@@ -367,7 +367,7 @@ static inline void list_splice_tail_init_rcu(struct list_head *list,* example is when items are added to the list, but never deleted. */ #define list_entry_lockless (ptr, type, member) \ - container_of((typeof(ptr))lockless_dereference(ptr), type, member) + container_of((typeof(ptr))READ_ONCE(ptr), type, member)/** * list_for_each_entry_lockless - iterate over rcu list of given type
include/linux/rcupdate.h +2 -2
@@ -346,7 +346,7 @@ static inline void rcu_preempt_sleep_check(void) { }#define __rcu_dereference_check (p, c, space) \ ({ \/* Dependency order vs. p above. */ \ - typeof(*p) *________p1 = (typeof(*p) *__force)lockless_dereference(p); \ + typeof(*p) *________p1 = (typeof(*p) *__force)READ_ONCE(p); \RCU_LOCKDEP_WARN (!(c),"suspicious rcu_dereference_check() usage" ); \rcu_dereference_sparse (p, space); \ ((typeof(*p) __force __kernel *)(________p1)); \
@@ -360,7 +360,7 @@ static inline void rcu_preempt_sleep_check(void) { }#define rcu_dereference_raw (p) \ ({ \/* Dependency order vs. p above. */ \ -typeof (p) ________p1 =lockless_dereference (p); \ +typeof (p) ________p1 =READ_ONCE (p); \ ((typeof(*p) __force __kernel *)(________p1)); \ })
kernel/events/core.c +2 -2
@@ -4231,7 +4231,7 @@ static void perf_remove_from_owner(struct perf_event *event)* indeed free this event, otherwise we need to serialize on * owner->perf_event_mutex. */ - owner =lockless_dereference (event->owner); + owner =READ_ONCE (event->owner);if (owner) {/* * Since delayed_put_task_struct() also drops the last
@@ -4328,7 +4328,7 @@ int perf_event_release_kernel(struct perf_event *event)* Cannot change, child events are not migrated, see the * comment with perf_event_ctx_lock_nested(). */ - ctx =lockless_dereference (child->ctx); + ctx =READ_ONCE (child->ctx);/* * Since child_mutex nests inside ctx::mutex, we must jump * through hoops. We start by grabbing a reference on the ctx.
kernel/seccomp.c +1 -1
@@ -189,7 +189,7 @@ static u32 seccomp_run_filters(const struct seccomp_data *sd,u32 ret =SECCOMP_RET_ALLOW ;/* Make sure cross-thread synced filter points somewhere sane. */ struct seccomp_filter *f = -lockless_dereference (current->seccomp.filter); +READ_ONCE (current->seccomp.filter);/* Ensure unexpected behavior doesn't result in failing open. */ if (unlikely (WARN_ON (f ==NULL )))
kernel/task_work.c +1 -1
@@ -67,7 +67,7 @@ task_work_cancel(struct task_struct *task, task_work_func_t func)* we raced with task_work_run(), *pprev == NULL/exited. */ raw_spin_lock_irqsave (&task->pi_lock, flags); -while ((work =lockless_dereference (*pprev))) { +while ((work =READ_ONCE (*pprev))) {if (work->func != func) pprev = &work->next;else if (cmpxchg (pprev, work, work->next) == work)
mm/slab.h +1 -1
@@ -258,7 +258,7 @@ cache_from_memcg_idx(struct kmem_cache *s, int idx)* memcg_caches issues a write barrier to match this (see * memcg_create_kmem_cache()). */ - cachep =lockless_dereference (arr->entries[idx]); + cachep =READ_ONCE (arr->entries[idx]);rcu_read_unlock ();return cachep;
Andrea Parri
maintainerRe: [RFC PATCH 1/2] arm64: mm: Use READ_ONCE/WRITE_ONCE when accessing page tables
In reply to Will DeaconHi Will,
none of my comments below represent objections to this patch, but let me remark:
Quoted 37 lines · Will Deacon
Hi Paul,
Quoted 15 lines · Paul E. McKenney
Quoted 14 lines · Will Deacon
Quoted 12 lines · Paul E. McKenney
Quoted 7 lines · Will Deacon
Ok, but where does that leave us wrt my initial proposal of moving
smp_read_barrier_depends()intoREAD_ONCEand getting rid oflockless_dereference?Michael (or anybody else running mainline on SMP Alpha) -- would you be able to give the diff below a spin and see whether there's a measurable performance impact?
This will be a sensitive test. The
smp_read_barrier_depends()can be removed fromlockless_dereference(). Without this removal Alpha will get two memory barriers fromrcu_dereference()and friends.Quoted 7 lines
Quoted 2 lines
Oh yes, good point. I was trying to keep the diff simple, but you're right that this is packing too many barriers. Fixed diff below.
Not seeing any objections thus far. If there are none by (say) the end of this week, I would be happy to queue a patch for the 4.16 merge window. That should give ample opportunity for further review and testing.
Ok, full patch below.
Will
--->8
From
15956d0cc6b37208d8542b1858a8d8b64227acf4Mon Sep 17 00:00:00 2001
From: Will Deacon <[email protected]>
Date: Thu, 5 Oct 2017 16:57:36 +0100
Subject: [PATCH] locking/barriers: Killlockless_dereference
Quoted 5 lines
lockless_dereferenceis a nice idea, but it's gained little traction in kernel code since it's introduction three years ago. This is partly because it's a pain to type, but also because usingREAD_ONCEinstead will work correctly on all architectures apart from Alpha, which is a fully supported but somewhat niche architecture these days.
lockless_dereference might be a mouthful, but it does (explicitly) say/remark: "Yep, we are relying on the following address dep. to be "in strong-ppo" ".
Such information will be lost or, at least, not immediately clear by just reading a READ_ONCE(). (And Yes, this information is only relevant when we "include" Alpha in the picture/analysis.)
Quoted 5 lines
This patch moves
smp_read_barrier_depends()(a NOP on all architectures other than Alpha) fromlockless_dereferenceintoREAD_ONCE, converts the few actual users over toREAD_ONCEand then finally removeslockless_dereferencealtogether.
Notice that several "potential users" of lockless_dereference are currently hidden in other call sites for smp_read_barrier_depends (i.e., cases where this barrier is not called from within a lock- less or an RCU dereference).
Some of these usages (e.g.,
include/linux/percpu-refcount.h:__ref_is_percpu, mm/ksm.c:get_ksm_page, security/keys/keyring.c:search_nested_keyrings )
precedes this barrier with a READ_ONCE; others (e.g.,
arch/alpha/include/asm/pgtable.h:pmd_offset, net/ipv4/netfilter/arp_tables.c:arpt_do_table kernel/kernel/events/uprobes.c:get_trampiline_vaddr )
with a plain read.
There also appear to be cases where the barrier is preceded by an ACCESS_ONCE (c.f, fs/dcache.c:prepend_name) or by an xchg_release (c.f., kernel/locking/qspinlock.c:queued_spin_lock_slowpath), and it would not be difficult to imagine/create different usages.
Quoted 2 lines
Signed-off-by Will Deacon <[email protected]>
I understand that we all agree we're missing a Tested-by here ;-).
Andrea
Quoted 377 lines
--- Documentation/memory-barriers.txt | 12 ------------ .../translations/ko_KR/memory-barriers.txt | 12 ------------ arch/x86/events/core.c | 2 +- arch/x86/include/asm/mmu_context.h | 4 ++-- arch/x86/kernel/ldt.c | 2 +- drivers/md/dm-mpath.c | 20 ++++++++++---------- fs/dcache.c | 4 ++-- fs/overlayfs/ovl_entry.h | 2 +- fs/overlayfs/readdir.c | 2 +- include/linux/compiler.h | 21 +-------------------- include/linux/rculist.h | 4 ++-- include/linux/rcupdate.h | 4 ++-- kernel/events/core.c | 4 ++-- kernel/seccomp.c | 2 +- kernel/task_work.c | 2 +- mm/slab.h | 2 +- 16 files changed, 28 insertions(+), 71 deletions(-)Documentation/memory-barriers.txt +0
-12@@ -1886,18 +1886,6 @@ There are some more advanced barrier functions: See Documentation/atomic_{t,bitops}.txt for more information. - (*) lockless_dereference(); - - This can be thought of as a pointer-fetch wrapper around the - smp_read_barrier_depends() data-dependency barrier. - - This is also similar to rcu_dereference(), but in cases where - object lifetime is handled by some mechanism other than RCU, for - example, when the objects removed only when the system goes down. - In addition, lockless_dereference() is used in some data structures - that can be used both with and without RCU. - - (*) dma_wmb(); (*) dma_rmb();Documentation/translations/ko_KR/memory-barriers.txt +0
-12@@ -1858,18 +1858,6 @@ Mandatory 배리어들은 SMP 시스템에서도 UP 시스템에서도 SMP 효 참고하세요. - (*) lockless_dereference(); - - 이 함수는 smp_read_barrier_depends() 데이터 의존성 배리어를 사용하는 - 포인터 읽어오기 래퍼(wrapper) 함수로 생각될 수 있습니다. - - 객체의 라이프타임이 RCU 외의 메커니즘으로 관리된다는 점을 제외하면 - rcu_dereference() 와도 유사한데, 예를 들면 객체가 시스템이 꺼질 때에만 - 제거되는 경우 등입니다. 또한, lockless_dereference() 은 RCU 와 함께 - 사용될수도, RCU 없이 사용될 수도 있는 일부 데이터 구조에 사용되고 - 있습니다. - - (*) dma_wmb(); (*) dma_rmb();arch/x86/events/core.c +1
-1@@ -2371,7 +2371,7 @@ static unsigned long get_segment_base(unsigned int segment)struct ldt_struct *ldt;/* IRQs are off, so this synchronizes with smp_store_release */ - ldt =lockless_dereference (current->active_mm->context.ldt); + ldt =READ_ONCE (current->active_mm->context.ldt);if (!ldt || idx >= ldt->nr_entries)return 0 ;arch/x86/include/asm/mmu_context.h +2
-2@@ -72,8 +72,8 @@ static inline void load_mm_ldt(struct mm_struct *mm)#ifdef CONFIG_MODIFY_LDT_SYSCALL struct ldt_struct *ldt; -/* lockless_dereference synchronizes with smp_store_release */ - ldt =lockless_dereference (mm->context.ldt); +/* READ_ONCE synchronizes with smp_store_release */ + ldt =READ_ONCE (mm->context.ldt);/* * Any change to mm->context.ldt is followed by an IPI to all arch/x86/kernel/ldt.c +1
-1@@ -101,7 +101,7 @@ static void finalize_ldt_struct(struct ldt_struct *ldt)static void install_ldt (struct mm_struct *current_mm,struct ldt_struct *ldt) { -/* Synchronizes with lockless_dereference in load_mm_ldt. */ +/* Synchronizes with READ_ONCE in load_mm_ldt. */ smp_store_release (¤t_mm->context.ldt, ldt);/* Activate the LDT for all CPUs using current_mm. */ drivers/md/dm-mpath.c +10
-10@@ -366,7 +366,7 @@ static struct pgpath *choose_path_in_pg(struct multipath *m, pgpath =path_to_pgpath (path); -if (unlikely (lockless_dereference (m->current_pg) != pg)) { +if (unlikely (READ_ONCE (m->current_pg) != pg)) {/* Only update current_pgpath if pg changed */ spin_lock_irqsave (&m->lock, flags); m->current_pgpath = pgpath;@@ -390,7 +390,7 @@ static struct pgpath *choose_pgpath(struct multipath *m, size_t nr_bytes) }/* Were we instructed to switch PG? */ -if (lockless_dereference (m->next_pg)) { +if (READ_ONCE (m->next_pg)) {spin_lock_irqsave (&m->lock, flags); pg = m->next_pg;if (!pg) {@@ -406,7 +406,7 @@ static struct pgpath *choose_pgpath(struct multipath *m, size_t nr_bytes)/* Don't change PG until it has no remaining paths */ check_current_pg: - pg =lockless_dereference (m->current_pg); + pg =READ_ONCE (m->current_pg);if (pg) { pgpath =choose_path_in_pg (m, pg, nr_bytes);if (!IS_ERR_OR_NULL (pgpath))@@ -473,7 +473,7 @@ static int multipath_clone_and_map(struct dm_target *ti, struct request *rq,struct request *clone;/* Do we need to select a new pgpath? */ - pgpath =lockless_dereference (m->current_pgpath); + pgpath =READ_ONCE (m->current_pgpath);if (!pgpath || !test_bit (MPATHF_QUEUE_IO , &m->flags)) pgpath =choose_pgpath (m, nr_bytes);@@ -535,7 +535,7 @@ static int __multipath_map_bio(struct multipath *m, struct bio *bio, struct dm_mbool queue_io;/* Do we need to select a new pgpath? */ - pgpath =lockless_dereference (m->current_pgpath); + pgpath =READ_ONCE (m->current_pgpath); queue_io =test_bit (MPATHF_QUEUE_IO , &m->flags);if (!pgpath || !queue_io) pgpath =choose_pgpath (m, nr_bytes);@@ -1804,7 +1804,7 @@ static int multipath_prepare_ioctl(struct dm_target *ti,struct pgpath *current_pgpath;int r; - current_pgpath =lockless_dereference (m->current_pgpath); + current_pgpath =READ_ONCE (m->current_pgpath);if (!current_pgpath) current_pgpath =choose_pgpath (m,0 );@@ -1826,7 +1826,7 @@ static int multipath_prepare_ioctl(struct dm_target *ti, }if (r == -ENOTCONN ) { -if (!lockless_dereference (m->current_pg)) { +if (!READ_ONCE (m->current_pg)) {/* Path status changed, redo selection */ (void )choose_pgpath (m,0 ); }@@ -1895,9 +1895,9 @@ static int multipath_busy(struct dm_target *ti)return (m->queue_mode !=DM_TYPE_MQ_REQUEST_BASED );/* Guess which priority_group will be used at next mapping time */ - pg =lockless_dereference (m->current_pg); - next_pg =lockless_dereference (m->next_pg); -if (unlikely (!lockless_dereference (m->current_pgpath) && next_pg)) + pg =READ_ONCE (m->current_pg); + next_pg =READ_ONCE (m->next_pg); +if (unlikely (!READ_ONCE (m->current_pgpath) && next_pg)) pg = next_pg;if (!pg) {fs/dcache.c +2
-2@@ -231,7 +231,7 @@ static inline int dentry_cmp(const struct dentry *dentry, const unsigned char *c {/* * Be careful about RCU walk racing with rename: -* use 'lockless_dereference' to fetch the name pointer. +* use 'READ_ONCE' to fetch the name pointer. * * NOTE! Even if a rename will mean that the length * was not loaded atomically, we don't care. The @@ -245,7 +245,7 @@ static inline int dentry_cmp(const struct dentry *dentry, const unsigned char *c* early because the data cannot match (there can * be no NUL in the ct/tcount data) */ -const unsigned char *cs =lockless_dereference (dentry->d_name.name); +const unsigned char *cs =READ_ONCE (dentry->d_name.name);return dentry_string_cmp (cs, ct, tcount); }fs/overlayfs/ovl_entry.h +1
-1@@ -74,5 +74,5 @@ static inline struct ovl_inode *OVL_I(struct inode *inode)static inline struct dentry *ovl_upperdentry_dereference (struct ovl_inode *oi) { -return lockless_dereference (oi->__upperdentry); +return READ_ONCE (oi->__upperdentry); }fs/overlayfs/readdir.c +1
-1@@ -754,7 +754,7 @@ static int ovl_dir_fsync(struct file *file, loff_t start, loff_t end,if (!od->is_upper &&OVL_TYPE_UPPER (ovl_path_type (dentry))) {struct inode *inode =file_inode (file); - realfile =lockless_dereference (od->upperfile); + realfile =READ_ONCE (od->upperfile);if (!realfile) {struct path upperpath;include/linux/compiler.h +1
-20@@ -340,6 +340,7 @@ static __always_inline void __write_once_size(volatile void *p, void *res, int s__read_once_size (&(x), __u.__c,sizeof (x)); \ else \ __read_once_size_nocheck(&(x), __u.__c, sizeof(x)); \ +smp_read_barrier_depends ();/* Enforce dependency ordering from x */ \ __u.__val; \ })#define READ_ONCE (x) __READ_ONCE(x, 1)@@ -604,24 +605,4 @@ static __always_inline void __write_once_size(volatile void *p, void *res, int s (volatile typeof(x) *)&(x); })#define ACCESS_ONCE (x) (*__ACCESS_ONCE(x)) -/** -* lockless_dereference() - safely load a pointer for later dereference -* @p: The pointer to load -* -* Similar to rcu_dereference(), but for situations where the pointed-to -* object's lifetime is managed by something other than RCU. That -* "something other" might be reference counting or simple immortality. -* -* The seemingly unused variable ___typecheck_p validates that @p is -* indeed a pointer type by using a pointer to typeof(*p) as the type. -* Taking a pointer to typeof(*p) again is needed in case p is void *. -*/ -#define lockless_dereference (p) \ -({ \ - typeof(p) _________p1 = READ_ONCE(p); \ - typeof(*(p)) *___typecheck_p __maybe_unused; \ - smp_read_barrier_depends();/* Dependency order vs. p above. */ \ - (_________p1); \ -}) -#endif /* __LINUX_COMPILER_H */ include/linux/rculist.h +2
-2@@ -274,7 +274,7 @@ static inline void list_splice_tail_init_rcu(struct list_head *list,* primitives such as list_add_rcu() as long as it's guarded by rcu_read_lock(). */ #define list_entry_rcu (ptr, type, member) \ - container_of(lockless_dereference(ptr), type, member) + container_of(READ_ONCE(ptr), type, member)/** * Where are list_empty_rcu() and list_first_entry_rcu()? @@ -367,7 +367,7 @@ static inline void list_splice_tail_init_rcu(struct list_head *list,* example is when items are added to the list, but never deleted. */ #define list_entry_lockless (ptr, type, member) \ - container_of((typeof(ptr))lockless_dereference(ptr), type, member) + container_of((typeof(ptr))READ_ONCE(ptr), type, member)/** * list_for_each_entry_lockless - iterate over rcu list of given type include/linux/rcupdate.h +2
-2@@ -346,7 +346,7 @@ static inline void rcu_preempt_sleep_check(void) { }#define __rcu_dereference_check (p, c, space) \ ({ \/* Dependency order vs. p above. */ \ - typeof(*p) *________p1 = (typeof(*p) *__force)lockless_dereference(p); \ + typeof(*p) *________p1 = (typeof(*p) *__force)READ_ONCE(p); \RCU_LOCKDEP_WARN (!(c),"suspicious rcu_dereference_check() usage" ); \rcu_dereference_sparse (p, space); \ ((typeof(*p) __force __kernel *)(________p1)); \@@ -360,7 +360,7 @@ static inline void rcu_preempt_sleep_check(void) { }#define rcu_dereference_raw (p) \ ({ \/* Dependency order vs. p above. */ \ -typeof (p) ________p1 =lockless_dereference (p); \ +typeof (p) ________p1 =READ_ONCE (p); \ ((typeof(*p) __force __kernel *)(________p1)); \ })kernel/events/core.c +2
-2@@ -4231,7 +4231,7 @@ static void perf_remove_from_owner(struct perf_event *event)* indeed free this event, otherwise we need to serialize on * owner->perf_event_mutex. */ - owner =lockless_dereference (event->owner); + owner =READ_ONCE (event->owner);if (owner) {/* * Since delayed_put_task_struct() also drops the last @@ -4328,7 +4328,7 @@ int perf_event_release_kernel(struct perf_event *event)* Cannot change, child events are not migrated, see the * comment with perf_event_ctx_lock_nested(). */ - ctx =lockless_dereference (child->ctx); + ctx =READ_ONCE (child->ctx);/* * Since child_mutex nests inside ctx::mutex, we must jump * through hoops. We start by grabbing a reference on the ctx. kernel/seccomp.c +1
-1@@ -189,7 +189,7 @@ static u32 seccomp_run_filters(const struct seccomp_data *sd,u32 ret =SECCOMP_RET_ALLOW ;/* Make sure cross-thread synced filter points somewhere sane. */ struct seccomp_filter *f = -lockless_dereference (current->seccomp.filter); +READ_ONCE (current->seccomp.filter);/* Ensure unexpected behavior doesn't result in failing open. */ if (unlikely (WARN_ON (f ==NULL )))kernel/task_work.c +1
-1@@ -67,7 +67,7 @@ task_work_cancel(struct task_struct *task, task_work_func_t func)* we raced with task_work_run(), *pprev == NULL/exited. */ raw_spin_lock_irqsave (&task->pi_lock, flags); -while ((work =lockless_dereference (*pprev))) { +while ((work =READ_ONCE (*pprev))) {if (work->func != func) pprev = &work->next;else if (cmpxchg (pprev, work, work->next) == work)mm/slab.h +1
-1@@ -258,7 +258,7 @@ cache_from_memcg_idx(struct kmem_cache *s, int idx)* memcg_caches issues a write barrier to match this (see * memcg_create_kmem_cache()). */ - cachep =lockless_dereference (arr->entries[idx]); + cachep =READ_ONCE (arr->entries[idx]);rcu_read_unlock ();return cachep;Signature · 1 line
2.1.4
Re: [RFC PATCH 1/2] arm64: mm: Use READ_ONCE/WRITE_ONCE when accessing page tables
Quoted 50 lines · Andrea Parri
Hi Will,
none of my comments below represent objections to this patch, but let me remark:
Quoted 37 lines · Will Deacon
Hi Paul,
Quoted 15 lines · Paul E. McKenney
Quoted 14 lines · Will Deacon
Quoted 12 lines · Paul E. McKenney
Quoted 7 lines · Will Deacon
Ok, but where does that leave us wrt my initial proposal of moving
smp_read_barrier_depends()intoREAD_ONCEand getting rid oflockless_dereference?Michael (or anybody else running mainline on SMP Alpha) -- would you be able to give the diff below a spin and see whether there's a measurable performance impact?
This will be a sensitive test. The
smp_read_barrier_depends()can be removed fromlockless_dereference(). Without this removal Alpha will get two memory barriers fromrcu_dereference()and friends.Quoted 7 lines
Quoted 2 lines
Oh yes, good point. I was trying to keep the diff simple, but you're right that this is packing too many barriers. Fixed diff below.
Not seeing any objections thus far. If there are none by (say) the end of this week, I would be happy to queue a patch for the 4.16 merge window. That should give ample opportunity for further review and testing.
Ok, full patch below.
Will
--->8
From
15956d0cc6b37208d8542b1858a8d8b64227acf4Mon Sep 17 00:00:00 2001
From: Will Deacon <[email protected]>
Date: Thu, 5 Oct 2017 16:57:36 +0100
Subject: [PATCH] locking/barriers: Killlockless_dereferenceQuoted 5 lines
lockless_dereferenceis a nice idea, but it's gained little traction in kernel code since it's introduction three years ago. This is partly because it's a pain to type, but also because usingREAD_ONCEinstead will work correctly on all architectures apart from Alpha, which is a fully supported but somewhat niche architecture these days.
Quoted 7 lines
lockless_dereferencemight be a mouthful, but it does (explicitly) say/remark: "Yep, we are relying on the following address dep. to be "in strong-ppo" ".Such information will be lost or, at least, not immediately clear by just reading a
READ_ONCE(). (And Yes, this information is only relevant when we "include" Alpha in the picture/analysis.)
It is possible to argue that lockless_dereference() should remain for this reason, even given READ_ONCE() containing smp_read_barrier_depends(). However, such arguments would be much more compelling if there were tools that cared about the difference.
Quoted 16 lines
Quoted 4 lines
This patch moves
smp_read_barrier_depends()(a NOP on all architectures other than Alpha) fromlockless_dereferenceintoREAD_ONCE, converts the few actual users over toREAD_ONCEand then finally removeslockless_dereferencealtogether.Notice that several "potential users" of
lockless_dereferenceare currently hidden in other call sites forsmp_read_barrier_depends(i.e., cases where this barrier is not called from within a lock- less or an RCU dereference).Some of these usages (e.g.,
include/linux/percpu-refcount.h:__ref_is_percpu, mm/ksm.c:get_ksm_page, security/keys/keyring.c:search_nested_keyrings )
Quoted 7 lines
precedes this barrier with a
READ_ONCE; others (e.g.,arch/alpha/include/asm/pgtable.h:pmd_offset, net/ipv4/netfilter/arp_tables.c:arpt_do_table kernel/kernel/events/uprobes.c:get_trampiline_vaddr )
with a plain read.
I would welcome patches for the cases where smp_read_barrier_depends() is preceded by READ_ONCE(). Perhaps the others should gain a READ_ONCE(), and I suspect that they should, but ultimately that decision is in the hands of the relevant maintainer, so any such patches need to be separated and will need at least an ack from the relevant maintainers.
Quoted 4 lines
There also appear to be cases where the barrier is preceded by an
ACCESS_ONCE(c.f, fs/dcache.c:prepend_name) or by anxchg_release(c.f., kernel/locking/qspinlock.c:queued_spin_lock_slowpath), and it would not be difficult to imagine/create different usages.
It would indeed be good to replace ACCESS_ONCE() with READ_ONCE() or with WRITE_ONCE() where this works. And yes, I agree that there are other usage patterns possible.
Quoted 3 lines
Quoted 1 line
Signed-off-by Will Deacon <[email protected]>I understand that we all agree we're missing a Tested-by here ;-).
Indeed, hence my "applied for testing and review". ;-)
Thanx, Paul
Quoted 381 lines
Andrea
Quoted 377 lines
--- Documentation/memory-barriers.txt | 12 ------------ .../translations/ko_KR/memory-barriers.txt | 12 ------------ arch/x86/events/core.c | 2 +- arch/x86/include/asm/mmu_context.h | 4 ++-- arch/x86/kernel/ldt.c | 2 +- drivers/md/dm-mpath.c | 20 ++++++++++---------- fs/dcache.c | 4 ++-- fs/overlayfs/ovl_entry.h | 2 +- fs/overlayfs/readdir.c | 2 +- include/linux/compiler.h | 21 +-------------------- include/linux/rculist.h | 4 ++-- include/linux/rcupdate.h | 4 ++-- kernel/events/core.c | 4 ++-- kernel/seccomp.c | 2 +- kernel/task_work.c | 2 +- mm/slab.h | 2 +- 16 files changed, 28 insertions(+), 71 deletions(-)Documentation/memory-barriers.txt +0
-12@@ -1886,18 +1886,6 @@ There are some more advanced barrier functions: See Documentation/atomic_{t,bitops}.txt for more information. - (*) lockless_dereference(); - - This can be thought of as a pointer-fetch wrapper around the - smp_read_barrier_depends() data-dependency barrier. - - This is also similar to rcu_dereference(), but in cases where - object lifetime is handled by some mechanism other than RCU, for - example, when the objects removed only when the system goes down. - In addition, lockless_dereference() is used in some data structures - that can be used both with and without RCU. - - (*) dma_wmb(); (*) dma_rmb();Documentation/translations/ko_KR/memory-barriers.txt +0
-12@@ -1858,18 +1858,6 @@ Mandatory 배리어들은 SMP 시스템에서도 UP 시스템에서도 SMP 효 참고하세요. - (*) lockless_dereference(); - - 이 함수는 smp_read_barrier_depends() 데이터 의존성 배리어를 사용하는 - 포인터 읽어오기 래퍼(wrapper) 함수로 생각될 수 있습니다. - - 객체의 라이프타임이 RCU 외의 메커니즘으로 관리된다는 점을 제외하면 - rcu_dereference() 와도 유사한데, 예를 들면 객체가 시스템이 꺼질 때에만 - 제거되는 경우 등입니다. 또한, lockless_dereference() 은 RCU 와 함께 - 사용될수도, RCU 없이 사용될 수도 있는 일부 데이터 구조에 사용되고 - 있습니다. - - (*) dma_wmb(); (*) dma_rmb();arch/x86/events/core.c +1
-1@@ -2371,7 +2371,7 @@ static unsigned long get_segment_base(unsigned int segment)struct ldt_struct *ldt;/* IRQs are off, so this synchronizes with smp_store_release */ - ldt =lockless_dereference (current->active_mm->context.ldt); + ldt =READ_ONCE (current->active_mm->context.ldt);if (!ldt || idx >= ldt->nr_entries)return 0 ;arch/x86/include/asm/mmu_context.h +2
-2@@ -72,8 +72,8 @@ static inline void load_mm_ldt(struct mm_struct *mm)#ifdef CONFIG_MODIFY_LDT_SYSCALL struct ldt_struct *ldt; -/* lockless_dereference synchronizes with smp_store_release */ - ldt =lockless_dereference (mm->context.ldt); +/* READ_ONCE synchronizes with smp_store_release */ + ldt =READ_ONCE (mm->context.ldt);/* * Any change to mm->context.ldt is followed by an IPI to all arch/x86/kernel/ldt.c +1
-1@@ -101,7 +101,7 @@ static void finalize_ldt_struct(struct ldt_struct *ldt)static void install_ldt (struct mm_struct *current_mm,struct ldt_struct *ldt) { -/* Synchronizes with lockless_dereference in load_mm_ldt. */ +/* Synchronizes with READ_ONCE in load_mm_ldt. */ smp_store_release (¤t_mm->context.ldt, ldt);/* Activate the LDT for all CPUs using current_mm. */ drivers/md/dm-mpath.c +10
-10@@ -366,7 +366,7 @@ static struct pgpath *choose_path_in_pg(struct multipath *m, pgpath =path_to_pgpath (path); -if (unlikely (lockless_dereference (m->current_pg) != pg)) { +if (unlikely (READ_ONCE (m->current_pg) != pg)) {/* Only update current_pgpath if pg changed */ spin_lock_irqsave (&m->lock, flags); m->current_pgpath = pgpath;@@ -390,7 +390,7 @@ static struct pgpath *choose_pgpath(struct multipath *m, size_t nr_bytes) }/* Were we instructed to switch PG? */ -if (lockless_dereference (m->next_pg)) { +if (READ_ONCE (m->next_pg)) {spin_lock_irqsave (&m->lock, flags); pg = m->next_pg;if (!pg) {@@ -406,7 +406,7 @@ static struct pgpath *choose_pgpath(struct multipath *m, size_t nr_bytes)/* Don't change PG until it has no remaining paths */ check_current_pg: - pg =lockless_dereference (m->current_pg); + pg =READ_ONCE (m->current_pg);if (pg) { pgpath =choose_path_in_pg (m, pg, nr_bytes);if (!IS_ERR_OR_NULL (pgpath))@@ -473,7 +473,7 @@ static int multipath_clone_and_map(struct dm_target *ti, struct request *rq,struct request *clone;/* Do we need to select a new pgpath? */ - pgpath =lockless_dereference (m->current_pgpath); + pgpath =READ_ONCE (m->current_pgpath);if (!pgpath || !test_bit (MPATHF_QUEUE_IO , &m->flags)) pgpath =choose_pgpath (m, nr_bytes);@@ -535,7 +535,7 @@ static int __multipath_map_bio(struct multipath *m, struct bio *bio, struct dm_mbool queue_io;/* Do we need to select a new pgpath? */ - pgpath =lockless_dereference (m->current_pgpath); + pgpath =READ_ONCE (m->current_pgpath); queue_io =test_bit (MPATHF_QUEUE_IO , &m->flags);if (!pgpath || !queue_io) pgpath =choose_pgpath (m, nr_bytes);@@ -1804,7 +1804,7 @@ static int multipath_prepare_ioctl(struct dm_target *ti,struct pgpath *current_pgpath;int r; - current_pgpath =lockless_dereference (m->current_pgpath); + current_pgpath =READ_ONCE (m->current_pgpath);if (!current_pgpath) current_pgpath =choose_pgpath (m,0 );@@ -1826,7 +1826,7 @@ static int multipath_prepare_ioctl(struct dm_target *ti, }if (r == -ENOTCONN ) { -if (!lockless_dereference (m->current_pg)) { +if (!READ_ONCE (m->current_pg)) {/* Path status changed, redo selection */ (void )choose_pgpath (m,0 ); }@@ -1895,9 +1895,9 @@ static int multipath_busy(struct dm_target *ti)return (m->queue_mode !=DM_TYPE_MQ_REQUEST_BASED );/* Guess which priority_group will be used at next mapping time */ - pg =lockless_dereference (m->current_pg); - next_pg =lockless_dereference (m->next_pg); -if (unlikely (!lockless_dereference (m->current_pgpath) && next_pg)) + pg =READ_ONCE (m->current_pg); + next_pg =READ_ONCE (m->next_pg); +if (unlikely (!READ_ONCE (m->current_pgpath) && next_pg)) pg = next_pg;if (!pg) {fs/dcache.c +2
-2@@ -231,7 +231,7 @@ static inline int dentry_cmp(const struct dentry *dentry, const unsigned char *c {/* * Be careful about RCU walk racing with rename: -* use 'lockless_dereference' to fetch the name pointer. +* use 'READ_ONCE' to fetch the name pointer. * * NOTE! Even if a rename will mean that the length * was not loaded atomically, we don't care. The @@ -245,7 +245,7 @@ static inline int dentry_cmp(const struct dentry *dentry, const unsigned char *c* early because the data cannot match (there can * be no NUL in the ct/tcount data) */ -const unsigned char *cs =lockless_dereference (dentry->d_name.name); +const unsigned char *cs =READ_ONCE (dentry->d_name.name);return dentry_string_cmp (cs, ct, tcount); }fs/overlayfs/ovl_entry.h +1
-1@@ -74,5 +74,5 @@ static inline struct ovl_inode *OVL_I(struct inode *inode)static inline struct dentry *ovl_upperdentry_dereference (struct ovl_inode *oi) { -return lockless_dereference (oi->__upperdentry); +return READ_ONCE (oi->__upperdentry); }fs/overlayfs/readdir.c +1
-1@@ -754,7 +754,7 @@ static int ovl_dir_fsync(struct file *file, loff_t start, loff_t end,if (!od->is_upper &&OVL_TYPE_UPPER (ovl_path_type (dentry))) {struct inode *inode =file_inode (file); - realfile =lockless_dereference (od->upperfile); + realfile =READ_ONCE (od->upperfile);if (!realfile) {struct path upperpath;include/linux/compiler.h +1
-20@@ -340,6 +340,7 @@ static __always_inline void __write_once_size(volatile void *p, void *res, int s__read_once_size (&(x), __u.__c,sizeof (x)); \ else \ __read_once_size_nocheck(&(x), __u.__c, sizeof(x)); \ +smp_read_barrier_depends ();/* Enforce dependency ordering from x */ \ __u.__val; \ })#define READ_ONCE (x) __READ_ONCE(x, 1)@@ -604,24 +605,4 @@ static __always_inline void __write_once_size(volatile void *p, void *res, int s (volatile typeof(x) *)&(x); })#define ACCESS_ONCE (x) (*__ACCESS_ONCE(x)) -/** -* lockless_dereference() - safely load a pointer for later dereference -* @p: The pointer to load -* -* Similar to rcu_dereference(), but for situations where the pointed-to -* object's lifetime is managed by something other than RCU. That -* "something other" might be reference counting or simple immortality. -* -* The seemingly unused variable ___typecheck_p validates that @p is -* indeed a pointer type by using a pointer to typeof(*p) as the type. -* Taking a pointer to typeof(*p) again is needed in case p is void *. -*/ -#define lockless_dereference (p) \ -({ \ - typeof(p) _________p1 = READ_ONCE(p); \ - typeof(*(p)) *___typecheck_p __maybe_unused; \ - smp_read_barrier_depends();/* Dependency order vs. p above. */ \ - (_________p1); \ -}) -#endif /* __LINUX_COMPILER_H */ include/linux/rculist.h +2
-2@@ -274,7 +274,7 @@ static inline void list_splice_tail_init_rcu(struct list_head *list,* primitives such as list_add_rcu() as long as it's guarded by rcu_read_lock(). */ #define list_entry_rcu (ptr, type, member) \ - container_of(lockless_dereference(ptr), type, member) + container_of(READ_ONCE(ptr), type, member)/** * Where are list_empty_rcu() and list_first_entry_rcu()? @@ -367,7 +367,7 @@ static inline void list_splice_tail_init_rcu(struct list_head *list,* example is when items are added to the list, but never deleted. */ #define list_entry_lockless (ptr, type, member) \ - container_of((typeof(ptr))lockless_dereference(ptr), type, member) + container_of((typeof(ptr))READ_ONCE(ptr), type, member)/** * list_for_each_entry_lockless - iterate over rcu list of given type include/linux/rcupdate.h +2
-2@@ -346,7 +346,7 @@ static inline void rcu_preempt_sleep_check(void) { }#define __rcu_dereference_check (p, c, space) \ ({ \/* Dependency order vs. p above. */ \ - typeof(*p) *________p1 = (typeof(*p) *__force)lockless_dereference(p); \ + typeof(*p) *________p1 = (typeof(*p) *__force)READ_ONCE(p); \RCU_LOCKDEP_WARN (!(c),"suspicious rcu_dereference_check() usage" ); \rcu_dereference_sparse (p, space); \ ((typeof(*p) __force __kernel *)(________p1)); \@@ -360,7 +360,7 @@ static inline void rcu_preempt_sleep_check(void) { }#define rcu_dereference_raw (p) \ ({ \/* Dependency order vs. p above. */ \ -typeof (p) ________p1 =lockless_dereference (p); \ +typeof (p) ________p1 =READ_ONCE (p); \ ((typeof(*p) __force __kernel *)(________p1)); \ })kernel/events/core.c +2
-2@@ -4231,7 +4231,7 @@ static void perf_remove_from_owner(struct perf_event *event)* indeed free this event, otherwise we need to serialize on * owner->perf_event_mutex. */ - owner =lockless_dereference (event->owner); + owner =READ_ONCE (event->owner);if (owner) {/* * Since delayed_put_task_struct() also drops the last @@ -4328,7 +4328,7 @@ int perf_event_release_kernel(struct perf_event *event)* Cannot change, child events are not migrated, see the * comment with perf_event_ctx_lock_nested(). */ - ctx =lockless_dereference (child->ctx); + ctx =READ_ONCE (child->ctx);/* * Since child_mutex nests inside ctx::mutex, we must jump * through hoops. We start by grabbing a reference on the ctx. kernel/seccomp.c +1
-1@@ -189,7 +189,7 @@ static u32 seccomp_run_filters(const struct seccomp_data *sd,u32 ret =SECCOMP_RET_ALLOW ;/* Make sure cross-thread synced filter points somewhere sane. */ struct seccomp_filter *f = -lockless_dereference (current->seccomp.filter); +READ_ONCE (current->seccomp.filter);/* Ensure unexpected behavior doesn't result in failing open. */ if (unlikely (WARN_ON (f ==NULL )))kernel/task_work.c +1
-1@@ -67,7 +67,7 @@ task_work_cancel(struct task_struct *task, task_work_func_t func)* we raced with task_work_run(), *pprev == NULL/exited. */ raw_spin_lock_irqsave (&task->pi_lock, flags); -while ((work =lockless_dereference (*pprev))) { +while ((work =READ_ONCE (*pprev))) {if (work->func != func) pprev = &work->next;else if (cmpxchg (pprev, work, work->next) == work)mm/slab.h +1
-1@@ -258,7 +258,7 @@ cache_from_memcg_idx(struct kmem_cache *s, int idx)* memcg_caches issues a write barrier to match this (see * memcg_create_kmem_cache()). */ - cachep =lockless_dereference (arr->entries[idx]); + cachep =READ_ONCE (arr->entries[idx]);rcu_read_unlock ();return cachep;Signature · 1 line
2.1.4
Re: [RFC PATCH 1/2] arm64: mm: Use READ_ONCE/WRITE_ONCE when accessing page tables
In reply to Will DeaconQuoted 4 lines · Will Deacon
This patch consistently uses these macros in the arch
code, as well as explicitly namespacing pointers to page table entries from the entries themselves by using adopting a 'p' suffix for the former (as is sometimes used elsewhere in the kernel source).
Would you consider splitting up this patch into two, where the second patch makes all the cosmetic changes? That would make the "meatier" patch easier to back-port and review.
RFC PATCH 2/2 mm: page_vma_mapped: Ensure pmd is loaded with READ_ONCE outside of lock
In reply to Will DeaconLoading the pmd without holding the pmd_lock exposes us to races with concurrent updaters of the page tables but, worse still, it also allows the compiler to cache the pmd value in a register and reuse it later on, even if we've performed a READ_ONCE in between and seen a more recent value.
In the case of page_vma_mapped_walk, this leads to the following crash when the pmd loaded for the initial pmd_trans_huge check is all zeroes and a subsequent valid table entry is loaded by check_pmd. We then proceed into map_pte, but the compiler re-uses the zero entry inside pte_offset_map, resulting in a junk pointer being installed in pvmw->pte:
[ 254.032812] PC is at check_pte+0x20/0x170 [ 254.032948] LR is at page_vma_mapped_walk+0x2e0/0x540 [...] [ 254.036114] Process doio (pid: 2463, stack limit = 0xffff00000f2e8000) [ 254.036361] Call trace: [ 254.038977] [<ffff000008233328>] check_pte+0x20/0x170 [ 254.039137] [<ffff000008233758>] page_vma_mapped_walk+0x2e0/0x540 [ 254.039332] [<ffff000008234adc>] page_mkclean_one+0xac/0x278 [ 254.039489] [<ffff000008234d98>] rmap_walk_file+0xf0/0x238 [ 254.039642] [<ffff000008236e74>] rmap_walk+0x64/0xa0 [ 254.039784] [<ffff0000082370c8>] page_mkclean+0x90/0xa8 [ 254.040029] [<ffff0000081f3c64>] clear_page_dirty_for_io+0x84/0x2a8 [ 254.040311] [<ffff00000832f984>] mpage_submit_page+0x34/0x98 [ 254.040518] [<ffff00000832fb4c>] mpage_process_page_bufs+0x164/0x170 [ 254.040743] [<ffff00000832fc8c>] mpage_prepare_extent_to_map+0x134/0x2b8 [ 254.040969] [<ffff00000833530c>] ext4_writepages+0x484/0xe30 [ 254.041175] [<ffff0000081f6ab4>] do_writepages+0x44/0xe8 [ 254.041372] [<ffff0000081e5bd4>] __filemap_fdatawrite_range+0xbc/0x110 [ 254.041568] [<ffff0000081e5e68>] file_write_and_wait_range+0x48/0xd8 [ 254.041739] [<ffff000008324310>] ext4_sync_file+0x80/0x4b8 [ 254.041907] [<ffff0000082bd434>] vfs_fsync_range+0x64/0xc0 [ 254.042106] [<ffff0000082332b4>] SyS_msync+0x194/0x1e8
This patch fixes the problem by ensuring that READ_ONCE is used before the initial checks on the pmd, and this value is subsequently used when checking whether or not the pmd is present. pmd_check is removed and the pmd_present check is inlined directly.
--- mm/page_vma_mapped.c | 25 ++++++++++--------------- 1 file changed, 10 insertions(+), 15 deletions(-)
mm/page_vma_mapped.c +10 -15
@@ -6,17 +6,6 @@#include "internal.h" -static inline bool check_pmd (struct page_vma_mapped_walk *pvmw) -{ -pmd_t pmde; -/* -* Make sure we don't re-load pmd between present and !trans_huge check. -* We need a consistent view. -*/ - pmde =READ_ONCE (*pvmw->pmd); -return pmd_present (pmde) && !pmd_trans_huge (pmde); -} -static inline bool not_found (struct page_vma_mapped_walk *pvmw) {page_vma_mapped_walk_done (pvmw);
@@ -116,6 +105,7 @@ bool page_vma_mapped_walk(struct page_vma_mapped_walk *pvmw)pgd_t *pgd;p4d_t *p4d;pud_t *pud; +pmd_t pmde;/* The only possible pmd mapping has been handled on last iteration */ if (pvmw->pmd && !pvmw->pte)
@@ -148,7 +138,13 @@ bool page_vma_mapped_walk(struct page_vma_mapped_walk *pvmw)if (!pud_present (*pud))return false; pvmw->pmd =pmd_offset (pud, pvmw->address); -if (pmd_trans_huge (*pvmw->pmd) ||is_pmd_migration_entry (*pvmw->pmd)) { +/* +* Make sure the pmd value isn't cached in a register by the +* compiler and used as a stale value after we've observed a +* subsequent update. +*/ + pmde =READ_ONCE (*pvmw->pmd); +if (pmd_trans_huge (pmde) ||is_pmd_migration_entry (pmde)) { pvmw->ptl =pmd_lock (mm, pvmw->pmd);if (likely (pmd_trans_huge (*pvmw->pmd))) {if (pvmw->flags &PVMW_MIGRATION )
@@ -175,9 +171,8 @@ bool page_vma_mapped_walk(struct page_vma_mapped_walk *pvmw)spin_unlock (pvmw->ptl); pvmw->ptl =NULL ; } - }else { -if (!check_pmd (pvmw)) -return false; + }else if (!pmd_present (pmde)) { +return false; }if (!map_pte (pvmw)) goto next_pte;
Signature · 1 line
2.1.4
Quoted 74 lines · Will Deacon
Hi,
We recently had a crash report[1] on arm64 that involved a bad dereference in the
page_vma_mappedcode during ext4 writeback with THP active. I can reproduce this on -rc2:[ 254.032812] PC is at check_pte+0x20/0x170 [ 254.032948] LR is at page_vma_mapped_walk+0x2e0/0x540 [...] [ 254.036114] Process doio (pid: 2463, stack limit = 0xffff00000f2e8000) [ 254.036361] Call trace: [ 254.038977] [<ffff000008233328>] check_pte+0x20/0x170 [ 254.039137] [<ffff000008233758>] page_vma_mapped_walk+0x2e0/0x540 [ 254.039332] [<ffff000008234adc>] page_mkclean_one+0xac/0x278 [ 254.039489] [<ffff000008234d98>] rmap_walk_file+0xf0/0x238 [ 254.039642] [<ffff000008236e74>] rmap_walk+0x64/0xa0 [ 254.039784] [<ffff0000082370c8>] page_mkclean+0x90/0xa8 [ 254.040029] [<ffff0000081f3c64>] clear_page_dirty_for_io+0x84/0x2a8 [ 254.040311] [<ffff00000832f984>] mpage_submit_page+0x34/0x98 [ 254.040518] [<ffff00000832fb4c>] mpage_process_page_bufs+0x164/0x170 [ 254.040743] [<ffff00000832fc8c>] mpage_prepare_extent_to_map+0x134/0x2b8 [ 254.040969] [<ffff00000833530c>] ext4_writepages+0x484/0xe30 [ 254.041175] [<ffff0000081f6ab4>] do_writepages+0x44/0xe8 [ 254.041372] [<ffff0000081e5bd4>] __filemap_fdatawrite_range+0xbc/0x110 [ 254.041568] [<ffff0000081e5e68>] file_write_and_wait_range+0x48/0xd8 [ 254.041739] [<ffff000008324310>] ext4_sync_file+0x80/0x4b8 [ 254.041907] [<ffff0000082bd434>] vfs_fsync_range+0x64/0xc0 [ 254.042106] [<ffff0000082332b4>] SyS_msync+0x194/0x1e8After digging into the issue, I found that we appear to be racing with a concurrent pmd update in
page_vma_mapped_walk, assumedly due a THP splitting operation. Looking at the code there:pvmw->pmd = pmd_offset(pud, pvmw->address); if (pmd_trans_huge(*pvmw->pmd) || is_pmd_migration_entry(*pvmw->pmd)) { [...] } else { if (!check_pmd(pvmw)) return false; } if (!map_pte(pvmw)) goto next_pte;what happens in the crashing scenario is that we see all zeroes for the PMD in
pmd_trans_huge(*pvmw->pmd), and so go to the 'else' case (migration isn't enabled, so the test is removed at compile-time).check_pmdthen does:pmde = READ_ONCE(*pvmw->pmd); return pmd_present(pmde) && !pmd_trans_huge(pmde);and reads a valid table entry for the PMD because the splitting has completed (i.e. the first dereference reads from the
pmdp_invalidatein the splitting code, whereas the second dereferenced reads from the followingpmd_populate). It returns true because we should descend to the PTE level inmap_pte.map_ptedoes:pvmw->pte = pte_offset_map(pvmw->pmd, pvmw->address);which on arm64 (and this appears to be the same on x86) ends up doing:
(pmd_page_paddr((*(pvmw->pmd))) + pte_index(pvmw->address) * sizeof(pte_t))as part of its calculation. However, this is horribly broken because GCC inlines everything and reuses the register it loaded for the initial
pmd_trans_hugecheck (when we loaded the value of zero) here, so we end up calculating a junk pointer and crashing when we dereference it. Disassembly at the end of the mail[2] for those who are curious.The moral of the story is that read-after-read (same address) ordering *only* applies if
READ_ONCEis used consistently. This means we need to fix page table dereferences in the core code as well as the arch code to avoid this problem. The two RFC patches in this series fix arm64 (which is a bigger fix that necessary since I clean things up too) andpage_vma_mapped_walk.
Quoted 6 lines
Comments welcome.
Will
[1] http://lists.infradead.org/pipermail/linux-arm-kernel/2017-September/532786.html
[2]
Hi Will,
The fix works for me. Thanks.
My cross-compiler is:
$ /home/yury/work/thunderx-tools-28/bin/aarch64-thunderx-linux-gnu-gcc --version aarch64-thunderx-linux-gnu-gcc (Cavium Inc. build 28) 7.1.0 Copyright (C) 2017 Free Software Foundation, Inc.
Yury
Quoted 46 lines
//
page_vma_mapped_walk
//pvmw->pmd=pmd_offset(pud, pvmw->address);
ldr x0, [x19, #24] //pvmw->pmd// if (
pmd_trans_huge(*pvmw->pmd)||is_pmd_migration_entry(*pvmw->pmd)) {ldr x1, [x0] // *pvmw->pmd cbz x1, ffff0000082336a0 <page_vma_mapped_walk+0x228> tbz w1, #1, ffff000008233788 <page_vma_mapped_walk+0x310> // pmd_trans_huge?// else if (!check_pmd(pvmw))
ldr x0, [x0] // READ_ONCE in check_pmd tst x0, x24 // pmd_present? b.eq ffff000008233538 <page_vma_mapped_walk+0xc0> // b.none tbz w0, #1, ffff000008233538 <page_vma_mapped_walk+0xc0> // pmd_trans_huge?// if (!map_pte(pvmw))
ldr x0, [x19, #16] //pvmw->address//
pvmw->pte=pte_offset_map(pvmw->pmd, pvmw->address);
and x1, x1, #0xfffffffff000 // Reusing the old value of*pvmw->pmd!!!
[...]--->8
Will Deacon (2):
arm64: mm: Use READ_ONCE/WRITE_ONCE when accessing page tables
mm:page_vma_mapped: Ensure pmd is loaded withREAD_ONCEoutside of
lockarch/arm64/include/asm/hugetlb.h | 2 +- arch/arm64/include/asm/kvm_mmu.h | 18 +-- arch/arm64/include/asm/mmu_context.h | 4 +- arch/arm64/include/asm/pgalloc.h | 42 +++--- arch/arm64/include/asm/pgtable.h | 29 ++-- arch/arm64/kernel/hibernate.c | 148 +++++++++--------- arch/arm64/mm/dump.c | 54 ++++--- arch/arm64/mm/fault.c | 44 +++--- arch/arm64/mm/hugetlbpage.c | 94 ++++++------ arch/arm64/mm/kasan_init.c | 62 ++++---- arch/arm64/mm/mmu.c | 281 ++++++++++++++++++----------------- arch/arm64/mm/pageattr.c | 30 ++-- mm/page_vma_mapped.c | 25 ++-- 13 files changed, 427 insertions(+), 406 deletions(-)Signature · 1 line
2.1.4
Quoted 68 lines · Will Deacon
Hi,
We recently had a crash report[1] on arm64 that involved a bad dereference in the
page_vma_mappedcode during ext4 writeback with THP active. I can reproduce this on -rc2:[ 254.032812] PC is at check_pte+0x20/0x170 [ 254.032948] LR is at page_vma_mapped_walk+0x2e0/0x540 [...] [ 254.036114] Process doio (pid: 2463, stack limit = 0xffff00000f2e8000) [ 254.036361] Call trace: [ 254.038977] [<ffff000008233328>] check_pte+0x20/0x170 [ 254.039137] [<ffff000008233758>] page_vma_mapped_walk+0x2e0/0x540 [ 254.039332] [<ffff000008234adc>] page_mkclean_one+0xac/0x278 [ 254.039489] [<ffff000008234d98>] rmap_walk_file+0xf0/0x238 [ 254.039642] [<ffff000008236e74>] rmap_walk+0x64/0xa0 [ 254.039784] [<ffff0000082370c8>] page_mkclean+0x90/0xa8 [ 254.040029] [<ffff0000081f3c64>] clear_page_dirty_for_io+0x84/0x2a8 [ 254.040311] [<ffff00000832f984>] mpage_submit_page+0x34/0x98 [ 254.040518] [<ffff00000832fb4c>] mpage_process_page_bufs+0x164/0x170 [ 254.040743] [<ffff00000832fc8c>] mpage_prepare_extent_to_map+0x134/0x2b8 [ 254.040969] [<ffff00000833530c>] ext4_writepages+0x484/0xe30 [ 254.041175] [<ffff0000081f6ab4>] do_writepages+0x44/0xe8 [ 254.041372] [<ffff0000081e5bd4>] __filemap_fdatawrite_range+0xbc/0x110 [ 254.041568] [<ffff0000081e5e68>] file_write_and_wait_range+0x48/0xd8 [ 254.041739] [<ffff000008324310>] ext4_sync_file+0x80/0x4b8 [ 254.041907] [<ffff0000082bd434>] vfs_fsync_range+0x64/0xc0 [ 254.042106] [<ffff0000082332b4>] SyS_msync+0x194/0x1e8After digging into the issue, I found that we appear to be racing with a concurrent pmd update in
page_vma_mapped_walk, assumedly due a THP splitting operation. Looking at the code there:pvmw->pmd = pmd_offset(pud, pvmw->address); if (pmd_trans_huge(*pvmw->pmd) || is_pmd_migration_entry(*pvmw->pmd)) { [...] } else { if (!check_pmd(pvmw)) return false; } if (!map_pte(pvmw)) goto next_pte;what happens in the crashing scenario is that we see all zeroes for the PMD in
pmd_trans_huge(*pvmw->pmd), and so go to the 'else' case (migration isn't enabled, so the test is removed at compile-time).check_pmdthen does:pmde = READ_ONCE(*pvmw->pmd); return pmd_present(pmde) && !pmd_trans_huge(pmde);and reads a valid table entry for the PMD because the splitting has completed (i.e. the first dereference reads from the
pmdp_invalidatein the splitting code, whereas the second dereferenced reads from the followingpmd_populate). It returns true because we should descend to the PTE level inmap_pte.map_ptedoes:pvmw->pte = pte_offset_map(pvmw->pmd, pvmw->address);which on arm64 (and this appears to be the same on x86) ends up doing:
(pmd_page_paddr((*(pvmw->pmd))) + pte_index(pvmw->address) * sizeof(pte_t))as part of its calculation. However, this is horribly broken because GCC inlines everything and reuses the register it loaded for the initial
pmd_trans_hugecheck (when we loaded the value of zero) here, so we end up calculating a junk pointer and crashing when we dereference it. Disassembly at the end of the mail[2] for those who are curious.
Quoted 7 lines
The moral of the story is that read-after-read (same address) ordering *only* applies if
READ_ONCEis used consistently. This means we need to fix page table dereferences in the core code as well as the arch code to avoid this problem. The two RFC patches in this series fix arm64 (which is a bigger fix that necessary since I clean things up too) andpage_vma_mapped_walk.Comments welcome.
Hi Will,
This fix works for me, tested with LTP rwtest 15 iterations on Qualcomm Centiq2400.
Compiler: gcc version 5.2.1 20151005 (Linaro GCC 5.2-2015.11-1)
Thanks,
Richard
Quoted 49 lines
Will
[1] http://lists.infradead.org/pipermail/linux-arm-kernel/2017-September/532786.html
[2]//
page_vma_mapped_walk
//pvmw->pmd=pmd_offset(pud, pvmw->address);
ldr x0, [x19, #24] //pvmw->pmd// if (
pmd_trans_huge(*pvmw->pmd)||is_pmd_migration_entry(*pvmw->pmd)) {ldr x1, [x0] // *pvmw->pmd cbz x1, ffff0000082336a0 <page_vma_mapped_walk+0x228> tbz w1, #1, ffff000008233788 <page_vma_mapped_walk+0x310> // pmd_trans_huge?// else if (!check_pmd(pvmw))
ldr x0, [x0] // READ_ONCE in check_pmd tst x0, x24 // pmd_present? b.eq ffff000008233538 <page_vma_mapped_walk+0xc0> // b.none tbz w0, #1, ffff000008233538 <page_vma_mapped_walk+0xc0> // pmd_trans_huge?// if (!map_pte(pvmw))
ldr x0, [x19, #16] //pvmw->address//
pvmw->pte=pte_offset_map(pvmw->pmd, pvmw->address);
and x1, x1, #0xfffffffff000 // Reusing the old value of*pvmw->pmd!!!
[...]--->8
Will Deacon (2):
arm64: mm: Use READ_ONCE/WRITE_ONCE when accessing page tables
mm:page_vma_mapped: Ensure pmd is loaded withREAD_ONCEoutside of
lockarch/arm64/include/asm/hugetlb.h | 2 +- arch/arm64/include/asm/kvm_mmu.h | 18 +-- arch/arm64/include/asm/mmu_context.h | 4 +- arch/arm64/include/asm/pgalloc.h | 42 +++--- arch/arm64/include/asm/pgtable.h | 29 ++-- arch/arm64/kernel/hibernate.c | 148 +++++++++--------- arch/arm64/mm/dump.c | 54 ++++--- arch/arm64/mm/fault.c | 44 +++--- arch/arm64/mm/hugetlbpage.c | 94 ++++++------ arch/arm64/mm/kasan_init.c | 62 ++++---- arch/arm64/mm/mmu.c | 281 ++++++++++++++++++----------------- arch/arm64/mm/pageattr.c | 30 ++-- mm/page_vma_mapped.c | 25 ++-- 13 files changed, 427 insertions(+), 406 deletions(-)
Signature · 3 lines
Qualcomm Datacenter Technologies as an affiliate of Qualcomm Technologies, Inc. Qualcomm Technologies, Inc. is a member of the Code Aurora Forum, a Linux Foundation Collaborative Project.
Quoted 7 lines · Will Deacon
The moral of the story is that read-after-read (same address) ordering *only* applies if
READ_ONCEis used consistently. This means we need to fix page table dereferences in the core code as well as the arch code to avoid this problem. The two RFC patches in this series fix arm64 (which is a bigger fix that necessary since I clean things up too) andpage_vma_mapped_walk.Comments welcome.
Thanks for this Will. I'll echo Timur's comment that it would be ideal to split this up into the critical piece needed for ordering access/update to the PMD in the face of a THP split and separately have the cosmetic cleanups. Needless to say, we've got a bunch of people who are tracking this one and tracking it ready for backport. We just got THP re-enabled so I'm pretty keen that we not have to disable again.
Jon.
Signature · 1 line
Computer Architect | Sent from my Fedora powered laptop
[+ Timur]
Quoted 8 lines · Jon Masters
Quoted 6 lines · Will Deacon
The moral of the story is that read-after-read (same address) ordering *only* applies if
READ_ONCEis used consistently. This means we need to fix page table dereferences in the core code as well as the arch code to avoid this problem. The two RFC patches in this series fix arm64 (which is a bigger fix that necessary since I clean things up too) andpage_vma_mapped_walk.
Quoted 8 lines
Quoted 1 line
Comments welcome.
Thanks for this Will. I'll echo Timur's comment that it would be ideal to split this up into the critical piece needed for ordering access/update to the PMD in the face of a THP split and separately have the cosmetic cleanups. Needless to say, we've got a bunch of people who are tracking this one and tracking it ready for backport. We just got THP re-enabled so I'm pretty keen that we not have to disable again.
Yeah, of course. I already posted a point diff to Yury in the original thread:
http://lists.infradead.org/pipermail/linux-arm-kernel/2017-September/533299.html
so I'd like to queue that as an arm64 fix after we've worked out the general direction of the full fix. I also don't see why other architectures (including x86) can't be hit by this, so an alternative (completely untested) approach would just be to take patch 2 of this series.
The full fix isn't just cosmetic; it's also addressing the wider problem of unannotated racing page table accesses outside of the specific failure case we've run into.
Will
Quoted 3 lines · Will Deacon
The full fix isn't just cosmetic; it's also addressing the wider problem of unannotated racing page table accesses outside of the specific failure case we've run into.
Let us know if there are additional tests we should be running on the Red Hat end. We've got high hundreds of ARM server systems at this point, including pretty much everything out there.
Jon.
Signature · 1 line
Computer Architect | Sent from my Fedora powered laptop
Quoted 9 lines · Jon Masters
Quoted 3 lines · Will Deacon
The full fix isn't just cosmetic; it's also addressing the wider problem of unannotated racing page table accesses outside of the specific failure case we've run into.
Let us know if there are additional tests we should be running on the Red Hat end. We've got high hundreds of ARM server systems at this point, including pretty much everything out there.
TBH, there's nothing ARM-specific about this issue afaict and it should be reproducible on x86 if the compiler can keep the initially loaded pmd live in a GPR for long enough.
As for wider problems, you want to stress anything that does page table modification concurrently with lockless walkers (although GUP looks mostly ok modulo the lack of pud_trans_huge support, which I'll try to fix if I find time).
Will