: lockless get_user_pages patchset
Subsystems (7)
cover : lockless get_user_pages patchset
Hi,
This patchset It impoves our well known oltp performance thingy by 10% on DB2 on a modest 2 socket x86 system. For a sense of scale, remember numbers being tossed around like direct-IO giving a 12% improvement; or hugepages giving a 9% improvement... heh.
I have a powerpc patch as well, but it needs benh to find me a bit in their pte to use.
These patches are on top of the previous 7 patches just sent.
Nick
PATCH 1/2 : x86: implement pte_special
Implement the pte_special bit for x86. This is required to support lockless get_user_pages, because we need to know whether or not we can refcount a particular page given only its pte (and no vma).
---
include/asm-x86/pgtable.h +5 -2
@@ -18,6 +18,7 @@#define _PAGE_BIT_UNUSED1 9/* available for programmer */ #define _PAGE_BIT_UNUSED2 10#define _PAGE_BIT_UNUSED3 11 +#define _PAGE_BIT_SPECIAL _PAGE_BIT_UNUSED1#define _PAGE_BIT_PAT_LARGE 12/* On 2MB or 1GB pages */ #define _PAGE_BIT_NX 63/* No execute: only valid after cpuid check */
@@ -40,6 +41,8 @@#define _PAGE_UNUSED3 (_AC(1, L)<<_PAGE_BIT_UNUSED3)#define _PAGE_PAT (_AC(1, L)<<_PAGE_BIT_PAT)#define _PAGE_PAT_LARGE (_AC(1, L)<<_PAGE_BIT_PAT_LARGE) +#define _PAGE_SPECIAL (_AC(1, L)<<_PAGE_BIT_SPECIAL) +#define __HAVE_ARCH_PTE_SPECIAL#if defined(CONFIG_X86_64 ) || defined(CONFIG_X86_PAE )#define _PAGE_NX (_AC(1, ULL) << _PAGE_BIT_NX)
@@ -149,7 +152,7 @@ static inline int pte_file(pte_t pte) {static inline int pte_huge (pte_t pte) {return pte_val (pte) & _PAGE_PSE; }static inline int pte_global (pte_t pte) {return pte_val (pte) & _PAGE_GLOBAL; }static inline int pte_exec (pte_t pte) {return !(pte_val (pte) & _PAGE_NX); } -static inline int pte_special (pte_t pte) {return 0 ; } +static inline int pte_special (pte_t pte) {return pte_val (pte) & _PAGE_SPECIAL; }static inline int pmd_large (pmd_t pte) { return (pmd_val(pte) & (_PAGE_PSE|_PAGE_PRESENT)) ==
@@ -167,7 +170,7 @@ static inline pte_t pte_mkhuge(pte_t ptestatic inline pte_t pte_clrhuge (pte_t pte) {return __pte (pte_val (pte) & ~(pteval_t )_PAGE_PSE); }static inline pte_t pte_mkglobal (pte_t pte) {return __pte (pte_val (pte) | _PAGE_GLOBAL); }static inline pte_t pte_clrglobal (pte_t pte) {return __pte (pte_val (pte) & ~(pteval_t )_PAGE_GLOBAL); } -static inline pte_t pte_mkspecial (pte_t pte) {return pte; } +static inline pte_t pte_mkspecial (pte_t pte) {return __pte (pte_val (pte) | _PAGE_SPECIAL); }extern pteval_t __supported_pte_mask;
Re: [patch 1/2]: x86: implement pte_special
From: Nick Piggin <[email protected]>
Date: Fri, 28 Mar 2008 03:55:41 +0100
include/asm-x86/pgtable.h line 45
Quoted 6 lines
include/asm-x86/pgtable.h +2
-0@@ -40,6 +41,8 @@#define _PAGE_UNUSED3 (_AC(1, L)<<_PAGE_BIT_UNUSED3)#define _PAGE_PAT (_AC(1, L)<<_PAGE_BIT_PAT)#define _PAGE_PAT_LARGE (_AC(1, L)<<_PAGE_BIT_PAT_LARGE) +#define _PAGE_SPECIAL (_AC(1, L)<<_PAGE_BIT_SPECIAL) +#define __HAVE_ARCH_PTE_SPECIAL
What tests __HAVE_ARCH_PTE_SPECIAL?
include/asm-x86/pgtable.h static inline pte_t pte_mkhuge(pte_t pte · line 173
Quoted 6 lines
include/asm-x86/pgtable.h +1
-1@@ -167,7 +170,7 @@ static inline pte_t pte_mkhuge(pte_t ptestatic inline pte_t pte_clrhuge (pte_t pte) {return __pte (pte_val (pte) & ~(pteval_t )_PAGE_PSE); }static inline pte_t pte_mkglobal (pte_t pte) {return __pte (pte_val (pte) | _PAGE_GLOBAL); }static inline pte_t pte_clrglobal (pte_t pte) {return __pte (pte_val (pte) & ~(pteval_t )_PAGE_GLOBAL); } -static inline pte_t pte_mkspecial (pte_t pte) {return pte; } +static inline pte_t pte_mkspecial (pte_t pte) {return __pte (pte_val (pte) | _PAGE_SPECIAL); }
And what calls pte_mkspecial?
I don't see any code that sets the special bit anywhere in these two patches.
What am I missing?
Re: [patch 1/2]: x86: implement pte_special
Quoted 21 lines · David Miller
From: Nick Piggin <[email protected]>
Date: Fri, 28 Mar 2008 03:55:41 +0100include/asm-x86/pgtable.h line 45
Quoted 6 lines
include/asm-x86/pgtable.h +2
-0@@ -40,6 +41,8 @@#define _PAGE_UNUSED3 (_AC(1, L)<<_PAGE_BIT_UNUSED3)#define _PAGE_PAT (_AC(1, L)<<_PAGE_BIT_PAT)#define _PAGE_PAT_LARGE (_AC(1, L)<<_PAGE_BIT_PAT_LARGE) +#define _PAGE_SPECIAL (_AC(1, L)<<_PAGE_BIT_SPECIAL) +#define __HAVE_ARCH_PTE_SPECIALWhat tests
__HAVE_ARCH_PTE_SPECIAL?include/asm-x86/pgtable.h static inline pte_t pte_mkhuge(pte_t pte · line 173
Quoted 6 lines
include/asm-x86/pgtable.h +1
-1@@ -167,7 +170,7 @@ static inline pte_t pte_mkhuge(pte_t ptestatic inline pte_t pte_clrhuge (pte_t pte) {return __pte (pte_val (pte) & ~(pteval_t )_PAGE_PSE); }static inline pte_t pte_mkglobal (pte_t pte) {return __pte (pte_val (pte) | _PAGE_GLOBAL); }static inline pte_t pte_clrglobal (pte_t pte) {return __pte (pte_val (pte) & ~(pteval_t )_PAGE_GLOBAL); } -static inline pte_t pte_mkspecial (pte_t pte) {return pte; } +static inline pte_t pte_mkspecial (pte_t pte) {return __pte (pte_val (pte) | _PAGE_SPECIAL); }And what calls
pte_mkspecial?
Quoted 4 lines
I don't see any code that sets the special bit anywhere in these two patches.
What am I missing?
Oh, sorry these 2 patches are on top of the previous 7 that I sent out. Hmm, only patch 2/7 went to linux-arch, which is the main consumer of this pte_special stuff, however if you want a more coherent view of those 7 patches, they are on linux-mm.
Basically, the pfn-based mapping insertion (vm_insert_pfn, remap_pfn_range) calls pte_mkspecial. And that tells fast_gup "hands off".
Re: [patch 1/2]: x86: implement pte_special
From: Nick Piggin <[email protected]>
Date: Fri, 28 Mar 2008 04:31:50 +0100
Quoted 2 lines
Basically, the pfn-based mapping insertion (
vm_insert_pfn,remap_pfn_range) callspte_mkspecial. And that tellsfast_gup"hands off".
I don't think it's wise to allocate a "soft PTE bit" for this on every platform, especially for such a limited use case.
Is it feasible to test the page instead? Or are we talking about cases where there may not be a backing page?
If the issue is to discern things like I/O mappings and such vs. real pages, there are ways a platform can handle that without a special bit.
That would leave us with real memory that does not have backing page structs, and we have a way to test that too.
The special PTE bit seems superfluous to me.
Re: [patch 1/2]: x86: implement pte_special
Quoted 8 lines · David Miller
From: Nick Piggin <[email protected]>
Date: Fri, 28 Mar 2008 04:31:50 +0100Quoted 2 lines
Basically, the pfn-based mapping insertion (
vm_insert_pfn,remap_pfn_range) callspte_mkspecial. And that tellsfast_gup"hands off".I don't think it's wise to allocate a "soft PTE bit" for this on every platform, especially for such a limited use case.
Sure, it will be up to the arch to decide whether or not to use it. If you can't spare a bit, then nothing changes, you just don't get a speedup ;)
But it is not limited to direct IO. If you have a look through drivers, there are quite a few things using it that might see improvement.
And even if it were limited to direct IO... there will be some platforms that want it, I suspect.
Quoted 2 lines
Is it feasible to test the page instead? Or are we talking about cases where there may not be a backing page?
Yes. Or there may be one but you are not allowed to touch it.
Quoted 8 lines
If the issue is to discern things like I/O mappings and such vs. real pages, there are ways a platform can handle that without a special bit.
That would leave us with real memory that does not have backing page structs, and we have a way to test that too.
The special PTE bit seems superfluous to me.
It has to be quite fast.
There are some platforms that can't really do that easily, so the pte bit isn't useless on all architectures. s390 for example, which is why it was introduced in the other patchset.
x86 has 3 bits which are usable by system software, so are not likely to be ever used by hardware. And in the entire life of Linux, nobody has ever wanted to use one :) So I think we're safe to use one on x86.
If bits ever run out, and there are as you say other ways to implement this, then it could be switched over then. Because nothing is going to be as fast as testing the pte (which we already have loaded in a register).
For sparc, if you can propose another method, we could look at that. But if you think it is such a limited use case, then cleanest and eaisest will just be to not implement it. I doubt many people to serious oltp on sparc (on Linux), so for that platform you are probably right. If another compelling use case comes up, then you can always reevaluate.
BTW. if you are still interested, then the powerpc64 patch might be a better starting point for you. I don't know how the sparc tlb flush design looks like, but if it doesn't do a synchronous IPI to invalidate other threads, then you can't use the x86 approach.
Re: [patch 1/2]: x86: implement pte_special
From: Nick Piggin <[email protected]>
Date: Fri, 28 Mar 2008 05:04:42 +0100
Quoted 4 lines
BTW. if you are still interested, then the powerpc64 patch might be a better starting point for you. I don't know how the sparc tlb flush design looks like, but if it doesn't do a synchronous IPI to invalidate other threads, then you can't use the x86 approach.
I have soft bits available on sparc64, that's not my issue.
My issue is that if you implemented this differently, every platform would get the optimization, without having to do anything special at all, and I think that's such a much nicer way.
Re: [patch 1/2]: x86: implement pte_special
Quoted 8 lines · David Miller
From: Nick Piggin <[email protected]>
Date: Fri, 28 Mar 2008 05:04:42 +0100Quoted 4 lines
BTW. if you are still interested, then the powerpc64 patch might be a better starting point for you. I don't know how the sparc tlb flush design looks like, but if it doesn't do a synchronous IPI to invalidate other threads, then you can't use the x86 approach.
Quoted 5 lines
I have soft bits available on sparc64, that's not my issue.
My issue is that if you implemented this differently, every platform would get the optimization, without having to do anything special at all, and I think that's such a much nicer way.
Oh, they wouldn't. It is completely tied to the low level details of their TLB and pagetable teardown design. That's the unfortunate part about it.
The other thing is that the "how do I know if I can refcount the page behind this (mm,vaddr,pte) tuple" can be quite arch specific as well. And it is also non-trivial to do because that information can be dynamic depending on what driver mapped in that given tuple.
It is *possible*, but not trivial.
Re: [patch 1/2]: x86: implement pte_special
From: Nick Piggin <[email protected]>
Date: Fri, 28 Mar 2008 05:15:20 +0100
Quoted 4 lines
The other thing is that the "how do I know if I can refcount the page behind this (mm,vaddr,pte) tuple" can be quite arch specific as well. And it is also non-trivial to do because that information can be dynamic depending on what driver mapped in that given tuple.
Those are good points.
Re: [patch 1/2]: x86: implement pte_special
Quoted 9 lines · David Miller
From: Nick Piggin <[email protected]>
Date: Fri, 28 Mar 2008 05:15:20 +0100Quoted 4 lines
The other thing is that the "how do I know if I can refcount the page behind this (mm,vaddr,pte) tuple" can be quite arch specific as well. And it is also non-trivial to do because that information can be dynamic depending on what driver mapped in that given tuple.
Those are good points.
I know what you mean though... it doesn't feel like it is perfect code just yet. However, given that it works on x86 and gives such good results today, I feel that I'd rather get this merged first, and then maybe when we get some more platforms on board and the code is a bit more mature then we can try to make it "nicer"...
Re: [patch 1/2]: x86: implement pte_special
In reply to Nick PigginQuoted 19 lines · Nick Piggin
Quoted 8 lines · David Miller
From: Nick Piggin <[email protected]>
Date: Fri, 28 Mar 2008 05:04:42 +0100Quoted 4 lines
BTW. if you are still interested, then the powerpc64 patch might be a better starting point for you. I don't know how the sparc tlb flush design looks like, but if it doesn't do a synchronous IPI to invalidate other threads, then you can't use the x86 approach.
Quoted 5 lines
I have soft bits available on sparc64, that's not my issue.
My issue is that if you implemented this differently, every platform would get the optimization, without having to do anything special at all, and I think that's such a much nicer way.
Oh, they wouldn't. It is completely tied to the low level details of their TLB and pagetable teardown design. That's the unfortunate part about it.
Quoted 6 lines
The other thing is that the "how do I know if I can refcount the page behind this (mm,vaddr,pte) tuple" can be quite arch specific as well. And it is also non-trivial to do because that information can be dynamic depending on what driver mapped in that given tuple.
It is *possible*, but not trivial.
And, btw, you'd still have to implement the actual fast_gup completely in arch code. So once you do that, you are free not to use pte_special for it anyway.
PATCH 2/2 : introduce fast_gup
In reply to Nick PigginIntroduce a new "fast_gup" (for want of a better name right now) which is basically a get_user_pages with a less general API (but still tends to be suited to the common case):
- task and mm are always current and current->mm
- force is always 0
- pages is always non-NULL
- don't pass back vmas
This restricted API can be implemented in a much more scalable way when the ptes are present, by walking the page tables locklessly.
Before anybody asks; it is impossible to just "detect" such a situation in the existing get_user_pages call, and branch to a fastpath in that case, because get_user_pages requres the mmap_sem is held over the call, wheras fast_gup does not.
This patch implements fast_gup as a generic function that falls back to get_user_pages. It converts key direct IO (and splice, for fun) callsites. It also implements an optimised version on x86, which does not take any locks in the fastpath.
On x86, we do an optimistic lockless pagetable walk, without taking any page table locks or even mmap_sem. Page table existence is guaranteed by turning interrupts off (combined with the fact that we're always looking up the current mm, means we can do the lockless page table walk within the constraints of the TLB shootdown design). Basically we can do this lockless pagetable walk in a similar manner to the way the CPU's pagetable walker does not have to take any locks to find present ptes.
Many other architectures could do the same thing. Those that don't IPI could potentially RCU free the page tables and do speculative references on the pages (a la lockless pagecache) to achieve a lockless fast_gup. I have actually got an implementation of this for powerpc.
This patch was found to give about 10% performance improvement on a 2 socket 8 core Intel Xeon system running an OLTP workload on DB2 v9.5
"To test the effects of the patch, an OLTP workload was run on an IBM x3850 M2 server with 2 processors (quad-core Intel Xeon processors at 2.93 GHz) using IBM DB2 v9.5 running Linux 2.6.24rc7 kernel. Comparing runs with and without the patch resulted in an overall performance benefit of ~9.8%. Correspondingly, oprofiles showed that samples from __up_read and __down_read routines that is seen during thread contention for system resources was reduced from 2.8% down to .05%. Monitoring the /proc/vmstat output from the patched run showed that the counter for fast_gup contained a very high number while the fast_gup_slow value was zero."
(fast_gup_slow is a counter we had for the number of times the slowpath was invoked).
The main reason for the improvement is that DB2 has multiple threads each issuing direct-IO. Direct-IO uses get_user_pages, and thus the threads contend the mmap_sem cacheline, and can also contend on page table locks.
I would anticipate larger performance gains on larger systems, however I think DB2 uses an adaptive mix of threads and processes, so it could be that thread contention remains pretty constant as machine size increases. In which case, we stuck with "only" a 10% gain.
Lots of other code could use this too (eg. grep drivers/)
--- arch/x86/mm/Makefile | 2 arch/x86/mm/gup.c | 193 ++++++++++++++++++++++++++++++++++++++++++++++ fs/bio.c | 8 - fs/direct-io.c | 10 -- fs/splice.c | 41 --------- include/asm-x86/uaccess.h | 5 + include/linux/mm.h | 19 ++++ 7 files changed, 225 insertions(+), 53 deletions(-)
fs/bio.c +2 -6
@@ -637,12 +637,8 @@ static struct bio *__bio_map_user_iov(stconst int local_nr_pages = end - start;const int page_limit = cur_page + local_nr_pages; -down_read (¤t->mm->mmap_sem); - ret =get_user_pages (current, current->mm, uaddr, - local_nr_pages, - write_to_vm,0 , &pages[cur_page],NULL ); -up_read (¤t->mm->mmap_sem); - + ret =fast_gup (uaddr, local_nr_pages, + write_to_vm, &pages[cur_page]);if (ret < local_nr_pages) { ret = -EFAULT ; goto out_unmap;
fs/direct-io.c +2 -8
@@ -150,17 +150,11 @@ static int dio_refill_pages(struct dio *int nr_pages; nr_pages =min (dio->total_pages - dio->curr_page,DIO_PAGES ); -down_read (¤t->mm->mmap_sem); - ret =get_user_pages ( - current,/* Task for fault acounting */ - current->mm,/* whose pages? */ + ret =fast_gup ( dio->curr_user_address,/* Where from? */ nr_pages,/* How many pages? */ dio->rw ==READ ,/* Write to memory? */ -0 ,/* force (?) */ - &dio->pages[0 ], -NULL );/* vmas */ -up_read (¤t->mm->mmap_sem); + &dio->pages[0 ]);/* Put results here */ if (ret <0 && dio->blocks_available && (dio->rw &WRITE )) {struct page *page =ZERO_PAGE (0 );
fs/splice.c +3 -38
71 lines not on this page · raw message
include/linux/mm.h +19 -0
@@ -12,6 +12,7 @@#include <linux/prio_tree.h> #include <linux/debug_locks.h> #include <linux/mm_types.h> +#include <linux/uaccess.h> /* for __HAVE_ARCH_FAST_GUP */ struct mempolicy ;struct anon_vma ;
@@ -795,6 +796,24 @@ extern int mprotect_fixup(struct vm_area struct vm_area_struct **pprev, unsigned long start, unsigned long end, unsigned long newflags); +#ifndef __HAVE_ARCH_FAST_GUP +/* Should be moved to asm-generic, and architectures can include it if they +* don't implement their own fast_gup. +*/ +#define fast_gup (start, nr_pages, write, pages) \ +({ \ + struct mm_struct *mm = current->mm; \ + int ret; \ + \ + down_read(&mm->mmap_sem); \ + ret = get_user_pages(current, mm, start, nr_pages, \ + write, 0, pages, NULL); \ + up_read(&mm->mmap_sem); \ + \ + ret; \ +}) +#endif +/* * A callback you can register to apply pressure to ageable caches. *
arch/x86/mm/Makefile +1 -1
@@ -1,4 +1,4 @@ -obj-y := init_$(BITS).o fault.o ioremap.o extable.o pageattr.o mmap.o +obj-y := init_$(BITS).o fault.o ioremap.o extable.o pageattr.o mmap.o gup.o obj-$(CONFIG_X86_32) += pgtable_32.o
arch/x86/mm/gup.c new file +198 -0
198 lines not on this page · raw message
include/asm-x86/uaccess.h +5 -0
@@ -3,3 +3,8 @@#else # include "uaccess_64.h" #endif + +#define __HAVE_ARCH_FAST_GUP +struct page ; +int fast_gup (unsigned long start,int nr_pages,int write,struct page **pages); +
Re: [patch 2/2]: introduce fast_gup
Quoted 4 lines · Nick Piggin
Introduce a new "
fast_gup" (for want of a better name right now) which is basically aget_user_pageswith a less general API (but still tends to be suited to the common case):
I did some quick tests here with two kernels - baseline was current -git with the io cpu affinity stuff, nick is that same base but with your fast_gup() applied. The test run was meant to show the best possible scenario, 1 thread per disk (11 in total) with 4kb block size. Each thread used async O_DIRECT reads, queue depth was 64.
For each kernel, a=0 means that completions were done normally and a=1 means that completions were moved to the submitter. Total runtime for each iteration is ~20 seconds, each test was run 3 times and the scores averaged (very little deviation was seen between runs).
Kernel bw usr(sec) sys(sec) bw/sys
----------------------------------------------------------------------
baseline,a=0 306MiB/s 3.490 14.308 21.39 baseline,a=1 309MiB/s 3.717 13.718 22.53 nick,a=0 310MiB/s 3.669 13.804 22.46 nick,a=1 311MiB/s 3.686 13.279 23.42
That last number is just bandwidth/systime. So baseline vs your patch gets about 5% better bw/sys utilization. fast_gup() + io affinity is about 9.5% better bw/sys.
The system is just a puny 2-way x86-64, two sockets with HT enabled. So I'd say the results look quite good!
Signature · 1 line
Jens Axboe
Peter Zijlstra
maintainerRe: [patch 2/2]: introduce fast_gup
In reply to Nick PigginQuoted 46 lines · Nick Piggin
+++
linux-2.6/arch/x86/mm/gup.c(quoted diff) +44
-0@@ -0,0 +1,198 @@ +/* + * Lockless fast_gup for x86 + * + * Copyright (C) 2007 Nick Piggin + * Copyright (C) 2007 Novell Inc. + */ +#include <linux/sched.h> +#include <linux/mm.h> +#include <linux/vmstat.h> +#include <asm/pgtable.h> + +/* + * The performance critical leaf functions are made noinline otherwise gcc + * inlines everything into a single function which results in too much + * register pressure. + */ +static noinline int gup_pte_range(pmd_t pmd, unsigned long addr, + unsigned long end, int write, struct page **pages, int *nr) +{ + unsigned long mask, result; + pte_t *ptep; + + result = _PAGE_PRESENT|_PAGE_USER; + if (write) + result |= _PAGE_RW; + mask = result | _PAGE_SPECIAL; + + ptep = pte_offset_map(&pmd, addr); + do { + /* + * XXX: careful. On 3-level 32-bit, the pte is 64 bits, and + * we need to make sure we load the low word first, then the + * high. This means _PAGE_PRESENT should be clear if the high + * word was not valid. Currently, the C compiler can issue + * the loads in any order, and I don't know of a wrapper + * function that will do this properly, so it is broken on + * 32-bit 3-level for the moment. + */ + pte_t pte = *ptep; + struct page *page; + + if ((pte_val(pte) & mask) != result) + return 0; + VM_BUG_ON(!pfn_valid(pte_pfn(pte)));
Review line 54
Quoted 10 lines
(quoted diff) +10
-0+ page = pte_page(pte); + get_page(page); + pages[*nr] = page; + (*nr)++; + + } while (ptep++, addr += PAGE_SIZE, addr != end); + pte_unmap(ptep - 1); + + return 1; +}
Would this be sufficient to address that comment's conern?
arch/x86/mm/gup.c +9 -1
@@ -36,8 +36,16 @@ static noinline int gup_pte_range(pmd_t* function that will do this properly, so it is broken on * 32-bit 3-level for the moment. */ -pte_t pte = *ptep;struct page *page; +pte_t pte; + +#ifdef CONFIG_X86_PAE + pte.pte_low = ptep->pte_low; +barrier (); + pte.pte_high = ptep->pte_high; +#else + pte = *ptep; +#endif if ((pte_val (pte) & mask) != result)return 0 ;
Re: [patch 2/2]: introduce fast_gup
Quoted 2 lines · Peter Zijlstra
Would this be sufficient to address that comment's conern?
It would be nicer to just add a "native_get_pte()" to x86, to match the already-existing "native_set_pte()".
And that "barrier()" should b "smp_rmb()". They may be the same code sequence, but from a conceptual angle, "smp_rmb()" makes a whole lot more sense.
Finally, I don't think that comment is correct in the first place. It's not that simple. The thing is, even *with* the memory barrier in place, we may have:
CPU#1 CPU#2
===== =====
fast_gup:
- read low word
native_set_pte_present:
- set low word to 0
- set high word to new value
- read high word
- set low word to new value
and so you read a low word that is associated with a *different* high word! Notice?
So trivial memory ordering is _not_ enough.
So I think the code literally needs to be something like this
#ifdef CONFIG_X86_PAE
static inline pte_t native_get_pte(pte_t *ptep)
{
pte_t pte;
retry:
pte.pte_low = ptep->pte_low;
smp_rmb();
pte.pte_high = ptep->pte_high;
smp_rmb();
if (unlikely(pte.pte_low != ptep->pte_low)
goto retry;
return pte;
}
#else
#define native_get_pte(ptep) (*(ptep))
#endifbut I have admittedly not really thought it fully through.
Linus
Peter Zijlstra
maintainerRe: [patch 2/2]: introduce fast_gup
Quoted 7 lines · Linus Torvalds
Quoted 2 lines · Peter Zijlstra
Would this be sufficient to address that comment's conern?
It would be nicer to just add a "
native_get_pte()" to x86, to match the already-existing "native_set_pte()".
See, I _knew_ I was missing something obvious :-/
Quoted 51 lines
And that "
barrier()" should b "smp_rmb()". They may be the same code sequence, but from a conceptual angle, "smp_rmb()" makes a whole lot more sense.Finally, I don't think that comment is correct in the first place. It's not that simple. The thing is, even *with* the memory barrier in place, we may have:
CPU#1 CPU#2
===== =====
fast_gup:
- read low word
native_set_pte_present:
- set low word to 0
- set high word to new value- read high word
- set low word to new value
and so you read a low word that is associated with a *different* high word! Notice?
So trivial memory ordering is _not_ enough.
So I think the code literally needs to be something like this
#ifdef CONFIG_X86_PAE static inline pte_t native_get_pte(pte_t *ptep) { pte_t pte; retry: pte.pte_low = ptep->pte_low; smp_rmb(); pte.pte_high = ptep->pte_high; smp_rmb(); if (unlikely(pte.pte_low != ptep->pte_low) goto retry; return pte; } #else #define native_get_pte(ptep) (*(ptep)) #endif
Quoted 1 line
but I have admittedly not really thought it fully through.
Looks sane here; Clark can you give this a spin?
Jeremy, did I get the paravirt stuff right?
---
arch/x86/mm/gup.c +1 -1
@@ -36,7 +36,7 @@ static noinline int gup_pte_range(pmd_t* function that will do this properly, so it is broken on * 32-bit 3-level for the moment. */ -pte_t pte = *ptep; +pte_t pte =get_pte (ptep);struct page *page;if ((pte_val (pte) & mask) != result)
arch/x86/kernel/paravirt_32.c +1 -0
@@ -450,6 +450,7 @@ struct pv_mmu_ops pv_mmu_ops = {#ifdef CONFIG_X86_PAE .set_pte_atomic = native_set_pte_atomic, .set_pte_present = native_set_pte_present, + .get_pte = native_get_pte, .set_pud = native_set_pud, .pte_clear = native_pte_clear, .pmd_clear = native_pmd_clear,
include/asm-x86/paravirt.h +13 -0
@@ -216,6 +216,7 @@ struct pv_mmu_ops {void (*set_pte_atomic)(pte_t *ptep,pte_t pteval);void (*set_pte_present)(struct mm_struct *mm,unsigned long addr,pte_t *ptep,pte_t pte); +void (*get_pte)(struct pte_t *ptep);void (*set_pud)(pud_t *pudp,pud_t pudval);void (*pte_clear)(struct mm_struct *mm,unsigned long addr,pte_t *ptep);void (*pmd_clear)(pmd_t *pmdp);
@@ -886,6 +887,13 @@ static inline void set_pte_present(struc pv_mmu_ops.set_pte_present (mm, addr, ptep, pte); } +static inline pte_t get_pte (struct pte_t *ptep) +{ + unsigned long long ret = PVOP_CALL1(unsigned long long, pv_mmu_ops.get_pte, ptep); + +return (pte_t ) { ret, ret >>32 }; +} +static inline void set_pmd (pmd_t *pmdp,pmd_t pmdval) { PVOP_VCALL3(pv_mmu_ops.set_pmd, pmdp,
@@ -941,6 +949,11 @@ static inline void set_pte_at(struct mm_PVOP_VCALL4 (pv_mmu_ops.set_pte_at, mm, addr, ptep, pteval.pte_low); } +static inline pte_t get_pte (struct pte_t *ptep) +{ + return PVOP_CALL1(unsigned long, pv_mmu_ops.get_pte, ptep); +} +static inline void set_pmd (pmd_t *pmdp,pmd_t pmdval) {PVOP_VCALL2 (pv_mmu_ops.set_pmd, pmdp, pmdval.pud.pgd.pgd);
include/asm-x86/pgtable-2level.h +6 -0
@@ -20,6 +20,10 @@ static inline void native_set_pte_at(str {native_set_pte (ptep, pte); } +static inline pte_t native_get_pte (pte_t *ptep) +{ +return *ptep; +}static inline void native_set_pmd (pmd_t *pmdp,pmd_t pmd) { *pmdp = pmd;
@@ -33,6 +37,8 @@ static inline void native_set_pmd(pmd_t#define set_pte_atomic (pteptr, pteval) set_pte(pteptr,pteval)#define set_pte_present (mm,addr,ptep,pteval) set_pte_at(mm,addr,ptep,pteval) +#define get_pte (ptep) native_get_pte(ptep) +#define pte_clear (mm,addr,xp) do { set_pte_at(mm, addr, xp, __pte(0)); } while (0)#define pmd_clear (xp) do { set_pmd(xp, __pmd(0)); } while (0)
include/asm-x86/pgtable-3level.h +15 -0
@@ -61,6 +61,20 @@ static inline void native_set_pte_presen ptep->pte_low = pte.pte_low; } +static inline pte_t native_get_pte (pte_t *ptep) +{ +pte_t pte; + +retry: + pte.pte_low = ptep->pte_low; +smp_rmb (); + pte.pte_high = ptep->pte_high; +smp_rmb (); +if (unlikely (pte.pte_low != ptep->pte_low)) + goto retry; +return pte; +} +static inline void native_set_pte_atomic (pte_t *ptep,pte_t pte) {set_64bit ((unsigned long long *)(ptep),native_pte_val (pte));
@@ -99,6 +113,7 @@ static inline void native_pmd_clear(pmd_#define set_pte_at (mm, addr, ptep, pte) native_set_pte_at(mm, addr, ptep, pte)#define set_pte_present (mm, addr, ptep, pte) native_set_pte_present(mm, addr, ptep, pte)#define set_pte_atomic (ptep, pte) native_set_pte_atomic(ptep, pte) +#define get_pte (ptep) native_get_pte(ptep)#define set_pmd (pmdp, pmd) native_set_pmd(pmdp, pmd)#define set_pud (pudp, pud) native_set_pud(pudp, pud)#define pte_clear (mm, addr, ptep) native_pte_clear(mm, addr, ptep)
Re: [patch 2/2]: introduce fast_gup
Quoted 2 lines · Peter Zijlstra
Jeremy, did I get the paravirt stuff right?
I don't think this is worth it to virtualize.
We access the page tables directly in any number of places, having a "get_pte()" indirection here is not going to help anything.
Just make it an x86-only inline function. In fact, you can keep it inside arch/x86/mm/gup.c, because nobody else is likely to ever even need it, since normal accesses are all supposed to be done under the page table spinlock, so they do not have this issue at all.
The indirection and virtualization thing is just going to complicate matters for no good reason.
Linus
Peter Zijlstra
maintainerRe: [patch 2/2]: introduce fast_gup
Quoted 4 lines · Linus Torvalds
Quoted 2 lines · Peter Zijlstra
Jeremy, did I get the paravirt stuff right?
Still wanting to know if I got it right.
Quoted 5 lines
I don't think this is worth it to virtualize.
We access the page tables directly in any number of places, having a "
get_pte()" indirection here is not going to help anything.
Quoted 7 lines
Just make it an x86-only inline function. In fact, you can keep it inside
arch/x86/mm/gup.c, because nobody else is likely to ever even need it, since normal accesses are all supposed to be done under the page table spinlock, so they do not have this issue at all.The indirection and virtualization thing is just going to complicate matters for no good reason.
Here you go ;-)
arch/x86/mm/gup.c +43 -0
@@ -9,6 +9,49 @@#include <linux/vmstat.h> #include <asm/pgtable.h> +#ifdef CONFIG_X86_PAE + +/* +* Companion to native_set_pte_present(); normal access takes the pte_lock +* and thus doesn't need it. +* +* This closes the race: +* +* CPU#1 CPU#2 +* ===== ===== +* +* fast_gup: +* - read low word +* +* native_set_pte_present: +* - set low word to 0 +* - set high word to new value +* +* - read high word +* +* - set low word to new value +* +*/ +static inline pte_t native_get_pte (pte_t *ptep) +{ +pte_t pte; + +retry: + pte.pte_low = ptep->pte_low; +smp_rmb (); + pte.pte_high = ptep->pte_high; +smp_rmb (); +if (unlikely (pte.pte_low != ptep->pte_low)) + goto retry; +return pte; +} + +#else + +#define native_get_pte (ptep) (*(ptep)) + +#endif +/* * The performance critical leaf functions are made noinline otherwise gcc * inlines everything into a single function which results in too much
Re: [patch 2/2]: introduce fast_gup
Quoted 2 lines · Peter Zijlstra
Here you go ;-)
I think you should _use_ the new functions too ;)
Linus
Peter Zijlstra
maintainerRe: [patch 2/2]: introduce fast_gup
Quoted 6 lines · Linus Torvalds
Quoted 2 lines · Peter Zijlstra
Here you go ;-)
I think you should _use_ the new functions too ;)
D'0h - clearly not my day today...
arch/x86/mm/gup.c +44 -1
@@ -9,6 +9,49 @@#include <linux/vmstat.h> #include <asm/pgtable.h> +#ifdef CONFIG_X86_PAE + +/* +* Companion to native_set_pte_present(); normal access takes the pte_lock +* and thus doesn't need it. +* +* This closes the race: +* +* CPU#1 CPU#2 +* ===== ===== +* +* fast_gup: +* - read low word +* +* native_set_pte_present: +* - set low word to 0 +* - set high word to new value +* +* - read high word +* +* - set low word to new value +* +*/ +static inline pte_t native_get_pte (pte_t *ptep) +{ +pte_t pte; + +retry: + pte.pte_low = ptep->pte_low; +smp_rmb (); + pte.pte_high = ptep->pte_high; +smp_rmb (); +if (unlikely (pte.pte_low != ptep->pte_low)) + goto retry; +return pte; +} + +#else + +#define native_get_pte (ptep) (*(ptep)) + +#endif +/* * The performance critical leaf functions are made noinline otherwise gcc * inlines everything into a single function which results in too much
@@ -36,7 +79,7 @@ static noinline int gup_pte_range(pmd_t* function that will do this properly, so it is broken on * 32-bit 3-level for the moment. */ -pte_t pte = *ptep; +pte_t pte =native_get_pte (ptep);struct page *page;if ((pte_val (pte) & mask) != result)
Re: [patch 2/2]: introduce fast_gup
Quoted 2 lines · Peter Zijlstra
D'0h - clearly not my day today...
Ok, I'm acking this one ;)
And yes, it would be nice if the gup patches would go in early, since I wouldn't be entirely surprised if other architectures didn't have some other subtle issues here. We've never accessed the page tables without locking before, so we've only had races with hardware, never software.
Linus
Re: [patch 2/2]: introduce fast_gup
Quoted 4 lines · Linus Torvalds
Quoted 1 line · Peter Zijlstra
Quoted 8 lines
Quoted 1 line
D'0h - clearly not my day today...
Ok, I'm acking this one ;)
And yes, it would be nice if the gup patches would go in early, since I wouldn't be entirely surprised if other architectures didn't have some other subtle issues here. We've never accessed the page tables without locking before, so we've only had races with hardware, never software.
Well I'd love them to go in 2.6.26. Andrew will be sending you some precursor patches soon, and then I can rediff the x86 fast_gup.
We actually do access the page tables without the traditional linux vm locking in architectures like powerpc that do software pagetable walks. That's why their pagetables are RCUed for example.
So the concept is actually more foreign to x86 than it is to some others.
BTW. we do have powerpc patches for fast_gup. The "problem" with that is that it requires my speculative page references from the lockless pagecache patches (basically fast_gup for powerpc is exactly like lockless pagecache but substitute the pagecache radix tree for the page tables). But that might have to wait for 2.6.27. At least we'll have the x86 fast_gup.
But the upshot is that I now have "real world" benchmark results to justify adding the complexity of speculative references. After that, adding lockless pagecache is more or less a noop ;) Everything's falling into place, mwa ha ha.
Re: [patch 2/2]: introduce fast_gup
In reply to Peter Zijlstraarch/x86/mm/gup.c line 45
Quoted 7 lines · Peter Zijlstra
arch/x86/mm/gup.c +7
-0+retry: + pte.pte_low = ptep->pte_low; +smp_rmb (); + pte.pte_high = ptep->pte_high; +smp_rmb (); +if (unlikely (pte.pte_low != ptep->pte_low)) + goto retry;
What about using `do { ... } while (...)' instead?
Gr{oetje,eeting}s,
Geert
--
Geert Uytterhoeven -- There's lots of Linux beyond ia32 -- [email protected]
In personal conversations with technical people, I call myself a hacker. But when I'm talking to journalists I just say "programmer" or something like that.
-- Linus Torvalds
Re: [patch 2/2]: introduce fast_gup
Quoted 10 lines · Geert Uytterhoeven
arch/x86/mm/gup.c line 45
Quoted 7 lines · Peter Zijlstra
arch/x86/mm/gup.c +7
-0+retry: + pte.pte_low = ptep->pte_low; +smp_rmb (); + pte.pte_high = ptep->pte_high; +smp_rmb (); +if (unlikely (pte.pte_low != ptep->pte_low)) + goto retry;What about using `do { ... } while (...)' instead?
Partly because it's not a loop. It's an error and retry event, and it generally happens zero times (and sometimes once). I don't think it should even _look_ like a loop.
So personally, I just tend to think that it's just more readable when it's written as being obviously not a regular loop. I'm not in the "gotos are harmful" camp.
And partly because I tend to distrust loops for these thigns is that historically gcc sometimes did stupid things for loops (it assumed that loops were hot and tried to align them etc, and the same didn't happen for branch targets). Of course, these days I suspect gcc can't even tell the difference any more (it probably turns it into the same internal representation), but old habits die hard.
Linus
Re: [patch 2/2]: introduce fast_gup
In reply to Peter ZijlstraQuoted 56 lines · Peter Zijlstra
Quoted 6 lines · Linus Torvalds
Quoted 2 lines · Peter Zijlstra
Would this be sufficient to address that comment's conern?
It would be nicer to just add a "
native_get_pte()" to x86, to match the already-existing "native_set_pte()".See, I _knew_ I was missing something obvious :-/
Quoted 44 lines
And that "
barrier()" should b "smp_rmb()". They may be the same code sequence, but from a conceptual angle, "smp_rmb()" makes a whole lot more sense.Finally, I don't think that comment is correct in the first place. It's not that simple. The thing is, even *with* the memory barrier in place, we may have:
CPU#1 CPU#2
===== =====
fast_gup:
- read low word
native_set_pte_present:
- set low word to 0
- set high word to new value- read high word
- set low word to new value
and so you read a low word that is associated with a *different* high word! Notice?
So trivial memory ordering is _not_ enough.
So I think the code literally needs to be something like this
#ifdef CONFIG_X86_PAE static inline pte_t native_get_pte(pte_t *ptep) { pte_t pte; retry: pte.pte_low = ptep->pte_low; smp_rmb(); pte.pte_high = ptep->pte_high; smp_rmb(); if (unlikely(pte.pte_low != ptep->pte_low) goto retry; return pte; } #else #define native_get_pte(ptep) (*(ptep))
Quoted 9 lines
Quoted 4 lines
#endifbut I have admittedly not really thought it fully through.
Looks sane here; Clark can you give this a spin?
Jeremy, did I get the paravirt stuff right?
You shouldn't need to do anything special for paravirt. set_pte is necessary because it may have side-effects (like a hypervisor call), but get_pte should be side-effect free. There's no other need for it; any special processing on the pte value itself is done in pte_val().
J
Re: [patch 2/2]: introduce fast_gup
In reply to Linus TorvaldsQuoted 22 lines · Linus Torvalds
Finally, I don't think that comment is correct in the first place. It's not that simple. The thing is, even *with* the memory barrier in place, we may have:
CPU#1 CPU#2
===== =====
fast_gup:
- read low word
native_set_pte_present:
- set low word to 0
- set high word to new value- read high word
- set low word to new value
and so you read a low word that is associated with a *different* high word! Notice?
So trivial memory ordering is _not_ enough.
So I think the code literally needs to be something like this
Quoted 17 lines
#ifdef CONFIG_X86_PAE
static inline pte_t native_get_pte(pte_t *ptep)
{
pte_t pte;
retry:
pte.pte_low = ptep->pte_low;
smp_rmb();
pte.pte_high = ptep->pte_high;
smp_rmb();
if (unlikely(pte.pte_low != ptep->pte_low)
goto retry;
return pte;
}I think this is still broken. Suppose that after reading pte_high native_set_pte() is called again on another cpu, changing pte_low back to the original value (but with a different pte_high). You now have pte_low from second native_set_pte() but pte_high from the first native_set_pte().
You could use cmpxchg8b to atomically load the pte, but at the expense of taking the cacheline for exclusive access.
Signature · 1 line
error compiling committee.c: too many arguments to function
Peter Zijlstra
maintainerRe: [patch 2/2]: introduce fast_gup
Quoted 44 lines · Avi Kivity
Quoted 25 lines · Linus Torvalds
Finally, I don't think that comment is correct in the first place. It's not that simple. The thing is, even *with* the memory barrier in place, we may have:
CPU#1 CPU#2
===== =====
fast_gup:
- read low word
native_set_pte_present:
- set low word to 0
- set high word to new value- read high word
- set low word to new value
and so you read a low word that is associated with a *different* high word! Notice?
So trivial memory ordering is _not_ enough.
So I think the code literally needs to be something like this
Quoted 17 lines
#ifdef CONFIG_X86_PAE static inline pte_t native_get_pte(pte_t *ptep) { pte_t pte; retry: pte.pte_low = ptep->pte_low; smp_rmb(); pte.pte_high = ptep->pte_high; smp_rmb(); if (unlikely(pte.pte_low != ptep->pte_low) goto retry; return pte; }
Quoted 5 lines
I think this is still broken. Suppose that after reading
pte_highnative_set_pte()is called again on another cpu, changingpte_lowback to the original value (but with a differentpte_high). You now havepte_lowfrom secondnative_set_pte()butpte_highfrom the firstnative_set_pte().
I think the idea was that for user pages we only use set_pte_present() which does the low=0 thing first.
Re: [patch 2/2]: introduce fast_gup
Quoted 44 lines · Peter Zijlstra
Quoted 42 lines · Avi Kivity
Quoted 40 lines · Linus Torvalds
Finally, I don't think that comment is correct in the first place. It's not that simple. The thing is, even *with* the memory barrier in place, we may have:
CPU#1 CPU#2
===== =====
fast_gup:
- read low word
native_set_pte_present:
- set low word to 0
- set high word to new value- read high word
- set low word to new value
and so you read a low word that is associated with a *different* high word! Notice?
So trivial memory ordering is _not_ enough.
So I think the code literally needs to be something like this
#ifdef CONFIG_X86_PAE static inline pte_t native_get_pte(pte_t *ptep) { pte_t pte; retry: pte.pte_low = ptep->pte_low; smp_rmb(); pte.pte_high = ptep->pte_high; smp_rmb(); if (unlikely(pte.pte_low != ptep->pte_low) goto retry; return pte; }
Quoted 6 lines
Quoted 2 lines
I think this is still broken. Suppose that after reading
pte_highnative_set_pte()is called again on another cpu, changingpte_lowback to the original value (but with a differentpte_high). You now havepte_lowfrom secondnative_set_pte()butpte_highfrom the firstnative_set_pte().I think the idea was that for user pages we only use
set_pte_present()which does the low=0 thing first.
Doesn't matter. The second native_set_pte() (or set_pte_present()) executes atomically:
fast_gup:
- read low word (l0)
native_set_pte_present:
- set low word to 0
- set high word to new value (h1)
- set low word to new value (l1)
- read high word (h1)
native_set_pte_present:
- set low word to 0
- set high word to new value (h2)
- set low word to new value (l2)
- re-read low word (l2)
If l2 happens to be equal to l0, then the check succeeds and we have a splintered pte h1:l0.
Signature · 1 line
error compiling committee.c: too many arguments to function
Peter Zijlstra
maintainerRe: [patch 2/2]: introduce fast_gup
Quoted 79 lines · Avi Kivity
Quoted 54 lines · Peter Zijlstra
Quoted 45 lines · Avi Kivity
Quoted 25 lines · Linus Torvalds
Finally, I don't think that comment is correct in the first place. It's not that simple. The thing is, even *with* the memory barrier in place, we may have:
CPU#1 CPU#2
===== =====
fast_gup:
- read low word
native_set_pte_present:
- set low word to 0
- set high word to new value- read high word
- set low word to new value
and so you read a low word that is associated with a *different* high word! Notice?
So trivial memory ordering is _not_ enough.
So I think the code literally needs to be something like this
Quoted 18 lines
#ifdef CONFIG_X86_PAE static inline pte_t native_get_pte(pte_t *ptep) { pte_t pte; retry: pte.pte_low = ptep->pte_low; smp_rmb(); pte.pte_high = ptep->pte_high; smp_rmb(); if (unlikely(pte.pte_low != ptep->pte_low) goto retry; return pte; }Quoted 6 lines
I think this is still broken. Suppose that after reading
pte_highnative_set_pte()is called again on another cpu, changingpte_lowback to the original value (but with a differentpte_high). You now havepte_lowfrom secondnative_set_pte()butpte_highfrom the firstnative_set_pte().Quoted 3 lines
I think the idea was that for user pages we only use
set_pte_present()which does the low=0 thing first.Doesn't matter. The second
native_set_pte()(orset_pte_present()) executes atomically:
fast_gup:
- read low word (l0)
native_set_pte_present:
- set low word to 0
- set high word to new value (h1)
- set low word to new value (l1)- read high word (h1)
native_set_pte_present:
- set low word to 0
- set high word to new value (h2)
- set low word to new value (l2)
Quoted 5 lines
- re-read low word (l2)
If l2 happens to be equal to l0, then the check succeeds and we have a splintered pte h1:l0.
ok, so lets use cmpxchg8.
arch/x86/mm/gup.c +14 -10
@@ -9,6 +9,19 @@#include <linux/vmstat.h> #include <asm/pgtable.h> +#ifdef CONFIG_X86_PAE + +static inline pte_t native_get_pte (pte_t *ptep) +{ +return native_make_pte (cmpxchg64 ((u64 *)ptep,0ULL ,0ULL )); +} + +#else + +#define native_get_pte (ptep) (*(ptep)) + +#endif +/* * The performance critical leaf functions are made noinline otherwise gcc * inlines everything into a single function which results in too much
@@ -27,16 +40,7 @@ static noinline int gup_pte_range(pmd_t ptep =pte_offset_map (&pmd, addr); do { - /* - * XXX: careful. On 3-level 32-bit, the pte is 64 bits, and - * we need to make sure we load the low word first, then the - * high. This means _PAGE_PRESENT should be clear if the high - * word was not valid. Currently, the C compiler can issue - * the loads in any order, and I don't know of a wrapper - * function that will do this properly, so it is broken on - * 32-bit 3-level for the moment. - */ - pte_t pte = *ptep; + pte_t pte = native_get_pte(ptep); struct page *page; if ((pte_val(pte) & mask) != result)
Re: [patch 2/2]: introduce fast_gup
Quoted 80 lines · Peter Zijlstra
Quoted 79 lines · Avi Kivity
Quoted 54 lines · Peter Zijlstra
Quoted 45 lines · Avi Kivity
Quoted 25 lines · Linus Torvalds
Finally, I don't think that comment is correct in the first place. It's not that simple. The thing is, even *with* the memory barrier in place, we may have:
CPU#1 CPU#2
===== =====
fast_gup:
- read low word
native_set_pte_present:
- set low word to 0
- set high word to new value- read high word
- set low word to new value
and so you read a low word that is associated with a *different* high word! Notice?
So trivial memory ordering is _not_ enough.
So I think the code literally needs to be something like this
Quoted 18 lines
#ifdef CONFIG_X86_PAE static inline pte_t native_get_pte(pte_t *ptep) { pte_t pte; retry: pte.pte_low = ptep->pte_low; smp_rmb(); pte.pte_high = ptep->pte_high; smp_rmb(); if (unlikely(pte.pte_low != ptep->pte_low) goto retry; return pte; }Quoted 6 lines
I think this is still broken. Suppose that after reading
pte_highnative_set_pte()is called again on another cpu, changingpte_lowback to the original value (but with a differentpte_high). You now havepte_lowfrom secondnative_set_pte()butpte_highfrom the firstnative_set_pte().Quoted 3 lines
I think the idea was that for user pages we only use
set_pte_present()which does the low=0 thing first.Doesn't matter. The second
native_set_pte()(orset_pte_present()) executes atomically:
fast_gup:
- read low word (l0)
native_set_pte_present:
- set low word to 0
- set high word to new value (h1)
- set low word to new value (l1)- read high word (h1)
native_set_pte_present:
- set low word to 0
- set high word to new value (h2)
- set low word to new value (l2)
Quoted 7 lines
Quoted 5 lines
- re-read low word (l2)
If l2 happens to be equal to l0, then the check succeeds and we have a splintered pte h1:l0.
ok, so lets use cmpxchg8.
That's horrible ;)
Anyway guys you are missing the other side of the equation -- that whenever _PAGE_PRESENT is cleared, all CPUs where current->mm might be == mm have to have a tlb flush. And we're holding off tlb flushes in fast_gup, that's the whole reason why it all works.
Indeed we do need Linus's loop, though, because I wasn't thinking of the teardown side when writing that comment it seems (teardowns under mmu_gather can and do set the pte to some arbitrary values before the IPI goes out -- but they will never contain _PAGE_PRESENT we can be sure).
Linus's loop I will use for PAE. I'd love to know whether the hardware walker actually does an atomic 64-bit load or not, though.
Re: [patch 2/2]: introduce fast_gup
Quoted 8 lines · Nick Piggin
That's horrible ;)
Anyway guys you are missing the other side of the equation -- that whenever
_PAGE_PRESENTis cleared, all CPUs wherecurrent->mmmight be == mm have to have a tlb flush. And we're holding off tlb flushes infast_gup, that's the whole reason why it all works.
Ah, clever. Thanks.
Signature · 1 line
error compiling committee.c: too many arguments to function
Re: [patch 2/2]: introduce fast_gup
In reply to Nick PigginQuoted 2 lines · Nick Piggin
Linus's loop I will use for PAE. I'd love to know whether the hardware walker actually does an atomic 64-bit load or not, though.
all x86 natural accesses (done by instructions) are MESI atomic as long as they lie on a natural word boundary. (which they do in the PTE case)
while the hardware walker is not an instruction, it would be highly unusal (and i'd claim, inherently broken) for the hardware walker to fetch a 64-bit pte value via two 32-bit accesses from two different versions of the same cacheline.
Ingo
Peter Zijlstra
maintainerRe: [patch 2/2]: introduce fast_gup
In reply to Nick PigginQuoted 17 lines · Nick Piggin
arch/x86/mm/gup.c new file +17
-0+static noinline int gup_pte_range(pmd_t pmd, unsigned long addr, +unsigned long end,int write,struct page **pages,int *nr) +{ +unsigned long mask, result; +pte_t *ptep; + + result = _PAGE_PRESENT|_PAGE_USER; +if (write) + result |= _PAGE_RW; + mask = result | _PAGE_SPECIAL; + + ptep =pte_offset_map (&pmd, addr); + do { + /* + * XXX: careful. On 3-level 32-bit, the pte is 64 bits, and + * we need to make sure we load the low word first, then the + * high. This means _PAGE_PRESENT should be clear if the high
arch/x86/mm/gup.c line 43
Quoted 10 lines
arch/x86/mm/gup.c new file +10
-0+* word was not valid. Currently, the C compiler can issue +* the loads in any order, and I don't know of a wrapper +* function that will do this properly, so it is broken on +* 32-bit 3-level for the moment. +*/ +pte_t pte = *ptep; +struct page *page; + +if ((pte_val (pte) & mask) != result) +return 0 ;
This return path fails to unmap the pmd.
Quoted 11 lines
arch/x86/mm/gup.c new file +11
-0+VM_BUG_ON (!pfn_valid (pte_pfn (pte))); + page =pte_page (pte); +get_page (page); + pages[*nr] = page; + (*nr)++; + + }while (ptep++, addr +=PAGE_SIZE , addr != end); +pte_unmap (ptep -1 ); + +return 1 ; +}
Re: [patch 2/2]: introduce fast_gup
Quoted 23 lines · Peter Zijlstra
Quoted 21 lines · Nick Piggin
arch/x86/mm/gup.c new file +21
-0+static noinline int gup_pte_range(pmd_t pmd, unsigned long addr, +unsigned long end,int write,struct page **pages,int *nr) +{ +unsigned long mask, result; +pte_t *ptep; + + result = _PAGE_PRESENT|_PAGE_USER; +if (write) + result |= _PAGE_RW; + mask = result | _PAGE_SPECIAL; + + ptep =pte_offset_map (&pmd, addr); + do { + /* + * XXX: careful. On 3-level 32-bit, the pte is 64 bits, and + * we need to make sure we load the low word first, then the + * high. This means _PAGE_PRESENT should be clear if the high + * word was not valid. Currently, the C compiler can issue + * the loads in any order, and I don't know of a wrapper + * function that will do this properly, so it is broken on + * 32-bit 3-level for the moment.
Quoted 8 lines
arch/x86/mm/gup.c line 43
Quoted 6 lines
arch/x86/mm/gup.c new file +6
-0+*/ +pte_t pte = *ptep; +struct page *page; + +if ((pte_val (pte) & mask) != result) +return 0 ;This return path fails to unmap the pmd.
Ah good catch. As you can see I haven't done any highmem testing ;) Which I will do so before sending upstream.
Re: [patch 2/2]: introduce fast_gup
Quoted 33 lines · Nick Piggin
Quoted 23 lines · Peter Zijlstra
Quoted 21 lines · Nick Piggin
arch/x86/mm/gup.c new file +21
-0+static noinline int gup_pte_range(pmd_t pmd, unsigned long addr, +unsigned long end,int write,struct page **pages,int *nr) +{ +unsigned long mask, result; +pte_t *ptep; + + result = _PAGE_PRESENT|_PAGE_USER; +if (write) + result |= _PAGE_RW; + mask = result | _PAGE_SPECIAL; + + ptep =pte_offset_map (&pmd, addr); + do { + /* + * XXX: careful. On 3-level 32-bit, the pte is 64 bits, and + * we need to make sure we load the low word first, then the + * high. This means _PAGE_PRESENT should be clear if the high + * word was not valid. Currently, the C compiler can issue + * the loads in any order, and I don't know of a wrapper + * function that will do this properly, so it is broken on + * 32-bit 3-level for the moment.Quoted 8 lines
arch/x86/mm/gup.c line 43
Quoted 6 lines
arch/x86/mm/gup.c new file +6
-0+*/ +pte_t pte = *ptep; +struct page *page; + +if ((pte_val (pte) & mask) != result) +return 0 ;This return path fails to unmap the pmd.
Quoted 2 lines
Ah good catch. As you can see I haven't done any highmem testing ;) Which I will do so before sending upstream.
Which will be when? We'd really like to see this in mainline as soon as possible and in -mm in the meanwhile.
Thanks,
Shaggy
Signature · 8 lines
David Kleikamp IBM Linux Technology Center -- To unsubscribe, send a message with 'unsubscribe linux-mm' in the body to [email protected]. For more info on Linux MM, see: http://www.linux-mm.org/ . Don't email: <a href=mailto:"[email protected]"> [email protected] </a>
Re: [patch 2/2]: introduce fast_gup
Quoted 5 lines · Dave Kleikamp
Quoted 2 lines
Ah good catch. As you can see I haven't done any highmem testing ;) Which I will do so before sending upstream.
Which will be when? We'd really like to see this in mainline as soon as possible and in -mm in the meanwhile.
Well I just got all the "hard" core mm stuff past Linus in this merge window, and got a couple of preexisting memory ordering bugs fixed.. So I am planning to get it into -mm, ready for the next merge window.
I'm a little concerned about Peter's instability reports, but maybe they're just an -rt thing. But I don't think we've found any holes in fast_gup yet (although I think I need to add one last check to ensure it won't pick up kernel addresses).
Thanks,
Nick
--
To unsubscribe, send a message with 'unsubscribe linux-mm' in the body to [email protected]. For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"[email protected]"> [email protected] </a>