my mmu notifiers
Subsystems (16)
PATCH 1/1 my mmu notifiers
Well I started reviewing the mmu notifier code, but it is kind of hard to know what you're talking about just by reading through code and not trying your suggestions for yourself...
So I implemented mmu notifiers slightly differently. Andrea's mmu notifiers are rather similar. However I have tried to make a point of minimising the impact the the core mm/. I don't see why we need to invalidate or flush anything when changing the pte to be _more_ permissive, and I don't understand the need for invalidate_begin/invalidate_end pairs at all. What I have done is basically create it so that the notifiers get called basically in the same place as the normal TLB flushing is done, and nowhere else.
I also wanted to avoid calling notifier code from inside eg. hardware TLB or pte manipulation primitives. These things are already pretty well spaghetti, so I'd like to just place them right where needed first... I think eventually it will need a bit of a rethink to make it more consistent and more general. But I prefer to do put them in the caller for the moment.
I have also attempted to write a skeleton driver. Not like Christoph's drivers, but one that actually does something. This one can mmap a window into its own virtual address space. It's not perfect yet (I need to replace page_mkwrite with ->fault in the core mm before I can get enough information to do protection properly I think). However I think it may be race-free in the fault vs unmap paths. It's pretty complex, I must say.
---
include/linux/mm_types.h +3 -0
@@ -228,6 +228,9 @@ struct mm_struct {#ifdef CONFIG_CGROUP_MEM_CONT struct mem_cgroup *mem_cgroup;#endif +#ifdef CONFIG_MMU_NOTIFIER +struct hlist_head mmu_notifier_list; +#endif };#endif /* _LINUX_MM_TYPES_H */
include/linux/mmu_notifier.h new file +69 -0
69 lines not on this page · raw message
kernel/fork.c +2 -0
@@ -43,6 +43,7 @@#include <linux/memcontrol.h> #include <linux/profile.h> #include <linux/rmap.h> +#include <linux/mmu_notifier.h> #include <linux/acct.h> #include <linux/tsacct_kern.h> #include <linux/cn_proc.h>
@@ -358,6 +359,7 @@ static struct mm_struct * mm_init(struct mm->ioctx_list =NULL ; mm->free_area_cache =TASK_UNMAPPED_BASE ; mm->cached_hole_size = ~0UL ; +mmu_notifier_init_mm (mm);mm_init_cgroup (mm, p);if (likely (!mm_alloc_pgd (mm))) {
mm/filemap_xip.c +1 -0
@@ -195,6 +195,7 @@ __xip_unmap (struct address_space * mapp/* Nuke the page table entry. */ flush_cache_page (vma, address,pte_pfn (*pte)); pteval =ptep_clear_flush (vma, address, pte); +mmu_notifier_unmap (mm, address);page_remove_rmap (page, vma);dec_mm_counter (mm, file_rss);BUG_ON (pte_dirty (pteval));
mm/fremap.c +2 -0
@@ -15,6 +15,7 @@#include <linux/rmap.h> #include <linux/module.h> #include <linux/syscalls.h> +#include <linux/mmu_notifier.h> #include <asm/mmu_context.h> #include <asm/cacheflush.h>
@@ -34,6 +35,7 @@ static void zap_pte(struct mm_struct *mmif (page) {if (pte_dirty (pte))set_page_dirty (page); +mmu_notifier_unmap (mm, addr);page_remove_rmap (page, vma);page_cache_release (page);update_hiwater_rss (mm);
mm/hugetlb.c +5 -0
@@ -14,6 +14,7 @@#include <linux/mempolicy.h> #include <linux/cpuset.h> #include <linux/mutex.h> +#include <linux/mmu_notifier.h> #include <asm/page.h> #include <asm/pgtable.h>
@@ -771,10 +772,12 @@ void __unmap_hugepage_range(struct vm_ar page =pte_page (pte);if (pte_dirty (pte))set_page_dirty (page); +mmu_notifier_unmap (mm, address);list_add (&page->lru, &page_list); }spin_unlock (&mm->page_table_lock);flush_tlb_range (vma, start, end); +mmu_notifier_invalidate_range (mm, start, end);list_for_each_entry_safe (page, tmp, &page_list, lru) {list_del (&page->lru);put_page (page);
@@ -1048,6 +1051,7 @@ void hugetlb_change_protection(struct vmcontinue ;if (!pte_none (*ptep)) { pte =huge_ptep_get_and_clear (mm, address, ptep); +mmu_notifier_unmap (mm, address); pte =pte_mkhuge (pte_modify (pte, newprot));set_huge_pte_at (mm, address, ptep, pte); }
@@ -1056,6 +1060,7 @@ void hugetlb_change_protection(struct vmspin_unlock (&vma->vm_file->f_mapping->i_mmap_lock);flush_tlb_range (vma, start, end); +mmu_notifier_invalidate_range (mm, start, end); }struct file_region {
mm/memory.c +11 -3
@@ -51,6 +51,7 @@#include <linux/init.h> #include <linux/writeback.h> #include <linux/memcontrol.h> +#include <linux/mmu_notifier.h> #include <asm/pgalloc.h> #include <asm/uaccess.h>
@@ -626,9 +627,10 @@ int copy_page_range(struct mm_struct *dsstatic unsigned long zap_pte_range (struct mmu_gather *tlb,struct vm_area_struct *vma,pmd_t *pmd, -unsigned long addr,unsigned long end, +unsigned long start,unsigned long end,long *zap_work,struct zap_details *details) { +unsigned long addr = start;struct mm_struct *mm = tlb->mm;pte_t *pte;spinlock_t *ptl;
@@ -670,6 +672,7 @@ static unsigned long zap_pte_range(struc } ptent =ptep_get_and_clear_full (mm, addr, pte, tlb->fullmm); +mmu_notifier_unmap (mm, addr);tlb_remove_tlb_entry (tlb, pte, addr);if (unlikely (!page))continue ;
@@ -702,6 +705,7 @@ static unsigned long zap_pte_range(strucpte_clear_not_present_full (mm, addr, pte, tlb->fullmm); }while (pte++, addr +=PAGE_SIZE , (addr != end && *zap_work >0 )); +mmu_notifier_invalidate_range (mm, start, end);add_mm_rss (mm, file_rss, anon_rss);arch_leave_lazy_mmu_mode ();pte_unmap_unlock (pte -1 , ptl);
@@ -981,6 +985,7 @@ no_page_table: }return page; } +EXPORT_SYMBOL (follow_page); int get_user_pages(struct task_struct *tsk, struct mm_struct *mm, unsigned long start, int len, int write, int force,
@@ -1676,6 +1681,7 @@ gotten:* thread doing COW. */ ptep_clear_flush (vma, address, page_table); +mmu_notifier_unmap (mm, address);set_pte_at (mm, address, page_table, entry);update_mmu_cache (vma, address, entry);lru_cache_add_active (new_page);
@@ -2200,7 +2206,7 @@ static int __do_fault(struct mm_struct * vmf.flags = flags; vmf.page =NULL ; -BUG_ON (vma->vm_flags &VM_PFNMAP ); +/* BUG_ON(vma->vm_flags & VM_PFNMAP); */ if (likely (vma->vm_ops->fault)) { ret = vma->vm_ops->fault (vma, &vmf);
@@ -2498,8 +2504,10 @@ static inline int handle_pte_fault(struc* This still avoids useless tlb flushes for .text page faults * with threads. */ -if (write_access) +if (write_access) {flush_tlb_page (vma, address); +mmu_notifier_invalidate_range (mm, address, address+PAGE_SIZE ); + } } unlock:pte_unmap_unlock (pte, ptl);
mm/mmap.c +2 -0
@@ -26,6 +26,7 @@#include <linux/mount.h> #include <linux/mempolicy.h> #include <linux/rmap.h> +#include <linux/mmu_notifier.h> #include <asm/uaccess.h> #include <asm/cacheflush.h>
@@ -2037,6 +2038,7 @@ void exit_mmap(struct mm_struct *mm)unsigned long end;/* mm's last user has gone, and its about to be pulled down */ +mmu_notifier_exit_mm (mm);arch_exit_mmap (mm);lru_add_drain ();
mm/mmu_notifier.c new file +85 -0
85 lines not on this page · raw message
mm/mprotect.c +4 -0
@@ -21,6 +21,7 @@#include <linux/syscalls.h> #include <linux/swap.h> #include <linux/swapops.h> +#include <linux/mmu_notifier.h> #include <asm/uaccess.h> #include <asm/pgtable.h> #include <asm/cacheflush.h>
@@ -45,6 +46,8 @@ static void change_pte_range(struct mm_s* into place. */ ptent =ptep_get_and_clear (mm, addr, pte); +mmu_notifier_unmap (mm, addr); + ptent =pte_modify (ptent, newprot);/* * Avoid taking write faults for pages we know to be
@@ -125,6 +128,7 @@ static void change_protection(struct vm_change_pud_range (mm, pgd, addr, next, newprot, dirty_accountable); }while (pgd++, addr = next, addr != end);flush_tlb_range (vma, start, end); +mmu_notifier_invalidate_range (mm, start, end); }int
mm/mremap.c +2 -0
@@ -18,6 +18,7 @@#include <linux/highmem.h> #include <linux/security.h> #include <linux/syscalls.h> +#include <linux/mmu_notifier.h> #include <asm/uaccess.h> #include <asm/cacheflush.h>
@@ -105,6 +106,7 @@ static void move_ptes(struct vm_area_strif (pte_none (*old_pte))continue ; pte =ptep_clear_flush (vma, old_addr, old_pte); +mmu_notifier_unmap (mm, old_addr); pte =move_pte (pte, new_vma->vm_page_prot, old_addr, new_addr);set_pte_at (mm, new_addr, new_pte, pte); }
mm/rmap.c +32 -6
@@ -49,6 +49,7 @@#include <linux/module.h> #include <linux/kallsyms.h> #include <linux/memcontrol.h> +#include <linux/mmu_notifier.h> #include <asm/tlbflush.h>
@@ -287,8 +288,12 @@ static int page_referenced_one(struct paif (vma->vm_flags &VM_LOCKED ) { referenced++; *mapcount =1 ;/* break early from loop */ - }else if (ptep_clear_flush_young (vma, address, pte)) - referenced++; + }else { +if (ptep_clear_flush_young (vma, address, pte)) + referenced++; +if (mmu_notifier_clear_young (mm, address)) + referenced++; + }/* Pretend the page is referenced if the task has the swap token and is in the middle of a page fault. */
@@ -455,6 +460,7 @@ static int page_mkclean_one(struct pageflush_cache_page (vma, address,pte_pfn (*pte)); entry =ptep_clear_flush (vma, address, pte); +mmu_notifier_unmap (mm, address); entry =pte_wrprotect (entry); entry =pte_mkclean (entry);set_pte_at (mm, address, pte, entry);
@@ -711,10 +717,21 @@ static int try_to_unmap_one(struct page* If it's recently referenced (perhaps page_referenced * skipped over this mm) then we should reactivate it. */ -if (!migration && ((vma->vm_flags &VM_LOCKED ) || - (ptep_clear_flush_young (vma, address, pte)))) { - ret =SWAP_FAIL ; - goto out_unmap; +if (!migration) { +int referenced; + +if (vma->vm_flags &VM_LOCKED ) { +fail: + ret =SWAP_FAIL ; + goto out_unmap; + } + referenced =0 ; +if (ptep_clear_flush_young (vma, address, pte)) + referenced =1 ; +if (mmu_notifier_clear_young (mm, address)) + referenced =1 ; +if (referenced) + goto fail; }/* Nuke the page table entry. */
@@ -724,6 +741,7 @@ static int try_to_unmap_one(struct page/* Move the dirty bit to the physical page now the pte is gone. */ if (pte_dirty (pteval))set_page_dirty (page); +mmu_notifier_unmap (mm, address);/* Update high watermark before we lower rss */ update_hiwater_rss (mm);
@@ -839,12 +857,19 @@ static void try_to_unmap_cluster(unsigneupdate_hiwater_rss (mm);for (; address < end; pte++, address +=PAGE_SIZE ) { +int referenced; +if (!pte_present (*pte))continue ; page =vm_normal_page (vma, address, *pte);BUG_ON (!page ||PageAnon (page)); + referenced =0 ;if (ptep_clear_flush_young (vma, address, pte)) + referenced =1 ; +if (mmu_notifier_clear_young (mm, address)) + referenced =1 ; +if (referenced)continue ;/* Nuke the page table entry. */
@@ -858,6 +883,7 @@ static void try_to_unmap_cluster(unsigne/* Move the dirty bit to the physical page now the pte is gone. */ if (pte_dirty (pteval))set_page_dirty (page); +mmu_notifier_unmap (mm, address);page_remove_rmap (page, vma);page_cache_release (page);
mm/Makefile +1 -1
@@ -33,4 +33,4 @@ obj-$(CONFIG_MIGRATION) += migrate.o obj-$(CONFIG_SMP) += allocpercpu.o obj-$(CONFIG_QUICKLIST) += quicklist.o obj-$(CONFIG_CGROUP_MEM_CONT) += memcontrol.o - +obj-$(CONFIG_MMU_NOTIFIER) += mmu_notifier.o
mm/Kconfig +4 -0
@@ -193,3 +193,7 @@ config NR_QUICK config VIRT_TO_BUS def_bool y depends on !ARCH_NO_VIRT_TO_BUS + +config MMU_NOTIFIER + bool "MMU notifiers" + def_bool y
--
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>
PATCH 1/1 my mmu notifier sample driver
drivers/char/mmu_notifier_skel.c new file +255 -0
@@ -0,0 +1,255 @@ +#include <linux/types.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/miscdevice.h> +#include <linux/slab.h> +#include <linux/sched.h> +#include <linux/mm.h> +#include <linux/fs.h> +#include <linux/mmu_notifier.h> +#include <linux/radix-tree.h> +#include <linux/seqlock.h> +#include <asm/tlbflush.h> + +static DEFINE_SPINLOCK (mmn_lock); +static RADIX_TREE(rmap_tree, GFP_ATOMIC); +static seqcount_t rmap_seq =SEQCNT_ZERO ; + +static int __rmap_add (unsigned long mem,unsigned long vaddr) +{ +int err; + + err =radix_tree_insert (&rmap_tree, mem >>PAGE_SHIFT , (void *)vaddr); + +return err; +} + +static void __rmap_del (unsigned long mem) +{ +void *ret; + + ret =radix_tree_delete (&rmap_tree, mem >>PAGE_SHIFT ); +BUG_ON (!ret); +} + +static unsigned long rmap_find (unsigned long mem) +{ +unsigned long vaddr; + +rcu_read_lock (); + vaddr = (unsigned long )radix_tree_lookup (&rmap_tree, mem >>PAGE_SHIFT ); +rcu_read_unlock (); + +return vaddr; +} + +static struct page *follow_page_atomic (struct mm_struct *mm,unsigned long address,int write) +{ +struct vm_area_struct *vma; + + vma =find_vma (mm, address); +if (!vma || (vma->vm_start > address)) +return NULL ; + +if (vma->vm_flags & (VM_IO |VM_PFNMAP )) +return NULL ; + +return follow_page (vma, address,FOLL_GET |(write ?FOLL_WRITE :0 )); +} + +static int mmn_vm_fault (struct vm_area_struct *vma,struct vm_fault *vmf) +{ +struct mm_struct *mm = vma->vm_mm; +unsigned long source_vaddr = (unsigned long )vmf->pgoff <<PAGE_SHIFT ; +unsigned long dest_vaddr = (unsigned long )vmf->virtual_address; +unsigned long pfn; +struct page *page; +pgprot_t prot; +int write = vmf->flags &FAULT_FLAG_WRITE ; +int ret; + +printk ("mmn_vm_fault %s@vaddr=%lx sourcing from %lx\n" , write ?"write" :"read" , dest_vaddr, source_vaddr); + +BUG_ON (mm != current->mm);/* disallow get_user_pages */ + +again: +spin_lock (&mmn_lock); +write_seqcount_begin (&rmap_seq); + page =follow_page_atomic (mm, source_vaddr, write); +if (unlikely (!page)) { +write_seqcount_end (&rmap_seq); +spin_unlock (&mmn_lock); + ret =get_user_pages (current, mm, source_vaddr, +1 , write,0 , &page,NULL ); +if (ret !=1 ) + goto out_err; +put_page (page); + goto again; + } + + ret =__rmap_add (source_vaddr, dest_vaddr); +if (ret) + goto out_lock; + + pfn =page_to_pfn (page); + prot = vma->vm_page_prot; +if (!write) + vma->vm_page_prot =vm_get_page_prot (vma->vm_flags & ~(VM_WRITE |VM_MAYWRITE )); + ret =vm_insert_pfn (vma, dest_vaddr, pfn); + vma->vm_page_prot = prot; +if (ret) { +if (ret == -EBUSY ) +WARN_ON (1 ); + goto out_rmap; + } +write_seqcount_end (&rmap_seq); +spin_unlock (&mmn_lock); +put_page (page); + +return VM_FAULT_NOPAGE ; + +out_rmap: +__rmap_del (source_vaddr); +out_lock: +write_seqcount_end (&rmap_seq); +spin_unlock (&mmn_lock); +put_page (page); +out_err: +switch (ret) { +case -EFAULT : +case -EEXIST : +case -EBUSY : +return VM_FAULT_SIGBUS ; +case -ENOMEM : +return VM_FAULT_OOM ; +default : +BUG (); + } +} + +struct vm_operations_struct mmn_vm_ops = { + .fault = mmn_vm_fault, +}; + +static int mmu_notifier_busy; +static struct mmu_notifier mmu_notifier; + +static int mmn_clear_young (struct mmu_notifier *mn,unsigned long address) +{ +unsigned long vaddr; +unsigned seq; +struct mm_struct *mm = mn->mm; +pgd_t *pgd; +pud_t *pud; +pmd_t *pmd; +pte_t *ptep, pte; + +do { + seq =read_seqcount_begin (&rmap_seq); + vaddr =rmap_find (address); + }while (read_seqcount_retry (&rmap_seq, seq)); + +if (vaddr ==0 ) +return 0 ; + +printk ("mmn_clear_young@vaddr=%lx sourced from %lx\n" , vaddr, address); + +spin_lock (&mmn_lock); + pgd =pgd_offset (mm, vaddr); + pud =pud_offset (pgd, vaddr); +if (pud) { + pmd =pmd_offset (pud, vaddr); +if (pmd) { + ptep =pte_offset_map (pmd, vaddr); +if (ptep) { + pte = *ptep; +if (!pte_present (pte)) { +/* x86 specific, don't have a vma */ +ptep_get_and_clear (mm, vaddr, ptep); +__flush_tlb_one (vaddr); + } +pte_unmap (ptep); + } + } + } +__rmap_del (address); +spin_unlock (&mmn_lock); + +return 1 ; +} + +static void mmn_unmap (struct mmu_notifier *mn,unsigned long address) +{ +mmn_clear_young (mn, address); +} + +static void mmn_release (struct mmu_notifier *mn) +{ + mmu_notifier_busy =0 ; +} + +static struct mmu_notifier_operations mmn_ops = { + .clear_young = mmn_clear_young, + .unmap = mmn_unmap, + .release = mmn_release, +}; + +static int mmn_mmap (struct file *file,struct vm_area_struct *vma) +{ +int busy; + +if ((vma->vm_flags & (VM_SHARED |VM_MAYWRITE )) ==VM_MAYWRITE ) +return -EINVAL ; + +spin_lock (&mmn_lock); + busy = mmu_notifier_busy; +if (!busy) + mmu_notifier_busy =1 ; +spin_unlock (&mmn_lock); +if (busy) +return -EBUSY ; + + vma->vm_flags |=VM_PFNMAP ; + vma->vm_ops = &mmn_vm_ops; + +mmu_notifier_init (&mmu_notifier, &mmn_ops, current->mm); +mmu_notifier_register (&mmu_notifier); + +return 0 ; +} + +static const struct file_operations mmn_fops = +{ + .owner =THIS_MODULE , + .llseek = no_llseek, + .mmap = mmn_mmap, +}; + +static struct miscdevice mmn_miscdev = +{ + .minor =MISC_DYNAMIC_MINOR , + .name ="mmn" , + .fops = &mmn_fops +}; + +static int __init mmn_init (void ) +{ +if (misc_register (&mmn_miscdev)) { +printk (KERN_ERR "mmn: unable to register device\n" ); +return -EIO ; + } +return 0 ; +} + +static void __exit mmn_exit(void) +{ +misc_deregister (&mmn_miscdev); +} + +MODULE_DESCRIPTION ("mmu_notifier skeleton driver" ); +MODULE_LICENSE ("GPL" ); + +module_init (mmn_init); +module_exit (mmn_exit); +
drivers/char/Kconfig +4 -0
@@ -4,6 +4,10 @@ menu "Character devices" +config MMU_NOTIFIER_SKEL + tristate "MMU Notifier skeleton driver" + default n + config VT bool "Virtual terminal" if EMBEDDED depends on !S390
drivers/char/Makefile +1 -0
@@ -97,6 +97,7 @@ obj-$(CONFIG_CS5535_GPIO) += cs5535_gpio obj-$(CONFIG_GPIO_VR41XX) += vr41xx_giu.o obj-$(CONFIG_GPIO_TB0219) += tb0219.o obj-$(CONFIG_TELCLOCK) += tlclk.o +obj-$(CONFIG_MMU_NOTIFIER_SKEL) += mmu_notifier_skel.o obj-$(CONFIG_MWAVE) += mwave/ obj-$(CONFIG_AGP) += agp/
-------------------------------------------------------------------------
This SF.net email is sponsored by: Microsoft Defy all challenges. Microsoft(R) Visual Studio 2008.
http://clk.atdmt.com/MRT/go/vse0120000070mrt/direct/01/
Quoted 8 lines · Nick Piggin
So I implemented mmu notifiers slightly differently. Andrea's mmu notifiers are rather similar. However I have tried to make a point of minimising the impact the the core
mm/. I don't see why we need to invalidate or flush anything when changing the pte to be _more_ permissive, and I don't understand the need for invalidate_begin/invalidate_end pairs at all. What I have done is basically create it so that the notifiers get called basically in the same place as the normal TLB flushing is done, and nowhere else.
Because XPMEM needs to be able to sleep during its callout. For that, we need to move this outside of the page table lock and suddenly we need the begin/end pair again. There was considerable discussion about this exact point numerous times. We tried to develop the most inclusive design possible. Our design would even be extendable to IB, assuming they made some very disruptive changes to their MPI and communication libraries. IB would suffer the same problems XPMEM does in that the TLB entries need to be removed on a remote host which is operating completely independently.
Thanks,
Robin
-------------------------------------------------------------------------
This SF.net email is sponsored by: Microsoft Defy all challenges. Microsoft(R) Visual Studio 2008.
http://clk.atdmt.com/MRT/go/vse0120000070mrt/direct/01/
Quoted 2 lines · Nick Piggin
are rather similar. However I have tried to make a point of minimising the impact the the core
mm/. I don't see why we need to invalidate or flush
I also tried hard to minimise the impact of the core mm/, I also argued with Christoph that cluttering mm/ wasn't a good idea for things like age_page that could be a 1 liner change instead of a multiple-liner change, without any loss of flexibility or readability.
Quoted 1 line
anything when changing the pte to be _more_ permissive, and I don't
Note that in my patch the invalidate_pages in mprotect can be trivially switched to a mprotect_pages with proper params. This will prevent page faults completely in the secondary MMU (there will only be tlb misses after the tlb flush just like for the core linux pte), and it'll allow all the secondary MMU pte blocks (512/1024 at time with my PT lock design) to be updated to have proper permissions matching the core linux pte.
Quoted 1 line
understand the need for invalidate_begin/invalidate_end pairs at all.
The need of the pairs is crystal clear to me: range_begin is needed for GRU _but_only_if_ range_end is called after releasing the reference that the VM holds on the page. _begin will flush the GRU tlb and at the same time it will take a mutex that will block further GRU tlb-miss-interrupts (no idea how they manange those nightmare locking, I didn't even try to add more locking to KVM and I get away with the fact KVM takes the pin on the page itself).
My patch calls invalidate_page/pages before the reference is released on the page, so GRU will work fine despite lack of range_begin. Furthermore with my patch GRU will be auto-serialized by the PT lock w/o the need of any additional locking.
Quoted 3 lines
What I have done is basically create it so that the notifiers get called basically in the same place as the normal TLB flushing is done, and nowhere else.
That was one of my objectives too.
Quoted 5 lines
I also wanted to avoid calling notifier code from inside eg. hardware TLB or pte manipulation primitives. These things are already pretty well spaghetti, so I'd like to just place them right where needed first... I think eventually it will need a bit of a rethink to make it more consistent and more general. But I prefer to do put them in the caller for the moment.
Your patch should also work for KVM but it's suboptimal, my patch can be orders of magnitude more efficient for GRU thanks to the invalidate_pages optimization. Christoph complained about having to call one method per pte.
And adding invalidate_range is useless unless you fully support xpmem. You're calling invalidate_range in places that can't sleep...
No idea why xpmem needs range_begin, I perfectly understand why GRU needs _begin with Chrisotph's patch (gru lacks the page pin) but I dunno why xpmem needs range_begin (xpmem has the page pin so I also think it could avoid using range_begin). Still to support GRU you need both to call invalidate_range in places that can sleep and you need the external rmap notifier. The moment you add xpmem into the equation your and my clean patches become Christoph's one...
Quoted 10 lines · Andrea Arcangeli
Quoted 1 line
understand the need for invalidate_begin/invalidate_end pairs at all.
The need of the pairs is crystal clear to me:
range_beginis needed for GRU _but_only_if_range_endis called after releasing the reference that the VM holds on the page._beginwill flush the GRU tlb and at the same time it will take a mutex that will block further GRU tlb-miss-interrupts (no idea how they manange those nightmare locking, I didn't even try to add more locking to KVM and I get away with the fact KVM takes the pin on the page itself).
As it turns out, no actual mutex is required. _begin_ simply increments a count of active range invalidates, _end_ decrements the count. New TLB dropins are deferred while range callouts are active.
This would appear to be racy but the GRU has special hardware that simplifies locking. When the GRU sees a TLB invalidate, all outstanding misses & potentially inflight TLB dropins are marked by the GRU with a "kill" bit. When the dropin finally occurs, the dropin is ignored & the instruction is simply restarted. The instruction will fault again & the TLB dropin will be repeated. This is optimized for the case where invalidates are rare - true for users of the GRU.
In general, though, I agree. Most users of mmu_notifiers would likely required a mutex or something equivalent.
--- jack
-------------------------------------------------------------------------
This SF.net email is sponsored by: Microsoft Defy all challenges. Microsoft(R) Visual Studio 2008.
http://clk.atdmt.com/MRT/go/vse0120000070mrt/direct/01/
Quoted 15 lines · Jack Steiner
Quoted 10 lines · Andrea Arcangeli
Quoted 1 line
understand the need for invalidate_begin/invalidate_end pairs at all.
The need of the pairs is crystal clear to me:
range_beginis needed for GRU _but_only_if_range_endis called after releasing the reference that the VM holds on the page._beginwill flush the GRU tlb and at the same time it will take a mutex that will block further GRU tlb-miss-interrupts (no idea how they manange those nightmare locking, I didn't even try to add more locking to KVM and I get away with the fact KVM takes the pin on the page itself).As it turns out, no actual mutex is required. _begin_ simply increments a count of active range invalidates, _end_ decrements the count. New TLB dropins are deferred while range callouts are active.
Quoted 7 lines
This would appear to be racy but the GRU has special hardware that simplifies locking. When the GRU sees a TLB invalidate, all outstanding misses & potentially inflight TLB dropins are marked by the GRU with a "kill" bit. When the dropin finally occurs, the dropin is ignored & the instruction is simply restarted. The instruction will fault again & the TLB dropin will be repeated. This is optimized for the case where invalidates are rare - true for users of the GRU.
OK (thanks to Robin as well). Now I understand why you are using it, but I don't understand why you don't defer new TLBs after the point where the linux pte changes. If you can do that, then you look and act much more like a TLB from the point of view of the Linux vm.
Quoted 24 lines · Nick Piggin
Quoted 15 lines · Jack Steiner
Quoted 10 lines · Andrea Arcangeli
Quoted 1 line
understand the need for invalidate_begin/invalidate_end pairs at all.
The need of the pairs is crystal clear to me:
range_beginis needed for GRU _but_only_if_range_endis called after releasing the reference that the VM holds on the page._beginwill flush the GRU tlb and at the same time it will take a mutex that will block further GRU tlb-miss-interrupts (no idea how they manange those nightmare locking, I didn't even try to add more locking to KVM and I get away with the fact KVM takes the pin on the page itself).As it turns out, no actual mutex is required. _begin_ simply increments a count of active range invalidates, _end_ decrements the count. New TLB dropins are deferred while range callouts are active.
Quoted 7 lines
This would appear to be racy but the GRU has special hardware that simplifies locking. When the GRU sees a TLB invalidate, all outstanding misses & potentially inflight TLB dropins are marked by the GRU with a "kill" bit. When the dropin finally occurs, the dropin is ignored & the instruction is simply restarted. The instruction will fault again & the TLB dropin will be repeated. This is optimized for the case where invalidates are rare - true for users of the GRU.
Quoted 4 lines
OK (thanks to Robin as well). Now I understand why you are using it, but I don't understand why you don't defer new TLBs after the point where the linux pte changes. If you can do that, then you look and act much more like a TLB from the point of view of the Linux vm.
Christoph was forced to put the invalidate_range callback _after_ dropping the PT lock because xpmem has to wait I/O there. But invalidate_range is called after freeing the VM reference on the pages so then GRU needed a _range_begin too because GRU has to flush the tlb before the VM reference on the page is released (xpmem and KVM pin the pages mapped by the secondary mmu, GRU doesn't). So then invalidate_range was renamed to invalidate_range_end.
Quoted 6 lines · Andrea Arcangeli
Quoted 4 lines · Nick Piggin
OK (thanks to Robin as well). Now I understand why you are using it, but I don't understand why you don't defer new TLBs after the point where the linux pte changes. If you can do that, then you look and act much more like a TLB from the point of view of the Linux vm.
Quoted 7 lines
Christoph was forced to put the
invalidate_rangecallback _after_ dropping the PT lock because xpmem has to wait I/O there. Butinvalidate_rangeis called after freeing the VM reference on the pages so then GRU needed a_range_begintoo because GRU has to flush the tlb before the VM reference on the page is released (xpmem and KVM pin the pages mapped by the secondary mmu, GRU doesn't). So theninvalidate_rangewas renamed toinvalidate_range_end.
Currently, xpmem blocks faults for the range specified at the _begin callout, then shoots down remote TLBs and does the put_page for all the pages in the specified range. The _end callout merely removes the block. We do not do any wait for I/O. By the time we return from the _begin callout, all activity by the remotes is stopped, pages are dereferenced, and future faults are blocked until released by the _end callout.
Thanks,
Robin
Quoted 2 lines · Jack Steiner
In general, though, I agree. Most users of
mmu_notifierswould likely required a mutex or something equivalent.
The skeletons shows how to do most of it using a spinlock and a counter.
Quoted 29 lines · Andrea Arcangeli
Quoted 2 lines · Nick Piggin
are rather similar. However I have tried to make a point of minimising the impact the the core
mm/. I don't see why we need to invalidate or flushI also tried hard to minimise the impact of the core
mm/, I also argued with Christoph that clutteringmm/wasn't a good idea for things likeage_pagethat could be a 1 liner change instead of a multiple-liner change, without any loss of flexibility or readability.Quoted 1 line
anything when changing the pte to be _more_ permissive, and I don't
Note that in my patch the
invalidate_pagesin mprotect can be trivially switched to amprotect_pageswith proper params. This will prevent page faults completely in the secondary MMU (there will only be tlb misses after the tlb flush just like for the core linux pte), and it'll allow all the secondary MMU pte blocks (512/1024 at time with my PT lock design) to be updated to have proper permissions matching the core linux pte.Quoted 1 line
understand the need for invalidate_begin/invalidate_end pairs at all.
The need of the pairs is crystal clear to me:
range_beginis needed for GRU _but_only_if_range_endis called after releasing the reference that the VM holds on the page._beginwill flush the GRU tlb and at the same time it will take a mutex that will block further GRU tlb-miss-interrupts (no idea how they manange those nightmare locking, I didn't even try to add more locking to KVM and I get away with the fact KVM takes the pin on the page itself).
Quoted 4 lines
My patch calls invalidate_page/pages before the reference is released on the page, so GRU will work fine despite lack of
range_begin. Furthermore with my patch GRU will be auto-serialized by the PT lock w/o the need of any additional locking.
That's why I don't understand the need for the pairs: it should be done like this.
Quoted 12 lines
Quoted 3 lines
What I have done is basically create it so that the notifiers get called basically in the same place as the normal TLB flushing is done, and nowhere else.
That was one of my objectives too.
Quoted 5 lines
I also wanted to avoid calling notifier code from inside eg. hardware TLB or pte manipulation primitives. These things are already pretty well spaghetti, so I'd like to just place them right where needed first... I think eventually it will need a bit of a rethink to make it more consistent and more general. But I prefer to do put them in the caller for the moment.
Quoted 4 lines
Your patch should also work for KVM but it's suboptimal, my patch can be orders of magnitude more efficient for GRU thanks to the
invalidate_pagesoptimization. Christoph complained about having to call one method per pte.
OK, I didn't see the invalidate_pages call...
Quoted 2 lines
And adding
invalidate_rangeis useless unless you fully support xpmem. You're callinginvalidate_rangein places that can't sleep...
I thought that could be used by a non-sleeping user (not intending to try supporting sleeping users). If it is useless then it should go away (BTW. I didn't see your recent patch, some of my confusion I think stems from Christoph's novel way of merging and splitting patches).
Quoted 7 lines
No idea why xpmem needs
range_begin, I perfectly understand why GRU needs_beginwith Chrisotph's patch (gru lacks the page pin) but I dunno why xpmem needsrange_begin(xpmem has the page pin so I also think it could avoid usingrange_begin). Still to support GRU you need both to callinvalidate_rangein places that can sleep and you need the external rmap notifier. The moment you add xpmem into the equation your and my clean patches become Christoph's one...
Sorry, I kind of didn't have time to follow the conversation so well before; are there patches posted for gru and/or xpmem?
Quoted 2 lines · Nick Piggin
That's why I don't understand the need for the pairs: it should be done like this.
Yes, except it can't be done like this for xpmem.
Quoted 1 line
OK, I didn't see the
invalidate_pagescall...
See the last patch I posted to Andrew, you've probably looked at the old patches, the old patches didn't work for GRU and didn't work for xpmem and they weren't optimized to cluster the invalidates for each 4k-large-pte.
Quoted 5 lines
I thought that could be used by a non-sleeping user (not intending to try supporting sleeping users). If it is useless then it should go away (BTW. I didn't see your recent patch, some of my confusion I think stems from Christoph's novel way of merging and splitting patches).
I kept improving my patch in case the VM maintainers would consider xpmem requirements not workable from a linux-VM point of view, and they preferred to have something obviously safe, strightforward and non intrusive, despite it doesn't support the only sleeping user out there I know of (xpmem). My patch supports KVM and GRU (and any other not sleeping user).
Quoted 10 lines
Quoted 7 lines
No idea why xpmem needs
range_begin, I perfectly understand why GRU needs_beginwith Chrisotph's patch (gru lacks the page pin) but I dunno why xpmem needsrange_begin(xpmem has the page pin so I also think it could avoid usingrange_begin). Still to support GRU you need both to callinvalidate_rangein places that can sleep and you need the external rmap notifier. The moment you add xpmem into the equation your and my clean patches become Christoph's one...Sorry, I kind of didn't have time to follow the conversation so well before; are there patches posted for gru and/or xpmem?
There's some xpmem code posted but the posted one isn't using the mmu notifiers yet. GRU code may be available from Jack. I only know for sure their requirements in terms of mmu notifiers.
-------------------------------------------------------------------------
This SF.net email is sponsored by: Microsoft Defy all challenges. Microsoft(R) Visual Studio 2008.
http://clk.atdmt.com/MRT/go/vse0120000070mrt/direct/01/
Quoted 5 lines · Nick Piggin
I thought that could be used by a non-sleeping user (not intending to try supporting sleeping users). If it is useless then it should go away (BTW. I didn't see your recent patch, some of my confusion I think stems from Christoph's novel way of merging and splitting patches).
What is so novel about introducing functionality step by step?
-------------------------------------------------------------------------
This SF.net email is sponsored by: Microsoft Defy all challenges. Microsoft(R) Visual Studio 2008.
http://clk.atdmt.com/MRT/go/vse0120000070mrt/direct/01/
Quoted 10 lines · Andrea Arcangeli
Quoted 1 line · Nick Piggin
anything when changing the pte to be _more_ permissive, and I don't
Note that in my patch the
invalidate_pagesin mprotect can be trivially switched to amprotect_pageswith proper params. This will prevent page faults completely in the secondary MMU (there will only be tlb misses after the tlb flush just like for the core linux pte), and it'll allow all the secondary MMU pte blocks (512/1024 at time with my PT lock design) to be updated to have proper permissions matching the core linux pte.
Sorry, I realise I still didn't get this through my head yet (and also have not seen your patch recently). So I don't know exactly what you are doing...
But why does _anybody_ (why does Christoph's patches) need to invalidate when they are going to be more permissive? This should be done lazily by the driver, I would have thought.
-------------------------------------------------------------------------
This SF.net email is sponsored by: Microsoft Defy all challenges. Microsoft(R) Visual Studio 2008.
http://clk.atdmt.com/MRT/go/vse0120000070mrt/direct/01/
Quoted 12 lines · Nick Piggin
Quoted 10 lines · Andrea Arcangeli
Quoted 1 line · Nick Piggin
anything when changing the pte to be _more_ permissive, and I don't
Note that in my patch the
invalidate_pagesin mprotect can be trivially switched to amprotect_pageswith proper params. This will prevent page faults completely in the secondary MMU (there will only be tlb misses after the tlb flush just like for the core linux pte), and it'll allow all the secondary MMU pte blocks (512/1024 at time with my PT lock design) to be updated to have proper permissions matching the core linux pte.
Quoted 7 lines
Sorry, I realise I still didn't get this through my head yet (and also have not seen your patch recently). So I don't know exactly what you are doing...
But why does _anybody_ (why does Christoph's patches) need to invalidate when they are going to be more permissive? This should be done lazily by the driver, I would have thought.
Agree. Although for most real applications, the performance difference is probably negligible.
--- jack
-------------------------------------------------------------------------
This SF.net email is sponsored by: Microsoft Defy all challenges. Microsoft(R) Visual Studio 2008.
http://clk.atdmt.com/MRT/go/vse0120000070mrt/direct/01/
Quoted 17 lines · Jack Steiner
Quoted 16 lines · Nick Piggin
Quoted 10 lines · Andrea Arcangeli
Quoted 1 line · Nick Piggin
anything when changing the pte to be _more_ permissive, and I don't
Note that in my patch the
invalidate_pagesin mprotect can be trivially switched to amprotect_pageswith proper params. This will prevent page faults completely in the secondary MMU (there will only be tlb misses after the tlb flush just like for the core linux pte), and it'll allow all the secondary MMU pte blocks (512/1024 at time with my PT lock design) to be updated to have proper permissions matching the core linux pte.Sorry, I realise I still didn't get this through my head yet (and also have not seen your patch recently). So I don't know exactly what you are doing...
Quoted 7 lines
Quoted 3 lines
But why does _anybody_ (why does Christoph's patches) need to invalidate when they are going to be more permissive? This should be done lazily by the driver, I would have thought.
Agree. Although for most real applications, the performance difference is probably negligible.
But importantly, doing it that way means you share test coverage with the CPU TLB flushing code, and you don't introduce a new concept to the VM.
So, it _has_ to be lazy flushing, IMO (as there doesn't seem to be a good reason otherwise). mprotect shouldn't really be a special case, because it still has to flush the CPU tlbs as well when restricting access.
Quoted 3 lines
Also, I'll try to post the driver within the next few days. It is still in development but it compiles and can successfully run most workloads on a system simulator.
Here is the source of the GRU driver. It is still in development but it compiles & runs (on IA64) in a system simulator.
The GRU is a hardware resource located in the chipset. It is mmaped into the user address space. The GRU contains functions such as load/store, scatter/gather, bcopy, etc. It is directly accessed by user instructions using user virtual addresses. GRU instructions (ex., bcopy) use user virtual addresses for operands. The GRU contains a large TLB that is functionally very similar to processor TLBs.
This version uses the V7 mmu notifier patch from Christoph. The changes to switch to Andrea's patch are trivial. (Note, however, that XPMEM still requires Christoph's patch).
The interesting parts relating to mmu_notifiers are in the following functions:
gru_try_dropin() - does TLB dropins gru_flush_tlb_range() - TLB flushing gru_mmuops_...() - all functions starting with "gru_mmuops_" gru_register_mmu_notifier() - registers notifiers
I have no doubt that there are bugs in the code. If you find them, please
let me know where they are .... :-)
Other comments appreciated, too.
Portions are rough but this
arch/ia64/sn/kernel/sn2/sn2_smp.c | 5 drivers/Makefile | 1 drivers/gru/Makefile | 4 drivers/gru/gru.h | 348 +++++++++++++ drivers/gru/gru_instructions.h | 502 +++++++++++++++++++ drivers/gru/grufault.c | 557 ++++++++++++++++++++++ drivers/gru/grufile.c | 453 +++++++++++++++++ drivers/gru/gruhandles.h | 655 +++++++++++++++++++++++++ drivers/gru/grukservices.c | 129 +++++ drivers/gru/grulib.h | 84 +++ drivers/gru/grumain.c | 958 ++++++++++++++++++++++++++++++++++++++ drivers/gru/grummuops.c | 376 ++++++++++++++ drivers/gru/gruprocfs.c | 309 ++++++++++++ drivers/gru/grutables.h | 517 ++++++++++++++++++++ drivers/sn/Kconfig | 7 15 files changed, 4905 insertions(+)
drivers/Makefile +1 -0
@@ -5,6 +5,7 @@ # Rewritten to use lists instead of if-statements. # +obj-$(CONFIG_GRU) += gru/ obj-$(CONFIG_PCI) += pci/ obj-$(CONFIG_PARISC) += parisc/ obj-$(CONFIG_RAPIDIO) += rapidio/
drivers/gru/Makefile new file +4 -0
@@ -0,0 +1,4 @@ +# +EXTRA_CFLAGS += -Werror -Wall +obj-$(CONFIG_GRU) := gru.o +gru-y := grufile.o grumain.o grufault.o grummuops.o gruprocfs.o grukservices.o
drivers/sn/Kconfig +7 -0
@@ -18,4 +18,11 @@ config SGI_IOC3 I/O controller or a PCI IOC3 serial card say Y. Otherwise say N. +config GRU + tristate "SGI GRU driver" + default y + ---help--- + This option enables basic support for the SGI UV GRU driver. + + endmenu
arch/ia64/sn/kernel/sn2/sn2_smp.c +5 -0
@@ -113,6 +113,11 @@ void sn_migrate(struct task_struct *taskpda_t *last_pda =pdacpu (task_thread_info (task)->last_cpu);volatile unsigned long *adr = last_pda->pio_write_status_addr;unsigned long val = last_pda->pio_write_status_val; +extern void gru_migrate_task (int ,int ); + +if (current->mm &&hlist_empty (¤t->mm->mmu_notifier.head) && +task_thread_info (current)->last_cpu !=task_cpu (current)) +gru_migrate_task (task_thread_info (current)->last_cpu,task_cpu (current));/* Drain PIO writes from old CPU's Shub */ while (unlikely((*adr & SH_PIO_WRITE_STATUS_PENDING_WRITE_COUNT_MASK)
drivers/gru/gru.h new file +348 -0
348 lines not on this page · raw message
drivers/gru/gru_instructions.h new file +502 -0
502 lines not on this page · raw message
drivers/gru/grufault.c new file +557 -0
557 lines not on this page · raw message
drivers/gru/grufile.c new file +453 -0
453 lines not on this page · raw message
drivers/gru/gruhandles.h new file +655 -0
655 lines not on this page · raw message
drivers/gru/grukservices.c new file +129 -0
129 lines not on this page · raw message
drivers/gru/grulib.h new file +84 -0
84 lines not on this page · raw message
drivers/gru/grumain.c new file +958 -0
239 lines not on this page · raw message
drivers/gru/grummuops.c new file +376 -0
376 lines not on this page · raw message
drivers/gru/gruprocfs.c new file +309 -0
309 lines not on this page · raw message
drivers/gru/grutables.h new file +517 -0
517 lines not on this page · raw message
The rest of this diff is not shown. View the raw message.
Part of the diff is not shown here. View the raw message.
Quoted 3 lines · Nick Piggin
Sorry, I realise I still didn't get this through my head yet (and also have not seen your patch recently). So I don't know exactly what you are doing...
The last version was posted here:
http://marc.info/?l=kvm-devel&m=120321732521533&w=2
Quoted 3 lines
But why does _anybody_ (why does Christoph's patches) need to invalidate when they are going to be more permissive? This should be done lazily by the driver, I would have thought.
This can be done lazily by the driver yes. The place where I've an invalidate_pages in mprotect however can also become less permissive. It's simpler to invalidate always and it's not guaranteed the secondary mmu page fault is capable of refreshing the spte across a writeprotect fault. In the future this can be changed to mprotect_pages though, so no page fault will happen in the secondary mmu.
PATCH 1/1 mmu notifiers #v6
Given Nick's comments I ported my version of the mmu notifiers to latest mainline. There are no known bugs AFIK and it's obviously safe (nothing is allowed to schedule inside rcu_read_lock taken by mmu_notifier() with my patch).
XPMEM simply can't use RCU for the registration locking if it wants to schedule inside the mmu notifier calls. So I guess it's better to add the XPMEM invalidate_range_end/begin/external-rmap as a whole different subsystem that will have to use a mutex (not RCU) to serialize, and at the same time that CONFIG_XPMEM will also have to switch the i_mmap_lock to a mutex. I doubt xpmem fits inside a CONFIG_MMU_NOTIFIER anymore, or we'll all run a bit slower because of it. It's really a call of how much we want to optimize the MMU notifier, by keeping things like RCU for the registration.
include/asm-generic/pgtable.h +2 -0
@@ -46,6 +46,7 @@ __young =ptep_test_and_clear_young (__vma, __address, __ptep); \if (__young) \flush_tlb_page (__vma, __address); \ + __young |=mmu_notifier_age_page ((__vma)->vm_mm, __address); \ __young; \ })#endif
@@ -86,6 +87,7 @@ do { \pte_t __pte; \ __pte =ptep_get_and_clear ((__vma)->vm_mm, __address, __ptep); \flush_tlb_page (__vma, __address); \ +mmu_notifier (invalidate_page, (__vma)->vm_mm, __address); \ __pte; \ })#endif
include/asm-s390/pgtable.h +1 -0
@@ -735,6 +735,7 @@ static inline pte_t ptep_clear_flush(str {pte_t pte = *ptep;ptep_invalidate (vma->vm_mm, address, ptep); +mmu_notifier (invalidate_page, vma->vm_mm, address);return pte; }
include/linux/mm_types.h +3 -0
@@ -10,6 +10,7 @@#include <linux/rbtree.h> #include <linux/rwsem.h> #include <linux/completion.h> +#include <linux/mmu_notifier.h> #include <asm/page.h> #include <asm/mmu.h>
@@ -228,6 +229,8 @@ struct mm_struct {#ifdef CONFIG_CGROUP_MEM_CONT struct mem_cgroup *mem_cgroup;#endif + + struct mmu_notifier_head mmu_notifier; /* MMU notifier list */ };#endif /* _LINUX_MM_TYPES_H */
include/linux/mmu_notifier.h new file +132 -0
132 lines not on this page · raw message
kernel/fork.c +1 -0
@@ -362,6 +362,7 @@ static struct mm_struct * mm_init(structif (likely (!mm_alloc_pgd (mm))) { mm->def_flags =0 ; +mmu_notifier_head_init (&mm->mmu_notifier);return mm; }
mm/Kconfig +4 -0
@@ -193,3 +193,7 @@ config VIRT_TO_BUS config VIRT_TO_BUS def_bool y depends on !ARCH_NO_VIRT_TO_BUS + +config MMU_NOTIFIER + def_bool y + bool "MMU notifier, for paging KVM/RDMA"
mm/Makefile +1 -1
@@ -33,4 +33,4 @@ obj-$(CONFIG_SMP) += allocpercpu.o obj-$(CONFIG_SMP) += allocpercpu.o obj-$(CONFIG_QUICKLIST) += quicklist.o obj-$(CONFIG_CGROUP_MEM_CONT) += memcontrol.o - +obj-$(CONFIG_MMU_NOTIFIER) += mmu_notifier.o
mm/hugetlb.c +1 -0
@@ -768,6 +768,7 @@ void __unmap_hugepage_range(struct vm_arif (pte_none (pte))continue ; +mmu_notifier (invalidate_page, mm, address); page =pte_page (pte);if (pte_dirty (pte))set_page_dirty (page);
mm/memory.c +7 -0
@@ -504,6 +504,7 @@ static int copy_pte_range(struct mm_struspinlock_t *src_ptl, *dst_ptl;int progress =0 ;int rss[2 ]; +unsigned long start; again: rss[1 ] = rss[0 ] =0 ;
@@ -515,6 +516,7 @@ again:spin_lock_nested (src_ptl,SINGLE_DEPTH_NESTING );arch_enter_lazy_mmu_mode (); + start = addr; do { /* * We are holding two locks at this point - either of them
@@ -535,6 +537,8 @@ again: }while (dst_pte++, src_pte++, addr +=PAGE_SIZE , addr != end);arch_leave_lazy_mmu_mode (); +if (is_cow_mapping (vma->vm_flags)) +mmu_notifier (invalidate_pages, vma->vm_mm, start, addr);spin_unlock (src_ptl);pte_unmap_nested (src_pte -1 );add_mm_rss (dst_mm, rss[0 ], rss[1 ]);
@@ -670,6 +674,7 @@ static unsigned long zap_pte_range(struc } ptent =ptep_get_and_clear_full (mm, addr, pte, tlb->fullmm); +mmu_notifier (invalidate_page, mm, addr);tlb_remove_tlb_entry (tlb, pte, addr);if (unlikely (!page))continue ;
@@ -1269,6 +1274,7 @@ static int remap_pte_range(struct mm_str {pte_t *pte;spinlock_t *ptl; +unsigned long start = addr; pte =pte_alloc_map_lock (mm, pmd, addr, &ptl);if (!pte)
@@ -1280,6 +1286,7 @@ static int remap_pte_range(struct mm_str pfn++; }while (pte++, addr +=PAGE_SIZE , addr != end);arch_leave_lazy_mmu_mode (); +mmu_notifier (invalidate_pages, mm, start, addr);pte_unmap_unlock (pte -1 , ptl);return 0 ; }
mm/mmap.c +1 -0
@@ -2048,6 +2048,7 @@ void exit_mmap(struct mm_struct *mm)vm_unacct_memory (nr_accounted);free_pgtables (&tlb, vma,FIRST_USER_ADDRESS ,0 );tlb_finish_mmu (tlb,0 , end); +mmu_notifier_release (mm);/* * Walk the list again, actually closing and freeing it,
mm/mmu_notifier.c new file +73 -0
73 lines not on this page · raw message
mm/mprotect.c +2 -0
@@ -32,6 +32,7 @@ static void change_pte_range(struct mm_s {pte_t *pte, oldpte;spinlock_t *ptl; +unsigned long start = addr; pte =pte_offset_map_lock (mm, pmd, addr, &ptl);arch_enter_lazy_mmu_mode ();
@@ -71,6 +72,7 @@ static void change_pte_range(struct mm_s }while (pte++, addr +=PAGE_SIZE , addr != end);arch_leave_lazy_mmu_mode (); +mmu_notifier (invalidate_pages, mm, start, addr);pte_unmap_unlock (pte -1 , ptl); }
-------------------------------------------------------------------------
This SF.net email is sponsored by: Microsoft Defy all challenges. Microsoft(R) Visual Studio 2008.
http://clk.atdmt.com/MRT/go/vse0120000070mrt/direct/01/
PATCH 1/1 KVM swapping (+ seqlock fix) with mmu notifiers #v6
This is the same as before but against the mmu notifier #v6 patch, running on top of 2.6.25-rc latest, and in this last update I fixed the last race condition with a seqlock. I described the exact fix in a earlier email, in short the seqlock-write is in the invalidate_page/pages, and the reader will re-issue gfn_to_page if it finds a seqlock read failure (see the change to paging_tmpl.h). With this on top of mmu notifier #v6 there are no more practical or theoretical known problems, nor in the kvm swapping, nor in the mmu notifier patch (which also supports all sleeping users not just KVM, without requiring a page pin).
arch/x86/kvm/Kconfig +1 -0
@@ -21,6 +21,7 @@ config KVM tristate "Kernel-based Virtual Machine (KVM) support" depends on HAVE_KVM && EXPERIMENTAL select PREEMPT_NOTIFIERS + select MMU_NOTIFIER select ANON_INODES ---help--- Support hosting fully virtualized guest machines using hardware
arch/x86/kvm/mmu.c +104 -0
110 lines not on this page · raw message
arch/x86/kvm/paging_tmpl.h +11 -0
@@ -370,6 +370,7 @@ static int FNAME(page_fault)(struct kvm_vcpu *vcpu, gva_t addr,int write_pt =0 ;int r;struct page *page; +unsigned mmu_seq;pgprintk ("%s: addr %lx err %x\n" , __FUNCTION__, addr, error_code);kvm_mmu_audit (vcpu,"pre page fault" );
@@ -397,6 +398,7 @@ static int FNAME(page_fault)(struct kvm_vcpu *vcpu, gva_t addr, }down_read (¤t->mm->mmap_sem); + mmu_seq =read_seqbegin (&vcpu->kvm->arch.mmu_notifier_invalidate_lock); page =gfn_to_page (vcpu->kvm, walker.gfn);up_read (¤t->mm->mmap_sem);
@@ -421,6 +423,15 @@ static int FNAME(page_fault)(struct kvm_vcpu *vcpu, gva_t addr, ++vcpu->stat.pf_fixed;kvm_mmu_audit (vcpu,"post page fault (fixed)" );spin_unlock (&vcpu->kvm->mmu_lock); + +if (read_seqretry (&vcpu->kvm->arch.mmu_notifier_invalidate_lock, mmu_seq)) { +down_read (¤t->mm->mmap_sem); +if (page !=gfn_to_page (vcpu->kvm, walker.gfn)) +BUG (); +up_read (¤t->mm->mmap_sem); +kvm_release_page_clean (page); + } +up_read (&vcpu->kvm->slots_lock);return write_pt;
arch/x86/kvm/x86.c +45 -0
57 lines not on this page · raw message
include/asm-x86/kvm_host.h +6 -0
@@ -13,6 +13,7 @@#include <linux/types.h> #include <linux/mm.h> +#include <linux/mmu_notifier.h> #include <linux/kvm.h> #include <linux/kvm_para.h>
@@ -294,6 +295,9 @@ struct kvm_arch{struct page *apic_access_page;gpa_t wall_clock; + +struct mmu_notifier mmu_notifier; +seqlock_t mmu_notifier_invalidate_lock; };struct kvm_vm_stat {
@@ -411,6 +415,8 @@ int kvm_mmu_create(struct kvm_vcpu *vcpu);int kvm_mmu_setup (struct kvm_vcpu *vcpu);void kvm_mmu_set_nonpresent_ptes (u64 trap_pte,u64 notrap_pte); +void kvm_unmap_hva (struct kvm *kvm,unsigned long hva); +int kvm_age_hva (struct kvm *kvm,unsigned long hva);int kvm_mmu_reset_context (struct kvm_vcpu *vcpu);void kvm_mmu_slot_remove_write_access (struct kvm *kvm,int slot);void kvm_mmu_zap_all (struct kvm *kvm);
This as usual is the KVM locking patch to browse memslots without the memslot lock mutex.
arch/x86/kvm/x86.c +14 -7
@@ -3245,16 +3245,23 @@ int kvm_arch_set_memory_region(struct kvm *kvm,*/ if (!user_alloc) {if (npages && !old.rmap) { +unsigned long userspace_addr; +down_write (¤t->mm->mmap_sem); - memslot->userspace_addr =do_mmap (NULL ,0 , - npages *PAGE_SIZE , -PROT_READ |PROT_WRITE , -MAP_SHARED |MAP_ANONYMOUS , -0 ); + userspace_addr =do_mmap (NULL ,0 , + npages *PAGE_SIZE , +PROT_READ |PROT_WRITE , +MAP_SHARED |MAP_ANONYMOUS , +0 );up_write (¤t->mm->mmap_sem); -if (IS_ERR ((void *)memslot->userspace_addr)) -return PTR_ERR ((void *)memslot->userspace_addr); +if (IS_ERR ((void *)userspace_addr)) +return PTR_ERR ((void *)userspace_addr); + +/* set userspace_addr atomically for kvm_hva_to_rmapp */ +spin_lock (&kvm->mmu_lock); + memslot->userspace_addr = userspace_addr; +spin_unlock (&kvm->mmu_lock); }else {if (!old.user_alloc && old.rmap) {int ret;
virt/kvm/kvm_main.c +13 -1
@@ -299,7 +299,15 @@ int __kvm_set_memory_region(struct kvm *kvm,memset (new.rmap,0 , npages *sizeof (*new.rmap)); new.user_alloc = user_alloc; - new.userspace_addr = mem->userspace_addr; +/* +* hva_to_rmmap() serialzies with the mmu_lock and to be +* safe it has to ignore memslots with !user_alloc && +* !userspace_addr. +*/ +if (user_alloc) + new.userspace_addr = mem->userspace_addr; +else + new.userspace_addr =0 ; }/* Allocate page dirty bitmap if needed */
@@ -312,14 +320,18 @@ int __kvm_set_memory_region(struct kvm *kvm,memset (new.dirty_bitmap,0 , dirty_bytes); } +spin_lock (&kvm->mmu_lock);if (mem->slot >= kvm->nmemslots) kvm->nmemslots = mem->slot +1 ; *memslot = new; +spin_unlock (&kvm->mmu_lock); r =kvm_arch_set_memory_region (kvm, mem, old, user_alloc);if (r) { +spin_lock (&kvm->mmu_lock); *memslot = old; +spin_unlock (&kvm->mmu_lock); goto out_free; }
PATCH 1/1 KVM swapping with mmu notifiers #v7
Same as before but one one hand ported to #v7 API and on the other hand ported to latest kvm.git.
arch/x86/kvm/Kconfig +1 -0
@@ -21,6 +21,7 @@ config KVM tristate "Kernel-based Virtual Machine (KVM) support" depends on HAVE_KVM && EXPERIMENTAL select PREEMPT_NOTIFIERS + select MMU_NOTIFIER select ANON_INODES ---help--- Support hosting fully virtualized guest machines using hardware
arch/x86/kvm/mmu.c +104 -0
110 lines not on this page · raw message
arch/x86/kvm/paging_tmpl.h +11 -0
@@ -380,6 +380,7 @@ static int FNAME(page_fault)(struct kvm_vcpu *vcpu, gva_t addr,int r;struct page *page;int largepage =0 ; +unsigned mmu_seq;pgprintk ("%s: addr %lx err %x\n" , __FUNCTION__, addr, error_code);kvm_mmu_audit (vcpu,"pre page fault" );
@@ -415,6 +416,7 @@ static int FNAME(page_fault)(struct kvm_vcpu *vcpu, gva_t addr, largepage =1 ; } } + mmu_seq =read_seqbegin (&vcpu->kvm->arch.mmu_notifier_invalidate_lock); page =gfn_to_page (vcpu->kvm, walker.gfn);up_read (¤t->mm->mmap_sem);
@@ -440,6 +442,15 @@ static int FNAME(page_fault)(struct kvm_vcpu *vcpu, gva_t addr, ++vcpu->stat.pf_fixed;kvm_mmu_audit (vcpu,"post page fault (fixed)" );spin_unlock (&vcpu->kvm->mmu_lock); + +if (read_seqretry (&vcpu->kvm->arch.mmu_notifier_invalidate_lock, mmu_seq)) { +down_read (¤t->mm->mmap_sem); +if (page !=gfn_to_page (vcpu->kvm, walker.gfn)) +BUG (); +up_read (¤t->mm->mmap_sem); +kvm_release_page_clean (page); + } +up_read (&vcpu->kvm->slots_lock);return write_pt;
arch/x86/kvm/x86.c +45 -0
57 lines not on this page · raw message
include/asm-x86/kvm_host.h +6 -0
@@ -13,6 +13,7 @@#include <linux/types.h> #include <linux/mm.h> +#include <linux/mmu_notifier.h> #include <linux/kvm.h> #include <linux/kvm_para.h>
@@ -303,6 +304,9 @@ struct kvm_arch{struct page *apic_access_page;gpa_t wall_clock; + +struct mmu_notifier mmu_notifier; +seqlock_t mmu_notifier_invalidate_lock; };struct kvm_vm_stat {
@@ -422,6 +426,8 @@ int kvm_mmu_create(struct kvm_vcpu *vcpu);int kvm_mmu_setup (struct kvm_vcpu *vcpu);void kvm_mmu_set_nonpresent_ptes (u64 trap_pte,u64 notrap_pte); +void kvm_unmap_hva (struct kvm *kvm,unsigned long hva); +int kvm_age_hva (struct kvm *kvm,unsigned long hva);int kvm_mmu_reset_context (struct kvm_vcpu *vcpu);void kvm_mmu_slot_remove_write_access (struct kvm *kvm,int slot);void kvm_mmu_zap_all (struct kvm *kvm);
As usual (for completeness) I append the change to the memslot readonly locking through kvm->mmu_lock:
arch/x86/kvm/x86.c +14 -7
@@ -3379,16 +3379,23 @@ int kvm_arch_set_memory_region(struct kvm *kvm,*/ if (!user_alloc) {if (npages && !old.rmap) { +unsigned long userspace_addr; +down_write (¤t->mm->mmap_sem); - memslot->userspace_addr =do_mmap (NULL ,0 , - npages *PAGE_SIZE , -PROT_READ |PROT_WRITE , -MAP_SHARED |MAP_ANONYMOUS , -0 ); + userspace_addr =do_mmap (NULL ,0 , + npages *PAGE_SIZE , +PROT_READ |PROT_WRITE , +MAP_SHARED |MAP_ANONYMOUS , +0 );up_write (¤t->mm->mmap_sem); -if (IS_ERR ((void *)memslot->userspace_addr)) -return PTR_ERR ((void *)memslot->userspace_addr); +if (IS_ERR ((void *)userspace_addr)) +return PTR_ERR ((void *)userspace_addr); + +/* set userspace_addr atomically for kvm_hva_to_rmapp */ +spin_lock (&kvm->mmu_lock); + memslot->userspace_addr = userspace_addr; +spin_unlock (&kvm->mmu_lock); }else {if (!old.user_alloc && old.rmap) {int ret;
virt/kvm/kvm_main.c +13 -1
@@ -326,7 +326,15 @@ int __kvm_set_memory_region(struct kvm *kvm,memset (new.rmap,0 , npages *sizeof (*new.rmap)); new.user_alloc = user_alloc; - new.userspace_addr = mem->userspace_addr; +/* +* hva_to_rmmap() serialzies with the mmu_lock and to be +* safe it has to ignore memslots with !user_alloc && +* !userspace_addr. +*/ +if (user_alloc) + new.userspace_addr = mem->userspace_addr; +else + new.userspace_addr =0 ; }if (npages && !new.lpage_info) {int largepages = npages /KVM_PAGES_PER_HPAGE ;
@@ -355,14 +363,18 @@ int __kvm_set_memory_region(struct kvm *kvm,memset (new.dirty_bitmap,0 , dirty_bytes); } +spin_lock (&kvm->mmu_lock);if (mem->slot >= kvm->nmemslots) kvm->nmemslots = mem->slot +1 ; *memslot = new; +spin_unlock (&kvm->mmu_lock); r =kvm_arch_set_memory_region (kvm, mem, old, user_alloc);if (r) { +spin_lock (&kvm->mmu_lock); *memslot = old; +spin_unlock (&kvm->mmu_lock); goto out_free; }
Re: [PATCH] KVM swapping with mmu notifiers #v7
ציטוט Andrea Arcangeli:
Quoted 24 lines
Same as before but one one hand ported to #v7 API and on the other hand ported to latest kvm.git.
Signed-off-by Andrea Arcangeli <[email protected]>arch/x86/kvm/Kconfig +1
-0@@ -21,6 +21,7 @@ config KVM tristate "Kernel-based Virtual Machine (KVM) support" depends on HAVE_KVM && EXPERIMENTAL select PREEMPT_NOTIFIERS + select MMU_NOTIFIER select ANON_INODES ---help--- Support hosting fully virtualized guest machines using hardwarearch/x86/kvm/mmu.c +0
-0@@ -642,6 +642,110 @@ static void rmap_write_protect(struct kvm *kvm, u64 gfn)account_shadowed (kvm, gfn); }
arch/x86/kvm/mmu.c static void rmap_write_protect(struct kvm *kvm, u64 gfn) · line 653
Quoted 10 lines
arch/x86/kvm/mmu.c +8
-0+static void kvm_unmap_spte (struct kvm *kvm,u64 *spte) +{ +struct page *page =pfn_to_page ((*spte &PT64_BASE_ADDR_MASK ) >>PAGE_SHIFT ); +get_page (page); +rmap_remove (kvm, spte); +set_shadow_pte (spte, shadow_trap_nonpresent_pte); +kvm_flush_remote_tlbs (kvm); +__free_page (page);
with large page support i think we need here put_page...
Quoted 312 lines
arch/x86/kvm/mmu.c +96
-0+} + +static void kvm_unmap_rmapp (struct kvm *kvm,unsigned long *rmapp) +{ +u64 *spte, *curr_spte; + + spte =rmap_next (kvm, rmapp,NULL ); +while (spte) { +BUG_ON (!(*spte &PT_PRESENT_MASK )); +rmap_printk ("kvm_rmap_unmap_hva: spte %p %llx\n" , spte, *spte); + curr_spte = spte; + spte =rmap_next (kvm, rmapp, spte); +kvm_unmap_spte (kvm, curr_spte); + } +} + +void kvm_unmap_hva (struct kvm *kvm,unsigned long hva) +{ +int i; + +/* +* If mmap_sem isn't taken, we can look the memslots with only +* the mmu_lock by skipping over the slots with userspace_addr == 0. +*/ +spin_lock (&kvm->mmu_lock); +for (i =0 ; i < kvm->nmemslots; i++) { +struct kvm_memory_slot *memslot = &kvm->memslots[i]; +unsigned long start = memslot->userspace_addr; +unsigned long end; + +/* mmu_lock protects userspace_addr */ +if (!start) +continue ; + + end = start + (memslot->npages <<PAGE_SHIFT ); +if (hva >= start && hva < end) { +gfn_t gfn_offset = (hva - start) >>PAGE_SHIFT ; +kvm_unmap_rmapp (kvm, &memslot->rmap[gfn_offset]); + } + } +spin_unlock (&kvm->mmu_lock); +} + +static int kvm_age_rmapp (struct kvm *kvm,unsigned long *rmapp) +{ +u64 *spte; +int young =0 ; + + spte =rmap_next (kvm, rmapp,NULL ); +while (spte) { +int _young; +u64 _spte = *spte; +BUG_ON (!(_spte &PT_PRESENT_MASK )); + _young = _spte &PT_ACCESSED_MASK ; +if (_young) { + young = !!_young; +set_shadow_pte (spte, _spte & ~PT_ACCESSED_MASK ); + } + spte =rmap_next (kvm, rmapp, spte); + } +return young; +} + +int kvm_age_hva (struct kvm *kvm,unsigned long hva) +{ +int i; +int young =0 ; + +/* +* If mmap_sem isn't taken, we can look the memslots with only +* the mmu_lock by skipping over the slots with userspace_addr == 0. +*/ +spin_lock (&kvm->mmu_lock); +for (i =0 ; i < kvm->nmemslots; i++) { +struct kvm_memory_slot *memslot = &kvm->memslots[i]; +unsigned long start = memslot->userspace_addr; +unsigned long end; + +/* mmu_lock protects userspace_addr */ +if (!start) +continue ; + + end = start + (memslot->npages <<PAGE_SHIFT ); +if (hva >= start && hva < end) { +gfn_t gfn_offset = (hva - start) >>PAGE_SHIFT ; + young |=kvm_age_rmapp (kvm, &memslot->rmap[gfn_offset]); + } + } +spin_unlock (&kvm->mmu_lock); + +if (young) +kvm_flush_remote_tlbs (kvm); + +return young; +} +#ifdef MMU_DEBUG static int is_empty_shadow_page (u64 *spt){
arch/x86/kvm/paging_tmpl.h +11
-0@@ -380,6 +380,7 @@ static int FNAME(page_fault)(struct kvm_vcpu *vcpu, gva_t addr,int r;struct page *page;int largepage =0 ; +unsigned mmu_seq;pgprintk ("%s: addr %lx err %x\n" , __FUNCTION__, addr, error_code);kvm_mmu_audit (vcpu,"pre page fault" );@@ -415,6 +416,7 @@ static int FNAME(page_fault)(struct kvm_vcpu *vcpu, gva_t addr, largepage =1 ; } } + mmu_seq =read_seqbegin (&vcpu->kvm->arch.mmu_notifier_invalidate_lock); page =gfn_to_page (vcpu->kvm, walker.gfn);up_read (¤t->mm->mmap_sem);@@ -440,6 +442,15 @@ static int FNAME(page_fault)(struct kvm_vcpu *vcpu, gva_t addr, ++vcpu->stat.pf_fixed;kvm_mmu_audit (vcpu,"post page fault (fixed)" );spin_unlock (&vcpu->kvm->mmu_lock); + +if (read_seqretry (&vcpu->kvm->arch.mmu_notifier_invalidate_lock, mmu_seq)) { +down_read (¤t->mm->mmap_sem); +if (page !=gfn_to_page (vcpu->kvm, walker.gfn)) +BUG (); +up_read (¤t->mm->mmap_sem); +kvm_release_page_clean (page); + } +up_read (&vcpu->kvm->slots_lock);return write_pt;arch/x86/kvm/x86.c +45
-0@@ -3319,6 +3319,47 @@ void kvm_arch_vcpu_uninit(struct kvm_vcpu *vcpu)free_page ((unsigned long )vcpu->arch.pio_data); } +static inline struct kvm *mmu_notifier_to_kvm (struct mmu_notifier *mn) +{ +struct kvm_arch *kvm_arch; + kvm_arch = container_of(mn, struct kvm_arch, mmu_notifier); + return container_of(kvm_arch, struct kvm, arch); +} + +void kvm_mmu_notifier_invalidate_page (struct mmu_notifier *mn, +struct mm_struct *mm, +unsigned long address) +{ +struct kvm *kvm =mmu_notifier_to_kvm (mn); +BUG_ON (mm != kvm->mm); +write_seqlock (&kvm->arch.mmu_notifier_invalidate_lock); +kvm_unmap_hva (kvm, address); +write_sequnlock (&kvm->arch.mmu_notifier_invalidate_lock); +} + +void kvm_mmu_notifier_invalidate_range_end (struct mmu_notifier *mn, +struct mm_struct *mm, +unsigned long start,unsigned long end) +{ +for (; start < end; start +=PAGE_SIZE ) +kvm_mmu_notifier_invalidate_page (mn, mm, start); +} + +int kvm_mmu_notifier_age_page (struct mmu_notifier *mn, +struct mm_struct *mm, +unsigned long address) +{ +struct kvm *kvm =mmu_notifier_to_kvm (mn); +BUG_ON (mm != kvm->mm); +return kvm_age_hva (kvm, address); +} + +static const struct mmu_notifier_ops kvm_mmu_notifier_ops = { + .invalidate_page = kvm_mmu_notifier_invalidate_page, + .invalidate_range_end = kvm_mmu_notifier_invalidate_range_end, + .age_page = kvm_mmu_notifier_age_page, +}; +struct kvm *kvm_arch_create_vm (void ) {struct kvm *kvm =kzalloc (sizeof (struct kvm ),GFP_KERNEL );@@ -3328,6 +3369,10 @@ struct kvm *kvm_arch_create_vm(void)INIT_LIST_HEAD (&kvm->arch.active_mmu_pages); + kvm->arch.mmu_notifier.ops = &kvm_mmu_notifier_ops; +mmu_notifier_register (&kvm->arch.mmu_notifier, current->mm); +seqlock_init (&kvm->arch.mmu_notifier_invalidate_lock); +return kvm; }include/asm-x86/kvm_host.h +6
-0@@ -13,6 +13,7 @@#include <linux/types.h> #include <linux/mm.h> +#include <linux/mmu_notifier.h> #include <linux/kvm.h> #include <linux/kvm_para.h> @@ -303,6 +304,9 @@ struct kvm_arch{struct page *apic_access_page;gpa_t wall_clock; + +struct mmu_notifier mmu_notifier; +seqlock_t mmu_notifier_invalidate_lock; };struct kvm_vm_stat {@@ -422,6 +426,8 @@ int kvm_mmu_create(struct kvm_vcpu *vcpu);int kvm_mmu_setup (struct kvm_vcpu *vcpu);void kvm_mmu_set_nonpresent_ptes (u64 trap_pte,u64 notrap_pte); +void kvm_unmap_hva (struct kvm *kvm,unsigned long hva); +int kvm_age_hva (struct kvm *kvm,unsigned long hva);int kvm_mmu_reset_context (struct kvm_vcpu *vcpu);void kvm_mmu_slot_remove_write_access (struct kvm *kvm,int slot);void kvm_mmu_zap_all (struct kvm *kvm);As usual (for completeness) I append the change to the memslot readonly locking through
kvm->mmu_lock:Signed-off-by Andrea Arcangeli <[email protected]>arch/x86/kvm/x86.c +14
-7@@ -3379,16 +3379,23 @@ int kvm_arch_set_memory_region(struct kvm *kvm,*/ if (!user_alloc) {if (npages && !old.rmap) { +unsigned long userspace_addr; +down_write (¤t->mm->mmap_sem); - memslot->userspace_addr =do_mmap (NULL ,0 , - npages *PAGE_SIZE , -PROT_READ |PROT_WRITE , -MAP_SHARED |MAP_ANONYMOUS , -0 ); + userspace_addr =do_mmap (NULL ,0 , + npages *PAGE_SIZE , +PROT_READ |PROT_WRITE , +MAP_SHARED |MAP_ANONYMOUS , +0 );up_write (¤t->mm->mmap_sem); -if (IS_ERR ((void *)memslot->userspace_addr)) -return PTR_ERR ((void *)memslot->userspace_addr); +if (IS_ERR ((void *)userspace_addr)) +return PTR_ERR ((void *)userspace_addr); + +/* set userspace_addr atomically for kvm_hva_to_rmapp */ +spin_lock (&kvm->mmu_lock); + memslot->userspace_addr = userspace_addr; +spin_unlock (&kvm->mmu_lock); }else {if (!old.user_alloc && old.rmap) {int ret;virt/kvm/kvm_main.c +13
-1@@ -326,7 +326,15 @@ int __kvm_set_memory_region(struct kvm *kvm,memset (new.rmap,0 , npages *sizeof (*new.rmap)); new.user_alloc = user_alloc; - new.userspace_addr = mem->userspace_addr; +/* +* hva_to_rmmap() serialzies with the mmu_lock and to be +* safe it has to ignore memslots with !user_alloc && +* !userspace_addr. +*/ +if (user_alloc) + new.userspace_addr = mem->userspace_addr; +else + new.userspace_addr =0 ; }if (npages && !new.lpage_info) {int largepages = npages /KVM_PAGES_PER_HPAGE ;@@ -355,14 +363,18 @@ int __kvm_set_memory_region(struct kvm *kvm,memset (new.dirty_bitmap,0 , dirty_bytes); } +spin_lock (&kvm->mmu_lock);if (mem->slot >= kvm->nmemslots) kvm->nmemslots = mem->slot +1 ; *memslot = new; +spin_unlock (&kvm->mmu_lock); r =kvm_arch_set_memory_region (kvm, mem, old, user_alloc);if (r) { +spin_lock (&kvm->mmu_lock); *memslot = old; +spin_unlock (&kvm->mmu_lock); goto out_free; }
Re: [PATCH] mmu notifiers #v6
In reply to Andrea ArcangeliQuoted 5 lines · Andrea Arcangeli
Given Nick's comments I ported my version of the mmu notifiers to latest mainline. There are no known bugs AFIK and it's obviously safe (nothing is allowed to schedule inside
rcu_read_locktaken bymmu_notifier()with my patch).
Quoted 9 lines
XPMEM simply can't use RCU for the registration locking if it wants to schedule inside the mmu notifier calls. So I guess it's better to add the XPMEM invalidate_range_end/begin/external-rmap as a whole different subsystem that will have to use a mutex (not RCU) to serialize, and at the same time that
CONFIG_XPMEMwill also have to switch thei_mmap_lockto a mutex. I doubt xpmem fits inside aCONFIG_MMU_NOTIFIERanymore, or we'll all run a bit slower because of it. It's really a call of how much we want to optimize the MMU notifier, by keeping things like RCU for the registration.
But won't that other "subsystem" cause us to have two seperate callouts that do equivalent things and therefore force a removal of this and go back to what Christoph has currently proposed?
Robin
Re: [PATCH] mmu notifiers #v6
Quoted 3 lines · Robin Holt
But won't that other "subsystem" cause us to have two seperate callouts that do equivalent things and therefore force a removal of this and go back to what Christoph has currently proposed?
The point is that a new kind of notifier that only supports sleeping users will allow to keep optimizing the mmu notifier patch for the non-sleeping users. If we keep going Christoph's way of having a single notifier that fits all he will have to:
1) drop the entire RCU locking from its patches (making all previous rcu discussions and fixes void) those discussions only made sense if applied to _my_ patch, not Christoph's patches as long as you pretend to sleep in any of his mmu notifier methods like invalidate_range_*.
2) probably modify the linux VM to replace the i_mmap_lock and perhaps PT lock with a mutex (see Nick's comments for details)
I'm unconvinced both the main linux VM and the mmu notifier should be changed like this just to support xpmem. All non-sleeping users don't need that. Nevertheless I'm fully welcome to support xpmem (and it's not my call nor my interest to comment if allocating skbs in try_to_unmap in order to unpin pages is workable, let's assume it's workable for the sake of this discussion) with a new config option that will also alter how the core VM works, in order to fully support the sleeping users for filebacked mappings.
This will also create less confusion in the registration. With Christoph's one-config-option-fits-all you had to half register into the mmu notifier (the sleeping calls, so not invalidate_page) and full register in the external rmap notifier, and I had to only half register into the mmu notifier (not range_begin) and not register in the rmap external notifier.
With two separate config options for sleeping and non sleeping users, I'll 100% register in the mmu notifier methods, and the non-sleeping users will 100% register the xpmem methods. You won't have to have designed the mmu notifier patches to understand how to use it.
In theory both KVM and GRU are free to use the xpmem methods too (the invalidate_page will be page_t based instead of [mm,addr] based, but that's possible to handle with KVM changes if one wants to), but if a distro only wants to support the sleeping users in their binary kernel images, they won't be forced to alter how the VM works to do that.
If there's agreement that the VM should alter its locking from spinlock to mutex for its own good, then Christoph's one-config-option-fits-all becomes a lot more appealing (replacing RCU with a mutex in the mmu notifier list registration locking isn't my main worry and the non-sleeping-users may be ok to live with it).
Re: [PATCH] mmu notifiers #v6
Quoted 8 lines · Andrea Arcangeli
I'm unconvinced both the main linux VM and the mmu notifier should be changed like this just to support xpmem. All non-sleeping users don't need that. Nevertheless I'm fully welcome to support xpmem (and it's not my call nor my interest to comment if allocating skbs in
try_to_unmapin order to unpin pages is workable, let's assume it's workable for the sake of this discussion) with a new config option that will also alter how the core VM works, in order to fully support the sleeping users for filebacked mappings.
We do not need to do any allocation in the messaging layer, all structures used for messaging are allocated at module load time. The allocation discussions we had early on were about trying to rearrange you notifiers to allow a seperate worker thread to do the invalidate and then the main thread would spin waiting for the worker to complete. That was canned by the moving your notifier to before the lock was grabbed which led us to the point of needing a _begin and _end.
Quoted 11 lines
This will also create less confusion in the registration. With Christoph's one-config-option-fits-all you had to half register into the mmu notifier (the sleeping calls, so not
invalidate_page) and full register in the external rmap notifier, and I had to only half register into the mmu notifier (notrange_begin) and not register in the rmap external notifier.With two separate config options for sleeping and non sleeping users, I'll 100% register in the mmu notifier methods, and the non-sleeping users will 100% register the xpmem methods. You won't have to have designed the mmu notifier patches to understand how to use it.
So, fundamentally, how would they be different? Would we be required to add another notifier list to the mm and have two seperate callout points? Reduction would end up with the same half-registered half-not-registered situation you point out above. Then further reduction would lead to the elimination of the callouts you have just proposed and using the _begin/_end callouts and we are back to Christoph's current patch.
Robin
Re: [PATCH] mmu notifiers #v6
Quoted 7 lines · Robin Holt
We do not need to do any allocation in the messaging layer, all structures used for messaging are allocated at module load time. The allocation discussions we had early on were about trying to rearrange you notifiers to allow a seperate worker thread to do the invalidate and then the main thread would spin waiting for the worker to complete. That was canned by the moving your notifier to before the lock was grabbed which led us to the point of needing a
_beginand_end.
I thought you called some net/* function inside the mmu notifier methods. Those always require several ram allocations internally.
Quoted 7 lines
So, fundamentally, how would they be different? Would we be required to add another notifier list to the mm and have two seperate callout points? Reduction would end up with the same half-registered half-not-registered situation you point out above. Then further reduction would lead to the elimination of the callouts you have just proposed and using the _begin/_end callouts and we are back to Christoph's current patch.
Did you miss Nick's argument that we'd need to change some VM lock to mutex and solve lock issues first? Are you implying mutex are more efficient for the VM? (you may seek support from preempt-rt folks at least) or are you implying the VM would better run slower with mutex in order to have a single config option?
-------------------------------------------------------------------------
This SF.net email is sponsored by: Microsoft Defy all challenges. Microsoft(R) Visual Studio 2008.
http://clk.atdmt.com/MRT/go/vse0120000070mrt/direct/01/
Re: [PATCH] mmu notifiers #v6
Quoted 11 lines · Andrea Arcangeli
Quoted 7 lines · Robin Holt
We do not need to do any allocation in the messaging layer, all structures used for messaging are allocated at module load time. The allocation discussions we had early on were about trying to rearrange you notifiers to allow a seperate worker thread to do the invalidate and then the main thread would spin waiting for the worker to complete. That was canned by the moving your notifier to before the lock was grabbed which led us to the point of needing a
_beginand_end.I thought you called some
net/*function inside the mmu notifier methods. Those always require several ram allocations internally.
Nope, that was the discussions with the IB folks. We only use XPC and both the messages we send and the XPC internals do not need to allocate.
Quoted 8 lines
Quoted 7 lines
So, fundamentally, how would they be different? Would we be required to add another notifier list to the mm and have two seperate callout points? Reduction would end up with the same half-registered half-not-registered situation you point out above. Then further reduction would lead to the elimination of the callouts you have just proposed and using the _begin/_end callouts and we are back to Christoph's current patch.
Quoted 5 lines
Did you miss Nick's argument that we'd need to change some VM lock to mutex and solve lock issues first? Are you implying mutex are more efficient for the VM? (you may seek support from preempt-rt folks at least) or are you implying the VM would better run slower with mutex in order to have a single config option?
That would be if we needed to support file backed mappings and hugetlbfs mappings. Currently (and for the last 6 years), XPMEM has not supported either of those. I don't view either as being a realistic possibility, but it is certainly something we would need to address before either could be supported.
Robin
-------------------------------------------------------------------------
This SF.net email is sponsored by: Microsoft Defy all challenges. Microsoft(R) Visual Studio 2008.
http://clk.atdmt.com/MRT/go/vse0120000070mrt/direct/01/
Re: [PATCH] mmu notifiers #v6
In reply to Andrea ArcangeliQuoted 5 lines · Andrea Arcangeli
If there's agreement that the VM should alter its locking from spinlock to mutex for its own good, then Christoph's one-config-option-fits-all becomes a lot more appealing (replacing RCU with a mutex in the mmu notifier list registration locking isn't my main worry and the non-sleeping-users may be ok to live with it).
Just from a high level view, in some cases we can just say that no we aren't going to support this. And this may well be one of those cases.
The more constraints placed on the VM, the harder it becomes to improve and adapt in future. And this seems like a pretty big restriction. (especially if we can eg. work around it completely by having a special purpose driver to get_user_pages on comm buffers as I suggested in the other mail).
At any rate, I believe Andrea's patch really places minimal or no further constraints than a regular CPU TLB (or the hash tables that some archs implement). So we're kind of in 2 different leagues here.
Re: [PATCH] mmu notifiers #v6
In reply to Andrea ArcangeliQuoted 2 lines · Andrea Arcangeli
XPMEM simply can't use RCU for the registration locking if it wants to schedule inside the mmu notifier calls. So I guess it's better to add
Whoa there. In Christoph's patch, we did not use rcu for the list. It was a simple hlist_head. The list manipulations were done under down_write(¤t->mm->mmap_sem) and would therefore not be racy. All the callout locations are already acquiring the mmap_sem at least readably, so we should be safe. Maybe I missed a race somewhere.
Thanks,
Robin
-------------------------------------------------------------------------
This SF.net email is sponsored by: Microsoft Defy all challenges. Microsoft(R) Visual Studio 2008.
http://clk.atdmt.com/MRT/go/vse0120000070mrt/direct/01/
Re: [PATCH] mmu notifiers #v6
Quoted 9 lines · Robin Holt
Quoted 2 lines · Andrea Arcangeli
XPMEM simply can't use RCU for the registration locking if it wants to schedule inside the mmu notifier calls. So I guess it's better to add
Whoa there. In Christoph's patch, we did not use rcu for the list. It was a simple
hlist_head. The list manipulations were done underdown_write(¤t->mm->mmap_sem)and would therefore not be racy. All the callout locations are already acquiring themmap_semat least readably, so we should be safe. Maybe I missed a race somewhere.
You missed quite a few, see when atomic=1 and when mmu_rmap_notifier is invoked for example.
Re: [PATCH] mmu notifiers #v6
In reply to Andrea ArcangeliQuoted 5 lines · Andrea Arcangeli
Given Nick's comments I ported my version of the mmu notifiers to latest mainline. There are no known bugs AFIK and it's obviously safe (nothing is allowed to schedule inside
rcu_read_locktaken bymmu_notifier()with my patch).
....
I ported the GRU driver to use the latest #v6 patch and ran a series of tests on it using our system simulator. The simulator is slow so true stress or swapping is not possible - at least within a finite amount of time.
Functionally, the #v6 patch seems to work for the GRU. However, I did notice two significant differences that make the #v6 performance worse for the GRU than Christoph's patch. I think one difference is easily fixable but the other is more difficult:
- the location of the mmu_notifier_release() callout is at a different place in the 2 patches. Christoph has the callout BEFORE the call to unmap_vmas() whereas you have it AFTER. The net result is that the GRU does a LOT of 1-page TLB flushes during process teardown. These flushes are not done with Christops's patch.
- the range callouts in Christoph's patch benefit the GRU because multiple TLB entries can be flushed with a single GRU instruction (the GRU hardware supports a range flush using a vaddr & length). The #v6 patch does a TLB flush for each page in the range. Flushing on the GRU is slow so being able to flush multiple pages with a single request is a benefit.
Seems like the latter difference could be significant for other users of mmu notifiers.
--- jack
Re: [PATCH] mmu notifiers #v6
In reply to Andrea ArcangeliQuoted 4 lines · Andrea Arcangeli
Given Nick's comments I ported my version of the mmu notifiers to latest mainline. There are no known bugs AFIK and it's obviously safe (nothing is allowed to schedule inside
rcu_read_locktaken bymmu_notifier()with my patch).
Thanks! Yes the seqlock you are using now ends up looking similar to what I did and I couldn't find a hole in that either. So I think this is going to work.
I do prefer some parts of my patch, however for everyone's sanity, I think you should be the maintainer of the mmu notifiers, and I will send you incremental changes that can be discussed more easily that way (nothing major, mainly style and minor things).
Quoted 9 lines
XPMEM simply can't use RCU for the registration locking if it wants to schedule inside the mmu notifier calls. So I guess it's better to add the XPMEM invalidate_range_end/begin/external-rmap as a whole different subsystem that will have to use a mutex (not RCU) to serialize, and at the same time that
CONFIG_XPMEMwill also have to switch thei_mmap_lockto a mutex. I doubt xpmem fits inside aCONFIG_MMU_NOTIFIERanymore, or we'll all run a bit slower because of it. It's really a call of how much we want to optimize the MMU notifier, by keeping things like RCU for the registration.
I agree: your coherent, non-sleeping mmu notifiers are pretty simple and unintrusive. The sleeping version is fundamentally going to either need to change VM locks, or be non-coherent, so I don't think there is a question of making one solution fit everybody. So the sleeping / xrmap patch should be kept either completely independent, or as an add-on to this one.
I will post some suggestions to you when I get a chance.
Re: [PATCH] mmu notifiers #v6
Quoted 2 lines · Nick Piggin
will send you incremental changes that can be discussed more easily that way (nothing major, mainly style and minor things).
I don't need to say you're very welcome ;).
Quoted 6 lines
I agree: your coherent, non-sleeping mmu notifiers are pretty simple and unintrusive. The sleeping version is fundamentally going to either need to change VM locks, or be non-coherent, so I don't think there is a question of making one solution fit everybody. So the sleeping / xrmap patch should be kept either completely independent, or as an add-on to this one.
The need to change the VM locks to fit the sleepable "mmu notifier" needs, I think is the major reason why the sleeping patch should be a separate config option unless you think the i_mmap_lock will benefit the VM for its own good regardless of the sleepable mmu notifiers. Otherwise we'll end up merging in mainline an API that can only satisfy the needs of the "sleeping users" that are only interested about anonymous memory. While the basic concept of the mmu notifiers is to cover the whole user visible address space, not just anonymous memory! Furthermore XPMEM users already asked to work on tmpfs/MAP_SHARED too...
Originally the trick that I was trying to remove the "atomic" param, was to defer the invalidate_range after dropping the i_mmap_lock. But clearly in truncate we'll have no more guarantees that nor the vma nor the MM still exists after spin_unlock(i_mmap_lock) is called... So it's simply impossible to call the mmu notifier out of the i_mmap_lock for truncate, and Christoph's patch looks unfixable without altering the VM core locking. Christoph's API one-config-fits-all can't really fit-all, but only the anonymous memory.
However if I wear a KVM hat, I cannot care less what is merged as long as .25 will be able to fully swap reliably a virtualized guest OS ;). This is why I'm totally willing to support any decision in favor of anything (including your own patch that would only work for KVM) that can be merged.
Quoted 1 line
I will post some suggestions to you when I get a chance.
I really want suggestions on Jack's concern about issuing an invalidate per pte entry or per-pte instead of per-range. I'll answer that in a separate email. For KVM my patch is already close to optimal because each single spte invalidate requires a fixed amount of work, but for GRU a large invalidate-range would be more efficient.
To address the GRU _valid_ concern, I can create a second version of my patch with range_begin/end instead of invalidate_pages, that still won't support sleeping users like XPMEM but only KVM and GRU. Then it's up to Christoph when he comes back to alter the vm locking so that those calls can sleep too... But that will require a much bigger change and then perhaps xpmem can share the same mmu notifiers when the config option to make the mmu notifier sleepable is enabled. But that part would better be incremental as it's not so obviously safe to merge as the mmu notifier themself.
Re: [PATCH] mmu notifiers #v6
Quoted 8 lines
I really want suggestions on Jack's concern about issuing an invalidate per pte entry or per-pte instead of per-range. I'll answer that in a separate email. For KVM my patch is already close to optimal because each single spte invalidate requires a fixed amount of work, but for GRU a large invalidate-range would be more efficient.
To address the GRU _valid_ concern, I can create a second version of my patch with range_begin/end instead of
invalidate_pages, that still
I don't know how much significance to place on this data, but it is a real data point.
I ran the GRU regression test suite on kernels with both types of mmu_notifiers. The kernel/driver using Christoph's patch had 1/7 the number of TLB invalidates as Andrea's patch.
This reduction is due to both differences I mentioned yesterday:
- different location of callout for address space teardown
- range callouts
Unfortunately, the current driver does not allow me to quantify which of the differences is most significant.
Also, I'll try to post the driver within the next few days. It is still in development but it compiles and can successfully run most workloads on a system simulator.
--- jack
PATCH 1/1 mmu notifiers #v7
Hello,
I hope this will can be considered final for .25 and be merged. Risk is zero, the only discussion here is to make an API that will last forever, functionality-wise all these patches provides zero risk and zero overhead when MMU_NOTIFIER=n. This last patch covers KVM and GRU and hopefully all other non-blocking users optimally, and the below API will hopefully last forever (but even if it lasts just for .25 and .26 is changed that's fine with us, it's a kernel _internal_ API anyway, there's absolutely nothing visible to userland).
What Christoph need to do when he's back from vacations to support sleepable mmu notifiers is to add a CONFIG_XPMEM config option that will switch the i_mmap_lock from a semaphore to a mutex (any other change to this patch will be minor compared to that) so XPMEM hardware will have kernels compiled that way. I don't see other sane ways to remove the "atomic" parameter from the API (apparently required by Andrew for merging something not restricted to the xpmem current usage with only anonymous memory) and I don't want to have such a locking-change intrusive dependency for all other non-blocking users that are fine without having to alter how the VM works (for example KVM and GRU). Very minor changes will be required to this patch to make it work after the VM locking will be altered (for example the CONFIG_XPMEM should also switch the mmu_register/unregister locking from RCU to mutex as well). XPMEM then will only compile if CONFIG_XPMEM=y and in turn the invalidate_range_* will support scheduling inside.
I don't think pretending to merge all in one block (I mean including xpmem support that requires blocking methods) is good idea anymore as long as we agree the "atomic" parameter shouldn't be merged. But we can quite easily agree on the below to be optimal for GRU/KVM and trivially extendible once a CONFIG_XPMEM will be added. So this first part can go in now I think.
include/linux/mm_types.h +3 -0
@@ -10,6 +10,7 @@#include <linux/rbtree.h> #include <linux/rwsem.h> #include <linux/completion.h> +#include <linux/mmu_notifier.h> #include <asm/page.h> #include <asm/mmu.h>
@@ -228,6 +229,8 @@ struct mm_struct {#ifdef CONFIG_CGROUP_MEM_CONT struct mem_cgroup *mem_cgroup;#endif + + struct mmu_notifier_head mmu_notifier; /* MMU notifier list */ };#endif /* _LINUX_MM_TYPES_H */
include/linux/mmu_notifier.h new file +159 -0
159 lines not on this page · raw message
kernel/fork.c +1 -0
@@ -362,6 +362,7 @@ static struct mm_struct * mm_init(structif (likely (!mm_alloc_pgd (mm))) { mm->def_flags =0 ; +mmu_notifier_head_init (&mm->mmu_notifier);return mm; }
mm/Kconfig +4 -0
@@ -193,3 +193,7 @@ config VIRT_TO_BUS config VIRT_TO_BUS def_bool y depends on !ARCH_NO_VIRT_TO_BUS + +config MMU_NOTIFIER + def_bool y + bool "MMU notifier, for paging KVM/RDMA"
mm/Makefile +1 -1
@@ -33,4 +33,4 @@ obj-$(CONFIG_SMP) += allocpercpu.o obj-$(CONFIG_SMP) += allocpercpu.o obj-$(CONFIG_QUICKLIST) += quicklist.o obj-$(CONFIG_CGROUP_MEM_CONT) += memcontrol.o - +obj-$(CONFIG_MMU_NOTIFIER) += mmu_notifier.o
mm/filemap_xip.c +1 -1
@@ -194,7 +194,7 @@ __xip_unmap (struct address_space * mappif (pte) {/* Nuke the page table entry. */ flush_cache_page (vma, address,pte_pfn (*pte)); - pteval =ptep_clear_flush (vma, address, pte); + pteval =ptep_clear_flush_notify (vma, address, pte);page_remove_rmap (page, vma);dec_mm_counter (mm, file_rss);BUG_ON (pte_dirty (pteval));
mm/fremap.c +2 -0
@@ -214,7 +214,9 @@ asmlinkage long sys_remap_file_pages(unsspin_unlock (&mapping->i_mmap_lock); } +mmu_notifier (invalidate_range_begin, mm, start, start + size); err =populate_range (mm, vma, start, size, pgoff); +mmu_notifier (invalidate_range_end, mm, start, start + size);if (!err && !(flags &MAP_NONBLOCK )) {if (unlikely (has_write_lock)) {downgrade_write (&mm->mmap_sem);
mm/hugetlb.c +2 -0
@@ -755,6 +755,7 @@ void __unmap_hugepage_range(struct vm_arBUG_ON (start & ~HPAGE_MASK );BUG_ON (end & ~HPAGE_MASK ); +mmu_notifier (invalidate_range_begin, mm, start, end);spin_lock (&mm->page_table_lock);for (address = start; address < end; address +=HPAGE_SIZE ) { ptep =huge_pte_offset (mm, address);
@@ -775,6 +776,7 @@ void __unmap_hugepage_range(struct vm_ar }spin_unlock (&mm->page_table_lock);flush_tlb_range (vma, start, end); +mmu_notifier (invalidate_range_end, mm, start, end);list_for_each_entry_safe (page, tmp, &page_list, lru) {list_del (&page->lru);put_page (page);
mm/memory.c +14 -2
@@ -611,6 +611,9 @@ int copy_page_range(struct mm_struct *dsif (is_vm_hugetlb_page (vma))return copy_hugetlb_page_range (dst_mm, src_mm, vma); +if (is_cow_mapping (vma->vm_flags)) +mmu_notifier (invalidate_range_begin, src_mm, addr, end); + dst_pgd =pgd_offset (dst_mm, addr); src_pgd =pgd_offset (src_mm, addr); do {
@@ -621,6 +624,11 @@ int copy_page_range(struct mm_struct *ds vma, addr, next))return -ENOMEM ; }while (dst_pgd++, src_pgd++, addr = next, addr != end); + +if (is_cow_mapping (vma->vm_flags)) +mmu_notifier (invalidate_range_end, src_mm, + vma->vm_start, end); +return 0 ; }
@@ -897,7 +905,9 @@ unsigned long zap_page_range(struct vm_alru_add_drain (); tlb =tlb_gather_mmu (mm,0 );update_hiwater_rss (mm); +mmu_notifier (invalidate_range_begin, mm, address, end); end =unmap_vmas (&tlb, vma, address, end, &nr_accounted, details); +mmu_notifier (invalidate_range_end, mm, address, end);if (tlb)tlb_finish_mmu (tlb, address, end);return end;
@@ -1463,10 +1473,11 @@ int apply_to_page_range(struct mm_struct {pgd_t *pgd;unsigned long next; -unsigned long end = addr + size; +unsigned long start = addr, end = addr + size;int err;BUG_ON (addr >= end); +mmu_notifier (invalidate_range_begin, mm, start, end); pgd =pgd_offset (mm, addr); do { next = pgd_addr_end(addr, end);
@@ -1474,6 +1485,7 @@ int apply_to_page_range(struct mm_structif (err)break ; }while (pgd++, addr = next, addr != end); +mmu_notifier (invalidate_range_end, mm, start, end);return err; }EXPORT_SYMBOL_GPL (apply_to_page_range);
@@ -1675,7 +1687,7 @@ gotten:* seen in the presence of one thread doing SMC and another * thread doing COW. */ -ptep_clear_flush (vma, address, page_table); +ptep_clear_flush_notify (vma, address, page_table);set_pte_at (mm, address, page_table, entry);update_mmu_cache (vma, address, entry);lru_cache_add_active (new_page);
mm/mmap.c +3 -0
@@ -1747,11 +1747,13 @@ static void unmap_region(struct mm_struclru_add_drain (); tlb =tlb_gather_mmu (mm,0 );update_hiwater_rss (mm); +mmu_notifier (invalidate_range_begin, mm, start, end);unmap_vmas (&tlb, vma, start, end, &nr_accounted,NULL );vm_unacct_memory (nr_accounted);free_pgtables (&tlb, vma, prev? prev->vm_end:FIRST_USER_ADDRESS , next? next->vm_start:0 );tlb_finish_mmu (tlb, start, end); +mmu_notifier (invalidate_range_end, mm, start, end); }/*
@@ -2048,6 +2050,7 @@ void exit_mmap(struct mm_struct *mm)vm_unacct_memory (nr_accounted);free_pgtables (&tlb, vma,FIRST_USER_ADDRESS ,0 );tlb_finish_mmu (tlb,0 , end); +mmu_notifier_release (mm);/* * Walk the list again, actually closing and freeing it,
mm/mmu_notifier.c new file +73 -0
73 lines not on this page · raw message
mm/mprotect.c +2 -0
@@ -198,10 +198,12 @@ success: dirty_accountable =1 ; } +mmu_notifier (invalidate_range_begin, mm, start, end);if (is_vm_hugetlb_page (vma))hugetlb_change_protection (vma, start, end, vma->vm_page_prot);else change_protection (vma, start, end, vma->vm_page_prot, dirty_accountable); +mmu_notifier (invalidate_range_end, mm, start, end);vm_stat_account (mm, oldflags, vma->vm_file, -nrpages);vm_stat_account (mm, newflags, vma->vm_file, nrpages);return 0 ;
mm/mremap.c +5 -0
@@ -74,6 +74,7 @@ static void move_ptes(struct vm_area_strstruct mm_struct *mm = vma->vm_mm;pte_t *old_pte, *new_pte, pte;spinlock_t *old_ptl, *new_ptl; +unsigned long old_start;if (vma->vm_file) {/*
@@ -100,6 +101,9 @@ static void move_ptes(struct vm_area_strspin_lock_nested (new_ptl,SINGLE_DEPTH_NESTING );arch_enter_lazy_mmu_mode (); + old_start = old_addr; +mmu_notifier (invalidate_range_begin, vma->vm_mm, + old_start, old_end);for (; old_addr < old_end; old_pte++, old_addr +=PAGE_SIZE , new_pte++, new_addr +=PAGE_SIZE ) {if (pte_none (*old_pte))
@@ -108,6 +112,7 @@ static void move_ptes(struct vm_area_str pte =move_pte (pte, new_vma->vm_page_prot, old_addr, new_addr);set_pte_at (mm, new_addr, new_pte, pte); } +mmu_notifier (invalidate_range_end, vma->vm_mm, old_start, old_end);arch_leave_lazy_mmu_mode ();if (new_ptl != old_ptl)
mm/rmap.c +6 -6
@@ -287,7 +287,7 @@ static int page_referenced_one(struct paif (vma->vm_flags &VM_LOCKED ) { referenced++; *mapcount =1 ;/* break early from loop */ - }else if (ptep_clear_flush_young (vma, address, pte)) + }else if (ptep_clear_flush_young_notify (vma, address, pte)) referenced++;/* Pretend the page is referenced if the task has the
@@ -454,7 +454,7 @@ static int page_mkclean_one(struct pagepte_t entry;flush_cache_page (vma, address,pte_pfn (*pte)); - entry =ptep_clear_flush (vma, address, pte); + entry =ptep_clear_flush_notify (vma, address, pte); entry =pte_wrprotect (entry); entry =pte_mkclean (entry);set_pte_at (mm, address, pte, entry);
@@ -712,14 +712,14 @@ static int try_to_unmap_one(struct page* skipped over this mm) then we should reactivate it. */ if (!migration && ((vma->vm_flags &VM_LOCKED ) || - (ptep_clear_flush_young (vma, address, pte)))) { + (ptep_clear_flush_young_notify (vma, address, pte)))) { ret =SWAP_FAIL ; goto out_unmap; }/* Nuke the page table entry. */ flush_cache_page (vma, address,page_to_pfn (page)); - pteval =ptep_clear_flush (vma, address, pte); + pteval =ptep_clear_flush_notify (vma, address, pte);/* Move the dirty bit to the physical page now the pte is gone. */ if (pte_dirty (pteval))
@@ -844,12 +844,12 @@ static void try_to_unmap_cluster(unsigne page =vm_normal_page (vma, address, *pte);BUG_ON (!page ||PageAnon (page)); -if (ptep_clear_flush_young (vma, address, pte)) +if (ptep_clear_flush_young_notify (vma, address, pte))continue ;/* Nuke the page table entry. */ flush_cache_page (vma, address,pte_pfn (*pte)); - pteval =ptep_clear_flush (vma, address, pte); + pteval =ptep_clear_flush_notify (vma, address, pte);/* If nonlinear, store the file page offset in the pte. */ if (page->index !=linear_page_index (vma, address))
-------------------------------------------------------------------------
This SF.net email is sponsored by: Microsoft Defy all challenges. Microsoft(R) Visual Studio 2008.
http://clk.atdmt.com/MRT/go/vse0120000070mrt/direct/01/
Re: [PATCH] mmu notifiers #v7
Quoted 35 lines · Andrea Arcangeli
Hello,
I hope this will can be considered final for .25 and be merged. Risk is zero, the only discussion here is to make an API that will last forever, functionality-wise all these patches provides zero risk and zero overhead when MMU_NOTIFIER=n. This last patch covers KVM and GRU and hopefully all other non-blocking users optimally, and the below API will hopefully last forever (but even if it lasts just for .25 and ..26 is changed that's fine with us, it's a kernel _internal_ API anyway, there's absolutely nothing visible to userland).
What Christoph need to do when he's back from vacations to support sleepable mmu notifiers is to add a
CONFIG_XPMEMconfig option that will switch thei_mmap_lockfrom a semaphore to a mutex (any other change to this patch will be minor compared to that) so XPMEM hardware will have kernels compiled that way. I don't see other sane ways to remove the "atomic" parameter from the API (apparently required by Andrew for merging something not restricted to the xpmem current usage with only anonymous memory) and I don't want to have such a locking-change intrusive dependency for all other non-blocking users that are fine without having to alter how the VM works (for example KVM and GRU). Very minor changes will be required to this patch to make it work after the VM locking will be altered (for example theCONFIG_XPMEMshould also switch the mmu_register/unregister locking from RCU to mutex as well). XPMEM then will only compile ifCONFIG_XPMEM=yand in turn the invalidate_range_* will support scheduling inside.I don't think pretending to merge all in one block (I mean including xpmem support that requires blocking methods) is good idea anymore as long as we agree the "atomic" parameter shouldn't be merged. But we can quite easily agree on the below to be optimal for GRU/KVM and trivially extendible once a
CONFIG_XPMEMwill be added. So this first part can go in now I think.
Quoted 2 lines
Signed-off-by Andrea Arcangeli <[email protected]>Signed-off-by Christoph Lameter <[email protected]>
Quoted 527 lines
include/linux/mm_types.h +3
-0@@ -10,6 +10,7 @@#include <linux/rbtree.h> #include <linux/rwsem.h> #include <linux/completion.h> +#include <linux/mmu_notifier.h> #include <asm/page.h> #include <asm/mmu.h> @@ -228,6 +229,8 @@ struct mm_struct {#ifdef CONFIG_CGROUP_MEM_CONT struct mem_cgroup *mem_cgroup;#endif + + struct mmu_notifier_head mmu_notifier; /* MMU notifier list */ };#endif /* _LINUX_MM_TYPES_H */ include/linux/mmu_notifier.h new file +159
-0@@ -0,0 +1,159 @@ +#ifndef _LINUX_MMU_NOTIFIER_H +#define _LINUX_MMU_NOTIFIER_H + +#include <linux/list.h> +#include <linux/spinlock.h> + +struct mmu_notifier ; + +struct mmu_notifier_ops { +/* +* Called when nobody can register any more notifier in the mm +* and after the "mn" notifier has been disarmed already. +*/ +void (*release)(struct mmu_notifier *mn, +struct mm_struct *mm); + +/* +* invalidate_page is called in atomic context after any pte +* has been updated and before dropping the PT lock required +* to update any Linux pte. Once the PT lock will be released +* the pte will have its final value to export through the +* secondary MMU. Before this is invoked any secondary MMU is +* still ok to read/write to the page previously pointed by +* the Linux pte because the old page hasn't been freed yet. +* If required set_page_dirty has to be called internally to +* this method. +*/ +void (*invalidate_page)(struct mmu_notifier *mn, +struct mm_struct *mm, +unsigned long address); + +/* +* Age page is called in atomic context inside the PT lock +* right after the VM is test-and-clearing the young/accessed +* bitflag in the pte. This way the VM will provide proper +* aging to the accesses to the page through the secondary +* MMUs and not only to the ones through the Linux pte. +*/ +int (*age_page)(struct mmu_notifier *mn, +struct mm_struct *mm, +unsigned long address); + +/* +* invalidate_range_begin() and invalidate_range_end() must be +* paired. Multiple invalidate_range_begin/ends may be nested +* or called concurrently. +*/ +void (*invalidate_range_begin)(struct mmu_notifier *mn, +struct mm_struct *mm, +unsigned long start,unsigned long end); +void (*invalidate_range_end)(struct mmu_notifier *mn, +struct mm_struct *mm, +unsigned long start,unsigned long end); +}; + +struct mmu_notifier { +struct hlist_node hlist; +const struct mmu_notifier_ops *ops; +}; + +#ifdef CONFIG_MMU_NOTIFIER + +struct mmu_notifier_head { +struct hlist_head head; +spinlock_t lock; +}; + +#include <linux/mm_types.h> + +/* +* RCU is used to traverse the list. A quiescent period needs to pass +* before the notifier is guaranteed to be visible to all threads. +*/ +extern void mmu_notifier_register (struct mmu_notifier *mn, +struct mm_struct *mm); +/* +* RCU is used to traverse the list. A quiescent period needs to pass +* before the "struct mmu_notifier" can be freed. Alternatively it +* can be synchronously freed inside ->release when the list can't +* change anymore and nobody could possibly walk it. +*/ +extern void mmu_notifier_unregister (struct mmu_notifier *mn, +struct mm_struct *mm); +extern void mmu_notifier_release (struct mm_struct *mm); +extern int mmu_notifier_age_page (struct mm_struct *mm, +unsigned long address); + +static inline void mmu_notifier_head_init (struct mmu_notifier_head *mnh) +{ +INIT_HLIST_HEAD (&mnh->head); +spin_lock_init (&mnh->lock); +} + +#define mmu_notifier(function, mm, args...) \ + do { \ + struct mmu_notifier *__mn; \ + struct hlist_node *__n; \ + \ + if (unlikely(!hlist_empty(&(mm)->mmu_notifier.head))) { \ + rcu_read_lock(); \ + hlist_for_each_entry_rcu(__mn, __n, \ + &(mm)->mmu_notifier.head, \ + hlist) \ + if (__mn->ops->function) \ + __mn->ops->function(__mn, \ + mm, \ + args); \ + rcu_read_unlock(); \ + } \ + } while (0) + +#define ptep_clear_flush_notify (__vma, __address, __ptep) \ +({ \ + pte_t __pte; \ + __pte = ptep_clear_flush(__vma, __address, __ptep); \ + mmu_notifier(invalidate_page, (__vma)->vm_mm, __address); \ + __pte; \ +}) + +#define ptep_clear_flush_young_notify (__vma, __address, __ptep) \ +({ \ + int __young; \ + __young = ptep_clear_flush_young(__vma, __address, __ptep); \ + __young |= mmu_notifier_age_page((__vma)->vm_mm, __address); \ + __young; \ +}) + +#else /* CONFIG_MMU_NOTIFIER */ + +struct mmu_notifier_head {}; + +#define mmu_notifier_register (mn, mm) do {} while(0) +#define mmu_notifier_unregister (mn, mm) do {} while (0) +#define mmu_notifier_release (mm) do {} while (0) +#define mmu_notifier_age_page (mm, address) ({ 0; }) +#define mmu_notifier_head_init (mmh) do {} while (0) + +/* +* Notifiers that use the parameters that they were passed so that the +* compiler does not complain about unused variables but does proper +* parameter checks even if !CONFIG_MMU_NOTIFIER. +* Macros generate no code. +*/ +#define mmu_notifier(function, mm, args...) \ + do { \ + if (0) { \ + struct mmu_notifier *__mn; \ + \ + __mn = (struct mmu_notifier *)(0x00ff); \ + __mn->ops->function(__mn, mm, args); \ + }; \ + } while (0) + +#define ptep_clear_flush_young_notify ptep_clear_flush_young +#define ptep_clear_flush_notify ptep_clear_flush + +#endif /* CONFIG_MMU_NOTIFIER */ + +#endif /* _LINUX_MMU_NOTIFIER_H */ kernel/fork.c +1
-0@@ -362,6 +362,7 @@ static struct mm_struct * mm_init(structif (likely (!mm_alloc_pgd (mm))) { mm->def_flags =0 ; +mmu_notifier_head_init (&mm->mmu_notifier);return mm; }mm/Kconfig +4
-0@@ -193,3 +193,7 @@ config VIRT_TO_BUS config VIRT_TO_BUS def_bool y depends on !ARCH_NO_VIRT_TO_BUS + +config MMU_NOTIFIER + def_bool y + bool "MMU notifier, for paging KVM/RDMA"mm/Makefile +1
-1@@ -33,4 +33,4 @@ obj-$(CONFIG_SMP) += allocpercpu.o obj-$(CONFIG_SMP) += allocpercpu.o obj-$(CONFIG_QUICKLIST) += quicklist.o obj-$(CONFIG_CGROUP_MEM_CONT) += memcontrol.o - +obj-$(CONFIG_MMU_NOTIFIER) += mmu_notifier.omm/filemap_xip.c +1
-1@@ -194,7 +194,7 @@ __xip_unmap (struct address_space * mappif (pte) {/* Nuke the page table entry. */ flush_cache_page (vma, address,pte_pfn (*pte)); - pteval =ptep_clear_flush (vma, address, pte); + pteval =ptep_clear_flush_notify (vma, address, pte);page_remove_rmap (page, vma);dec_mm_counter (mm, file_rss);BUG_ON (pte_dirty (pteval));mm/fremap.c +2
-0@@ -214,7 +214,9 @@ asmlinkage long sys_remap_file_pages(unsspin_unlock (&mapping->i_mmap_lock); } +mmu_notifier (invalidate_range_begin, mm, start, start + size); err =populate_range (mm, vma, start, size, pgoff); +mmu_notifier (invalidate_range_end, mm, start, start + size);if (!err && !(flags &MAP_NONBLOCK )) {if (unlikely (has_write_lock)) {downgrade_write (&mm->mmap_sem);mm/hugetlb.c +2
-0@@ -755,6 +755,7 @@ void __unmap_hugepage_range(struct vm_arBUG_ON (start & ~HPAGE_MASK );BUG_ON (end & ~HPAGE_MASK ); +mmu_notifier (invalidate_range_begin, mm, start, end);spin_lock (&mm->page_table_lock);for (address = start; address < end; address +=HPAGE_SIZE ) { ptep =huge_pte_offset (mm, address);@@ -775,6 +776,7 @@ void __unmap_hugepage_range(struct vm_ar }spin_unlock (&mm->page_table_lock);flush_tlb_range (vma, start, end); +mmu_notifier (invalidate_range_end, mm, start, end);list_for_each_entry_safe (page, tmp, &page_list, lru) {list_del (&page->lru);put_page (page);mm/memory.c +14
-2@@ -611,6 +611,9 @@ int copy_page_range(struct mm_struct *dsif (is_vm_hugetlb_page (vma))return copy_hugetlb_page_range (dst_mm, src_mm, vma); +if (is_cow_mapping (vma->vm_flags)) +mmu_notifier (invalidate_range_begin, src_mm, addr, end); + dst_pgd =pgd_offset (dst_mm, addr); src_pgd =pgd_offset (src_mm, addr); do {@@ -621,6 +624,11 @@ int copy_page_range(struct mm_struct *ds vma, addr, next))return -ENOMEM ; }while (dst_pgd++, src_pgd++, addr = next, addr != end); + +if (is_cow_mapping (vma->vm_flags)) +mmu_notifier (invalidate_range_end, src_mm, + vma->vm_start, end); +return 0 ; }@@ -897,7 +905,9 @@ unsigned long zap_page_range(struct vm_alru_add_drain (); tlb =tlb_gather_mmu (mm,0 );update_hiwater_rss (mm); +mmu_notifier (invalidate_range_begin, mm, address, end); end =unmap_vmas (&tlb, vma, address, end, &nr_accounted, details); +mmu_notifier (invalidate_range_end, mm, address, end);if (tlb)tlb_finish_mmu (tlb, address, end);return end;@@ -1463,10 +1473,11 @@ int apply_to_page_range(struct mm_struct {pgd_t *pgd;unsigned long next; -unsigned long end = addr + size; +unsigned long start = addr, end = addr + size;int err;BUG_ON (addr >= end); +mmu_notifier (invalidate_range_begin, mm, start, end); pgd =pgd_offset (mm, addr); do { next = pgd_addr_end(addr, end);@@ -1474,6 +1485,7 @@ int apply_to_page_range(struct mm_structif (err)break ; }while (pgd++, addr = next, addr != end); +mmu_notifier (invalidate_range_end, mm, start, end);return err; }EXPORT_SYMBOL_GPL (apply_to_page_range);@@ -1675,7 +1687,7 @@ gotten:* seen in the presence of one thread doing SMC and another * thread doing COW. */ -ptep_clear_flush (vma, address, page_table); +ptep_clear_flush_notify (vma, address, page_table);set_pte_at (mm, address, page_table, entry);update_mmu_cache (vma, address, entry);lru_cache_add_active (new_page);mm/mmap.c +3
-0@@ -1747,11 +1747,13 @@ static void unmap_region(struct mm_struclru_add_drain (); tlb =tlb_gather_mmu (mm,0 );update_hiwater_rss (mm); +mmu_notifier (invalidate_range_begin, mm, start, end);unmap_vmas (&tlb, vma, start, end, &nr_accounted,NULL );vm_unacct_memory (nr_accounted);free_pgtables (&tlb, vma, prev? prev->vm_end:FIRST_USER_ADDRESS , next? next->vm_start:0 );tlb_finish_mmu (tlb, start, end); +mmu_notifier (invalidate_range_end, mm, start, end); }/* @@ -2048,6 +2050,7 @@ void exit_mmap(struct mm_struct *mm)vm_unacct_memory (nr_accounted);free_pgtables (&tlb, vma,FIRST_USER_ADDRESS ,0 );tlb_finish_mmu (tlb,0 , end); +mmu_notifier_release (mm);/* * Walk the list again, actually closing and freeing it, mm/mmu_notifier.c new file +73
-0@@ -0,0 +1,73 @@ +/* +* linux/mm/mmu_notifier.c +* +* Copyright (C) 2008 Qumranet, Inc. +* Copyright (C) 2008 SGI +* Christoph Lameter <[email protected]> +* +* This work is licensed under the terms of the GNU GPL, version 2. See +* the COPYING file in the top-level directory. +*/ + +#include <linux/mmu_notifier.h> +#include <linux/module.h> +#include <linux/rcupdate.h> + +/* +* No synchronization. This function can only be called when only a single +* process remains that performs teardown. +*/ +void mmu_notifier_release (struct mm_struct *mm) +{ +struct mmu_notifier *mn; +struct hlist_node *n, *tmp; + +if (unlikely (!hlist_empty (&mm->mmu_notifier.head))) { +hlist_for_each_entry_safe (mn, n, tmp, + &mm->mmu_notifier.head, hlist) { +hlist_del (&mn->hlist); +if (mn->ops->release) + mn->ops->release (mn, mm); + } + } +} + +/* +* If no young bitflag is supported by the hardware, ->age_page can +* unmap the address and return 1 or 0 depending if the mapping previously +* existed or not. +*/ +int mmu_notifier_age_page (struct mm_struct *mm,unsigned long address) +{ +struct mmu_notifier *mn; +struct hlist_node *n; +int young =0 ; + +if (unlikely (!hlist_empty (&mm->mmu_notifier.head))) { +rcu_read_lock (); +hlist_for_each_entry_rcu (mn, n, + &mm->mmu_notifier.head, hlist) { +if (mn->ops->age_page) + young |= mn->ops->age_page (mn, mm, address); + } +rcu_read_unlock (); + } + +return young; +} + +void mmu_notifier_register (struct mmu_notifier *mn,struct mm_struct *mm) +{ +spin_lock (&mm->mmu_notifier.lock); +hlist_add_head_rcu (&mn->hlist, &mm->mmu_notifier.head); +spin_unlock (&mm->mmu_notifier.lock); +} +EXPORT_SYMBOL_GPL (mmu_notifier_register); + +void mmu_notifier_unregister (struct mmu_notifier *mn,struct mm_struct *mm) +{ +spin_lock (&mm->mmu_notifier.lock); +hlist_del_rcu (&mn->hlist); +spin_unlock (&mm->mmu_notifier.lock); +} +EXPORT_SYMBOL_GPL (mmu_notifier_unregister);mm/mprotect.c +2
-0@@ -198,10 +198,12 @@ success: dirty_accountable =1 ; } +mmu_notifier (invalidate_range_begin, mm, start, end);if (is_vm_hugetlb_page (vma))hugetlb_change_protection (vma, start, end, vma->vm_page_prot);else change_protection (vma, start, end, vma->vm_page_prot, dirty_accountable); +mmu_notifier (invalidate_range_end, mm, start, end);vm_stat_account (mm, oldflags, vma->vm_file, -nrpages);vm_stat_account (mm, newflags, vma->vm_file, nrpages);return 0 ;mm/mremap.c +5
-0@@ -74,6 +74,7 @@ static void move_ptes(struct vm_area_strstruct mm_struct *mm = vma->vm_mm;pte_t *old_pte, *new_pte, pte;spinlock_t *old_ptl, *new_ptl; +unsigned long old_start;if (vma->vm_file) {/* @@ -100,6 +101,9 @@ static void move_ptes(struct vm_area_strspin_lock_nested (new_ptl,SINGLE_DEPTH_NESTING );arch_enter_lazy_mmu_mode (); + old_start = old_addr; +mmu_notifier (invalidate_range_begin, vma->vm_mm, + old_start, old_end);for (; old_addr < old_end; old_pte++, old_addr +=PAGE_SIZE , new_pte++, new_addr +=PAGE_SIZE ) {if (pte_none (*old_pte))@@ -108,6 +112,7 @@ static void move_ptes(struct vm_area_str pte =move_pte (pte, new_vma->vm_page_prot, old_addr, new_addr);set_pte_at (mm, new_addr, new_pte, pte); } +mmu_notifier (invalidate_range_end, vma->vm_mm, old_start, old_end);arch_leave_lazy_mmu_mode ();if (new_ptl != old_ptl)mm/rmap.c +6
-6@@ -287,7 +287,7 @@ static int page_referenced_one(struct paif (vma->vm_flags &VM_LOCKED ) { referenced++; *mapcount =1 ;/* break early from loop */ - }else if (ptep_clear_flush_young (vma, address, pte)) + }else if (ptep_clear_flush_young_notify (vma, address, pte)) referenced++;/* Pretend the page is referenced if the task has the @@ -454,7 +454,7 @@ static int page_mkclean_one(struct pagepte_t entry;flush_cache_page (vma, address,pte_pfn (*pte)); - entry =ptep_clear_flush (vma, address, pte); + entry =ptep_clear_flush_notify (vma, address, pte); entry =pte_wrprotect (entry); entry =pte_mkclean (entry);set_pte_at (mm, address, pte, entry);@@ -712,14 +712,14 @@ static int try_to_unmap_one(struct page* skipped over this mm) then we should reactivate it. */ if (!migration && ((vma->vm_flags &VM_LOCKED ) || - (ptep_clear_flush_young (vma, address, pte)))) { + (ptep_clear_flush_young_notify (vma, address, pte)))) { ret =SWAP_FAIL ; goto out_unmap; }/* Nuke the page table entry. */ flush_cache_page (vma, address,page_to_pfn (page)); - pteval =ptep_clear_flush (vma, address, pte); + pteval =ptep_clear_flush_notify (vma, address, pte);/* Move the dirty bit to the physical page now the pte is gone. */ if (pte_dirty (pteval))@@ -844,12 +844,12 @@ static void try_to_unmap_cluster(unsigne page =vm_normal_page (vma, address, *pte);BUG_ON (!page ||PageAnon (page)); -if (ptep_clear_flush_young (vma, address, pte)) +if (ptep_clear_flush_young_notify (vma, address, pte))continue ;/* Nuke the page table entry. */ flush_cache_page (vma, address,pte_pfn (*pte)); - pteval =ptep_clear_flush (vma, address, pte); + pteval =ptep_clear_flush_notify (vma, address, pte);/* If nonlinear, store the file page offset in the pte. */ if (page->index !=linear_page_index (vma, address))
Re: [PATCH] mmu notifiers #v7
In reply to Andrea ArcangeliHi Izik & kvm-devel,
Just wanted to remind that if we'll converge on #v7, the ksm code in replace_page will have to call ptep_clear_flush_notify too (just like do_wp_page).
-------------------------------------------------------------------------
This SF.net email is sponsored by: Microsoft Defy all challenges. Microsoft(R) Visual Studio 2008.
http://clk.atdmt.com/MRT/go/vse0120000070mrt/direct/01/
Re: [PATCH] mmu notifiers #v7
In reply to Andrea ArcangeliQuoted 5 lines · Andrea Arcangeli
I hope this will can be considered final for .25 and be merged. Risk is zero, the only discussion here is to make an API that will last forever, functionality-wise all these patches provides zero risk and zero overhead when MMU_NOTIFIER=n. This last patch covers KVM and GRU and hopefully all other non-blocking users optimally, and the below
Ok so it somehow works slowly with GRU and you are happy with it. What about the RDMA folks etc etc?
Quoted 3 lines
API will hopefully last forever (but even if it lasts just for .25 and .26 is changed that's fine with us, it's a kernel _internal_ API anyway, there's absolutely nothing visible to userland).
Would it not be better to have a solution that fits all instead of hacking something in now and then having to modify it later?
Quoted 8 lines
What Christoph need to do when he's back from vacations to support sleepable mmu notifiers is to add a
CONFIG_XPMEMconfig option that will switch thei_mmap_lockfrom a semaphore to a mutex (any other change to this patch will be minor compared to that) so XPMEM hardware will have kernels compiled that way. I don't see other sane ways to remove the "atomic" parameter from the API (apparently required by Andrew for merging something not restricted to the xpmem current usage with only anonymous memory) and I don't want to have such a
Quoted 8 lines
locking-change intrusive dependency for all other non-blocking users that are fine without having to alter how the VM works (for example KVM and GRU). Very minor changes will be required to this patch to make it work after the VM locking will be altered (for example the
CONFIG_XPMEMshould also switch the mmu_register/unregister locking from RCU to mutex as well). XPMEM then will only compile ifCONFIG_XPMEM=yand in turn the invalidate_range_* will support scheduling inside.
Hmmm.. There were earlier discussions of changing the anon vma lock to a rw lock because of contention issues in large systems. Maybe we can just generally switch the locks taken while walking rmaps to semaphores? That would still require to put the invalidate outside of the pte lock.
-------------------------------------------------------------------------
This SF.net email is sponsored by: Microsoft Defy all challenges. Microsoft(R) Visual Studio 2008.
http://clk.atdmt.com/MRT/go/vse0120000070mrt/direct/01/
Re: [kvm-devel] [PATCH] mmu notifiers #v7
Quoted 1 line · Christoph Lameter
Ok so it somehow works slowly with GRU and you are happy with it. What
As far as GRU is concerned, performance is the same as with your patch (Jack can confirm).
Quoted 1 line
about the RDMA folks etc etc?
If RDMA/IB folks needed to block in invalidate_range, I guess they need to do so on top of tmpfs too, and that never worked with your patch anyway.
Quoted 2 lines
Would it not be better to have a solution that fits all instead of hacking something in now and then having to modify it later?
The whole point is that your solution fits only GRU and KVM too.
XPMEM in your patch works in a hacked mode limited to anonymous memory only, Robin already received incoming mail asking to allow xpmem to work on more than anonymous memory, so your solution-that-fits-all doesn't actually fit some of Robin's customer needs. So if it doesn't even entirely satisfy xpmem users, imagine the other potential blocking-users of this code.
Quoted 4 lines
Hmmm.. There were earlier discussions of changing the anon vma lock to a rw lock because of contention issues in large systems. Maybe we can just generally switch the locks taken while walking rmaps to semaphores? That would still require to put the invalidate outside of the pte lock.
anon_vma lock can remain a spinlock unless you also want to schedule inside try_to_unmap.
If converting the i_mmap_lock to a mutex is a big trouble, another way that might work to allow invalidate_range to block, would be to try to boost the mm_users to prevent the mmu_notifier_release to run in another cpu the moment after i_mmap_lock spinlock is unlocked. But even if that works, it'll run slower and the mmu notifiers RCU locking should be switched to a mutex, so it'd be nice to have it as a separate option.
Re: [kvm-devel] [PATCH] mmu notifiers #v7
Quoted 3 lines · Andrea Arcangeli
If RDMA/IB folks needed to block in
invalidate_range, I guess they need to do so on top of tmpfs too, and that never worked with your patch anyway.
How about blocking in invalidate_page()? It can be made to work...
Quoted 4 lines
Quoted 2 lines
Would it not be better to have a solution that fits all instead of hacking something in now and then having to modify it later?
The whole point is that your solution fits only GRU and KVM too.
Well so we do not address the issues?
Quoted 6 lines
XPMEM in your patch works in a hacked mode limited to anonymous memory only, Robin already received incoming mail asking to allow xpmem to work on more than anonymous memory, so your solution-that-fits-all doesn't actually fit some of Robin's customer needs. So if it doesn't even entirely satisfy xpmem users, imagine the other potential blocking-users of this code.
The solutions have been mentioned...
Quoted 2 lines
anon_vmalock can remain a spinlock unless you also want to schedule insidetry_to_unmap.
Either that or a separate rmap as also mentioned before.
Re: [kvm-devel] [PATCH] mmu notifiers #v7
Quoted 7 lines · Christoph Lameter
Quoted 3 lines · Andrea Arcangeli
If RDMA/IB folks needed to block in
invalidate_range, I guess they need to do so on top of tmpfs too, and that never worked with your patch anyway.How about blocking in
invalidate_page()? It can be made to work...
Yes, it can be made to work with even more extended VM changes than to only allow invalidate_range to schedule. Those core VM changes should only be done "by default" (w/o CONFIG_XPMEM=y), if they're doing good to the VM regardless of xpmem requirements. And I'm not really sure of that. I think they don't do any good or they would be a mutex already...
Quoted 1 line
Well so we do not address the issues?
I'm not suggesting not to address the issues, just that those issues requires VM core changes, and likely those changes should be switchable under a CONFIG_XPMEM, so I see no reason to delay the mmu notifier until those changes are done and merged too. It's kind of a separate problem.
Quoted 1 line
Either that or a separate rmap as also mentioned before.
DRI also wants invalidate_page by (mm,addr).
Re: [kvm-devel] [PATCH] mmu notifiers #v7
Quoted 5 lines · Andrea Arcangeli
I'm not suggesting not to address the issues, just that those issues requires VM core changes, and likely those changes should be switchable under a
CONFIG_XPMEM, so I see no reason to delay the mmu notifier until those changes are done and merged too. It's kind of a separate problem.
No its the core problem of the mmu notifier. It needs to be usable for a lot of scenarios.
Re: [PATCH] mmu notifiers #v7
In reply to Andrea ArcangeliQuoted 8 lines · Andrea Arcangeli
What Christoph need to do when he's back from vacations to support sleepable mmu notifiers is to add a
CONFIG_XPMEMconfig option that will switch thei_mmap_lockfrom a semaphore to a mutex (any other change to this patch will be minor compared to that) so XPMEM hardware will have kernels compiled that way. I don't see other sane ways to remove the "atomic" parameter from the API (apparently required by Andrew for merging something not restricted to the xpmem current usage with only anonymous memory) and I don't want to have such a
Quoted 8 lines
locking-change intrusive dependency for all other non-blocking users that are fine without having to alter how the VM works (for example KVM and GRU). Very minor changes will be required to this patch to make it work after the VM locking will be altered (for example the
CONFIG_XPMEMshould also switch the mmu_register/unregister locking from RCU to mutex as well). XPMEM then will only compile ifCONFIG_XPMEM=yand in turn the invalidate_range_* will support scheduling inside.
This is not going to work even if the mutex would work as easily as you think since the patch here still does an rcu_lock/unlock around a callback.
Quoted 6 lines
I don't think pretending to merge all in one block (I mean including xpmem support that requires blocking methods) is good idea anymore as long as we agree the "atomic" parameter shouldn't be merged. But we can quite easily agree on the below to be optimal for GRU/KVM and trivially extendible once a
CONFIG_XPMEMwill be added. So this first part can go in now I think.
Changing the locking for the callouts for users of the mmu notivier that f.e. require a response via the network (RDMA, XPMEM etc) is not trivial at all. RCU lock cannot be used. So we are looking at totally disjunct methods for those users who have to sleep.
include/linux/mmu_notifier.h line 15
Quoted 7 lines
include/linux/mmu_notifier.h new file +7
-0+struct mmu_notifier_ops { +/* +* Called when nobody can register any more notifier in the mm +* and after the "mn" notifier has been disarmed already. +*/ +void (*release)(struct mmu_notifier *mn, +struct mm_struct *mm);
Who disarms the notifier? Why is the method not called to disarm the notifier on exit?
mm/filemap_xip.c __xip_unmap (struct address_space * mapp · line 200
Quoted 13 lines
mm/Makefile +1
-0+obj-$(CONFIG_MMU_NOTIFIER) += mmu_notifier.omm/filemap_xip.c +1
-1@@ -194,7 +194,7 @@ __xip_unmap (struct address_space * mappif (pte) {/* Nuke the page table entry. */ flush_cache_page (vma, address,pte_pfn (*pte)); - pteval =ptep_clear_flush (vma, address, pte); + pteval =ptep_clear_flush_notify (vma, address, pte);page_remove_rmap (page, vma);dec_mm_counter (mm, file_rss);BUG_ON (pte_dirty (pteval));
Well a bit better but now we have to modify both the macro and the code in teh VM. It would be easier to put the notify call in here.
mm/filemap_xip.c void exit_mmap(struct mm_struct *mm) · line 2053
Quoted 5 lines
mm/filemap_xip.c +1
-0@@ -2048,6 +2050,7 @@ void exit_mmap(struct mm_struct *mm)vm_unacct_memory (nr_accounted);free_pgtables (&tlb, vma,FIRST_USER_ADDRESS ,0 );tlb_finish_mmu (tlb,0 , end); +mmu_notifier_release (mm);
The release should be called much earlier to allow the driver to release all resources in one go. This way each vma must be processed individually. For our gobs of memory this method may create a scaling problem on exit().
Re: [PATCH] mmu notifiers #v7
Quoted 1 line · Christoph Lameter
Quoted 1 line
make it work after the VM locking will be altered (for example the
^^^^^^^^^^^^^^^
Quoted 1 line
Quoted 1 line
CONFIG_XPMEMshould also switch the mmu_register/unregister locking
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Quoted 1 line
Quoted 1 line
from RCU to mutex as well). XPMEM then will only compile if
^^^^^^^^^^^^^^^^^^^^^^^^^
Quoted 5 lines
Quoted 2 lines
CONFIG_XPMEM=yand in turn the invalidate_range_* will support scheduling inside.This is not going to work even if the mutex would work as easily as you think since the patch here still does an rcu_lock/unlock around a callback.
See underlined.
Quoted 10 lines
Review
Quoted 7 lines
(quoted diff) +7
-0+struct mmu_notifier_ops { + /* + * Called when nobody can register any more notifier in the mm + * and after the "mn" notifier has been disarmed already. + */ + void (*release)(struct mmu_notifier *mn, + struct mm_struct *mm);Who disarms the notifier? Why is the method not called to disarm the notifier on exit?
The notifier is auto-disarmed by mmu_notifier_release, your patch works the same way. ->release is further called just in case anybody wants to know the notifier was disarmed.
Quoted 9 lines
Review void exit_mmap(struct mm_struct *mm) · line 2053
Quoted 5 lines
(quoted diff) +1
-0@@ -2048,6 +2050,7 @@ void exit_mmap(struct mm_struct *mm) vm_unacct_memory(nr_accounted); free_pgtables(&tlb, vma, FIRST_USER_ADDRESS, 0); tlb_finish_mmu(tlb, 0, end); + mmu_notifier_release(mm);The release should be called much earlier to allow the driver to release all resources in one go. This way each vma must be processed individually. For our gobs of memory this method may create a scaling problem on
exit().
Good point, it has to be called earlier for GRU, but it's not a performance issue. GRU doesn't pin the pages so it should make the global invalidate in ->release _before_ unmap_vmas. Linux can't fault in the ptes anymore because mm_users is zero so there's no need of a ->release_begin/end, the _begin is enough.
In #v6 I was invalidating inside unmap_vmas so it was ok. The performance issues you're talking about refers to #v6 I guess, for #v7 there's a single call.
Thanks!
mm/mmap.c +1 -1
@@ -2039,6 +2039,7 @@ void exit_mmap(struct mm_struct *mm)unsigned long end;/* mm's last user has gone, and its about to be pulled down */ +mmu_notifier_release (mm);arch_exit_mmap (mm);lru_add_drain ();
@@ -2050,7 +2051,6 @@ void exit_mmap(struct mm_struct *mm)vm_unacct_memory (nr_accounted);free_pgtables (&tlb, vma,FIRST_USER_ADDRESS ,0 );tlb_finish_mmu (tlb,0 , end); -mmu_notifier_release (mm);/* * Walk the list again, actually closing and freeing it,
Re: [PATCH] mmu notifiers #v7
Quoted 4 lines · Andrea Arcangeli
Quoted 2 lines
This is not going to work even if the mutex would work as easily as you think since the patch here still does an rcu_lock/unlock around a callback.
See underlined.
Mutex is not acceptable for performance reasons. I think we can just drop the RCU lock if we simply unregister the mmu notifier in release and forbid the drivers from removing themselves from the notification chain. They can simply do nothing until release. At that time there is no concurrency and thus its safe to remove even without rcu locking.
Quoted 5 lines
Good point, it has to be called earlier for GRU, but it's not a performance issue. GRU doesn't pin the pages so it should make the global invalidate in ->release _before_
unmap_vmas. Linux can't fault in the ptes anymore becausemm_usersis zero so there's no need of a ->release_begin/end, the_beginis enough.
I do not follow you about the _begin without end but the following fix seems okay.
Re: [PATCH] mmu notifiers #v7
In reply to Andrea ArcangeliQuoted 10 lines
Quoted 3 lines
The release should be called much earlier to allow the driver to release all resources in one go. This way each vma must be processed individually. For our gobs of memory this method may create a scaling problem on
exit().Good point, it has to be called earlier for GRU, but it's not a performance issue. GRU doesn't pin the pages so it should make the global invalidate in ->release _before_
unmap_vmas. Linux can't fault in the ptes anymore becausemm_usersis zero so there's no need of a ->release_begin/end, the_beginis enough.
I disagree. The location of the callout IS a performance issue. In simple comparisons of the 2 patches (Christoph's vs. Andrea's), Andrea's has a 7X increase in the number of TLB purges being issued to the GRU. TLB flushing is slow and can impact the performance of of tasks using the GRU.
--- jack
Re: [PATCH] mmu notifiers #v7
Quoted 3 lines · Jack Steiner
I disagree. The location of the callout IS a performance issue. In simple comparisons of the 2 patches (Christoph's vs. Andrea's), Andrea's has a 7X increase in the number of TLB purges being issued to the GRU. TLB flushing
Are you sure that you're referring to #v7?
Re: [PATCH] mmu notifiers #v7
Quoted 6 lines · Andrea Arcangeli
Quoted 3 lines · Jack Steiner
I disagree. The location of the callout IS a performance issue. In simple comparisons of the 2 patches (Christoph's vs. Andrea's), Andrea's has a 7X increase in the number of TLB purges being issued to the GRU. TLB flushing
Are you sure that you're referring to #v7?
Jack: AFAICT Andrea moved the release callout and things will be fine in the next release.
Re: [PATCH] mmu notifiers #v7
In reply to Andrea Arcangeliinclude/linux/mmu_notifier.h line 66
Quoted 4 lines · Andrea Arcangeli
include/linux/mmu_notifier.h new file +4
-0+struct mmu_notifier_head { +struct hlist_head head; +spinlock_t lock; +};
Still think that the lock here is not of too much use and can be easily replaced by mmap_sem.
Quoted 7 lines
include/linux/mmu_notifier.h new file +7
-0+#define mmu_notifier(function, mm, args...) \ + do { \ + struct mmu_notifier *__mn; \ + struct hlist_node *__n; \ + \ + if (unlikely(!hlist_empty(&(mm)->mmu_notifier.head))) { \ + rcu_read_lock(); \
include/linux/mmu_notifier.h line 110
Quoted 10 lines
include/linux/mmu_notifier.h new file +10
-0+hlist_for_each_entry_rcu (__mn, __n, \ + &(mm)->mmu_notifier.head, \ + hlist) \ +if (__mn->ops->function) \ + __mn->ops->function (__mn, \ + mm, \ + args); \ +rcu_read_unlock (); \ + } \ + }while (0 )
Andrew recomended local variables for parameters used multile times. This means the mm parameter here.
Quoted 5 lines
include/linux/mmu_notifier.h new file +5
-0+/* +* Notifiers that use the parameters that they were passed so that the +* compiler does not complain about unused variables but does proper +* parameter checks even if !CONFIG_MMU_NOTIFIER. +* Macros generate no code.
include/linux/mmu_notifier.h line 152
Quoted 10 lines
include/linux/mmu_notifier.h new file +10
-0+*/ +#define mmu_notifier(function, mm, args...) \ + do { \ + if (0) { \ + struct mmu_notifier *__mn; \ + \ + __mn = (struct mmu_notifier *)(0x00ff); \ + __mn->ops->function(__mn, mm, args); \ + }; \ + } while (0)
Note also Andrew's comments on the use of 0x00ff...
Quoted 8 lines
mm/mmu_notifier.c new file +8
-0+/* +* No synchronization. This function can only be called when only a single +* process remains that performs teardown. +*/ +void mmu_notifier_release (struct mm_struct *mm) +{ +struct mmu_notifier *mn; +struct hlist_node *n, *tmp;
mm/mmu_notifier.c line 33
Quoted 10 lines
mm/mmu_notifier.c new file +10
-0+ +if (unlikely (!hlist_empty (&mm->mmu_notifier.head))) { +hlist_for_each_entry_safe (mn, n, tmp, + &mm->mmu_notifier.head, hlist) { +hlist_del (&mn->hlist); +if (mn->ops->release) + mn->ops->release (mn, mm); + } + } +}
One could avoid a hlist_for_each_entry_safe here by simply always deleting the first object.
Also re the _notify variants: The binding to pte_clear_flush_young etc will become a problem for notifiers that want to sleep because pte_clear_flush is usually called with the pte lock held. See f.e. try_to_unmap_one, page_mkclean_one etc.
It would be better if the notifier calls could be moved outside of the pte lock.
Re: [PATCH] mmu notifiers #v7
Quoted 2 lines · Christoph Lameter
Still think that the lock here is not of too much use and can be easily replaced by
mmap_sem.
I can use the mmap_sem.
Quoted 12 lines
Quoted 12 lines
include/linux/mmu_notifier.h new file +12
-0+#define mmu_notifier(function, mm, args...) \ + do { \ + struct mmu_notifier *__mn; \ + struct hlist_node *__n; \ + \ + if (unlikely(!hlist_empty(&(mm)->mmu_notifier.head))) { \ + rcu_read_lock(); \ + hlist_for_each_entry_rcu(__mn, __n, \ + &(mm)->mmu_notifier.head, \ + hlist) \ + if (__mn->ops->function) \ + __mn->ops->function(__mn, \
Quoted 8 lines
include/linux/mmu_notifier.h line 110
Quoted 5 lines
include/linux/mmu_notifier.h new file +5
-0+ mm, \ + args); \ +rcu_read_unlock (); \ + } \ + }while (0 )Andrew recomended local variables for parameters used multile times. This means the mm parameter here.
I don't exactly see what "buggy macro" meant? I already use parenthesis as needed to avoid the need of local variables to be safe. Not really sure what's buggy, sorry!
Quoted 1 line
Note also Andrew's comments on the use of
0x00ff...
I thought I tried the (void) but it didn't work and your solution worked, but perhaps I did something wrong, I'll try again with (void) nevertheless.
Quoted 13 lines
Quoted 13 lines
mm/mmu_notifier.c new file +13
-0+/* +* No synchronization. This function can only be called when only a single +* process remains that performs teardown. +*/ +void mmu_notifier_release (struct mm_struct *mm) +{ +struct mmu_notifier *mn; +struct hlist_node *n, *tmp; + +if (unlikely (!hlist_empty (&mm->mmu_notifier.head))) { +hlist_for_each_entry_safe (mn, n, tmp, + &mm->mmu_notifier.head, hlist) { +hlist_del (&mn->hlist);
Quoted 8 lines
mm/mmu_notifier.c line 33
Quoted 5 lines
mm/mmu_notifier.c new file +5
-0+if (mn->ops->release) + mn->ops->release (mn, mm); + } + } +}One could avoid a
hlist_for_each_entry_safehere by simply always deleting the first object.
Agreed, the current construct come from the fact we previously didn't assume nobody could ever call mmu_notifier_unregister by the time mm_users is 0.
Quoted 4 lines
Also re the
_notifyvariants: The binding topte_clear_flush_youngetc will become a problem for notifiers that want to sleep becausepte_clear_flushis usually called with the pte lock held. See f.e.try_to_unmap_one,page_mkclean_oneetc.
Calling __free_page out of the PT lock is much bigger change. do_wp_page will require changes anyway when the sleepable notifiers are merged.
Quoted 2 lines
It would be better if the notifier calls could be moved outside of the pte lock.
The point is that it can't make a difference right now, and my objective was to avoid unnecessary source code duplication (later it will be necessary, right now it isn't). By the time you rework do_wp_page, removing _notify will be a very minor detail compared to the rest of the changes to do_wp_page IMHO. Expanding it now won't provide a real advantage later.
-------------------------------------------------------------------------
This SF.net email is sponsored by: Microsoft Defy all challenges. Microsoft(R) Visual Studio 2008.
http://clk.atdmt.com/MRT/go/vse0120000070mrt/direct/01/
Andrew Morton
maintainerRe: [PATCH] mmu notifiers #v7
Quoted 21 lines · On Fri, 29 Feb 2008 01:40:01 +0100 Andrea Arcangeli <[email protected]> wrote:
Quoted 12 lines
Quoted 12 lines
include/linux/mmu_notifier.h new file +12
-0+#define mmu_notifier(function, mm, args...) \ + do { \ + struct mmu_notifier *__mn; \ + struct hlist_node *__n; \ + \ + if (unlikely(!hlist_empty(&(mm)->mmu_notifier.head))) { \ + rcu_read_lock(); \ + hlist_for_each_entry_rcu(__mn, __n, \ + &(mm)->mmu_notifier.head, \ + hlist) \ + if (__mn->ops->function) \ + __mn->ops->function(__mn, \Quoted 8 lines
include/linux/mmu_notifier.h line 110
Quoted 5 lines
include/linux/mmu_notifier.h new file +5
-0+ mm, \ + args); \ +rcu_read_unlock (); \ + } \ + }while (0 )Andrew recomended local variables for parameters used multile times. This means the mm parameter here.
Quoted 1 line
I don't exactly see what "buggy macro" meant?
multiple refernces to the argument, so
mmu_notifier(foo, bar(), zot);
will call bar() either once or twice.
Unlikely in this case, but bad practice. Easily fixable by using another temporary.
Re: [PATCH] mmu notifiers #v7
In reply to Andrea ArcangeliQuoted 8 lines · Andrea Arcangeli
Quoted 4 lines
Also re the
_notifyvariants: The binding topte_clear_flush_youngetc will become a problem for notifiers that want to sleep becausepte_clear_flushis usually called with the pte lock held. See f.e.try_to_unmap_one,page_mkclean_oneetc.Calling
__free_pageout of the PT lock is much bigger change.do_wp_pagewill require changes anyway when the sleepable notifiers are merged.
I thought you wanted to get rid of the sync via pte lock? What changes to do_wp_page do you envision?
Quoted 9 lines
Quoted 2 lines
It would be better if the notifier calls could be moved outside of the pte lock.
The point is that it can't make a difference right now, and my objective was to avoid unnecessary source code duplication (later it will be necessary, right now it isn't). By the time you rework
do_wp_page, removing_notifywill be a very minor detail compared to the rest of the changes todo_wp_pageIMHO. Expanding it now won't provide a real advantage later.
What is the trouble with the current do_wp_page modifications? There is no need for invalidate_page() there so far. invalidate_range() does the trick there.
-------------------------------------------------------------------------
This SF.net email is sponsored by: Microsoft Defy all challenges. Microsoft(R) Visual Studio 2008.
http://clk.atdmt.com/MRT/go/vse0120000070mrt/direct/01/
Re: [PATCH] mmu notifiers #v7
Quoted 1 line · Christoph Lameter
I thought you wanted to get rid of the sync via pte lock?
Sure. _notify is happening inside the pt lock by coincidence, to reduce the changes to mm/* as long as the mmu notifiers aren't sleep capable.
Quoted 1 line
What changes to
do_wp_pagedo you envision?
Converting it to invalidate_range_begin/end.
Quoted 3 lines
What is the trouble with the current
do_wp_pagemodifications? There is no need forinvalidate_page()there so far.invalidate_range()does the trick there.
No trouble, it's just that I didn't want to mangle over the logic of do_wp_page unless it was strictly required, the patch has to be obviously safe. You need to keep that bit of your patch to make the mmu notifiers sleepable.
Re: [PATCH] mmu notifiers #v7
Quoted 6 lines · Andrea Arcangeli
Quoted 1 line · Christoph Lameter
I thought you wanted to get rid of the sync via pte lock?
Sure.
_notifyis happening inside the pt lock by coincidence, to reduce the changes tomm/*as long as the mmu notifiers aren't sleep capable.
Ok if this is a coincidence then it would be better to separate the notifier callouts from the pte macro calls.
PATCH 1/1 mmu notifiers #v8
In reply to Andrea ArcangeliDifference between #v7 and #v8:
1) s/age_page/clear_flush_young/ (Nick's suggestion)
2) macro fix (Andrew)
3) move release before final unmap_vmas (for GRU, Jack/Christoph)
4) microoptimize mmu_notifier_unregister (Christoph)
5) use mmap_sem for registration serialization (Christoph)
The (void)xxx in macros doesn't work with "args". Christoph's solution look best in avoiding warnings, even if it forces to make the mmu notifier operation structure visible even if MMU_NOTIFIER=n (that's the only downside).
I didn't drop invalidate_page, because invalidate_range_begin/end would be slower for usages like KVM/GRU (we don't need a begin/end there because where invalidate_page is called, the VM holds a reference on the page). do_wp_page should also use invalidate_page since it can free the page after dropping the PT lock without losing any performance (that's not true for the places where invalidate_range is called).
It'd be nice if everyone involved can agree to converge on this API for .25. KVM/GRU (and perhaps Quadrics) and similar usages will be fully covered in .25. This is a kernel internal API so there's no problem if all the methods will become sleep capable only starting only in .26. The brainer part of the VM work to do to make it sleep capable is pretty much orthogonal with this patch.
include/linux/mm_types.h +3 -0
@@ -10,6 +10,7 @@#include <linux/rbtree.h> #include <linux/rwsem.h> #include <linux/completion.h> +#include <linux/mmu_notifier.h> #include <asm/page.h> #include <asm/mmu.h>
@@ -228,6 +229,8 @@ struct mm_struct {#ifdef CONFIG_CGROUP_MEM_CONT struct mem_cgroup *mem_cgroup;#endif + + struct mmu_notifier_head mmu_notifier; /* MMU notifier list */ };#endif /* _LINUX_MM_TYPES_H */
include/linux/mmu_notifier.h new file +161 -0
161 lines not on this page · raw message
kernel/fork.c +1 -0
@@ -362,6 +362,7 @@ static struct mm_struct * mm_init(structif (likely (!mm_alloc_pgd (mm))) { mm->def_flags =0 ; +mmu_notifier_head_init (&mm->mmu_notifier);return mm; }
mm/Kconfig +4 -0
@@ -193,3 +193,7 @@ config VIRT_TO_BUS config VIRT_TO_BUS def_bool y depends on !ARCH_NO_VIRT_TO_BUS + +config MMU_NOTIFIER + def_bool y + bool "MMU notifier, for paging KVM/RDMA"
mm/Makefile +1 -1
@@ -33,4 +33,4 @@ obj-$(CONFIG_SMP) += allocpercpu.o obj-$(CONFIG_SMP) += allocpercpu.o obj-$(CONFIG_QUICKLIST) += quicklist.o obj-$(CONFIG_CGROUP_MEM_CONT) += memcontrol.o - +obj-$(CONFIG_MMU_NOTIFIER) += mmu_notifier.o
mm/filemap_xip.c +1 -1
@@ -194,7 +194,7 @@ __xip_unmap (struct address_space * mappif (pte) {/* Nuke the page table entry. */ flush_cache_page (vma, address,pte_pfn (*pte)); - pteval =ptep_clear_flush (vma, address, pte); + pteval =ptep_clear_flush_notify (vma, address, pte);page_remove_rmap (page, vma);dec_mm_counter (mm, file_rss);BUG_ON (pte_dirty (pteval));
mm/fremap.c +2 -0
@@ -214,7 +214,9 @@ asmlinkage long sys_remap_file_pages(unsspin_unlock (&mapping->i_mmap_lock); } +mmu_notifier (invalidate_range_begin, mm, start, start + size); err =populate_range (mm, vma, start, size, pgoff); +mmu_notifier (invalidate_range_end, mm, start, start + size);if (!err && !(flags &MAP_NONBLOCK )) {if (unlikely (has_write_lock)) {downgrade_write (&mm->mmap_sem);
mm/hugetlb.c +2 -0
@@ -755,6 +755,7 @@ void __unmap_hugepage_range(struct vm_arBUG_ON (start & ~HPAGE_MASK );BUG_ON (end & ~HPAGE_MASK ); +mmu_notifier (invalidate_range_begin, mm, start, end);spin_lock (&mm->page_table_lock);for (address = start; address < end; address +=HPAGE_SIZE ) { ptep =huge_pte_offset (mm, address);
@@ -775,6 +776,7 @@ void __unmap_hugepage_range(struct vm_ar }spin_unlock (&mm->page_table_lock);flush_tlb_range (vma, start, end); +mmu_notifier (invalidate_range_end, mm, start, end);list_for_each_entry_safe (page, tmp, &page_list, lru) {list_del (&page->lru);put_page (page);
mm/memory.c +14 -2
@@ -611,6 +611,9 @@ int copy_page_range(struct mm_struct *dsif (is_vm_hugetlb_page (vma))return copy_hugetlb_page_range (dst_mm, src_mm, vma); +if (is_cow_mapping (vma->vm_flags)) +mmu_notifier (invalidate_range_begin, src_mm, addr, end); + dst_pgd =pgd_offset (dst_mm, addr); src_pgd =pgd_offset (src_mm, addr); do {
@@ -621,6 +624,11 @@ int copy_page_range(struct mm_struct *ds vma, addr, next))return -ENOMEM ; }while (dst_pgd++, src_pgd++, addr = next, addr != end); + +if (is_cow_mapping (vma->vm_flags)) +mmu_notifier (invalidate_range_end, src_mm, + vma->vm_start, end); +return 0 ; }
@@ -897,7 +905,9 @@ unsigned long zap_page_range(struct vm_alru_add_drain (); tlb =tlb_gather_mmu (mm,0 );update_hiwater_rss (mm); +mmu_notifier (invalidate_range_begin, mm, address, end); end =unmap_vmas (&tlb, vma, address, end, &nr_accounted, details); +mmu_notifier (invalidate_range_end, mm, address, end);if (tlb)tlb_finish_mmu (tlb, address, end);return end;
@@ -1463,10 +1473,11 @@ int apply_to_page_range(struct mm_struct {pgd_t *pgd;unsigned long next; -unsigned long end = addr + size; +unsigned long start = addr, end = addr + size;int err;BUG_ON (addr >= end); +mmu_notifier (invalidate_range_begin, mm, start, end); pgd =pgd_offset (mm, addr); do { next = pgd_addr_end(addr, end);
@@ -1474,6 +1485,7 @@ int apply_to_page_range(struct mm_structif (err)break ; }while (pgd++, addr = next, addr != end); +mmu_notifier (invalidate_range_end, mm, start, end);return err; }EXPORT_SYMBOL_GPL (apply_to_page_range);
@@ -1675,7 +1687,7 @@ gotten:* seen in the presence of one thread doing SMC and another * thread doing COW. */ -ptep_clear_flush (vma, address, page_table); +ptep_clear_flush_notify (vma, address, page_table);set_pte_at (mm, address, page_table, entry);update_mmu_cache (vma, address, entry);lru_cache_add_active (new_page);
mm/mmap.c +3 -0
@@ -1747,11 +1747,13 @@ static void unmap_region(struct mm_struclru_add_drain (); tlb =tlb_gather_mmu (mm,0 );update_hiwater_rss (mm); +mmu_notifier (invalidate_range_begin, mm, start, end);unmap_vmas (&tlb, vma, start, end, &nr_accounted,NULL );vm_unacct_memory (nr_accounted);free_pgtables (&tlb, vma, prev? prev->vm_end:FIRST_USER_ADDRESS , next? next->vm_start:0 );tlb_finish_mmu (tlb, start, end); +mmu_notifier (invalidate_range_end, mm, start, end); }/*
@@ -2037,6 +2039,7 @@ void exit_mmap(struct mm_struct *mm)unsigned long end;/* mm's last user has gone, and its about to be pulled down */ +mmu_notifier_release (mm);arch_exit_mmap (mm);lru_add_drain ();
mm/mmu_notifier.c new file +75 -0
75 lines not on this page · raw message
mm/mprotect.c +2 -0
@@ -198,10 +198,12 @@ success: dirty_accountable =1 ; } +mmu_notifier (invalidate_range_begin, mm, start, end);if (is_vm_hugetlb_page (vma))hugetlb_change_protection (vma, start, end, vma->vm_page_prot);else change_protection (vma, start, end, vma->vm_page_prot, dirty_accountable); +mmu_notifier (invalidate_range_end, mm, start, end);vm_stat_account (mm, oldflags, vma->vm_file, -nrpages);vm_stat_account (mm, newflags, vma->vm_file, nrpages);return 0 ;
mm/mremap.c +5 -0
@@ -74,6 +74,7 @@ static void move_ptes(struct vm_area_strstruct mm_struct *mm = vma->vm_mm;pte_t *old_pte, *new_pte, pte;spinlock_t *old_ptl, *new_ptl; +unsigned long old_start;if (vma->vm_file) {/*
@@ -100,6 +101,9 @@ static void move_ptes(struct vm_area_strspin_lock_nested (new_ptl,SINGLE_DEPTH_NESTING );arch_enter_lazy_mmu_mode (); + old_start = old_addr; +mmu_notifier (invalidate_range_begin, vma->vm_mm, + old_start, old_end);for (; old_addr < old_end; old_pte++, old_addr +=PAGE_SIZE , new_pte++, new_addr +=PAGE_SIZE ) {if (pte_none (*old_pte))
@@ -108,6 +112,7 @@ static void move_ptes(struct vm_area_str pte =move_pte (pte, new_vma->vm_page_prot, old_addr, new_addr);set_pte_at (mm, new_addr, new_pte, pte); } +mmu_notifier (invalidate_range_end, vma->vm_mm, old_start, old_end);arch_leave_lazy_mmu_mode ();if (new_ptl != old_ptl)
mm/rmap.c +6 -6
@@ -287,7 +287,7 @@ static int page_referenced_one(struct paif (vma->vm_flags &VM_LOCKED ) { referenced++; *mapcount =1 ;/* break early from loop */ - }else if (ptep_clear_flush_young (vma, address, pte)) + }else if (ptep_clear_flush_young_notify (vma, address, pte)) referenced++;/* Pretend the page is referenced if the task has the
@@ -454,7 +454,7 @@ static int page_mkclean_one(struct pagepte_t entry;flush_cache_page (vma, address,pte_pfn (*pte)); - entry =ptep_clear_flush (vma, address, pte); + entry =ptep_clear_flush_notify (vma, address, pte); entry =pte_wrprotect (entry); entry =pte_mkclean (entry);set_pte_at (mm, address, pte, entry);
@@ -712,14 +712,14 @@ static int try_to_unmap_one(struct page* skipped over this mm) then we should reactivate it. */ if (!migration && ((vma->vm_flags &VM_LOCKED ) || - (ptep_clear_flush_young (vma, address, pte)))) { + (ptep_clear_flush_young_notify (vma, address, pte)))) { ret =SWAP_FAIL ; goto out_unmap; }/* Nuke the page table entry. */ flush_cache_page (vma, address,page_to_pfn (page)); - pteval =ptep_clear_flush (vma, address, pte); + pteval =ptep_clear_flush_notify (vma, address, pte);/* Move the dirty bit to the physical page now the pte is gone. */ if (pte_dirty (pteval))
@@ -844,12 +844,12 @@ static void try_to_unmap_cluster(unsigne page =vm_normal_page (vma, address, *pte);BUG_ON (!page ||PageAnon (page)); -if (ptep_clear_flush_young (vma, address, pte)) +if (ptep_clear_flush_young_notify (vma, address, pte))continue ;/* Nuke the page table entry. */ flush_cache_page (vma, address,pte_pfn (*pte)); - pteval =ptep_clear_flush (vma, address, pte); + pteval =ptep_clear_flush_notify (vma, address, pte);/* If nonlinear, store the file page offset in the pte. */ if (page->index !=linear_page_index (vma, address))
Re: [PATCH] mmu notifiers #v8 + xpmem
Here an example of the futher orthogonal work to do on top of #v8 during .26-rc to make the whole mmu notifier API sleep capable.
1) Every single ptep_clear_flush_young_notify and ptep_clear_flush_notify must be converted like the below. The below is the conversion of a single one. do_wp_page has been converted by Christoph already but with invalidate_range (should be changed to invalidate_page by releasing the refcount on the page after calling invalidate_page). Hope it's clear why I'd rather not depend on these changes to be merged in .25 in order to have the mmu notifier included in .25.
2) Then after all this conversion work is finished, it's trivial to delete ptep_clear_flush_young_notify and ptep_clear_flush_notify from mmu_notifier.h (they will be unused macros once the conversion is complete).
3) After that the VM has to be changed to convert anon_vma lock and i_mmap_lock spinlocks to mutex/rwsemaphore.
4) Then finally the mmu_notifier_unregister must be dropped to make the mmu notifier sleep capable with RCU in the mmu_notifier() fast path.
It's unclear at this point if 3/4 should be switchable and happening under a CONFIG_XPMEM or similar or if everyone will benefit from those spinlock becoming mutex (the only one that is certain to appreciate such a change is preempt-rt, the rest of the userbase I don't know for sure and I'd be more confortable with a TPC number comparison before doing such a chance by default, but I leave the commentary on such a change to linux-mm in a separate thread).
mm/rmap.c +11 -3
@@ -274,7 +274,7 @@ static int page_referenced_one(struct paunsigned long address;pte_t *pte;spinlock_t *ptl; -int referenced =0 ; +int referenced =0 , clear_flush_young =0 ; address =vma_address (page, vma);if (address == -EFAULT )
@@ -287,8 +287,11 @@ static int page_referenced_one(struct paif (vma->vm_flags &VM_LOCKED ) { referenced++; *mapcount =1 ;/* break early from loop */ - }else if (ptep_clear_flush_young_notify (vma, address, pte)) - referenced++; + }else { + clear_flush_young =1 ; +if (ptep_clear_flush_young (vma, address, pte)) + referenced++; + }/* Pretend the page is referenced if the task has the swap token and is in the middle of a page fault. */
@@ -298,6 +301,11 @@ static int page_referenced_one(struct pa (*mapcount)--;pte_unmap_unlock (pte, ptl); + +if (clear_flush_young) + referenced +=mmu_notifier_clear_flush_young (vma->vm_mm, + address); + out:return referenced; }
Re: [PATCH] mmu notifiers #v8 + xpmem
Quoted 2 lines · Andrea Arcangeli
4) Then finally the
mmu_notifier_unregistermust be dropped to make the mmu notifier sleep capable with RCU in themmu_notifier()fast path.
Or require PREEMPTIBLE_RCU, that can handle sleeps..
Re: [PATCH] mmu notifiers #v8
In reply to Andrea ArcangeliQuoted 8 lines · Andrea Arcangeli
Difference between #v7 and #v8:
1) s/age_page/clear_flush_young/ (Nick's suggestion)
2) macro fix (Andrew)
3) move release before finalunmap_vmas(for GRU, Jack/Christoph)
4) microoptimizemmu_notifier_unregister(Christoph)
5) usemmap_semfor registration serialization (Christoph)
Quoted 4 lines
The (void)xxx in macros doesn't work with "args". Christoph's solution look best in avoiding warnings, even if it forces to make the mmu notifier operation structure visible even if MMU_NOTIFIER=n (that's the only downside).
I have a couple of "cleanup" patches that change the structure of this to something I prefer. Others may not, but I'll post them for debate anyway.
Quoted 7 lines
I didn't drop
invalidate_page, because invalidate_range_begin/end would be slower for usages like KVM/GRU (we don't need a begin/end there because whereinvalidate_pageis called, the VM holds a reference on the page).do_wp_pageshould also useinvalidate_pagesince it can free the page after dropping the PT lock without losing any performance (that's not true for the places whereinvalidate_rangeis called).
I'm still not completely happy with this. I had a very quick look at the GRU driver, but I don't see why it can't be implemented more like the regular TLB model, and have TLB insertions depend on the linux pte, and do invalidates _after_ restricting permissions to the pte.
Ie. I'd still like to get rid of invalidate_range_begin, and get rid of invalidate calls from places where permissions are relaxed.
Quoted 3 lines
It'd be nice if everyone involved can agree to converge on this API for .25. KVM/GRU (and perhaps Quadrics) and similar usages will be fully covered in .25.
If we can agree on the API, then I don't see any reason why it can't go into 2.6.25, unless someome wants more time to review it (but 2.6.25 release should be quite far away still so there should be quite a bit of time).
Re: [PATCH] mmu notifiers #v8
Quoted 2 lines · Nick Piggin
to something I prefer. Others may not, but I'll post them for debate anyway.
Sure, thanks!
Quoted 8 lines
Quoted 7 lines
I didn't drop
invalidate_page, because invalidate_range_begin/end would be slower for usages like KVM/GRU (we don't need a begin/end there because whereinvalidate_pageis called, the VM holds a reference on the page).do_wp_pageshould also useinvalidate_pagesince it can free the page after dropping the PT lock without losing any performance (that's not true for the places whereinvalidate_rangeis called).
Quoted 8 lines
I'm still not completely happy with this. I had a very quick look at the GRU driver, but I don't see why it can't be implemented more like the regular TLB model, and have TLB insertions depend on the linux pte, and do invalidates _after_ restricting permissions to the pte.
Ie. I'd still like to get rid of
invalidate_range_begin, and get rid of invalidate calls from places where permissions are relaxed.
_begin exists because by the time _end is called, the VM already dropped the reference on the page. This way we can do a single invalidate no matter how large the range is. I don't see ways to remove _begin while still invoking _end a single time for the whole range.
Quoted 4 lines
If we can agree on the API, then I don't see any reason why it can't go into 2.6.25, unless someome wants more time to review it (but 2.6.25 release should be quite far away still so there should be quite a bit of time).
Cool! ;)
Re: [PATCH] mmu notifiers #v8
Quoted 20 lines · Andrea Arcangeli
Quoted 2 lines · Nick Piggin
to something I prefer. Others may not, but I'll post them for debate anyway.
Sure, thanks!
Quoted 14 lines
Quoted 7 lines
I didn't drop
invalidate_page, because invalidate_range_begin/end would be slower for usages like KVM/GRU (we don't need a begin/end there because whereinvalidate_pageis called, the VM holds a reference on the page).do_wp_pageshould also useinvalidate_pagesince it can free the page after dropping the PT lock without losing any performance (that's not true for the places whereinvalidate_rangeis called).I'm still not completely happy with this. I had a very quick look at the GRU driver, but I don't see why it can't be implemented more like the regular TLB model, and have TLB insertions depend on the linux pte, and do invalidates _after_ restricting permissions to the pte.
Quoted 8 lines
Quoted 2 lines
Ie. I'd still like to get rid of
invalidate_range_begin, and get rid of invalidate calls from places where permissions are relaxed.
_beginexists because by the time_endis called, the VM already dropped the reference on the page. This way we can do a single invalidate no matter how large the range is. I don't see ways to remove_beginwhile still invoking_enda single time for the whole range.
Is this just a GRU problem? Can't we just require them to take a ref on the page (IIRC Jack said GRU could be changed to more like a TLB model).
Re: [PATCH] mmu notifiers #v8
Quoted 3 lines · Nick Piggin
Is this just a GRU problem? Can't we just require them to take a ref on the page (IIRC Jack said GRU could be changed to more like a TLB model).
Yes, it's just a GRU problem, it tries to optimize performance by calling follow_page only in the fast path, and fallbacks to get_user_pages; put_page in the slow path. xpmem could also send the message in _begin and wait the message in _end, to reduce the wait time. But if you forge GRU to call get_user_pages only (like KVM does), the _begin can be removed. In theory we could also optimize KVM to use follow_page only if the pte is already established. I'm not sure how much that is a worthwhile optimization though.
However note that Quadrics also had a callback before and one after, so they may be using the callback before for similar optimizations. But functionality-wise _end is the only required bit if everyone takes refcounts like KVM and XPMEM do.
Re: [PATCH] mmu notifiers #v8
In reply to Nick PigginQuoted 30 lines · Nick Piggin
Quoted 20 lines · Andrea Arcangeli
Quoted 2 lines · Nick Piggin
to something I prefer. Others may not, but I'll post them for debate anyway.
Sure, thanks!
Quoted 14 lines
Quoted 7 lines
I didn't drop
invalidate_page, because invalidate_range_begin/end would be slower for usages like KVM/GRU (we don't need a begin/end there because whereinvalidate_pageis called, the VM holds a reference on the page).do_wp_pageshould also useinvalidate_pagesince it can free the page after dropping the PT lock without losing any performance (that's not true for the places whereinvalidate_rangeis called).I'm still not completely happy with this. I had a very quick look at the GRU driver, but I don't see why it can't be implemented more like the regular TLB model, and have TLB insertions depend on the linux pte, and do invalidates _after_ restricting permissions to the pte.
Quoted 8 lines
Quoted 2 lines
Ie. I'd still like to get rid of
invalidate_range_begin, and get rid of invalidate calls from places where permissions are relaxed.
_beginexists because by the time_endis called, the VM already dropped the reference on the page. This way we can do a single invalidate no matter how large the range is. I don't see ways to remove_beginwhile still invoking_enda single time for the whole range.
Quoted 3 lines
Is this just a GRU problem? Can't we just require them to take a ref on the page (IIRC Jack said GRU could be changed to more like a TLB model).
Maintaining a long-term reference on a page is a problem. The GRU does not currently maintain tables to track the pages for which dropins have been done.
The GRU has a large internal TLB and is designed to reference up to 8PB of memory. The size of the tables to track this many referenced pages would be a problem (at best).
Re: [PATCH] mmu notifiers #v8
Quoted 35 lines · Jack Steiner
Quoted 30 lines · Nick Piggin
Quoted 20 lines · Andrea Arcangeli
Quoted 2 lines · Nick Piggin
to something I prefer. Others may not, but I'll post them for debate anyway.
Sure, thanks!
Quoted 14 lines
Quoted 7 lines
I didn't drop
invalidate_page, because invalidate_range_begin/end would be slower for usages like KVM/GRU (we don't need a begin/end there because whereinvalidate_pageis called, the VM holds a reference on the page).do_wp_pageshould also useinvalidate_pagesince it can free the page after dropping the PT lock without losing any performance (that's not true for the places whereinvalidate_rangeis called).I'm still not completely happy with this. I had a very quick look at the GRU driver, but I don't see why it can't be implemented more like the regular TLB model, and have TLB insertions depend on the linux pte, and do invalidates _after_ restricting permissions to the pte.
Quoted 8 lines
Quoted 2 lines
Ie. I'd still like to get rid of
invalidate_range_begin, and get rid of invalidate calls from places where permissions are relaxed.
_beginexists because by the time_endis called, the VM already dropped the reference on the page. This way we can do a single invalidate no matter how large the range is. I don't see ways to remove_beginwhile still invoking_enda single time for the whole range.Quoted 3 lines
Is this just a GRU problem? Can't we just require them to take a ref on the page (IIRC Jack said GRU could be changed to more like a TLB model).
Quoted 6 lines
Maintaining a long-term reference on a page is a problem. The GRU does not currently maintain tables to track the pages for which dropins have been done.
The GRU has a large internal TLB and is designed to reference up to 8PB of memory. The size of the tables to track this many referenced pages would be a problem (at best).
Is it any worse a problem than the pagetables of the processes which have their virtual memory exported to GRU? AFAIKS, no; it is on the same magnitude of difficulty. So you could do it without introducing any fundamental problem (memory usage might be increased by some constant factor, but I think we can cope with that in order to make the core patch really nice and simple).
It is going to be really easy to add more weird and wonderful notifiers later that deviate from our standard TLB model. It would be much harder to remove them. So I really want to see everyone conform to this model first. Numbers and comparisons can be brought out afterwards if people want to attempt to make such changes.
Re: [PATCH] mmu notifiers #v8
Quoted 23 lines · Nick Piggin
Quoted 22 lines · Jack Steiner
Quoted 21 lines · Nick Piggin
Quoted 20 lines · Andrea Arcangeli
Quoted 2 lines · Nick Piggin
to something I prefer. Others may not, but I'll post them for debate anyway.
Sure, thanks!
Quoted 14 lines
Quoted 7 lines
I didn't drop
invalidate_page, because invalidate_range_begin/end would be slower for usages like KVM/GRU (we don't need a begin/end there because whereinvalidate_pageis called, the VM holds a reference on the page).do_wp_pageshould also useinvalidate_pagesince it can free the page after dropping the PT lock without losing any performance (that's not true for the places whereinvalidate_rangeis called).I'm still not completely happy with this. I had a very quick look at the GRU driver, but I don't see why it can't be implemented more like the regular TLB model, and have TLB insertions depend on the linux pte, and do invalidates _after_ restricting permissions to the pte.
Quoted 8 lines
Quoted 8 lines
Quoted 8 lines
Quoted 8 lines
Quoted 2 lines
Ie. I'd still like to get rid of
invalidate_range_begin, and get rid of invalidate calls from places where permissions are relaxed.
_beginexists because by the time_endis called, the VM already dropped the reference on the page. This way we can do a single invalidate no matter how large the range is. I don't see ways to remove_beginwhile still invoking_enda single time for the whole range.
The range invalidates have a performance advantage for the GRU. TLB invalidates on the GRU are relatively slow (usec) and interfere somewhat with the performance of other active GRU instructions. Invalidating a large chunk of addresses with a single GRU TLBINVAL operation is must faster than issuing a stream of single page TLBINVALs.
I expect this performance advantage will also apply to other users of mmuops.
Quoted 12 lines
Quoted 11 lines
Quoted 4 lines
Is this just a GRU problem? Can't we just require them to take a ref on the page (IIRC Jack said GRU could be changed to more like a TLB model).
Maintaining a long-term reference on a page is a problem. The GRU does not currently maintain tables to track the pages for which dropins have been done.
The GRU has a large internal TLB and is designed to reference up to 8PB of memory. The size of the tables to track this many referenced pages would be a problem (at best).
Quoted 6 lines
Is it any worse a problem than the pagetables of the processes which have their virtual memory exported to GRU? AFAIKS, no; it is on the same magnitude of difficulty. So you could do it without introducing any fundamental problem (memory usage might be increased by some constant factor, but I think we can cope with that in order to make the core patch really nice and simple).
Functionally, the GRU is very close to what I would consider to be the "standard TLB" model. Dropins and flushs map closely to processor dropins and flushes for cpus. The internal structure of the GRU TLB is identical to the TLB of existing cpus. Requiring the GRU driver to track dropins with long term page references seems to me a deviation from having the basic mmuops support a "standard TLB" model. AFAIK, no other processor requires this.
Tracking TLB dropins (and long term page references) could be done but it adds significant complexity and scaling issues. The size of the tables to track many TB (to PB) of memory can get large. If the memory is being referenced by highly threaded applications, then the problem becomes even more complex. Either tables must be replicated per-thread (and require even more memory), or the table structure becomes even more complex to deal with node locality, cacheline bouncing, etc.
Try to avoid a requirement to track dropins with long term page references.
Quoted 3 lines
It is going to be really easy to add more weird and wonderful notifiers later that deviate from our standard TLB model. It would be much harder to remove them. So I really want to see everyone conform to this model first.
Agree.
-------------------------------------------------------------------------
This SF.net email is sponsored by: Microsoft Defy all challenges. Microsoft(R) Visual Studio 2008.
http://clk.atdmt.com/MRT/go/vse0120000070mrt/direct/01/
Re: [PATCH] mmu notifiers #v8
Quoted 8 lines · Jack Steiner
The range invalidates have a performance advantage for the GRU. TLB invalidates on the GRU are relatively slow (usec) and interfere somewhat with the performance of other active GRU instructions. Invalidating a large chunk of addresses with a single GRU TLBINVAL operation is must faster than issuing a stream of single page TLBINVALs.
I expect this performance advantage will also apply to other users of mmuops.
In theory this would apply to kvm as well (coalesce tlb flush IPIs, lookup shadow page table once), but is it really a fast path? What triggers range operations for your use cases?
Signature · 1 line
error compiling committee.c: too many arguments to function
Re: [PATCH] mmu notifiers #v8
Quoted 11 lines · Avi Kivity
Quoted 10 lines · Jack Steiner
The range invalidates have a performance advantage for the GRU. TLB invalidates
on the GRU are relatively slow (usec) and interfere somewhat with the performance
of other active GRU instructions. Invalidating a large chunk of addresses with
a single GRU TLBINVAL operation is must faster than issuing a stream of single
page TLBINVALs.
Quoted 7 lines
Quoted 3 lines
I expect this performance advantage will also apply to other users of mmuops.
In theory this would apply to kvm as well (coalesce tlb flush IPIs, lookup shadow page table once), but is it really a fast path? What triggers range operations for your use cases?
Although not frequent, an unmap of a multiple TB object could be quite painful if each page was invalidated individually instead of 1 invalidate for the entire range. This is even worse if the application is threaded and the object has been reference by many GRUs (there are 16 GRU ports per node - each potentially has to be invalidated).
Forks (again, not frequent) would be another case.
-------------------------------------------------------------------------
This SF.net email is sponsored by: Microsoft Defy all challenges. Microsoft(R) Visual Studio 2008.
http://clk.atdmt.com/MRT/go/vse0120000070mrt/direct/01/
Re: [PATCH] mmu notifiers #v8
In reply to Jack SteinerQuoted 15 lines · Jack Steiner
Quoted 7 lines · Nick Piggin
Quoted 6 lines
Maintaining a long-term reference on a page is a problem. The GRU does not currently maintain tables to track the pages for which dropins have been done.
The GRU has a large internal TLB and is designed to reference up to 8PB of memory. The size of the tables to track this many referenced pages would be a problem (at best).
Quoted 6 lines
Is it any worse a problem than the pagetables of the processes which have their virtual memory exported to GRU? AFAIKS, no; it is on the same magnitude of difficulty. So you could do it without introducing any fundamental problem (memory usage might be increased by some constant factor, but I think we can cope with that in order to make the core patch really nice and simple).
Quoted 7 lines
Functionally, the GRU is very close to what I would consider to be the "standard TLB" model. Dropins and flushs map closely to processor dropins and flushes for cpus. The internal structure of the GRU TLB is identical to the TLB of existing cpus. Requiring the GRU driver to track dropins with long term page references seems to me a deviation from having the basic mmuops support a "standard TLB" model. AFAIK, no other processor requires this.
That is because the CPU TLBs have the mmu_gather batching APIs which avoid the problem. It would be possible to do something similar for GRU which would involve taking a reference for each page-to-be-invalidated in invalidate_page, and release them when you invalidate_range. Or else do some other scheme which makes mmu notifiers work similarly to the mmu gather API. But not just go an invent something completely different in the form of this invalidate_begin,clear linux pte,invalidate_end API.
Quoted 7 lines
Tracking TLB dropins (and long term page references) could be done but it adds significant complexity and scaling issues. The size of the tables to track many TB (to PB) of memory can get large. If the memory is being referenced by highly threaded applications, then the problem becomes even more complex. Either tables must be replicated per-thread (and require even more memory), or the table structure becomes even more complex to deal with node locality, cacheline bouncing, etc.
I don't think it would be that significant in terms of complexity or scaling.
For a quick solution, you could stick a radix tree in each of your mmu notifiers registered (ie. one per mm), which is indexed on virtual address
Quoted 1 line
Quoted 1 line
PAGE_SHIFT, and returns the struct page *. Size is no different than
page tables, and locking is pretty scalable.
After that, I would really like to see whether the numbers justify larger changes.
Re: [PATCH] mmu notifiers #v8
Quoted 24 lines · Nick Piggin
Quoted 15 lines · Jack Steiner
Quoted 7 lines · Nick Piggin
Quoted 6 lines
Maintaining a long-term reference on a page is a problem. The GRU does not currently maintain tables to track the pages for which dropins have been done.
The GRU has a large internal TLB and is designed to reference up to 8PB of memory. The size of the tables to track this many referenced pages would be a problem (at best).
Quoted 6 lines
Is it any worse a problem than the pagetables of the processes which have their virtual memory exported to GRU? AFAIKS, no; it is on the same magnitude of difficulty. So you could do it without introducing any fundamental problem (memory usage might be increased by some constant factor, but I think we can cope with that in order to make the core patch really nice and simple).
Quoted 7 lines
Functionally, the GRU is very close to what I would consider to be the "standard TLB" model. Dropins and flushs map closely to processor dropins and flushes for cpus. The internal structure of the GRU TLB is identical to the TLB of existing cpus. Requiring the GRU driver to track dropins with long term page references seems to me a deviation from having the basic mmuops support a "standard TLB" model. AFAIK, no other processor requires this.
Quoted 7 lines
That is because the CPU TLBs have the
mmu_gatherbatching APIs which avoid the problem. It would be possible to do something similar for GRU which would involve taking a reference for each page-to-be-invalidated ininvalidate_page, and release them when youinvalidate_range. Or else do some other scheme which makes mmu notifiers work similarly to the mmu gather API. But not just go an invent something completely different in the form of this invalidate_begin,clear linux pte,invalidate_end API.
Correct. If the mmu_gather were passed on the mmuops callout and the callout were done at the same point as the tlb_finish_mmu(), the GRU could efficiently work w/o the range invalidates. A range invalidate might still be slightly more efficient but not measureable so. The net difference is not worth the extra complexity of range callouts.
Quoted 13 lines
Quoted 9 lines
Tracking TLB dropins (and long term page references) could be done but it adds significant complexity and scaling issues. The size of the tables to track many TB (to PB) of memory can get large. If the memory is being referenced by highly threaded applications, then the problem becomes even more complex. Either tables must be replicated per-thread (and require even more memory), or the table structure becomes even more complex to deal with node locality, cacheline bouncing, etc.
I don't think it would be that significant in terms of complexity or scaling.
Quoted 7 lines
For a quick solution, you could stick a radix tree in each of your mmu notifiers registered (ie. one per mm), which is indexed on virtual address
Quoted 1 line
Quoted 1 line
PAGE_SHIFT, and returns the struct page *. Size is no different thanpage tables, and locking is pretty scalable.
After that, I would really like to see whether the numbers justify larger changes.
I'm still concerned about performance. Each dropin would first have to access an additional data structure that would most likely be non-node-local and non-cache-resident. The net effect would be measurable but not a killer.
I haven't thought about locking requirements for the radix tree. Most accesses would be read-only & updates infrequent. Any chance of an RCU-based radix implementation? Otherwise, don't we add the potential for hot locks/cachelines for threaded applications ???
Re: [PATCH] mmu notifiers #v8
Quoted 4 lines · Jack Steiner
I haven't thought about locking requirements for the radix tree. Most accesses would be read-only & updates infrequent. Any chance of an RCU-based radix implementation? Otherwise, don't we add the potential for hot locks/cachelines for threaded applications ???
The current radix tree implementation in the kernel is RCU capable. We just don't have many RCU users yet.
Re: [PATCH] mmu notifiers #v8
Quoted 10 lines · Peter Zijlstra
Quoted 4 lines · Jack Steiner
I haven't thought about locking requirements for the radix tree. Most accesses would be read-only & updates infrequent. Any chance of an RCU-based radix implementation? Otherwise, don't we add the potential for hot locks/cachelines for threaded applications ???
The current radix tree implementation in the kernel is RCU capable. We just don't have many RCU users yet.
Ahhh. You are right. I thought I looked but obviously missed it.
-------------------------------------------------------------------------
This SF.net email is sponsored by: Microsoft Defy all challenges. Microsoft(R) Visual Studio 2008.
http://clk.atdmt.com/MRT/go/vse0120000070mrt/direct/01/
Re: [PATCH] mmu notifiers #v8
In reply to Nick PigginQuoted 5 lines · Nick Piggin
It is going to be really easy to add more weird and wonderful notifiers later that deviate from our standard TLB model. It would be much harder to remove them. So I really want to see everyone conform to this model first. Numbers and comparisons can be brought out afterwards if people want to attempt to make such changes.
Still do not see how that could be done. The model here is tightly bound to ptes. AFAICT this could be implemented in arch code like the paravirt ops.
Re: [PATCH] mmu notifiers #v8
In reply to Nick PigginQuoted 8 lines · Nick Piggin
I'm still not completely happy with this. I had a very quick look at the GRU driver, but I don't see why it can't be implemented more like the regular TLB model, and have TLB insertions depend on the linux pte, and do invalidates _after_ restricting permissions to the pte.
Ie. I'd still like to get rid of
invalidate_range_begin, and get rid of invalidate calls from places where permissions are relaxed.
Isnt this more a job for paravirt ops if it is so tightly bound to page tables? Are we not adding another similar API?
Quoted 4 lines
If we can agree on the API, then I don't see any reason why it can't go into 2.6.25, unless someome wants more time to review it (but 2.6.25 release should be quite far away still so there should be quite a bit of time).
API still has rcu issues and the example given for making things sleepable is only working for the aging callback. The most important callback is for try_to_unmao and page_mkclean. This means the API is still not generic enough and likely not extendable as needed in its present form.
Re: [PATCH] mmu notifiers #v8
Quoted 4 lines · Christoph Lameter
API still has rcu issues and the example given for making things sleepable is only working for the aging callback. The most important callback is for
try_to_unmaoandpage_mkclean. This means the API is still not generic enough and likely not extendable as needed in its present form.
I converted only one of those _notify as an example of how it should be done, because I assumed you volunteer to convert the other ones yourself during .26. It's useless to convert all of them right now, because the i_mmap_lock and anon_vma locks are still going to be spinlocks in .25.
Re: [PATCH] mmu notifiers #v8
In reply to Christoph LameterQuoted 8 lines · Christoph Lameter
Quoted 6 lines · Nick Piggin
I'm still not completely happy with this. I had a very quick look at the GRU driver, but I don't see why it can't be implemented more like the regular TLB model, and have TLB insertions depend on the linux pte, and do invalidates _after_ restricting permissions to the pte.
Quoted 5 lines
Quoted 2 lines
Ie. I'd still like to get rid of
invalidate_range_begin, and get rid of invalidate calls from places where permissions are relaxed.Isnt this more a job for paravirt ops if it is so tightly bound to page tables? Are we not adding another similar API?
Um, it's bound to the *Linux page tables*, yes. And I have no idea why you would use the paravirt ops for this.
Re: [PATCH] mmu notifiers #v8
Quoted 2 lines · Nick Piggin
Um, it's bound to the *Linux page tables*, yes. And I have no idea why you would use the paravirt ops for this.
paravirt ops allows interception of page table operations?
Re: [PATCH] mmu notifiers #v8
Quoted 6 lines · Christoph Lameter
Quoted 2 lines · Nick Piggin
Um, it's bound to the *Linux page tables*, yes. And I have no idea why you would use the paravirt ops for this.
paravirt ops allows interception of page table operations?
Maybe possible but it's totally the wrong API for it.
Re: [PATCH] mmu notifiers #v8
In reply to Andrea ArcangeliQuoted 1 line · Andrea Arcangeli
Difference between #v7 and #v8:
[patch] mmu-v8: demacro
Remove the macros from mmu_notifier.h, in favour of functions.
This requires untangling the include order circular dependencies as well, so just remove struct mmu_notifier_head in favour of just using the hlist in mm_struct.
---
include/linux/mmu_notifier.h +80 -47
@@ -55,12 +55,13 @@ struct mmu_notifier {#ifdef CONFIG_MMU_NOTIFIER -struct mmu_notifier_head { -struct hlist_head head; -}; -#include <linux/mm_types.h> +static inline int mm_has_notifiers (struct mm_struct *mm) +{ +return unlikely (!hlist_empty (&mm->mmu_notifier_list)); +} +/* * Must hold the mmap_sem for write. *
@@ -79,33 +80,59 @@ extern void mmu_notifier_register(struct*/ extern void mmu_notifier_unregister (struct mmu_notifier *mn,struct mm_struct *mm); -extern void mmu_notifier_release (struct mm_struct *mm); -extern int mmu_notifier_clear_flush_young (struct mm_struct *mm, + +extern void __mmu_notifier_release (struct mm_struct *mm); +extern int __mmu_notifier_clear_flush_young (struct mm_struct *mm,unsigned long address); +extern void __mmu_notifier_invalidate_page (struct mm_struct *mm, +unsigned long address); +extern void __mmu_notifier_invalidate_range_begin (struct mm_struct *mm, +unsigned long start,unsigned long end); +extern void __mmu_notifier_invalidate_range_end (struct mm_struct *mm, +unsigned long start,unsigned long end); + + +static inline void mmu_notifier_release (struct mm_struct *mm) +{ +if (mm_has_notifiers (mm)) +__mmu_notifier_release (mm); +} + +static inline int mmu_notifier_clear_flush_young (struct mm_struct *mm, +unsigned long address) +{ +if (mm_has_notifiers (mm)) +return __mmu_notifier_clear_flush_young (mm, address); +return 0 ; +} + +static inline void mmu_notifier_invalidate_page (struct mm_struct *mm, +unsigned long address) +{ +if (mm_has_notifiers (mm)) +__mmu_notifier_invalidate_page (mm, address); +} + +static inline void mmu_notifier_invalidate_range_begin (struct mm_struct *mm, +unsigned long start,unsigned long end) +{ +if (mm_has_notifiers (mm)) +__mmu_notifier_invalidate_range_begin (mm, start, end); +} + +static inline void mmu_notifier_invalidate_range_end (struct mm_struct *mm, +unsigned long start,unsigned long end) +{ +if (mm_has_notifiers (mm)) +__mmu_notifier_invalidate_range_end (mm, start, end); +} -static inline void mmu_notifier_head_init (struct mmu_notifier_head *mnh) +static inline void mmu_notifier_mm_init (struct mm_struct *mm) { -INIT_HLIST_HEAD (&mnh->head); +INIT_HLIST_HEAD (&mm->mmu_notifier_list); } -#define mmu_notifier(function, mm, args...) \ - do { \ - struct mmu_notifier *__mn; \ - struct hlist_node *__n; \ - struct mm_struct * __mm = mm; \ - \ - if (unlikely(!hlist_empty(&__mm->mmu_notifier.head))) { \ - rcu_read_lock(); \ - hlist_for_each_entry_rcu(__mn, __n, \ - &__mm->mmu_notifier.head, \ - hlist) \ - if (__mn->ops->function) \ - __mn->ops->function(__mn, \ - __mm, \ - args); \ - rcu_read_unlock(); \ - } \ - } while (0) +#define ptep_clear_flush_notify (__vma, __address, __ptep) \ ({ \
@@ -113,7 +140,7 @@ static inline void mmu_notifier_head_inistruct vm_area_struct * ___vma = __vma; \unsigned long ___address = __address; \ __pte =ptep_clear_flush (___vma, ___address, __ptep); \ -mmu_notifier (invalidate_page, ___vma->vm_mm, ___address); \ +mmu_notifier_invalidate_page (___vma->vm_mm, ___address); \ __pte; \ })
@@ -130,28 +157,34 @@ static inline void mmu_notifier_head_ini#else /* CONFIG_MMU_NOTIFIER */ -struct mmu_notifier_head {}; +static inline void mmu_notifier_release (struct mm_struct *mm) +{ +} -#define mmu_notifier_register (mn, mm) do {} while(0) -#define mmu_notifier_unregister (mn, mm) do {} while (0) -#define mmu_notifier_release (mm) do {} while (0) -#define mmu_notifier_head_init (mmh) do {} while (0) +static inline int mmu_notifier_clear_flush_young (struct mm_struct *mm, +unsigned long address) +{ +return 0 ; +} -/* -* Notifiers that use the parameters that they were passed so that the -* compiler does not complain about unused variables but does proper -* parameter checks even if !CONFIG_MMU_NOTIFIER. -* Macros generate no code. -*/ -#define mmu_notifier(function, mm, args...) \ - do { \ - if (0) { \ - struct mmu_notifier *__mn; \ - \ - __mn = (struct mmu_notifier *)(0x00ff); \ - __mn->ops->function(__mn, mm, args); \ - }; \ - } while (0) +static inline void mmu_notifier_invalidate_page (struct mm_struct *mm, +unsigned long address) +{ +} + +static inline void mmu_notifier_invalidate_range_begin (struct mm_struct *mm, +unsigned long start,unsigned long end) +{ +} + +static inline void mmu_notifier_invalidate_range_end (struct mm_struct *mm, +unsigned long start,unsigned long end) +{ +} + +static inline void mmu_notifier_mm_init (struct mm_struct *mm) +{ +}#define ptep_clear_flush_young_notify ptep_clear_flush_young#define ptep_clear_flush_notify ptep_clear_flush
mm/mmu_notifier.c +54 -15
103 lines not on this page · raw message
mm/fremap.c +3 -2
@@ -15,6 +15,7 @@#include <linux/rmap.h> #include <linux/module.h> #include <linux/syscalls.h> +#include <linux/mmu_notifier.h> #include <asm/mmu_context.h> #include <asm/cacheflush.h>
@@ -214,9 +215,9 @@ asmlinkage long sys_remap_file_pages(unsspin_unlock (&mapping->i_mmap_lock); } -mmu_notifier (invalidate_range_begin, mm, start, start + size); +mmu_notifier_invalidate_range_begin (mm, start, start + size); err =populate_range (mm, vma, start, size, pgoff); -mmu_notifier (invalidate_range_end, mm, start, start + size); +mmu_notifier_invalidate_range_end (mm, start, start + size);if (!err && !(flags &MAP_NONBLOCK )) {if (unlikely (has_write_lock)) {downgrade_write (&mm->mmap_sem);
mm/hugetlb.c +3 -2
@@ -14,6 +14,7 @@#include <linux/mempolicy.h> #include <linux/cpuset.h> #include <linux/mutex.h> +#include <linux/mmu_notifier.h> #include <asm/page.h> #include <asm/pgtable.h>
@@ -755,7 +756,7 @@ void __unmap_hugepage_range(struct vm_arBUG_ON (start & ~HPAGE_MASK );BUG_ON (end & ~HPAGE_MASK ); -mmu_notifier (invalidate_range_begin, mm, start, end); +mmu_notifier_invalidate_range_begin (mm, start, end);spin_lock (&mm->page_table_lock);for (address = start; address < end; address +=HPAGE_SIZE ) { ptep =huge_pte_offset (mm, address);
@@ -776,7 +777,7 @@ void __unmap_hugepage_range(struct vm_ar }spin_unlock (&mm->page_table_lock);flush_tlb_range (vma, start, end); -mmu_notifier (invalidate_range_end, mm, start, end); +mmu_notifier_invalidate_range_end (mm, start, end);list_for_each_entry_safe (page, tmp, &page_list, lru) {list_del (&page->lru);put_page (page);
mm/memory.c +7 -6
@@ -51,6 +51,7 @@#include <linux/init.h> #include <linux/writeback.h> #include <linux/memcontrol.h> +#include <linux/mmu_notifier.h> #include <asm/pgalloc.h> #include <asm/uaccess.h>
@@ -612,7 +613,7 @@ int copy_page_range(struct mm_struct *dsreturn copy_hugetlb_page_range (dst_mm, src_mm, vma);if (is_cow_mapping (vma->vm_flags)) -mmu_notifier (invalidate_range_begin, src_mm, addr, end); +mmu_notifier_invalidate_range_begin (src_mm, addr, end); dst_pgd =pgd_offset (dst_mm, addr); src_pgd =pgd_offset (src_mm, addr);
@@ -626,7 +627,7 @@ int copy_page_range(struct mm_struct *ds }while (dst_pgd++, src_pgd++, addr = next, addr != end);if (is_cow_mapping (vma->vm_flags)) -mmu_notifier (invalidate_range_end, src_mm, +mmu_notifier_invalidate_range_end (src_mm, vma->vm_start, end);return 0 ;
@@ -905,9 +906,9 @@ unsigned long zap_page_range(struct vm_alru_add_drain (); tlb =tlb_gather_mmu (mm,0 );update_hiwater_rss (mm); -mmu_notifier (invalidate_range_begin, mm, address, end); +mmu_notifier_invalidate_range_begin (mm, address, end); end =unmap_vmas (&tlb, vma, address, end, &nr_accounted, details); -mmu_notifier (invalidate_range_end, mm, address, end); +mmu_notifier_invalidate_range_end (mm, address, end);if (tlb)tlb_finish_mmu (tlb, address, end);return end;
@@ -1477,7 +1478,7 @@ int apply_to_page_range(struct mm_structint err;BUG_ON (addr >= end); -mmu_notifier (invalidate_range_begin, mm, start, end); +mmu_notifier_invalidate_range_begin (mm, start, end); pgd =pgd_offset (mm, addr); do { next = pgd_addr_end(addr, end);
@@ -1485,7 +1486,7 @@ int apply_to_page_range(struct mm_structif (err)break ; }while (pgd++, addr = next, addr != end); -mmu_notifier (invalidate_range_end, mm, start, end); +mmu_notifier_invalidate_range_end (mm, start, end);return err; }EXPORT_SYMBOL_GPL (apply_to_page_range);
mm/mmap.c +3 -2
@@ -26,6 +26,7 @@#include <linux/mount.h> #include <linux/mempolicy.h> #include <linux/rmap.h> +#include <linux/mmu_notifier.h> #include <asm/uaccess.h> #include <asm/cacheflush.h>
@@ -1747,13 +1748,13 @@ static void unmap_region(struct mm_struclru_add_drain (); tlb =tlb_gather_mmu (mm,0 );update_hiwater_rss (mm); -mmu_notifier (invalidate_range_begin, mm, start, end); +mmu_notifier_invalidate_range_begin (mm, start, end);unmap_vmas (&tlb, vma, start, end, &nr_accounted,NULL );vm_unacct_memory (nr_accounted);free_pgtables (&tlb, vma, prev? prev->vm_end:FIRST_USER_ADDRESS , next? next->vm_start:0 );tlb_finish_mmu (tlb, start, end); -mmu_notifier (invalidate_range_end, mm, start, end); +mmu_notifier_invalidate_range_end (mm, start, end); }/*
mm/mprotect.c +3 -2
@@ -21,6 +21,7 @@#include <linux/syscalls.h> #include <linux/swap.h> #include <linux/swapops.h> +#include <linux/mmu_notifier.h> #include <asm/uaccess.h> #include <asm/pgtable.h> #include <asm/cacheflush.h>
@@ -198,12 +199,12 @@ success: dirty_accountable =1 ; } -mmu_notifier (invalidate_range_begin, mm, start, end); +mmu_notifier_invalidate_range_begin (mm, start, end);if (is_vm_hugetlb_page (vma))hugetlb_change_protection (vma, start, end, vma->vm_page_prot);else change_protection (vma, start, end, vma->vm_page_prot, dirty_accountable); -mmu_notifier (invalidate_range_end, mm, start, end); +mmu_notifier_invalidate_range_end (mm, start, end);vm_stat_account (mm, oldflags, vma->vm_file, -nrpages);vm_stat_account (mm, newflags, vma->vm_file, nrpages);return 0 ;
mm/mremap.c +3 -2
@@ -18,6 +18,7 @@#include <linux/highmem.h> #include <linux/security.h> #include <linux/syscalls.h> +#include <linux/mmu_notifier.h> #include <asm/uaccess.h> #include <asm/cacheflush.h>
@@ -102,7 +103,7 @@ static void move_ptes(struct vm_area_strarch_enter_lazy_mmu_mode (); old_start = old_addr; -mmu_notifier (invalidate_range_begin, vma->vm_mm, +mmu_notifier_invalidate_range_begin (vma->vm_mm, old_start, old_end);for (; old_addr < old_end; old_pte++, old_addr +=PAGE_SIZE , new_pte++, new_addr +=PAGE_SIZE ) {
@@ -112,7 +113,7 @@ static void move_ptes(struct vm_area_str pte =move_pte (pte, new_vma->vm_page_prot, old_addr, new_addr);set_pte_at (mm, new_addr, new_pte, pte); } -mmu_notifier (invalidate_range_end, vma->vm_mm, old_start, old_end); +mmu_notifier_invalidate_range_end (vma->vm_mm, old_start, old_end);arch_leave_lazy_mmu_mode ();if (new_ptl != old_ptl)
include/linux/mm_types.h +3 -3
@@ -10,7 +10,6 @@#include <linux/rbtree.h> #include <linux/rwsem.h> #include <linux/completion.h> -#include <linux/mmu_notifier.h> #include <asm/page.h> #include <asm/mmu.h>
@@ -229,8 +228,9 @@ struct mm_struct {#ifdef CONFIG_CGROUP_MEM_CONT struct mem_cgroup *mem_cgroup;#endif - - struct mmu_notifier_head mmu_notifier; /* MMU notifier list */ +#ifdef CONFIG_MMU_NOTIFIER +struct hlist_head mmu_notifier_list; +#endif };#endif /* _LINUX_MM_TYPES_H */
mm/rmap.c +1 -0
@@ -49,6 +49,7 @@#include <linux/module.h> #include <linux/kallsyms.h> #include <linux/memcontrol.h> +#include <linux/mmu_notifier.h> #include <asm/tlbflush.h>
Re: [PATCH] mmu notifiers #v8
I like the patch.
Re: [PATCH] mmu notifiers #v8
In reply to Andrea ArcangeliQuoted 1 line · Andrea Arcangeli
Difference between #v7 and #v8:
This one on top of the previous patch
[patch] mmu-v8: typesafe
Move definition of struct mmu_notifier and struct mmu_notifier_ops under CONFIG_MMU_NOTIFIER to ensure they doesn't get dereferenced when they don't make sense.
---
include/linux/mmu_notifier.h +4 -4
@@ -3,8 +3,12 @@#include <linux/list.h> #include <linux/spinlock.h> +#include <linux/mm_types.h> struct mmu_notifier ; +struct mmu_notifier_ops ; + +#ifdef CONFIG_MMU_NOTIFIER struct mmu_notifier_ops {/*
@@ -53,10 +57,6 @@ struct mmu_notifier {const struct mmu_notifier_ops *ops; }; -#ifdef CONFIG_MMU_NOTIFIER - -#include <linux/mm_types.h> -static inline int mm_has_notifiers (struct mm_struct *mm) {return unlikely (!hlist_empty (&mm->mmu_notifier_list));
Re: [PATCH] mmu notifiers #v8
Quoted 3 lines · Nick Piggin
Move definition of struct
mmu_notifierand structmmu_notifier_opsunderCONFIG_MMU_NOTIFIERto ensure they doesn't get dereferenced when they don't make sense.
The callbacks take a mmu_notifier parameter. So how does this compile for !MMU_NOTIFIER?
Re: [PATCH] mmu notifiers #v8
In reply to Andrea ArcangeliQuoted 1 line · Andrea Arcangeli
Difference between #v7 and #v8:
Here is just a couple of checkpatch fixes on top of the last patches.
include/linux/mmu_notifier.h +3 -3
@@ -46,7 +46,7 @@ struct mmu_notifier_ops {*/ void (*invalidate_range_begin)(struct mmu_notifier *mn,struct mm_struct *mm, -unsigned long start,unsigned long end); +unsigned long start,unsigned long end);void (*invalidate_range_end)(struct mmu_notifier *mn,struct mm_struct *mm,unsigned long start,unsigned long end);
@@ -137,7 +137,7 @@ static inline void mmu_notifier_mm_init(#define ptep_clear_flush_notify (__vma, __address, __ptep) \ ({ \ pte_t __pte; \ - struct vm_area_struct * ___vma = __vma; \ + struct vm_area_struct *___vma = __vma; \ unsigned long ___address = __address; \ __pte = ptep_clear_flush(___vma, ___address, __ptep); \ mmu_notifier_invalidate_page(___vma->vm_mm, ___address); \
@@ -147,7 +147,7 @@ static inline void mmu_notifier_mm_init(#define ptep_clear_flush_young_notify (__vma, __address, __ptep) \ ({ \ int __young; \ - struct vm_area_struct * ___vma = __vma; \ + struct vm_area_struct *___vma = __vma; \ unsigned long ___address = __address; \ __young = ptep_clear_flush_young(___vma, ___address, __ptep); \ __young |= mmu_notifier_clear_flush_young(___vma->vm_mm, \
PATCH 1/1 mmu notifiers #v9
In reply to Andrea ArcangeliThe only difference are Nick's changes (thanks Nick, nice work!) plus a fix to make it compile.
About the removal of _begin I'm not strongly opposed to it, but I personally think that it's unnecessary if _begin avoids to build new data structures with a fixed ram (and cpu) cost per_page_ and at the same time deferring _end after the whole tlb_gather page freeing is reducing the number of invalidates.
.26 will allow all the methods to sleep by following the roadmap described in the #v8 patch.
KVM so far is swapping fine on top of this.
include/linux/mm_types.h +3 -0
@@ -228,6 +228,9 @@ struct mm_struct {#ifdef CONFIG_CGROUP_MEM_CONT struct mem_cgroup *mem_cgroup;#endif +#ifdef CONFIG_MMU_NOTIFIER +struct hlist_head mmu_notifier_list; +#endif };#endif /* _LINUX_MM_TYPES_H */
include/linux/mmu_notifier.h new file +194 -0
194 lines not on this page · raw message
kernel/fork.c +2 -0
@@ -53,6 +53,7 @@#include <linux/tty.h> #include <linux/proc_fs.h> #include <linux/blkdev.h> +#include <linux/mmu_notifier.h> #include <asm/pgtable.h> #include <asm/pgalloc.h>
@@ -362,6 +363,7 @@ static struct mm_struct * mm_init(structif (likely (!mm_alloc_pgd (mm))) { mm->def_flags =0 ; +mmu_notifier_mm_init (mm);return mm; }
mm/Kconfig +4 -0
@@ -193,3 +193,7 @@ config VIRT_TO_BUS config VIRT_TO_BUS def_bool y depends on !ARCH_NO_VIRT_TO_BUS + +config MMU_NOTIFIER + def_bool y + bool "MMU notifier, for paging KVM/RDMA"
mm/Makefile +1 -1
@@ -33,4 +33,4 @@ obj-$(CONFIG_SMP) += allocpercpu.o obj-$(CONFIG_SMP) += allocpercpu.o obj-$(CONFIG_QUICKLIST) += quicklist.o obj-$(CONFIG_CGROUP_MEM_CONT) += memcontrol.o - +obj-$(CONFIG_MMU_NOTIFIER) += mmu_notifier.o
mm/filemap_xip.c +1 -1
@@ -194,7 +194,7 @@ __xip_unmap (struct address_space * mappif (pte) {/* Nuke the page table entry. */ flush_cache_page (vma, address,pte_pfn (*pte)); - pteval =ptep_clear_flush (vma, address, pte); + pteval =ptep_clear_flush_notify (vma, address, pte);page_remove_rmap (page, vma);dec_mm_counter (mm, file_rss);BUG_ON (pte_dirty (pteval));
mm/fremap.c +3 -0
@@ -15,6 +15,7 @@#include <linux/rmap.h> #include <linux/module.h> #include <linux/syscalls.h> +#include <linux/mmu_notifier.h> #include <asm/mmu_context.h> #include <asm/cacheflush.h>
@@ -214,7 +215,9 @@ asmlinkage long sys_remap_file_pages(unsspin_unlock (&mapping->i_mmap_lock); } +mmu_notifier_invalidate_range_begin (mm, start, start + size); err =populate_range (mm, vma, start, size, pgoff); +mmu_notifier_invalidate_range_end (mm, start, start + size);if (!err && !(flags &MAP_NONBLOCK )) {if (unlikely (has_write_lock)) {downgrade_write (&mm->mmap_sem);
mm/hugetlb.c +3 -0
@@ -14,6 +14,7 @@#include <linux/mempolicy.h> #include <linux/cpuset.h> #include <linux/mutex.h> +#include <linux/mmu_notifier.h> #include <asm/page.h> #include <asm/pgtable.h>
@@ -755,6 +756,7 @@ void __unmap_hugepage_range(struct vm_arBUG_ON (start & ~HPAGE_MASK );BUG_ON (end & ~HPAGE_MASK ); +mmu_notifier_invalidate_range_begin (mm, start, end);spin_lock (&mm->page_table_lock);for (address = start; address < end; address +=HPAGE_SIZE ) { ptep =huge_pte_offset (mm, address);
@@ -775,6 +777,7 @@ void __unmap_hugepage_range(struct vm_ar }spin_unlock (&mm->page_table_lock);flush_tlb_range (vma, start, end); +mmu_notifier_invalidate_range_end (mm, start, end);list_for_each_entry_safe (page, tmp, &page_list, lru) {list_del (&page->lru);put_page (page);
mm/memory.c +15 -2
@@ -51,6 +51,7 @@#include <linux/init.h> #include <linux/writeback.h> #include <linux/memcontrol.h> +#include <linux/mmu_notifier.h> #include <asm/pgalloc.h> #include <asm/uaccess.h>
@@ -611,6 +612,9 @@ int copy_page_range(struct mm_struct *dsif (is_vm_hugetlb_page (vma))return copy_hugetlb_page_range (dst_mm, src_mm, vma); +if (is_cow_mapping (vma->vm_flags)) +mmu_notifier_invalidate_range_begin (src_mm, addr, end); + dst_pgd =pgd_offset (dst_mm, addr); src_pgd =pgd_offset (src_mm, addr); do {
@@ -621,6 +625,11 @@ int copy_page_range(struct mm_struct *ds vma, addr, next))return -ENOMEM ; }while (dst_pgd++, src_pgd++, addr = next, addr != end); + +if (is_cow_mapping (vma->vm_flags)) +mmu_notifier_invalidate_range_end (src_mm, + vma->vm_start, end); +return 0 ; }
@@ -897,7 +906,9 @@ unsigned long zap_page_range(struct vm_alru_add_drain (); tlb =tlb_gather_mmu (mm,0 );update_hiwater_rss (mm); +mmu_notifier_invalidate_range_begin (mm, address, end); end =unmap_vmas (&tlb, vma, address, end, &nr_accounted, details); +mmu_notifier_invalidate_range_end (mm, address, end);if (tlb)tlb_finish_mmu (tlb, address, end);return end;
@@ -1463,10 +1474,11 @@ int apply_to_page_range(struct mm_struct {pgd_t *pgd;unsigned long next; -unsigned long end = addr + size; +unsigned long start = addr, end = addr + size;int err;BUG_ON (addr >= end); +mmu_notifier_invalidate_range_begin (mm, start, end); pgd =pgd_offset (mm, addr); do { next = pgd_addr_end(addr, end);
@@ -1474,6 +1486,7 @@ int apply_to_page_range(struct mm_structif (err)break ; }while (pgd++, addr = next, addr != end); +mmu_notifier_invalidate_range_end (mm, start, end);return err; }EXPORT_SYMBOL_GPL (apply_to_page_range);
@@ -1675,7 +1688,7 @@ gotten:* seen in the presence of one thread doing SMC and another * thread doing COW. */ -ptep_clear_flush (vma, address, page_table); +ptep_clear_flush_notify (vma, address, page_table);set_pte_at (mm, address, page_table, entry);update_mmu_cache (vma, address, entry);lru_cache_add_active (new_page);
mm/mmap.c +4 -0
@@ -26,6 +26,7 @@#include <linux/mount.h> #include <linux/mempolicy.h> #include <linux/rmap.h> +#include <linux/mmu_notifier.h> #include <asm/uaccess.h> #include <asm/cacheflush.h>
@@ -1747,11 +1748,13 @@ static void unmap_region(struct mm_struclru_add_drain (); tlb =tlb_gather_mmu (mm,0 );update_hiwater_rss (mm); +mmu_notifier_invalidate_range_begin (mm, start, end);unmap_vmas (&tlb, vma, start, end, &nr_accounted,NULL );vm_unacct_memory (nr_accounted);free_pgtables (&tlb, vma, prev? prev->vm_end:FIRST_USER_ADDRESS , next? next->vm_start:0 );tlb_finish_mmu (tlb, start, end); +mmu_notifier_invalidate_range_end (mm, start, end); }/*
@@ -2037,6 +2040,7 @@ void exit_mmap(struct mm_struct *mm)unsigned long end;/* mm's last user has gone, and its about to be pulled down */ +mmu_notifier_release (mm);arch_exit_mmap (mm);lru_add_drain ();
mm/mmu_notifier.c new file +114 -0
114 lines not on this page · raw message
mm/mprotect.c +3 -0
@@ -21,6 +21,7 @@#include <linux/syscalls.h> #include <linux/swap.h> #include <linux/swapops.h> +#include <linux/mmu_notifier.h> #include <asm/uaccess.h> #include <asm/pgtable.h> #include <asm/cacheflush.h>
@@ -198,10 +199,12 @@ success: dirty_accountable =1 ; } +mmu_notifier_invalidate_range_begin (mm, start, end);if (is_vm_hugetlb_page (vma))hugetlb_change_protection (vma, start, end, vma->vm_page_prot);else change_protection (vma, start, end, vma->vm_page_prot, dirty_accountable); +mmu_notifier_invalidate_range_end (mm, start, end);vm_stat_account (mm, oldflags, vma->vm_file, -nrpages);vm_stat_account (mm, newflags, vma->vm_file, nrpages);return 0 ;
mm/mremap.c +6 -0
@@ -18,6 +18,7 @@#include <linux/highmem.h> #include <linux/security.h> #include <linux/syscalls.h> +#include <linux/mmu_notifier.h> #include <asm/uaccess.h> #include <asm/cacheflush.h>
@@ -74,6 +75,7 @@ static void move_ptes(struct vm_area_strstruct mm_struct *mm = vma->vm_mm;pte_t *old_pte, *new_pte, pte;spinlock_t *old_ptl, *new_ptl; +unsigned long old_start;if (vma->vm_file) {/*
@@ -100,6 +102,9 @@ static void move_ptes(struct vm_area_strspin_lock_nested (new_ptl,SINGLE_DEPTH_NESTING );arch_enter_lazy_mmu_mode (); + old_start = old_addr; +mmu_notifier_invalidate_range_begin (vma->vm_mm, + old_start, old_end);for (; old_addr < old_end; old_pte++, old_addr +=PAGE_SIZE , new_pte++, new_addr +=PAGE_SIZE ) {if (pte_none (*old_pte))
@@ -108,6 +113,7 @@ static void move_ptes(struct vm_area_str pte =move_pte (pte, new_vma->vm_page_prot, old_addr, new_addr);set_pte_at (mm, new_addr, new_pte, pte); } +mmu_notifier_invalidate_range_end (vma->vm_mm, old_start, old_end);arch_leave_lazy_mmu_mode ();if (new_ptl != old_ptl)
mm/rmap.c +7 -6
@@ -49,6 +49,7 @@#include <linux/module.h> #include <linux/kallsyms.h> #include <linux/memcontrol.h> +#include <linux/mmu_notifier.h> #include <asm/tlbflush.h>
@@ -287,7 +288,7 @@ static int page_referenced_one(struct paif (vma->vm_flags &VM_LOCKED ) { referenced++; *mapcount =1 ;/* break early from loop */ - }else if (ptep_clear_flush_young (vma, address, pte)) + }else if (ptep_clear_flush_young_notify (vma, address, pte)) referenced++;/* Pretend the page is referenced if the task has the
@@ -454,7 +455,7 @@ static int page_mkclean_one(struct pagepte_t entry;flush_cache_page (vma, address,pte_pfn (*pte)); - entry =ptep_clear_flush (vma, address, pte); + entry =ptep_clear_flush_notify (vma, address, pte); entry =pte_wrprotect (entry); entry =pte_mkclean (entry);set_pte_at (mm, address, pte, entry);
@@ -712,14 +713,14 @@ static int try_to_unmap_one(struct page* skipped over this mm) then we should reactivate it. */ if (!migration && ((vma->vm_flags &VM_LOCKED ) || - (ptep_clear_flush_young (vma, address, pte)))) { + (ptep_clear_flush_young_notify (vma, address, pte)))) { ret =SWAP_FAIL ; goto out_unmap; }/* Nuke the page table entry. */ flush_cache_page (vma, address,page_to_pfn (page)); - pteval =ptep_clear_flush (vma, address, pte); + pteval =ptep_clear_flush_notify (vma, address, pte);/* Move the dirty bit to the physical page now the pte is gone. */ if (pte_dirty (pteval))
@@ -844,12 +845,12 @@ static void try_to_unmap_cluster(unsigne page =vm_normal_page (vma, address, *pte);BUG_ON (!page ||PageAnon (page)); -if (ptep_clear_flush_young (vma, address, pte)) +if (ptep_clear_flush_young_notify (vma, address, pte))continue ;/* Nuke the page table entry. */ flush_cache_page (vma, address,pte_pfn (*pte)); - pteval =ptep_clear_flush (vma, address, pte); + pteval =ptep_clear_flush_notify (vma, address, pte);/* If nonlinear, store the file page offset in the pte. */ if (page->index !=linear_page_index (vma, address))
PATCH 1/1 KVM swapping with mmu notifiers #v9
Notably the registration now requires the mmap_sem in write mode.
arch/x86/kvm/Kconfig +1 -0
@@ -21,6 +21,7 @@ config KVM tristate "Kernel-based Virtual Machine (KVM) support" depends on HAVE_KVM && EXPERIMENTAL select PREEMPT_NOTIFIERS + select MMU_NOTIFIER select ANON_INODES ---help--- Support hosting fully virtualized guest machines using hardware
arch/x86/kvm/mmu.c +104 -0
110 lines not on this page · raw message
arch/x86/kvm/paging_tmpl.h +11 -0
@@ -380,6 +380,7 @@ static int FNAME(page_fault)(struct kvm_vcpu *vcpu, gva_t addr,int r;struct page *page;int largepage =0 ; +unsigned mmu_seq;pgprintk ("%s: addr %lx err %x\n" , __FUNCTION__, addr, error_code);kvm_mmu_audit (vcpu,"pre page fault" );
@@ -415,6 +416,7 @@ static int FNAME(page_fault)(struct kvm_vcpu *vcpu, gva_t addr, largepage =1 ; } } + mmu_seq =read_seqbegin (&vcpu->kvm->arch.mmu_notifier_invalidate_lock); page =gfn_to_page (vcpu->kvm, walker.gfn);up_read (¤t->mm->mmap_sem);
@@ -440,6 +442,15 @@ static int FNAME(page_fault)(struct kvm_vcpu *vcpu, gva_t addr, ++vcpu->stat.pf_fixed;kvm_mmu_audit (vcpu,"post page fault (fixed)" );spin_unlock (&vcpu->kvm->mmu_lock); + +if (read_seqretry (&vcpu->kvm->arch.mmu_notifier_invalidate_lock, mmu_seq)) { +down_read (¤t->mm->mmap_sem); +if (page !=gfn_to_page (vcpu->kvm, walker.gfn)) +BUG (); +up_read (¤t->mm->mmap_sem); +kvm_release_page_clean (page); + } +up_read (&vcpu->kvm->slots_lock);return write_pt;
arch/x86/kvm/x86.c +49 -0
67 lines not on this page · raw message
include/asm-x86/kvm_host.h +6 -0
@@ -13,6 +13,7 @@#include <linux/types.h> #include <linux/mm.h> +#include <linux/mmu_notifier.h> #include <linux/kvm.h> #include <linux/kvm_para.h>
@@ -303,6 +304,9 @@ struct kvm_arch{struct page *apic_access_page;gpa_t wall_clock; + +struct mmu_notifier mmu_notifier; +seqlock_t mmu_notifier_invalidate_lock; };struct kvm_vm_stat {
@@ -422,6 +426,8 @@ int kvm_mmu_create(struct kvm_vcpu *vcpu);int kvm_mmu_setup (struct kvm_vcpu *vcpu);void kvm_mmu_set_nonpresent_ptes (u64 trap_pte,u64 notrap_pte); +void kvm_unmap_hva (struct kvm *kvm,unsigned long hva); +int kvm_age_hva (struct kvm *kvm,unsigned long hva);int kvm_mmu_reset_context (struct kvm_vcpu *vcpu);void kvm_mmu_slot_remove_write_access (struct kvm *kvm,int slot);void kvm_mmu_zap_all (struct kvm *kvm);
As usual memslot browsing with mmu_lock.
arch/x86/kvm/x86.c +14 -7
@@ -3379,16 +3379,23 @@ int kvm_arch_set_memory_region(struct kvm *kvm,*/ if (!user_alloc) {if (npages && !old.rmap) { +unsigned long userspace_addr; +down_write (¤t->mm->mmap_sem); - memslot->userspace_addr =do_mmap (NULL ,0 , - npages *PAGE_SIZE , -PROT_READ |PROT_WRITE , -MAP_SHARED |MAP_ANONYMOUS , -0 ); + userspace_addr =do_mmap (NULL ,0 , + npages *PAGE_SIZE , +PROT_READ |PROT_WRITE , +MAP_SHARED |MAP_ANONYMOUS , +0 );up_write (¤t->mm->mmap_sem); -if (IS_ERR ((void *)memslot->userspace_addr)) -return PTR_ERR ((void *)memslot->userspace_addr); +if (IS_ERR ((void *)userspace_addr)) +return PTR_ERR ((void *)userspace_addr); + +/* set userspace_addr atomically for kvm_hva_to_rmapp */ +spin_lock (&kvm->mmu_lock); + memslot->userspace_addr = userspace_addr; +spin_unlock (&kvm->mmu_lock); }else {if (!old.user_alloc && old.rmap) {int ret;
virt/kvm/kvm_main.c +13 -1
@@ -326,7 +326,15 @@ int __kvm_set_memory_region(struct kvm *kvm,memset (new.rmap,0 , npages *sizeof (*new.rmap)); new.user_alloc = user_alloc; - new.userspace_addr = mem->userspace_addr; +/* +* hva_to_rmmap() serialzies with the mmu_lock and to be +* safe it has to ignore memslots with !user_alloc && +* !userspace_addr. +*/ +if (user_alloc) + new.userspace_addr = mem->userspace_addr; +else + new.userspace_addr =0 ; }if (npages && !new.lpage_info) {int largepages = npages /KVM_PAGES_PER_HPAGE ;
@@ -355,14 +363,18 @@ int __kvm_set_memory_region(struct kvm *kvm,memset (new.dirty_bitmap,0 , dirty_bytes); } +spin_lock (&kvm->mmu_lock);if (mem->slot >= kvm->nmemslots) kvm->nmemslots = mem->slot +1 ; *memslot = new; +spin_unlock (&kvm->mmu_lock); r =kvm_arch_set_memory_region (kvm, mem, old, user_alloc);if (r) { +spin_lock (&kvm->mmu_lock); *memslot = old; +spin_unlock (&kvm->mmu_lock); goto out_free; }
Re: [PATCH] KVM swapping with mmu notifiers #v9
ציטוט Andrea Arcangeli:
Quoted 23 lines
Notably the registration now requires the
mmap_semin write mode.Signed-off-by Andrea Arcangeli <[email protected]>arch/x86/kvm/Kconfig +1
-0@@ -21,6 +21,7 @@ config KVM tristate "Kernel-based Virtual Machine (KVM) support" depends on HAVE_KVM && EXPERIMENTAL select PREEMPT_NOTIFIERS + select MMU_NOTIFIER select ANON_INODES ---help--- Support hosting fully virtualized guest machines using hardwarearch/x86/kvm/mmu.c +0
-0@@ -642,6 +642,110 @@ static void rmap_write_protect(struct kvm *kvm, u64 gfn)account_shadowed (kvm, gfn); }
arch/x86/kvm/mmu.c static void rmap_write_protect(struct kvm *kvm, u64 gfn) · line 653
Quoted 10 lines
arch/x86/kvm/mmu.c +8
-0+static void kvm_unmap_spte (struct kvm *kvm,u64 *spte) +{ +struct page *page =pfn_to_page ((*spte &PT64_BASE_ADDR_MASK ) >>PAGE_SHIFT ); +get_page (page); +rmap_remove (kvm, spte); +set_shadow_pte (spte, shadow_trap_nonpresent_pte); +kvm_flush_remote_tlbs (kvm); +__free_page (page);
i wrote to you about this before (i didnt get answer for this so i write again) with large pages support i think we need to use here put_page
Quoted 322 lines
arch/x86/kvm/mmu.c +96
-0+} + +static void kvm_unmap_rmapp (struct kvm *kvm,unsigned long *rmapp) +{ +u64 *spte, *curr_spte; + + spte =rmap_next (kvm, rmapp,NULL ); +while (spte) { +BUG_ON (!(*spte &PT_PRESENT_MASK )); +rmap_printk ("kvm_rmap_unmap_hva: spte %p %llx\n" , spte, *spte); + curr_spte = spte; + spte =rmap_next (kvm, rmapp, spte); +kvm_unmap_spte (kvm, curr_spte); + } +} + +void kvm_unmap_hva (struct kvm *kvm,unsigned long hva) +{ +int i; + +/* +* If mmap_sem isn't taken, we can look the memslots with only +* the mmu_lock by skipping over the slots with userspace_addr == 0. +*/ +spin_lock (&kvm->mmu_lock); +for (i =0 ; i < kvm->nmemslots; i++) { +struct kvm_memory_slot *memslot = &kvm->memslots[i]; +unsigned long start = memslot->userspace_addr; +unsigned long end; + +/* mmu_lock protects userspace_addr */ +if (!start) +continue ; + + end = start + (memslot->npages <<PAGE_SHIFT ); +if (hva >= start && hva < end) { +gfn_t gfn_offset = (hva - start) >>PAGE_SHIFT ; +kvm_unmap_rmapp (kvm, &memslot->rmap[gfn_offset]); + } + } +spin_unlock (&kvm->mmu_lock); +} + +static int kvm_age_rmapp (struct kvm *kvm,unsigned long *rmapp) +{ +u64 *spte; +int young =0 ; + + spte =rmap_next (kvm, rmapp,NULL ); +while (spte) { +int _young; +u64 _spte = *spte; +BUG_ON (!(_spte &PT_PRESENT_MASK )); + _young = _spte &PT_ACCESSED_MASK ; +if (_young) { + young = !!_young; +set_shadow_pte (spte, _spte & ~PT_ACCESSED_MASK ); + } + spte =rmap_next (kvm, rmapp, spte); + } +return young; +} + +int kvm_age_hva (struct kvm *kvm,unsigned long hva) +{ +int i; +int young =0 ; + +/* +* If mmap_sem isn't taken, we can look the memslots with only +* the mmu_lock by skipping over the slots with userspace_addr == 0. +*/ +spin_lock (&kvm->mmu_lock); +for (i =0 ; i < kvm->nmemslots; i++) { +struct kvm_memory_slot *memslot = &kvm->memslots[i]; +unsigned long start = memslot->userspace_addr; +unsigned long end; + +/* mmu_lock protects userspace_addr */ +if (!start) +continue ; + + end = start + (memslot->npages <<PAGE_SHIFT ); +if (hva >= start && hva < end) { +gfn_t gfn_offset = (hva - start) >>PAGE_SHIFT ; + young |=kvm_age_rmapp (kvm, &memslot->rmap[gfn_offset]); + } + } +spin_unlock (&kvm->mmu_lock); + +if (young) +kvm_flush_remote_tlbs (kvm); + +return young; +} +#ifdef MMU_DEBUG static int is_empty_shadow_page (u64 *spt){
arch/x86/kvm/paging_tmpl.h +11
-0@@ -380,6 +380,7 @@ static int FNAME(page_fault)(struct kvm_vcpu *vcpu, gva_t addr,int r;struct page *page;int largepage =0 ; +unsigned mmu_seq;pgprintk ("%s: addr %lx err %x\n" , __FUNCTION__, addr, error_code);kvm_mmu_audit (vcpu,"pre page fault" );@@ -415,6 +416,7 @@ static int FNAME(page_fault)(struct kvm_vcpu *vcpu, gva_t addr, largepage =1 ; } } + mmu_seq =read_seqbegin (&vcpu->kvm->arch.mmu_notifier_invalidate_lock); page =gfn_to_page (vcpu->kvm, walker.gfn);up_read (¤t->mm->mmap_sem);@@ -440,6 +442,15 @@ static int FNAME(page_fault)(struct kvm_vcpu *vcpu, gva_t addr, ++vcpu->stat.pf_fixed;kvm_mmu_audit (vcpu,"post page fault (fixed)" );spin_unlock (&vcpu->kvm->mmu_lock); + +if (read_seqretry (&vcpu->kvm->arch.mmu_notifier_invalidate_lock, mmu_seq)) { +down_read (¤t->mm->mmap_sem); +if (page !=gfn_to_page (vcpu->kvm, walker.gfn)) +BUG (); +up_read (¤t->mm->mmap_sem); +kvm_release_page_clean (page); + } +up_read (&vcpu->kvm->slots_lock);return write_pt;arch/x86/kvm/x86.c +49
-0@@ -25,6 +25,7 @@#include <linux/module.h> #include <linux/mman.h> #include <linux/highmem.h> +#include <linux/mmu_notifier.h> #include <asm/uaccess.h> #include <asm/msr.h> @@ -3319,6 +3320,48 @@ void kvm_arch_vcpu_uninit(struct kvm_vcpu *vcpu)free_page ((unsigned long )vcpu->arch.pio_data); } +static inline struct kvm *mmu_notifier_to_kvm (struct mmu_notifier *mn) +{ +struct kvm_arch *kvm_arch; + kvm_arch = container_of(mn, struct kvm_arch, mmu_notifier); + return container_of(kvm_arch, struct kvm, arch); +} + +void kvm_mmu_notifier_invalidate_page (struct mmu_notifier *mn, +struct mm_struct *mm, +unsigned long address) +{ +struct kvm *kvm =mmu_notifier_to_kvm (mn); +BUG_ON (mm != kvm->mm); +write_seqlock (&kvm->arch.mmu_notifier_invalidate_lock); +kvm_unmap_hva (kvm, address); +write_sequnlock (&kvm->arch.mmu_notifier_invalidate_lock); +} + +void kvm_mmu_notifier_invalidate_range_end (struct mmu_notifier *mn, +struct mm_struct *mm, +unsigned long start, +unsigned long end) +{ +for (; start < end; start +=PAGE_SIZE ) +kvm_mmu_notifier_invalidate_page (mn, mm, start); +} + +int kvm_mmu_notifier_clear_flush_young (struct mmu_notifier *mn, +struct mm_struct *mm, +unsigned long address) +{ +struct kvm *kvm =mmu_notifier_to_kvm (mn); +BUG_ON (mm != kvm->mm); +return kvm_age_hva (kvm, address); +} + +static const struct mmu_notifier_ops kvm_mmu_notifier_ops = { + .invalidate_page = kvm_mmu_notifier_invalidate_page, + .invalidate_range_end = kvm_mmu_notifier_invalidate_range_end, + .clear_flush_young = kvm_mmu_notifier_clear_flush_young, +}; +struct kvm *kvm_arch_create_vm (void ) {struct kvm *kvm =kzalloc (sizeof (struct kvm ),GFP_KERNEL );@@ -3328,6 +3371,12 @@ struct kvm *kvm_arch_create_vm(void)INIT_LIST_HEAD (&kvm->arch.active_mmu_pages); + kvm->arch.mmu_notifier.ops = &kvm_mmu_notifier_ops; +down_write (¤t->mm->mmap_sem); +mmu_notifier_register (&kvm->arch.mmu_notifier, current->mm); +up_write (¤t->mm->mmap_sem); +seqlock_init (&kvm->arch.mmu_notifier_invalidate_lock); +return kvm; }include/asm-x86/kvm_host.h +6
-0@@ -13,6 +13,7 @@#include <linux/types.h> #include <linux/mm.h> +#include <linux/mmu_notifier.h> #include <linux/kvm.h> #include <linux/kvm_para.h> @@ -303,6 +304,9 @@ struct kvm_arch{struct page *apic_access_page;gpa_t wall_clock; + +struct mmu_notifier mmu_notifier; +seqlock_t mmu_notifier_invalidate_lock; };struct kvm_vm_stat {@@ -422,6 +426,8 @@ int kvm_mmu_create(struct kvm_vcpu *vcpu);int kvm_mmu_setup (struct kvm_vcpu *vcpu);void kvm_mmu_set_nonpresent_ptes (u64 trap_pte,u64 notrap_pte); +void kvm_unmap_hva (struct kvm *kvm,unsigned long hva); +int kvm_age_hva (struct kvm *kvm,unsigned long hva);int kvm_mmu_reset_context (struct kvm_vcpu *vcpu);void kvm_mmu_slot_remove_write_access (struct kvm *kvm,int slot);void kvm_mmu_zap_all (struct kvm *kvm);As usual memslot browsing with
mmu_lock.Signed-off-by Andrea Arcangeli <[email protected]>arch/x86/kvm/x86.c +14
-7@@ -3379,16 +3379,23 @@ int kvm_arch_set_memory_region(struct kvm *kvm,*/ if (!user_alloc) {if (npages && !old.rmap) { +unsigned long userspace_addr; +down_write (¤t->mm->mmap_sem); - memslot->userspace_addr =do_mmap (NULL ,0 , - npages *PAGE_SIZE , -PROT_READ |PROT_WRITE , -MAP_SHARED |MAP_ANONYMOUS , -0 ); + userspace_addr =do_mmap (NULL ,0 , + npages *PAGE_SIZE , +PROT_READ |PROT_WRITE , +MAP_SHARED |MAP_ANONYMOUS , +0 );up_write (¤t->mm->mmap_sem); -if (IS_ERR ((void *)memslot->userspace_addr)) -return PTR_ERR ((void *)memslot->userspace_addr); +if (IS_ERR ((void *)userspace_addr)) +return PTR_ERR ((void *)userspace_addr); + +/* set userspace_addr atomically for kvm_hva_to_rmapp */ +spin_lock (&kvm->mmu_lock); + memslot->userspace_addr = userspace_addr; +spin_unlock (&kvm->mmu_lock); }else {if (!old.user_alloc && old.rmap) {int ret;virt/kvm/kvm_main.c +13
-1@@ -326,7 +326,15 @@ int __kvm_set_memory_region(struct kvm *kvm,memset (new.rmap,0 , npages *sizeof (*new.rmap)); new.user_alloc = user_alloc; - new.userspace_addr = mem->userspace_addr; +/* +* hva_to_rmmap() serialzies with the mmu_lock and to be +* safe it has to ignore memslots with !user_alloc && +* !userspace_addr. +*/ +if (user_alloc) + new.userspace_addr = mem->userspace_addr; +else + new.userspace_addr =0 ; }if (npages && !new.lpage_info) {int largepages = npages /KVM_PAGES_PER_HPAGE ;@@ -355,14 +363,18 @@ int __kvm_set_memory_region(struct kvm *kvm,memset (new.dirty_bitmap,0 , dirty_bytes); } +spin_lock (&kvm->mmu_lock);if (mem->slot >= kvm->nmemslots) kvm->nmemslots = mem->slot +1 ; *memslot = new; +spin_unlock (&kvm->mmu_lock); r =kvm_arch_set_memory_region (kvm, mem, old, user_alloc);if (r) { +spin_lock (&kvm->mmu_lock); *memslot = old; +spin_unlock (&kvm->mmu_lock); goto out_free; }
RFC 1/1 Notifier for Externally Mapped Memory (EMM)
Stripped things down and did what Andrea and I talked about last Friday. No invalidate_page callbacks. No ops anymore. Simple linked list for notifier. No RCU. Added the code to rmap.h and rmap.c (after all it is concerned with handling mappings).
This patch implements a simple callback for device drivers that establish their own references to pages (KVM, GRU, XPmem, RDMA/Infiniband, DMA engines etc). These references are unknown to the VM (therefore external).
With these callbacks it is possible for the device driver to release external references when the VM requests it. This enables swapping, page migration and allows support of remapping, permission changes etc etc for externally mapped memory.
With this functionality it becomes possible to avoid pinning or mlocking pages (commonly done to stop the VM from unmapping pages).
A device driver must subscribe to a process using
emm_register_notifier
The VM will then perform callbacks for operations that unmap or change permissions of pages in that address space. When the process terminates the callback function is called with emm_release.
Callbacks are performed before and after the unmapping action of the VM.
emm_invalidate_start before
emm_invalidate_end after
Callbacks are mostly performed in a non atomic context. However, in various places spinlocks are held to traverse rmaps. So this patch here is only useful for those devices that can remove mappings in an atomic context (f.e. KVM/GRU).
If the rmap traversal spinlocks are converted to semaphores then all callbacks willbe performed in a nonatomic context. Callouts can stay where they are.
--- include/linux/mm_types.h | 3 + include/linux/rmap.h | 51 +++++++++++++++++++++++++++++++++ kernel/fork.c | 3 + mm/Kconfig | 5 +++ mm/filemap_xip.c | 5 +++ mm/fremap.c | 2 + mm/hugetlb.c | 4 ++ mm/memory.c | 32 ++++++++++++++++++-- mm/mmap.c | 3 + mm/mprotect.c | 3 + mm/mremap.c | 5 +++ mm/rmap.c | 72 ++++++++++++++++++++++++++++++++++++++++++++++- 12 files changed, 183 insertions(+), 5 deletions(-)
include/linux/mm_types.h +3 -0
@@ -225,6 +225,9 @@ struct mm_struct {/* aio bits */ rwlock_t ioctx_list_lock;struct kioctx *ioctx_list; +#ifdef CONFIG_EMM_NOTIFIER +struct emm_notifier *emm_notifier; +#endif #ifdef CONFIG_CGROUP_MEM_CONT struct mem_cgroup *mem_cgroup;#endif
mm/Kconfig +5 -0
@@ -193,3 +193,8 @@ config NR_QUICK config VIRT_TO_BUS def_bool y depends on !ARCH_NO_VIRT_TO_BUS + +config EMM_NOTIFIER + def_bool n + bool "External Mapped Memory Notifier for drivers directly mapping memory" +
mm/mmap.c +3 -0
@@ -1747,11 +1747,13 @@ static void unmap_region(struct mm_struclru_add_drain (); tlb =tlb_gather_mmu (mm,0 );update_hiwater_rss (mm); +emm_notify (mm, emm_invalidate_start, start, end);unmap_vmas (&tlb, vma, start, end, &nr_accounted,NULL );vm_unacct_memory (nr_accounted);free_pgtables (&tlb, vma, prev? prev->vm_end:FIRST_USER_ADDRESS , next? next->vm_start:0 );tlb_finish_mmu (tlb, start, end); +emm_notify (mm, emm_invalidate_end, start, end); }/*
@@ -2038,6 +2040,7 @@ void exit_mmap(struct mm_struct *mm)/* mm's last user has gone, and its about to be pulled down */ arch_exit_mmap (mm); +emm_notify (mm, emm_release,0 ,TASK_SIZE );lru_add_drain ();flush_cache_mm (mm);
mm/mprotect.c +3 -0
@@ -21,6 +21,7 @@#include <linux/syscalls.h> #include <linux/swap.h> #include <linux/swapops.h> +#include <linux/rmap.h> #include <asm/uaccess.h> #include <asm/pgtable.h> #include <asm/cacheflush.h>
@@ -198,10 +199,12 @@ success: dirty_accountable =1 ; } +emm_notify (mm, emm_invalidate_start, start, end);if (is_vm_hugetlb_page (vma))hugetlb_change_protection (vma, start, end, vma->vm_page_prot);else change_protection (vma, start, end, vma->vm_page_prot, dirty_accountable); +emm_notify (mm, emm_invalidate_end, start, end);vm_stat_account (mm, oldflags, vma->vm_file, -nrpages);vm_stat_account (mm, newflags, vma->vm_file, nrpages);return 0 ;
mm/mremap.c +5 -0
@@ -18,6 +18,7 @@#include <linux/highmem.h> #include <linux/security.h> #include <linux/syscalls.h> +#include <linux/rmap.h> #include <asm/uaccess.h> #include <asm/cacheflush.h>
@@ -74,7 +75,9 @@ static void move_ptes(struct vm_area_strstruct mm_struct *mm = vma->vm_mm;pte_t *old_pte, *new_pte, pte;spinlock_t *old_ptl, *new_ptl; +unsigned long old_start = old_addr; +emm_notify (mm, emm_invalidate_start, old_start, old_end);if (vma->vm_file) {/* * Subtle point from Rajesh Venkatasubramanian: before
@@ -98,6 +101,7 @@ static void move_ptes(struct vm_area_str new_ptl =pte_lockptr (mm, new_pmd);if (new_ptl != old_ptl)spin_lock_nested (new_ptl,SINGLE_DEPTH_NESTING ); +arch_enter_lazy_mmu_mode (); for (; old_addr < old_end; old_pte++, old_addr += PAGE_SIZE,
@@ -116,6 +120,7 @@ static void move_ptes(struct vm_area_strpte_unmap_unlock (old_pte -1 , old_ptl);if (mapping)spin_unlock (&mapping->i_mmap_lock); +emm_notify (mm, emm_invalidate_end, old_start, old_end); }#define LATENCY_LIMIT (64 * PAGE_SIZE)
mm/rmap.c +71 -1
125 lines not on this page · raw message
mm/memory.c +28 -4
@@ -611,6 +611,9 @@ int copy_page_range(struct mm_struct *dsif (is_vm_hugetlb_page (vma))return copy_hugetlb_page_range (dst_mm, src_mm, vma); +if (is_cow_mapping (vma->vm_flags)) +emm_notify (src_mm, emm_invalidate_start, addr, end); + dst_pgd =pgd_offset (dst_mm, addr); src_pgd =pgd_offset (src_mm, addr); do {
@@ -621,6 +624,10 @@ int copy_page_range(struct mm_struct *ds vma, addr, next))return -ENOMEM ; }while (dst_pgd++, src_pgd++, addr = next, addr != end); + +if (is_cow_mapping (vma->vm_flags)) +emm_notify (src_mm, emm_invalidate_end, addr, end); +return 0 ; }
@@ -897,7 +904,11 @@ unsigned long zap_page_range(struct vm_alru_add_drain (); tlb =tlb_gather_mmu (mm,0 );update_hiwater_rss (mm); + +/* i_mmap_lock may be held */ +emm_notify (mm, emm_invalidate_start, address, end); end =unmap_vmas (&tlb, vma, address, end, &nr_accounted, details); +emm_notify (mm, emm_invalidate_end, address, end);if (tlb)tlb_finish_mmu (tlb, address, end);return end;
@@ -1340,6 +1351,7 @@ int remap_pfn_range(struct vm_area_strucpgd_t *pgd;unsigned long next;unsigned long end = addr +PAGE_ALIGN (size); +unsigned long start = addr;struct mm_struct *mm = vma->vm_mm;int err;
@@ -1372,6 +1384,7 @@ int remap_pfn_range(struct vm_area_strucBUG_ON (addr >= end); pfn -= addr >>PAGE_SHIFT ; pgd =pgd_offset (mm, addr); +emm_notify (mm, emm_invalidate_start, start, end);flush_cache_range (vma, addr, end); do { next = pgd_addr_end(addr, end);
@@ -1380,6 +1393,7 @@ int remap_pfn_range(struct vm_area_strucif (err)break ; }while (pgd++, addr = next, addr != end); +emm_notify (mm, emm_invalidate_end, start, end);return err; }EXPORT_SYMBOL (remap_pfn_range);
@@ -1463,10 +1477,12 @@ int apply_to_page_range(struct mm_struct {pgd_t *pgd;unsigned long next; +unsigned long start = addr;unsigned long end = addr + size;int err;BUG_ON (addr >= end); +emm_notify (mm, emm_invalidate_start, start, end); pgd =pgd_offset (mm, addr); do { next = pgd_addr_end(addr, end);
@@ -1474,6 +1490,7 @@ int apply_to_page_range(struct mm_structif (err)break ; }while (pgd++, addr = next, addr != end); +emm_notify (mm, emm_invalidate_end, start, end);return err; }EXPORT_SYMBOL_GPL (apply_to_page_range);
@@ -1614,8 +1631,10 @@ static int do_wp_page(struct mm_struct * page_table =pte_offset_map_lock (mm, pmd, address, &ptl);page_cache_release (old_page); -if (!pte_same (*page_table, orig_pte)) - goto unlock; +if (!pte_same (*page_table, orig_pte)) { +pte_unmap_unlock (page_table, ptl); + goto check_dirty; + } page_mkwrite =1 ; }
@@ -1631,7 +1650,8 @@ static int do_wp_page(struct mm_struct *if (ptep_set_access_flags (vma, address, page_table, entry,1 ))update_mmu_cache (vma, address, entry); ret |=VM_FAULT_WRITE ; - goto unlock; +pte_unmap_unlock (page_table, ptl); + goto check_dirty; }/*
@@ -1653,6 +1673,7 @@ gotten:if (mem_cgroup_charge (new_page, mm,GFP_KERNEL )) goto oom_free_new; +emm_notify (mm, emm_invalidate_start, address, address +PAGE_SIZE );/* * Re-check the pte - we dropped the lock */
@@ -1691,8 +1712,11 @@ gotten:page_cache_release (new_page);if (old_page)page_cache_release (old_page); -unlock: +pte_unmap_unlock (page_table, ptl); +emm_notify (mm, emm_invalidate_end, address, address +PAGE_SIZE ); + +check_dirty:if (dirty_page) {if (vma->vm_file)file_update_time (vma->vm_file);
include/linux/rmap.h +51 -0
55 lines not on this page · raw message
kernel/fork.c +3 -0
@@ -362,6 +362,9 @@ static struct mm_struct * mm_init(structif (likely (!mm_alloc_pgd (mm))) { mm->def_flags =0 ; +#ifdef CONFIG_EMM_NOTIFIER + mm->emm_notifier =NULL ; +#endif return mm; }
mm/filemap_xip.c +5 -0
@@ -190,6 +190,9 @@ __xip_unmap (struct address_space * mapp address = vma->vm_start + ((pgoff - vma->vm_pgoff) <<PAGE_SHIFT );BUG_ON (address < vma->vm_start || address >= vma->vm_end); +/* i_mmap_lock held */ +emm_notify (mm, emm_invalidate_start, + address, address +PAGE_SIZE ); pte =page_check_address (page, mm, address, &ptl);if (pte) {/* Nuke the page table entry. */
@@ -201,6 +204,8 @@ __xip_unmap (struct address_space * mapppte_unmap_unlock (pte, ptl);page_cache_release (page); } +emm_notify (mm, emm_invalidate_end, + address, address +PAGE_SIZE ); }spin_unlock (&mapping->i_mmap_lock); }
mm/fremap.c +2 -0
@@ -214,7 +214,9 @@ asmlinkage long sys_remap_file_pages(unsspin_unlock (&mapping->i_mmap_lock); } +emm_notify (mm, emm_invalidate_start, start, end); err =populate_range (mm, vma, start, size, pgoff); +emm_notify (mm, emm_invalidate_end, start, end);if (!err && !(flags &MAP_NONBLOCK )) {if (unlikely (has_write_lock)) {downgrade_write (&mm->mmap_sem);
mm/hugetlb.c +4 -0
@@ -14,6 +14,7 @@#include <linux/mempolicy.h> #include <linux/cpuset.h> #include <linux/mutex.h> +#include <linux/rmap.h> #include <asm/page.h> #include <asm/pgtable.h>
@@ -755,6 +756,8 @@ void __unmap_hugepage_range(struct vm_arBUG_ON (start & ~HPAGE_MASK );BUG_ON (end & ~HPAGE_MASK ); +/* i_mmap_lock held */ +emm_notify (mm, emm_invalidate_start, start, end);spin_lock (&mm->page_table_lock);for (address = start; address < end; address +=HPAGE_SIZE ) { ptep =huge_pte_offset (mm, address);
@@ -775,6 +778,7 @@ void __unmap_hugepage_range(struct vm_ar }spin_unlock (&mm->page_table_lock);flush_tlb_range (vma, start, end); +emm_notify (mm, emm_invalidate_end, start, end);list_for_each_entry_safe (page, tmp, &page_list, lru) {list_del (&page->lru);put_page (page);
-------------------------------------------------------------------------
This SF.net email is sponsored by: Microsoft Defy all challenges. Microsoft(R) Visual Studio 2008.
http://clk.atdmt.com/MRT/go/vse0120000070mrt/direct/01/
[Early draft] Conversion of i_mmap_lock to semaphore
Not there but the system boots and is usable. Complains about atomic contexts because the tlb functions use a get_cpu() and thus disable preempt.
Not sure yet what to do about the cond_resched_lock stuff etc.
Convert i_mmap_lock to i_mmap_sem
The conversion to a rwsemaphore allows callbacks during rmap traversal for files in a non atomic context. A rw style lock allows concurrent walking of the reverse map.
--- arch/x86/mm/hugetlbpage.c | 4 ++-- fs/hugetlbfs/inode.c | 4 ++-- fs/inode.c | 2 +- include/linux/fs.h | 2 +- include/linux/mm.h | 2 +- kernel/fork.c | 4 ++-- mm/filemap.c | 8 ++++---- mm/filemap_xip.c | 4 ++-- mm/fremap.c | 4 ++-- mm/hugetlb.c | 11 +++++------ mm/memory.c | 28 ++++++++-------------------- mm/migrate.c | 4 ++-- mm/mmap.c | 16 ++++++++-------- mm/mremap.c | 4 ++-- mm/rmap.c | 20 +++++++++----------- 15 files changed, 51 insertions(+), 66 deletions(-)
arch/x86/mm/hugetlbpage.c +2 -2
@@ -69,7 +69,7 @@ static void huge_pmd_share(struct mm_strif (!vma_shareable (vma, addr))return ; -spin_lock (&mapping->i_mmap_lock); +down_read (&mapping->i_mmap_sem); vma_prio_tree_foreach(svma, &iter, &mapping->i_mmap, idx, idx) {if (svma == vma)continue ;
@@ -94,7 +94,7 @@ static void huge_pmd_share(struct mm_strput_page (virt_to_page (spte));spin_unlock (&mm->page_table_lock); out: -spin_unlock (&mapping->i_mmap_lock); +up_read (&mapping->i_mmap_sem); }/*
fs/hugetlbfs/inode.c +2 -2
@@ -454,10 +454,10 @@ static int hugetlb_vmtruncate(struct ino pgoff = offset >>PAGE_SHIFT ;i_size_write (inode, offset); -spin_lock (&mapping->i_mmap_lock); +down_read (&mapping->i_mmap_sem);if (!prio_tree_empty (&mapping->i_mmap))hugetlb_vmtruncate_list (&mapping->i_mmap, pgoff); -spin_unlock (&mapping->i_mmap_lock); +up_read (&mapping->i_mmap_sem);truncate_hugepages (inode, offset);return 0 ; }
fs/inode.c +1 -1
@@ -210,7 +210,7 @@ void inode_init_once(struct inode *inodeINIT_LIST_HEAD (&inode->i_devices);INIT_RADIX_TREE (&inode->i_data.page_tree,GFP_ATOMIC );rwlock_init (&inode->i_data.tree_lock); -spin_lock_init (&inode->i_data.i_mmap_lock); +init_rwsem (&inode->i_data.i_mmap_sem);INIT_LIST_HEAD (&inode->i_data.private_list);spin_lock_init (&inode->i_data.private_lock);INIT_RAW_PRIO_TREE_ROOT (&inode->i_data.i_mmap);
include/linux/fs.h +1 -1
@@ -503,7 +503,7 @@ struct address_space {unsigned int i_mmap_writable;/* count VM_SHARED mappings */ struct prio_tree_root i_mmap;/* tree of private and shared mappings */ struct list_head i_mmap_nonlinear;/*list VM_NONLINEAR mappings */ -spinlock_t i_mmap_lock;/* protect tree, count, list */ +struct rw_semaphore i_mmap_sem;/* protect tree, count, list */ unsigned int truncate_count;/* Cover race condition with truncate */ unsigned long nrpages;/* number of total pages */ pgoff_t writeback_index;/* writeback starts here */
include/linux/mm.h +1 -1
@@ -709,7 +709,7 @@ struct zap_details {struct address_space *check_mapping;/* Check page->mapping if set */ pgoff_t first_index;/* Lowest page->index to unmap */ pgoff_t last_index;/* Highest page->index to unmap */ -spinlock_t *i_mmap_lock;/* For unmap_mapping_range: */ +struct rw_semaphore *i_mmap_sem;/* For unmap_mapping_range: */ unsigned long truncate_count;/* Compare vm_truncate_count */ };
kernel/fork.c +2 -2
@@ -273,12 +273,12 @@ static int dup_mmap(struct mm_struct *mmatomic_dec (&inode->i_writecount);/* insert tmp into the share list, just after mpnt */ -spin_lock (&file->f_mapping->i_mmap_lock); +down_write (&file->f_mapping->i_mmap_sem); tmp->vm_truncate_count = mpnt->vm_truncate_count;flush_dcache_mmap_lock (file->f_mapping);vma_prio_tree_add (tmp, mpnt);flush_dcache_mmap_unlock (file->f_mapping); -spin_unlock (&file->f_mapping->i_mmap_lock); +up_write (&file->f_mapping->i_mmap_sem); }/*
mm/filemap.c +4 -4
@@ -62,16 +62,16 @@ generic_file_direct_IO(int rw, struct ki/* * Lock ordering: * -* ->i_mmap_lock (vmtruncate) +* ->i_mmap_sem (vmtruncate) * ->private_lock (__free_pte->__set_page_dirty_buffers) * ->swap_lock (exclusive_swap_page, others) * ->mapping->tree_lock * * ->i_mutex -* ->i_mmap_lock (truncate->unmap_mapping_range) +* ->i_mmap_sem (truncate->unmap_mapping_range) * * ->mmap_sem -* ->i_mmap_lock +* ->i_mmap_sem * ->page_table_lock or pte_lock (various, mainly in memory.c) * ->mapping->tree_lock (arch-dependent flush_dcache_mmap_lock) *
@@ -88,7 +88,7 @@ generic_file_direct_IO(int rw, struct ki* ->sb_lock (fs/fs-writeback.c) * ->mapping->tree_lock (__sync_single_inode) * -* ->i_mmap_lock +* ->i_mmap_sem * ->anon_vma.lock (vma_adjust) * * ->anon_vma.lock
mm/filemap_xip.c +2 -2
@@ -184,7 +184,7 @@ __xip_unmap (struct address_space * mappif (!page)return ; -spin_lock (&mapping->i_mmap_lock); +down_read (&mapping->i_mmap_sem); vma_prio_tree_foreach(vma, &iter, &mapping->i_mmap, pgoff, pgoff) { mm = vma->vm_mm; address = vma->vm_start +
@@ -207,7 +207,7 @@ __xip_unmap (struct address_space * mappemm_notify (mm, emm_invalidate_end, address, address +PAGE_SIZE ); } -spin_unlock (&mapping->i_mmap_lock); +up_read (&mapping->i_mmap_sem); }/*
mm/fremap.c +2 -2
@@ -205,13 +205,13 @@ asmlinkage long sys_remap_file_pages(uns } goto out; } -spin_lock (&mapping->i_mmap_lock); +down_write (&mapping->i_mmap_sem);flush_dcache_mmap_lock (mapping); vma->vm_flags |=VM_NONLINEAR ;vma_prio_tree_remove (vma, &mapping->i_mmap);vma_nonlinear_insert (vma, &mapping->i_mmap_nonlinear);flush_dcache_mmap_unlock (mapping); -spin_unlock (&mapping->i_mmap_lock); +up_write (&mapping->i_mmap_sem); }emm_notify (mm, emm_invalidate_start, start, end);
mm/hugetlb.c +5 -6
@@ -746,7 +746,7 @@ void __unmap_hugepage_range(struct vm_arstruct page *page;struct page *tmp;/* -* A page gathering list, protected by per file i_mmap_lock. The +* A page gathering list, protected by per file i_mmap_sem. The * lock is used to avoid list corruption from multiple unmapping * of the same page since we are using page->lru. */
@@ -756,7 +756,6 @@ void __unmap_hugepage_range(struct vm_arBUG_ON (start & ~HPAGE_MASK );BUG_ON (end & ~HPAGE_MASK ); -/* i_mmap_lock held */ emm_notify (mm, emm_invalidate_start, start, end);spin_lock (&mm->page_table_lock);for (address = start; address < end; address +=HPAGE_SIZE ) {
@@ -797,9 +796,9 @@ void unmap_hugepage_range(struct vm_area* do nothing in this case. */ if (vma->vm_file) { -spin_lock (&vma->vm_file->f_mapping->i_mmap_lock); +down_write (&vma->vm_file->f_mapping->i_mmap_sem);__unmap_hugepage_range (vma, start, end); -spin_unlock (&vma->vm_file->f_mapping->i_mmap_lock); +up_write (&vma->vm_file->f_mapping->i_mmap_sem); } }
@@ -1042,7 +1041,7 @@ void hugetlb_change_protection(struct vmBUG_ON (address >= end);flush_cache_range (vma, address, end); -spin_lock (&vma->vm_file->f_mapping->i_mmap_lock); +down_read (&vma->vm_file->f_mapping->i_mmap_sem);spin_lock (&mm->page_table_lock);for (; address < end; address +=HPAGE_SIZE ) { ptep =huge_pte_offset (mm, address);
@@ -1057,7 +1056,7 @@ void hugetlb_change_protection(struct vm } }spin_unlock (&mm->page_table_lock); -spin_unlock (&vma->vm_file->f_mapping->i_mmap_lock); +up_read (&vma->vm_file->f_mapping->i_mmap_sem);flush_tlb_range (vma, start, end); }
mm/memory.c +8 -20
@@ -830,7 +830,6 @@ unsigned long unmap_vmas(struct mmu_gathunsigned long tlb_start =0 ;/* For tlb_finish_mmu */ int tlb_start_valid =0 ;unsigned long start = start_addr; -spinlock_t *i_mmap_lock = details? details->i_mmap_lock:NULL ;int fullmm = (*tlbp)->fullmm;for ( ; vma && vma->vm_start < end_addr; vma = vma->vm_next) {
@@ -868,21 +867,11 @@ unsigned long unmap_vmas(struct mmu_gathtlb_finish_mmu (*tlbp, tlb_start, start); -if (need_resched () || - (i_mmap_lock &&spin_needbreak (i_mmap_lock))) { -if (i_mmap_lock) { - *tlbp =NULL ; - goto out; - } -cond_resched (); - } - *tlbp =tlb_gather_mmu (vma->vm_mm, fullmm); tlb_start_valid =0 ; zap_work =ZAP_BLOCK_SIZE ; } } -out:return start;/* which is now the end (or restart) address */ }
@@ -905,7 +894,6 @@ unsigned long zap_page_range(struct vm_a tlb =tlb_gather_mmu (mm,0 );update_hiwater_rss (mm); -/* i_mmap_lock may be held */ emm_notify (mm, emm_invalidate_start, address, end); end =unmap_vmas (&tlb, vma, address, end, &nr_accounted, details);emm_notify (mm, emm_invalidate_end, address, end);
@@ -1749,7 +1737,7 @@ unwritable_page:/* * Helper functions for unmap_mapping_range(). * -* __ Notes on dropping i_mmap_lock to reduce latency while unmapping __ +* __ Notes on dropping i_mmap_sem to reduce latency while unmapping __ * * We have to restart searching the prio_tree whenever we drop the lock, * since the iterator is only valid while the lock is held, and anyway
@@ -1768,7 +1756,7 @@ unwritable_page:* can't efficiently keep all vmas in step with mapping->truncate_count: * so instead reset them all whenever it wraps back to 0 (then go to 1). * mapping->truncate_count and vma->vm_truncate_count are protected by -* i_mmap_lock. +* i_mmap_sem. * * In order to make forward progress despite repeatedly restarting some * large vma, note the restart_addr from unmap_vmas when it breaks out:
@@ -1818,7 +1806,7 @@ again: restart_addr =zap_page_range (vma, start_addr, end_addr - start_addr, details); - need_break =need_resched () ||spin_needbreak (details->i_mmap_lock); + need_break =need_resched ();if (restart_addr >= end_addr) {/* We have now completed this vma: mark it so */
@@ -1832,9 +1820,9 @@ again: goto again; } -spin_unlock (details->i_mmap_lock); +up_write (details->i_mmap_sem);cond_resched (); -spin_lock (details->i_mmap_lock); +down_write (details->i_mmap_sem);return -EINTR ; }
@@ -1928,9 +1916,9 @@ void unmap_mapping_range(struct address_ details.last_index = hba + hlen -1 ;if (details.last_index < details.first_index) details.last_index =ULONG_MAX ; - details.i_mmap_lock = &mapping->i_mmap_lock; + details.i_mmap_sem = &mapping->i_mmap_sem; -spin_lock (&mapping->i_mmap_lock); +down_write (&mapping->i_mmap_sem);/* Protect against endless unmapping loops */ mapping->truncate_count++;
@@ -1945,7 +1933,7 @@ void unmap_mapping_range(struct address_unmap_mapping_range_tree (&mapping->i_mmap, &details);if (unlikely (!list_empty (&mapping->i_mmap_nonlinear)))unmap_mapping_range_list (&mapping->i_mmap_nonlinear, &details); -spin_unlock (&mapping->i_mmap_lock); +up_write (&mapping->i_mmap_sem); }EXPORT_SYMBOL (unmap_mapping_range);
mm/migrate.c +2 -2
@@ -202,12 +202,12 @@ static void remove_file_migration_ptes(sif (!mapping)return ; -spin_lock (&mapping->i_mmap_lock); +down_read (&mapping->i_mmap_sem); vma_prio_tree_foreach(vma, &iter, &mapping->i_mmap, pgoff, pgoff)remove_migration_pte (vma, old, new); -spin_unlock (&mapping->i_mmap_lock); +up_read (&mapping->i_mmap_sem); }/*
mm/mmap.c +8 -8
@@ -186,7 +186,7 @@ error: }/* -* Requires inode->i_mapping->i_mmap_lock +* Requires inode->i_mapping->i_mmap_sem */ static void __remove_shared_vm_struct (struct vm_area_struct *vma,struct file *file,struct address_space *mapping)
@@ -214,9 +214,9 @@ void unlink_file_vma(struct vm_area_struif (file) {struct address_space *mapping = file->f_mapping; -spin_lock (&mapping->i_mmap_lock); +down_write (&mapping->i_mmap_sem);__remove_shared_vm_struct (vma, file, mapping); -spin_unlock (&mapping->i_mmap_lock); +up_write (&mapping->i_mmap_sem); } }
@@ -439,7 +439,7 @@ static void vma_link(struct mm_struct *m mapping = vma->vm_file->f_mapping;if (mapping) { -spin_lock (&mapping->i_mmap_lock); +down_write (&mapping->i_mmap_sem); vma->vm_truncate_count = mapping->truncate_count; }anon_vma_lock (vma);
@@ -449,7 +449,7 @@ static void vma_link(struct mm_struct *manon_vma_unlock (vma);if (mapping) -spin_unlock (&mapping->i_mmap_lock); +up_write (&mapping->i_mmap_sem); mm->map_count++;validate_mm (mm);
@@ -536,7 +536,7 @@ again: remove_next = 1 + (end > next-> mapping = file->f_mapping;if (!(vma->vm_flags &VM_NONLINEAR )) root = &mapping->i_mmap; -spin_lock (&mapping->i_mmap_lock); +down_write (&mapping->i_mmap_sem);if (importer && vma->vm_truncate_count != next->vm_truncate_count) {/*
@@ -620,7 +620,7 @@ again: remove_next = 1 + (end > next->if (anon_vma)spin_unlock (&anon_vma->lock);if (mapping) -spin_unlock (&mapping->i_mmap_lock); +up_write (&mapping->i_mmap_sem);if (remove_next) {if (file)
@@ -2064,7 +2064,7 @@ void exit_mmap(struct mm_struct *mm)/* Insert vm structure into process list sorted by address * and into the inode's i_mmap tree. If vm_file is non-NULL -* then i_mmap_lock is taken here. +* then i_mmap_sem is taken here. */ int insert_vm_struct (struct mm_struct * mm,struct vm_area_struct * vma) {
mm/mremap.c +2 -2
@@ -86,7 +86,7 @@ static void move_ptes(struct vm_area_str* and we propagate stale pages into the dst afterward. */ mapping = vma->vm_file->f_mapping; -spin_lock (&mapping->i_mmap_lock); +down_write (&mapping->i_mmap_sem);if (new_vma->vm_truncate_count && new_vma->vm_truncate_count != vma->vm_truncate_count) new_vma->vm_truncate_count =0 ;
@@ -119,7 +119,7 @@ static void move_ptes(struct vm_area_strpte_unmap_nested (new_pte -1 );pte_unmap_unlock (old_pte -1 , old_ptl);if (mapping) -spin_unlock (&mapping->i_mmap_lock); +up_write (&mapping->i_mmap_sem);emm_notify (mm, emm_invalidate_end, old_start, old_end); }
mm/rmap.c +9 -11
@@ -24,7 +24,7 @@* inode->i_alloc_sem (vmtruncate_range) * mm->mmap_sem * page->flags PG_locked (lock_page) -* mapping->i_mmap_lock +* mapping->i_mmap_sem * anon_vma->lock * mm->page_table_lock or pte_lock * zone->lru_lock (in mark_page_accessed, isolate_lru_page)
@@ -368,14 +368,14 @@ static int page_referenced_file(struct p* The page lock not only makes sure that page->mapping cannot * suddenly be NULLified by truncation, it makes sure that the * structure at mapping cannot be freed and reused yet, -* so we can safely take mapping->i_mmap_lock. +* so we can safely take mapping->i_mmap_sem. */ BUG_ON (!PageLocked (page)); -spin_lock (&mapping->i_mmap_lock); +down_read (&mapping->i_mmap_sem);/* -* i_mmap_lock does not stabilize mapcount at all, but mapcount +* i_mmap_sem does not stabilize mapcount at all, but mapcount * is more likely to be accurate if we note it after spinning. */ mapcount =page_mapcount (page);
@@ -398,7 +398,7 @@ static int page_referenced_file(struct pbreak ; } -spin_unlock (&mapping->i_mmap_lock); +up_read (&mapping->i_mmap_sem);return referenced; }
@@ -482,12 +482,12 @@ static int page_mkclean_file(struct addrBUG_ON (PageAnon (page)); -spin_lock (&mapping->i_mmap_lock); +down_read (&mapping->i_mmap_sem); vma_prio_tree_foreach(vma, &iter, &mapping->i_mmap, pgoff, pgoff) {if (vma->vm_flags &VM_SHARED ) ret +=page_mkclean_one (page, vma); } -spin_unlock (&mapping->i_mmap_lock); +up_read (&mapping->i_mmap_sem);return ret; }
@@ -923,7 +923,7 @@ static int try_to_unmap_file(struct pageunsigned long max_nl_size =0 ;unsigned int mapcount; -spin_lock (&mapping->i_mmap_lock); +down_read (&mapping->i_mmap_sem); vma_prio_tree_foreach(vma, &iter, &mapping->i_mmap, pgoff, pgoff) { ret =try_to_unmap_one (page, vma, migration);if (ret ==SWAP_FAIL || !page_mapped (page))
@@ -960,7 +960,6 @@ static int try_to_unmap_file(struct page mapcount =page_mapcount (page);if (!mapcount) goto out; -cond_resched_lock (&mapping->i_mmap_lock); max_nl_size = (max_nl_size +CLUSTER_SIZE -1 ) &CLUSTER_MASK ;if (max_nl_cursor ==0 )
@@ -982,7 +981,6 @@ static int try_to_unmap_file(struct page } vma->vm_private_data = (void *) max_nl_cursor; } -cond_resched_lock (&mapping->i_mmap_lock); max_nl_cursor +=CLUSTER_SIZE ; }while (max_nl_cursor <= max_nl_size);
@@ -994,7 +992,7 @@ static int try_to_unmap_file(struct pagelist_for_each_entry (vma, &mapping->i_mmap_nonlinear, shared.vm_set.list) vma->vm_private_data =NULL ; out: -spin_unlock (&mapping->i_mmap_lock); +up_write (&mapping->i_mmap_sem);return ret; }
-------------------------------------------------------------------------
This SF.net email is sponsored by: Microsoft Defy all challenges. Microsoft(R) Visual Studio 2008.
http://clk.atdmt.com/MRT/go/vse0120000070mrt/direct/01/
Re: [RFC] Notifier for Externally Mapped Memory (EMM)
In reply to Christoph LameterQuoted 10 lines · Christoph Lameter
mm/rmap.c +2
-0@@ -446,6 +450,8 @@ static int page_mkclean_one(struct pageif (address == -EFAULT ) goto out; +/* rmap lock held */ +emm_notify (mm, emm_invalidate_start, address, address +PAGE_SIZE ); pte =page_check_address (page, mm, address, &ptl);if (!pte) goto out;
mm/rmap.c static int page_mkclean_one(struct page · line 474
Quoted 7 lines
mm/rmap.c +1
-0@@ -462,6 +468,7 @@ static int page_mkclean_one(struct page }pte_unmap_unlock (pte, ptl); +emm_notify (mm, emm_invalidate_end, address, address +PAGE_SIZE ); out:return ret; }
I could have ripped invalidate_page from my patch too, except I didn't want to slow down those paths for the known-common-users when not even GRU would get any benefit from two hooks when only one is needed.
When working with single pages it's more efficient and preferable to call invalidate_page and only later release the VM reference on the page.
Re: [RFC] Notifier for Externally Mapped Memory (EMM)
Quoted 3 lines · Andrea Arcangeli
When working with single pages it's more efficient and preferable to call
invalidate_pageand only later release the VM reference on the page.
But as you pointed out before that path is a slow path anyways. Its rarely taken. Having a single eviction callback simplifies design.
Plus the device driver can still check if the mapping was of PAGE_SIZE and then implement its own optimization.
Re: [RFC] Notifier for Externally Mapped Memory (EMM)
Quoted 1 line · Christoph Lameter
But as you pointed out before that path is a slow path anyways. Its rarely
It's a slow path but I don't see why you think two hooks are better than one, when only one is necessary.
I once ripped invalidate_page while working on #v8 but then I reintroduced it because I thought reducing the total number of hooks was beneficial to the core linux VM (even if only a microoptimization, I sure agree about that, but it's trivial to add one hook instead of two hooks there, so a microoptimization was worth it IMHO).
Your API is also too restrictive, if we'll happen to need one more method that doesn't take just (start,end) we'll have to cause all drivers to have significant changes instead of one-liners to use whatever new feature.
Quoted 1 line
taken. Having a single eviction callback simplifies design.
IMHO the design is actually same and I don't understand why you rewrote it once more time in a less flexibile way (on a style side you're not even using hlist), dropping RCU (not sure how you replace it with), etc....
Your implementation has the same bug you had in your first V1, see how you're not clearing the spte young bits if the pte young bit is set. Once you fix that, your change in the ptep_clear_flush_young path will look remarkably similar to the patch I posted incremental with #v8 to make ->clear_flush_young sleep capable...
Converging in a single design is great, but it'd be nice if we could converge into a single implementation, and my last patch doesn't have any bug and I think it's quite nicer too (also including Nick cleanup work) but then I may be biased ;).
But as usual I'm entirely satisfied by your brand new EMM Notifier to be merged and all perfecting work done on my MMU notifier patch over the weeks by multiple developers (including you) to be dropped for good, as long as we can enable the new advanced KVM features in 2.6.25.
Re: [RFC] Notifier for Externally Mapped Memory (EMM)
Quoted 6 lines · Andrea Arcangeli
I once ripped
invalidate_pagewhile working on #v8 but then I reintroduced it because I thought reducing the total number of hooks was beneficial to the core linux VM (even if only a microoptimization, I sure agree about that, but it's trivial to add one hook instead of two hooks there, so a microoptimization was worth it IMHO).
Well the problem is if one does not have the begin/end hooks then reliable clearing of the mapping may not be possible. begin/end allow holding off new references and that avoids the issue that would come with an single callback that could race with something else.
Quoted 4 lines
Your API is also too restrictive, if we'll happen to need one more method that doesn't take just (start,end) we'll have to cause all drivers to have significant changes instead of one-liners to use whatever new feature.
What would that be? I think the API need to stay as simple as possible. And this set is pretty minimal and easy to understand. Not having the invalidate_page() removes a troublespot from the API.
Quoted 4 lines
IMHO the design is actually same and I don't understand why you rewrote it once more time in a less flexibile way (on a style side you're not even using hlist), dropping RCU (not sure how you replace it with), etc....
All of that is needed in order to allow sleeping in the future. Your version locks us into atomic callbacks. It also makes the API needlessly complex.
RCU means that the callbacks occur in an atomic context.
Quoted 4 lines
Converging in a single design is great, but it'd be nice if we could converge into a single implementation, and my last patch doesn't have any bug and I think it's quite nicer too (also including Nick cleanup work) but then I may be biased ;).
It is the atomic dead end that we want to avoid. And your patch is exactly that. Both the invalidate_page and the RCU locks us into this.
Quoted 5 lines
But as usual I'm entirely satisfied by your brand new EMM Notifier to be merged and all perfecting work done on my MMU notifier patch over the weeks by multiple developers (including you) to be dropped for good, as long as we can enable the new advanced KVM features in 2.6.25.
Well I really want us to have one API that is suitable for multiple purposes and that allows a generic use by device drivers for multiple purposes. The discussion in the last month have made that possible. I am glad that you do not see any major issues with the patch. I sure wish I would not have to post a competing patchset because I want things to be merged ASAP and get this over with. But we need to have at minimum clear way to support sleeping with the existing API in the future.
-------------------------------------------------------------------------
This SF.net email is sponsored by: Microsoft Defy all challenges. Microsoft(R) Visual Studio 2008.
http://clk.atdmt.com/MRT/go/vse0120000070mrt/direct/01/
Re: [RFC] Notifier for Externally Mapped Memory (EMM)
Quoted 1 line · Christoph Lameter
RCU means that the callbacks occur in an atomic context.
Not really, if it requires moving the VM locks to sleepable locks under a .config option, I think its also fair to require PREEMPT_RCU.
OTOH, if you want to unconditionally move the VM locks to sleepable locks you have a point.
Re: [RFC] Notifier for Externally Mapped Memory (EMM)
Quoted 7 lines · Peter Zijlstra
Quoted 1 line · Christoph Lameter
RCU means that the callbacks occur in an atomic context.
Not really, if it requires moving the VM locks to sleepable locks under a .config option, I think its also fair to require
PREEMPT_RCU.
Which would make the patchset pretty complex. RCU is not needed with a single linked list. Linked list operations can exploit atomic pointer updates and we only tear down the list when a single execution thread remains.
Having said that: Here a couple of updates to address Andrea's complaint that we not check the referenced bit from the external mapper when the rerferences bit is set on an OS pte.
Plus two barriers to ensure that a new emm notifier object becomes visible before the base pointer is updated.
--- mm/rmap.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-)
mm/rmap.c +6 -4
@@ -298,10 +298,10 @@ static int page_referenced_one(struct pa (*mapcount)--;pte_unmap_unlock (pte, ptl); -if (!referenced) -/* rmap lock held */ - referenced =emm_notify (mm, emm_referenced, - address, address +PAGE_SIZE ); + +/* rmap lock held */ +if (emm_notify (mm, emm_referenced, address, address +PAGE_SIZE )) + referenced =1 ; out:return referenced; }
@@ -1057,6 +1057,7 @@ EXPORT_SYMBOL_GPL(emm_notifier_release);void emm_notifier_register (struct emm_notifier *e,struct mm_struct *mm) { e->next = mm->emm_notifier; +smp_wmb (); mm->emm_notifier = e; }EXPORT_SYMBOL_GPL (emm_notifier_register);
@@ -1069,6 +1070,7 @@ int __emm_notify(struct mm_struct *mm, eint x;while (e) { +smp_rmb ();if (e->func) { x = e->func (e, mm, op, start, end);if (x)
-------------------------------------------------------------------------
This SF.net email is sponsored by: Microsoft Defy all challenges. Microsoft(R) Visual Studio 2008.
http://clk.atdmt.com/MRT/go/vse0120000070mrt/direct/01/
Re: [RFC] Notifier for Externally Mapped Memory (EMM)
Quoted 7 lines · Christoph Lameter
Quoted 5 lines · Peter Zijlstra
Quoted 1 line · Christoph Lameter
RCU means that the callbacks occur in an atomic context.
Quoted 7 lines
Quoted 2 lines
Not really, if it requires moving the VM locks to sleepable locks under a .config option, I think its also fair to require
PREEMPT_RCU.Which would make the patchset pretty complex. RCU is not needed with a single linked list. Linked list operations can exploit atomic pointer updates and we only tear down the list when a single execution thread remains.
OK, that constraint on removal makes it work.
Quoted 19 lines
Having said that: Here a couple of updates to address Andrea's complaint that we not check the referenced bit from the external mapper when the rerferences bit is set on an OS pte.
Plus two barriers to ensure that a new emm notifier object becomes visible before the base pointer is updated.
Signed-off-by Christoph Lameter <[email protected]>--- mm/rmap.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-)Index:
linux-2.6/mm/rmap.c
===================================================================mm/rmap.c +0
-0@@ -298,10 +298,10 @@ static int page_referenced_one(struct pa
mm/rmap.c static int page_referenced_one(struct pa · line 304
Quoted 10 lines
mm/rmap.c +4
-4(*mapcount)--;pte_unmap_unlock (pte, ptl); -if (!referenced) -/* rmap lock held */ - referenced =emm_notify (mm, emm_referenced, - address, address +PAGE_SIZE ); + +/* rmap lock held */ +if (emm_notify (mm, emm_referenced, address, address +PAGE_SIZE )) + referenced =1 ;
referenced++; seems more in-style with the rest of that code..
Quoted 12 lines
mm/rmap.c +1
-0out:return referenced; }@@ -1057,6 +1057,7 @@ EXPORT_SYMBOL_GPL(emm_notifier_release);void emm_notifier_register (struct emm_notifier *e,struct mm_struct *mm) { e->next = mm->emm_notifier; +smp_wmb (); mm->emm_notifier = e; }EXPORT_SYMBOL_GPL (emm_notifier_register);
mm/rmap.c int __emm_notify(struct mm_struct *mm, e · line 1076
Quoted 7 lines
mm/rmap.c +1
-0@@ -1069,6 +1070,7 @@ int __emm_notify(struct mm_struct *mm, eint x;while (e) { +smp_rmb ();if (e->func) { x = e->func (e, mm, op, start, end);if (x)
We generally require comments around barriers..
Re: [RFC] Notifier for Externally Mapped Memory (EMM)
FWIW, I'll cut the kvm and openfabrics lists from any future posts. I'm getting tired of the bounces.
-------------------------------------------------------------------------
This SF.net email is sponsored by: Microsoft Defy all challenges. Microsoft(R) Visual Studio 2008.
http://clk.atdmt.com/MRT/go/vse0120000070mrt/direct/01/
Re: [RFC] Notifier for Externally Mapped Memory (EMM)
In reply to Peter ZijlstraQuoted 12 lines · Peter Zijlstra
Quoted 2 lines · Christoph Lameter
RCU means that the callbacks occur in an atomic context.
Not really, if it requires moving the VM locks to sleepable locks under a .config option, I think its also fair to require
PREEMPT_RCU.OTOH, if you want to unconditionally move the VM locks to sleepable locks you have a point.
Isn't that out of the question for .25?
I really wish we can get the atomic variant in now, and add on sleepability in .26, updating users if necessary.
Signature · 1 line
Do not meddle in the internals of kernels, for they are subtle and quick to panic.
Re: [RFC] Notifier for Externally Mapped Memory (EMM)
Quoted 1 line · Avi Kivity
Isn't that out of the question for .25?
I keep hearing this mantra. What is so compelling about the .25 release? When seems to be more important than what. While I understand product release cycles, etc. and can certainly agree with them. I would like to know with what I am being asked to agree.
That said, I agree we should probably finish getting the comments on Andrea's most recent patch, if any, cleared up and put that one in.
Robin
-------------------------------------------------------------------------
This SF.net email is sponsored by: Microsoft Defy all challenges. Microsoft(R) Visual Studio 2008.
http://clk.atdmt.com/MRT/go/vse0120000070mrt/direct/01/
Re: [RFC] Notifier for Externally Mapped Memory (EMM)
Quoted 11 lines · Robin Holt
Quoted 2 lines · Avi Kivity
Isn't that out of the question for .25?
I keep hearing this mantra. What is so compelling about the .25 release? When seems to be more important than what. While I understand product release cycles, etc. and can certainly agree with them. I would like to know with what I am being asked to agree.
kvm gained the ability to swap in 2.6.25. Without mmu notifiers, though, the guest can still easily pin all of its memory.
Quoted 3 lines
That said, I agree we should probably finish getting the comments on Andrea's most recent patch, if any, cleared up and put that one in.
Great. Thanks.
Signature · 1 line
Do not meddle in the internals of kernels, for they are subtle and quick to panic.
Re: [kvm-devel] [RFC] Notifier for Externally Mapped Memory (EMM)
In reply to Robin HoltQuoted 8 lines · Robin Holt
Quoted 1 line · Avi Kivity
Isn't that out of the question for .25?
I keep hearing this mantra. What is so compelling about the .25 release? When seems to be more important than what. While I understand product release cycles, etc. and can certainly agree with them. I would like to know with what I am being asked to agree.
The main reason is that several kvm exciting features are dependent on mmu notifiers:
- It enables full guest swapping (as opposed to partial today)
- It enables memory ballooning
- It enables running Izik Eidus's Kernel Shared Pages module that unify guest pages together.
The patchset is kernel-internal, stable and reviewed. Even if the interface will be changed in .26 it won't have noticeable effect.
So since its stable, internal, reviewed, needed to enable important kvm features we like to see it in for .25.
Regards,
Dor
Quoted 13 lines
That said, I agree we should probably finish getting the comments on Andrea's most recent patch, if any, cleared up and put that one in.
Robin
-------------------------------------------------------------------------
This SF.net email is sponsored by: Microsoft Defy all challenges. Microsoft(R) Visual Studio 2008.
http://clk.atdmt.com/MRT/go/vse0120000070mrt/direct/01/
_______________________________________________
kvm-devel mailing list [email protected]
https://lists.sourceforge.net/lists/listinfo/kvm-devel
PATCH 1/1 2/4 move all invalidate_page outside of PT lock (#v9 was 1/4)
In reply to Christoph LameterQuoted 2 lines · Christoph Lameter
It is the atomic dead end that we want to avoid. And your patch is exactly that. Both the
invalidate_pageand the RCU locks us into this.
I preferred to answer with code to avoid any possible misunderstanding (I through already tried to explain with words and I obviously failed miserably if you ended up writing such an erratic weird claim like above ;).
This below simple patch invalidates the "invalidate_page" part, the next patch will invalidate the RCU part, and btw in a way that doesn't forbid unregistering the mmu notifiers at runtime (like your brand new EMM does).
This is incremental with my #v9. I still ask Andrew/Linus to merge the #v9 patch I posted a few days ago in .25 so KVM/GRU will be 100% covered in a optimal way on all respects and with maximum flexibility for future changes of API (to allow for future methods that may take more than start,end, this was pointed out once by both me and Avi). My #v9 is zero risk for .25 and it sure worth merging now.
Then in .26 we'll modify the semantics of the API to be blocking starting with the below patchx. This is a kernel _internal_ API, and we aren't distributions that have to respect kabi here, but even if we were, making methods sleepable is a 100% backwards compatible semantical change, so there's no possible reason to defer the #v9 merging. The changes in .26 will be transparent to any user (even if they don't need to! even if we turn out to be totally wrong about .26 requiring a minor change of API everything will be perfectly fine). Nothing of this is visible to userland so we can change it at any time as we wish.
The reason I keep this incremental (unlike your EMM that does everything all at the same time mixed in a single patch) is to decrease the non obviously safe mangling over mm/* during .25. The below patch is simple, but not as obviously safe as s/ptep_clear_flush/ptep_clear_flush_notify/.
include/linux/mmu_notifier.h +0 -24
@@ -134,27 +134,6 @@ static inline void mmu_notifier_mm_init( -#define ptep_clear_flush_notify (__vma, __address, __ptep) \ -({ \ - pte_t __pte; \ - struct vm_area_struct *___vma = __vma; \ - unsigned long ___address = __address; \ - __pte = ptep_clear_flush(___vma, ___address, __ptep); \ - mmu_notifier_invalidate_page(___vma->vm_mm, ___address); \ - __pte; \ -}) - -#define ptep_clear_flush_young_notify (__vma, __address, __ptep) \ -({ \ - int __young; \ - struct vm_area_struct *___vma = __vma; \ - unsigned long ___address = __address; \ - __young = ptep_clear_flush_young(___vma, ___address, __ptep); \ - __young |= mmu_notifier_clear_flush_young(___vma->vm_mm, \ - ___address); \ - __young; \ -}) -#else /* CONFIG_MMU_NOTIFIER */ static inline void mmu_notifier_release (struct mm_struct *mm)
@@ -186,9 +165,6 @@ static inline void mmu_notifier_mm_init( { } -#define ptep_clear_flush_young_notify ptep_clear_flush_young -#define ptep_clear_flush_notify ptep_clear_flush -#endif /* CONFIG_MMU_NOTIFIER */ #endif /* _LINUX_MMU_NOTIFIER_H */
mm/filemap_xip.c +3 -1
@@ -194,11 +194,13 @@ __xip_unmap (struct address_space * mappif (pte) {/* Nuke the page table entry. */ flush_cache_page (vma, address,pte_pfn (*pte)); - pteval =ptep_clear_flush_notify (vma, address, pte); + pteval =ptep_clear_flush (vma, address, pte);page_remove_rmap (page, vma);dec_mm_counter (mm, file_rss);BUG_ON (pte_dirty (pteval));pte_unmap_unlock (pte, ptl); +/* must invalidate_page _before_ freeing the page */ +mmu_notifier_invalidate_page (mm, address);page_cache_release (page); } }
mm/memory.c +13 -5
@@ -1626,9 +1626,10 @@ static int do_wp_page(struct mm_struct **/ page_table =pte_offset_map_lock (mm, pmd, address, &ptl); -page_cache_release (old_page); + new_page =NULL ;if (!pte_same (*page_table, orig_pte)) goto unlock; +page_cache_release (old_page); page_mkwrite =1 ; }
@@ -1644,6 +1645,7 @@ static int do_wp_page(struct mm_struct *if (ptep_set_access_flags (vma, address, page_table, entry,1 ))update_mmu_cache (vma, address, entry); ret |=VM_FAULT_WRITE ; + old_page = new_page =NULL ; goto unlock; }
@@ -1688,7 +1690,7 @@ gotten:* seen in the presence of one thread doing SMC and another * thread doing COW. */ -ptep_clear_flush_notify (vma, address, page_table); +ptep_clear_flush (vma, address, page_table);set_pte_at (mm, address, page_table, entry);update_mmu_cache (vma, address, entry);lru_cache_add_active (new_page);
@@ -1700,12 +1702,18 @@ gotten: }else mem_cgroup_uncharge_page (new_page); -if (new_page) +unlock: +pte_unmap_unlock (page_table, ptl); + +if (new_page) { +if (new_page == old_page) +/* cow happened, notify before releasing old_page */ +mmu_notifier_invalidate_page (mm, address);page_cache_release (new_page); + }if (old_page)page_cache_release (old_page); -unlock: -pte_unmap_unlock (page_table, ptl); +if (dirty_page) {if (vma->vm_file)file_update_time (vma->vm_file);
mm/rmap.c +25 -10
@@ -275,7 +275,7 @@ static int page_referenced_one(struct paunsigned long address;pte_t *pte;spinlock_t *ptl; -int referenced =0 ; +int referenced =0 , clear_flush_young =0 ; address =vma_address (page, vma);if (address == -EFAULT )
@@ -288,8 +288,11 @@ static int page_referenced_one(struct paif (vma->vm_flags &VM_LOCKED ) { referenced++; *mapcount =1 ;/* break early from loop */ - }else if (ptep_clear_flush_young_notify (vma, address, pte)) - referenced++; + }else { + clear_flush_young =1 ; +if (ptep_clear_flush_young (vma, address, pte)) + referenced++; + }/* Pretend the page is referenced if the task has the swap token and is in the middle of a page fault. */
@@ -299,6 +302,10 @@ static int page_referenced_one(struct pa (*mapcount)--;pte_unmap_unlock (pte, ptl); + +if (clear_flush_young) + referenced +=mmu_notifier_clear_flush_young (mm, address); + out:return referenced; }
@@ -455,7 +462,7 @@ static int page_mkclean_one(struct pagepte_t entry;flush_cache_page (vma, address,pte_pfn (*pte)); - entry =ptep_clear_flush_notify (vma, address, pte); + entry =ptep_clear_flush (vma, address, pte); entry =pte_wrprotect (entry); entry =pte_mkclean (entry);set_pte_at (mm, address, pte, entry);
@@ -463,6 +470,10 @@ static int page_mkclean_one(struct page }pte_unmap_unlock (pte, ptl); + +if (ret) +mmu_notifier_invalidate_page (mm, address); + out:return ret; }
@@ -712,15 +723,14 @@ static int try_to_unmap_one(struct page* If it's recently referenced (perhaps page_referenced * skipped over this mm) then we should reactivate it. */ -if (!migration && ((vma->vm_flags &VM_LOCKED ) || - (ptep_clear_flush_young_notify (vma, address, pte)))) { +if (!migration && (vma->vm_flags &VM_LOCKED )) { ret =SWAP_FAIL ; goto out_unmap; }/* Nuke the page table entry. */ flush_cache_page (vma, address,page_to_pfn (page)); - pteval =ptep_clear_flush_notify (vma, address, pte); + pteval =ptep_clear_flush (vma, address, pte);/* Move the dirty bit to the physical page now the pte is gone. */ if (pte_dirty (pteval))
@@ -775,6 +785,8 @@ static int try_to_unmap_one(struct page out_unmap:pte_unmap_unlock (pte, ptl); +if (ret !=SWAP_FAIL ) +mmu_notifier_invalidate_page (mm, address); out:return ret; }
@@ -813,7 +825,7 @@ static void try_to_unmap_cluster(unsignespinlock_t *ptl;struct page *page;unsigned long address; -unsigned long end; +unsigned long start, end; address = (vma->vm_start + cursor) &CLUSTER_MASK ; end = address +CLUSTER_SIZE ;
@@ -834,6 +846,8 @@ static void try_to_unmap_cluster(unsigneif (!pmd_present (*pmd))return ; + start = address; +mmu_notifier_invalidate_range_begin (mm, start, end); pte =pte_offset_map_lock (mm, pmd, address, &ptl);/* Update high watermark before we lower rss */
@@ -845,12 +859,12 @@ static void try_to_unmap_cluster(unsigne page =vm_normal_page (vma, address, *pte);BUG_ON (!page ||PageAnon (page)); -if (ptep_clear_flush_young_notify (vma, address, pte)) +if (ptep_clear_flush_young (vma, address, pte))continue ;/* Nuke the page table entry. */ flush_cache_page (vma, address,pte_pfn (*pte)); - pteval =ptep_clear_flush_notify (vma, address, pte); + pteval =ptep_clear_flush (vma, address, pte);/* If nonlinear, store the file page offset in the pte. */ if (page->index !=linear_page_index (vma, address))
@@ -866,6 +880,7 @@ static void try_to_unmap_cluster(unsigne (*mapcount)--; }pte_unmap_unlock (pte -1 , ptl); +mmu_notifier_invalidate_range_end (mm, start, end); }static int try_to_unmap_anon (struct page *page,int migration)
Re: [PATCH] 3/4 combine RCU with seqlock to allow mmu notifier methods to sleep (#v9 was 1/4)
This combines the non-sleep-capable RCU locking of #v9 with a seqlock so the mmu notifier fast path will require zero cacheline writes/bouncing while still providing mmu_notifier_unregister and allowing to schedule inside the mmu notifier methods. If we drop mmu_notifier_unregister we can as well drop all seqlock and rcu_read_lock()s. But this locking scheme combination is sexy enough and 100% scalable (the mmu_notifier_list cacheline will be preloaded anyway and that will most certainly include the sequence number value in l1 for free even in Christoph's NUMA systems) so IMHO it worth to keep mmu_notifier_unregister.
include/linux/mm_types.h +2 -0
@@ -10,6 +10,7 @@#include <linux/rbtree.h> #include <linux/rwsem.h> #include <linux/completion.h> +#include <linux/seqlock.h> #include <asm/page.h> #include <asm/mmu.h>
@@ -230,6 +231,7 @@ struct mm_struct {#endif #ifdef CONFIG_MMU_NOTIFIER struct hlist_head mmu_notifier_list; +seqlock_t mmu_notifier_lock;#endif };
include/linux/mmu_notifier.h +1 -0
@@ -130,6 +130,7 @@ static inline void mmu_notifier_mm_init(static inline void mmu_notifier_mm_init (struct mm_struct *mm) {INIT_HLIST_HEAD (&mm->mmu_notifier_list); +seqlock_init (&mm->mmu_notifier_lock); }
mm/mmu_notifier.c +47 -4
116 lines not on this page · raw message
PATCH 1/1 4/4 i_mmap_lock spinlock2rwsem (#v9 was 1/4)
This is a rediff of Christoph's plain i_mmap_lock2rwsem patch on top of #v9 1/4 + 2/4 + 3/4 (hence this is called 4/4). This is mostly to show that after 3/4, any patch that plugs on the EMM patchset will plug nicely on top of my MMU notifer patchset too.
The patch trigger bug checks here in modprobe:
BUG_ON(mm->nr_ptes > (FIRST_USER_ADDRESS+PMD_SIZE-1)>>PMD_SHIFT);
kjournald starting. Commit interval 5 seconds
EXT3-fs: mounted filesystem with ordered data mode.
VFS: Mounted root (ext3 filesystem) readonly. Freeing unused kernel memory: 252k freed
------------[ cut here ]------------ kernel BUG at mm/mmap.c:2063! invalid opcode: 0000 [1] SMP CPU 0 Modules linked in: Pid: 1123, comm: modprobe.sh Not tainted 2.6.25-rc3 #22 RIP: 0010:[<ffffffff80269368>] [<ffffffff80269368>] exit_mmap+0xef/0xfa RSP: 0000:ffff81003c79bed8 EFLAGS: 00010206 RAX: 0000000000000000 RBX: ffff810001004840 RCX: ffff81003c79bee0 RDX: 0000000000000000 RSI: ffff81003c5e8918 RDI: ffff81003d8048c0 RBP: 0000000000000000 R08: 0000000000000008 R09: ffff810002c00040 R10: 0000000000000002 R11: ffff810001009180 R12: ffff81003c57b800 R13: 0000000000000000 R14: 00000000005f0db0 R15: 00007fff3f2af234 FS: 00007f283714b6f0(0000) GS:ffffffff80694000(0000) knlGS:0000000000000000 CS: 0010 DS: 0000 ES: 0000 CR0: 000000008005003b CR2: 0000000000458f40 CR3: 0000000000201000 CR4: 00000000000006e0 DR0: 0000000000000000 DR1: 0000000000000000 DR2: 0000000000000000 DR3: 0000000000000000 DR6: 00000000ffff0ff0 DR7: 0000000000000400 Process modprobe.sh (pid: 1123, threadinfo ffff81003c79a000, task ffff81003cf9ca50) Stack: 0000000000000091 ffff810001004840 ffff81003c57b800 ffff81003c57b880 0000000000000000 ffffffff8022f7bf 0000000000000001 0000000000000001 ffff81003cf9ca50 ffffffff802349b6 0000000000000292 ffffffff80354c63 Call Trace: [<ffffffff8022f7bf>] mmput+0x30/0x9d [<ffffffff802349b6>] do_exit+0x223/0x66c [<ffffffff80354c63>] __up_read+0x13/0x8a [<ffffffff80234e6e>] do_group_exit+0x6f/0x8a [<ffffffff8020bd3b>] system_call_after_swapgs+0x7b/0x80 Code: 7b 18 e8 4a 5c 00 00 c7 43 08 00 00 00 00 eb 0b 48 89 ef e8 d1 fe ff ff 48 89 c5 48 85 ed 75 f0 49 modprobe.sh[1114]: segfault at 0 ip 7f998d2e972b sp 7fff959d8ed0 error 4 in libc-2.6.1.so[7f998d27b000+136000]
I didn't look into this but it shows how it would be risky to make this change in .25. It's a bit strange that the bugcheck triggers given I've preempt disabled (I mean CONFIG_PREEMPT_VOLUNTARY=y, nobody should turn off that config option) and so even if code depended on the implicit preempt_disable in spin_lock, no race should happen. The down_read sections at first glance didn't seem capable of altering nr_ptes, but I didn't look seriously into the above. I rediffed it just to be 100% on par with EMM sleep-capabilities (but while retaining more features and cleaner code I hope).
------------------
From: Christoph Lameter <[email protected]>
Subject: Conversion of i_mmap_lock to semaphore
Not there but the system boots and is usable. Complains about atomic contexts because the tlb functions use a get_cpu() and thus disable preempt.
Not sure yet what to do about the cond_resched_lock stuff etc.
Convert i_mmap_lock to i_mmap_sem
The conversion to a rwsemaphore allows callbacks during rmap traversal for files in a non atomic context. A rw style lock allows concurrent walking of the reverse map.
--- arch/x86/mm/hugetlbpage.c | 4 ++-- fs/hugetlbfs/inode.c | 4 ++-- fs/inode.c | 2 +- include/linux/fs.h | 2 +- include/linux/mm.h | 2 +- kernel/fork.c | 4 ++-- mm/filemap.c | 8 ++++---- mm/filemap_xip.c | 4 ++-- mm/fremap.c | 4 ++-- mm/hugetlb.c | 11 +++++------ mm/memory.c | 28 ++++++++-------------------- mm/migrate.c | 4 ++-- mm/mmap.c | 16 ++++++++-------- mm/mremap.c | 4 ++-- mm/rmap.c | 20 +++++++++----------- 15 files changed, 51 insertions(+), 66 deletions(-)
arch/x86/mm/hugetlbpage.c +2 -2
@@ -69,7 +69,7 @@ static void huge_pmd_share(struct mm_strif (!vma_shareable (vma, addr))return ; -spin_lock (&mapping->i_mmap_lock); +down_read (&mapping->i_mmap_sem); vma_prio_tree_foreach(svma, &iter, &mapping->i_mmap, idx, idx) {if (svma == vma)continue ;
@@ -94,7 +94,7 @@ static void huge_pmd_share(struct mm_strput_page (virt_to_page (spte));spin_unlock (&mm->page_table_lock); out: -spin_unlock (&mapping->i_mmap_lock); +up_read (&mapping->i_mmap_sem); }/*
fs/hugetlbfs/inode.c +2 -2
@@ -454,10 +454,10 @@ static int hugetlb_vmtruncate(struct ino pgoff = offset >>PAGE_SHIFT ;i_size_write (inode, offset); -spin_lock (&mapping->i_mmap_lock); +down_read (&mapping->i_mmap_sem);if (!prio_tree_empty (&mapping->i_mmap))hugetlb_vmtruncate_list (&mapping->i_mmap, pgoff); -spin_unlock (&mapping->i_mmap_lock); +up_read (&mapping->i_mmap_sem);truncate_hugepages (inode, offset);return 0 ; }
fs/inode.c +1 -1
@@ -210,7 +210,7 @@ void inode_init_once(struct inode *inodeINIT_LIST_HEAD (&inode->i_devices);INIT_RADIX_TREE (&inode->i_data.page_tree,GFP_ATOMIC );rwlock_init (&inode->i_data.tree_lock); -spin_lock_init (&inode->i_data.i_mmap_lock); +init_rwsem (&inode->i_data.i_mmap_sem);INIT_LIST_HEAD (&inode->i_data.private_list);spin_lock_init (&inode->i_data.private_lock);INIT_RAW_PRIO_TREE_ROOT (&inode->i_data.i_mmap);
include/linux/fs.h +1 -1
@@ -503,7 +503,7 @@ struct address_space {unsigned int i_mmap_writable;/* count VM_SHARED mappings */ struct prio_tree_root i_mmap;/* tree of private and shared mappings */ struct list_head i_mmap_nonlinear;/*list VM_NONLINEAR mappings */ -spinlock_t i_mmap_lock;/* protect tree, count, list */ +struct rw_semaphore i_mmap_sem;/* protect tree, count, list */ unsigned int truncate_count;/* Cover race condition with truncate */ unsigned long nrpages;/* number of total pages */ pgoff_t writeback_index;/* writeback starts here */
include/linux/mm.h +1 -1
@@ -709,7 +709,7 @@ struct zap_details {struct address_space *check_mapping;/* Check page->mapping if set */ pgoff_t first_index;/* Lowest page->index to unmap */ pgoff_t last_index;/* Highest page->index to unmap */ -spinlock_t *i_mmap_lock;/* For unmap_mapping_range: */ +struct rw_semaphore *i_mmap_sem;/* For unmap_mapping_range: */ unsigned long truncate_count;/* Compare vm_truncate_count */ };
kernel/fork.c +2 -2
@@ -274,12 +274,12 @@ static int dup_mmap(struct mm_struct *mmatomic_dec (&inode->i_writecount);/* insert tmp into the share list, just after mpnt */ -spin_lock (&file->f_mapping->i_mmap_lock); +down_write (&file->f_mapping->i_mmap_sem); tmp->vm_truncate_count = mpnt->vm_truncate_count;flush_dcache_mmap_lock (file->f_mapping);vma_prio_tree_add (tmp, mpnt);flush_dcache_mmap_unlock (file->f_mapping); -spin_unlock (&file->f_mapping->i_mmap_lock); +up_write (&file->f_mapping->i_mmap_sem); }/*
mm/filemap.c +4 -4
@@ -62,16 +62,16 @@ generic_file_direct_IO(int rw, struct ki/* * Lock ordering: * -* ->i_mmap_lock (vmtruncate) +* ->i_mmap_sem (vmtruncate) * ->private_lock (__free_pte->__set_page_dirty_buffers) * ->swap_lock (exclusive_swap_page, others) * ->mapping->tree_lock * * ->i_mutex -* ->i_mmap_lock (truncate->unmap_mapping_range) +* ->i_mmap_sem (truncate->unmap_mapping_range) * * ->mmap_sem -* ->i_mmap_lock +* ->i_mmap_sem * ->page_table_lock or pte_lock (various, mainly in memory.c) * ->mapping->tree_lock (arch-dependent flush_dcache_mmap_lock) *
@@ -88,7 +88,7 @@ generic_file_direct_IO(int rw, struct ki* ->sb_lock (fs/fs-writeback.c) * ->mapping->tree_lock (__sync_single_inode) * -* ->i_mmap_lock +* ->i_mmap_sem * ->anon_vma.lock (vma_adjust) * * ->anon_vma.lock
mm/filemap_xip.c +2 -2
@@ -184,7 +184,7 @@ __xip_unmap (struct address_space * mappif (!page)return ; -spin_lock (&mapping->i_mmap_lock); +down_read (&mapping->i_mmap_sem); vma_prio_tree_foreach(vma, &iter, &mapping->i_mmap, pgoff, pgoff) { mm = vma->vm_mm; address = vma->vm_start +
@@ -204,7 +204,7 @@ __xip_unmap (struct address_space * mapppage_cache_release (page); } } -spin_unlock (&mapping->i_mmap_lock); +up_read (&mapping->i_mmap_sem); }/*
mm/fremap.c +2 -2
@@ -206,13 +206,13 @@ asmlinkage long sys_remap_file_pages(uns } goto out; } -spin_lock (&mapping->i_mmap_lock); +down_write (&mapping->i_mmap_sem);flush_dcache_mmap_lock (mapping); vma->vm_flags |=VM_NONLINEAR ;vma_prio_tree_remove (vma, &mapping->i_mmap);vma_nonlinear_insert (vma, &mapping->i_mmap_nonlinear);flush_dcache_mmap_unlock (mapping); -spin_unlock (&mapping->i_mmap_lock); +up_write (&mapping->i_mmap_sem); }mmu_notifier_invalidate_range_begin (mm, start, start + size);
mm/hugetlb.c +5 -5
@@ -746,7 +746,7 @@ void __unmap_hugepage_range(struct vm_arstruct page *page;struct page *tmp;/* -* A page gathering list, protected by per file i_mmap_lock. The +* A page gathering list, protected by per file i_mmap_sem. The * lock is used to avoid list corruption from multiple unmapping * of the same page since we are using page->lru. */
@@ -796,9 +796,9 @@ void unmap_hugepage_range(struct vm_area* do nothing in this case. */ if (vma->vm_file) { -spin_lock (&vma->vm_file->f_mapping->i_mmap_lock); +down_write (&vma->vm_file->f_mapping->i_mmap_sem);__unmap_hugepage_range (vma, start, end); -spin_unlock (&vma->vm_file->f_mapping->i_mmap_lock); +up_write (&vma->vm_file->f_mapping->i_mmap_sem); } }
@@ -1041,7 +1041,7 @@ void hugetlb_change_protection(struct vmBUG_ON (address >= end);flush_cache_range (vma, address, end); -spin_lock (&vma->vm_file->f_mapping->i_mmap_lock); +down_read (&vma->vm_file->f_mapping->i_mmap_sem);spin_lock (&mm->page_table_lock);for (; address < end; address +=HPAGE_SIZE ) { ptep =huge_pte_offset (mm, address);
@@ -1056,7 +1056,7 @@ void hugetlb_change_protection(struct vm } }spin_unlock (&mm->page_table_lock); -spin_unlock (&vma->vm_file->f_mapping->i_mmap_lock); +up_read (&vma->vm_file->f_mapping->i_mmap_sem);flush_tlb_range (vma, start, end); }
mm/memory.c +8 -19
@@ -832,7 +832,6 @@ unsigned long unmap_vmas(struct mmu_gathunsigned long tlb_start =0 ;/* For tlb_finish_mmu */ int tlb_start_valid =0 ;unsigned long start = start_addr; -spinlock_t *i_mmap_lock = details? details->i_mmap_lock:NULL ;int fullmm = (*tlbp)->fullmm;for ( ; vma && vma->vm_start < end_addr; vma = vma->vm_next) {
@@ -870,21 +869,11 @@ unsigned long unmap_vmas(struct mmu_gathtlb_finish_mmu (*tlbp, tlb_start, start); -if (need_resched () || - (i_mmap_lock &&spin_needbreak (i_mmap_lock))) { -if (i_mmap_lock) { - *tlbp =NULL ; - goto out; - } -cond_resched (); - } - *tlbp =tlb_gather_mmu (vma->vm_mm, fullmm); tlb_start_valid =0 ; zap_work =ZAP_BLOCK_SIZE ; } } -out:return start;/* which is now the end (or restart) address */ }
@@ -1746,7 +1735,7 @@ unwritable_page:/* * Helper functions for unmap_mapping_range(). * -* __ Notes on dropping i_mmap_lock to reduce latency while unmapping __ +* __ Notes on dropping i_mmap_sem to reduce latency while unmapping __ * * We have to restart searching the prio_tree whenever we drop the lock, * since the iterator is only valid while the lock is held, and anyway
@@ -1765,7 +1754,7 @@ unwritable_page:* can't efficiently keep all vmas in step with mapping->truncate_count: * so instead reset them all whenever it wraps back to 0 (then go to 1). * mapping->truncate_count and vma->vm_truncate_count are protected by -* i_mmap_lock. +* i_mmap_sem. * * In order to make forward progress despite repeatedly restarting some * large vma, note the restart_addr from unmap_vmas when it breaks out:
@@ -1815,7 +1804,7 @@ again: restart_addr =zap_page_range (vma, start_addr, end_addr - start_addr, details); - need_break =need_resched () ||spin_needbreak (details->i_mmap_lock); + need_break =need_resched ();if (restart_addr >= end_addr) {/* We have now completed this vma: mark it so */
@@ -1829,9 +1818,9 @@ again: goto again; } -spin_unlock (details->i_mmap_lock); +up_write (details->i_mmap_sem);cond_resched (); -spin_lock (details->i_mmap_lock); +down_write (details->i_mmap_sem);return -EINTR ; }
@@ -1925,9 +1914,9 @@ void unmap_mapping_range(struct address_ details.last_index = hba + hlen -1 ;if (details.last_index < details.first_index) details.last_index =ULONG_MAX ; - details.i_mmap_lock = &mapping->i_mmap_lock; + details.i_mmap_sem = &mapping->i_mmap_sem; -spin_lock (&mapping->i_mmap_lock); +down_write (&mapping->i_mmap_sem);/* Protect against endless unmapping loops */ mapping->truncate_count++;
@@ -1942,7 +1931,7 @@ void unmap_mapping_range(struct address_unmap_mapping_range_tree (&mapping->i_mmap, &details);if (unlikely (!list_empty (&mapping->i_mmap_nonlinear)))unmap_mapping_range_list (&mapping->i_mmap_nonlinear, &details); -spin_unlock (&mapping->i_mmap_lock); +up_write (&mapping->i_mmap_sem); }EXPORT_SYMBOL (unmap_mapping_range);
mm/migrate.c +2 -2
@@ -202,12 +202,12 @@ static void remove_file_migration_ptes(sif (!mapping)return ; -spin_lock (&mapping->i_mmap_lock); +down_read (&mapping->i_mmap_sem); vma_prio_tree_foreach(vma, &iter, &mapping->i_mmap, pgoff, pgoff)remove_migration_pte (vma, old, new); -spin_unlock (&mapping->i_mmap_lock); +up_read (&mapping->i_mmap_sem); }/*
mm/mmap.c +8 -8
@@ -187,7 +187,7 @@ error: }/* -* Requires inode->i_mapping->i_mmap_lock +* Requires inode->i_mapping->i_mmap_sem */ static void __remove_shared_vm_struct (struct vm_area_struct *vma,struct file *file,struct address_space *mapping)
@@ -215,9 +215,9 @@ void unlink_file_vma(struct vm_area_struif (file) {struct address_space *mapping = file->f_mapping; -spin_lock (&mapping->i_mmap_lock); +down_write (&mapping->i_mmap_sem);__remove_shared_vm_struct (vma, file, mapping); -spin_unlock (&mapping->i_mmap_lock); +up_write (&mapping->i_mmap_sem); } }
@@ -440,7 +440,7 @@ static void vma_link(struct mm_struct *m mapping = vma->vm_file->f_mapping;if (mapping) { -spin_lock (&mapping->i_mmap_lock); +down_write (&mapping->i_mmap_sem); vma->vm_truncate_count = mapping->truncate_count; }anon_vma_lock (vma);
@@ -450,7 +450,7 @@ static void vma_link(struct mm_struct *manon_vma_unlock (vma);if (mapping) -spin_unlock (&mapping->i_mmap_lock); +up_write (&mapping->i_mmap_sem); mm->map_count++;validate_mm (mm);
@@ -537,7 +537,7 @@ again: remove_next = 1 + (end > next-> mapping = file->f_mapping;if (!(vma->vm_flags &VM_NONLINEAR )) root = &mapping->i_mmap; -spin_lock (&mapping->i_mmap_lock); +down_write (&mapping->i_mmap_sem);if (importer && vma->vm_truncate_count != next->vm_truncate_count) {/*
@@ -621,7 +621,7 @@ again: remove_next = 1 + (end > next->if (anon_vma)spin_unlock (&anon_vma->lock);if (mapping) -spin_unlock (&mapping->i_mmap_lock); +up_write (&mapping->i_mmap_sem);if (remove_next) {if (file)
@@ -2065,7 +2065,7 @@ void exit_mmap(struct mm_struct *mm)/* Insert vm structure into process list sorted by address * and into the inode's i_mmap tree. If vm_file is non-NULL -* then i_mmap_lock is taken here. +* then i_mmap_sem is taken here. */ int insert_vm_struct (struct mm_struct * mm,struct vm_area_struct * vma) {
mm/mremap.c +2 -2
@@ -85,7 +85,7 @@ static void move_ptes(struct vm_area_str* and we propagate stale pages into the dst afterward. */ mapping = vma->vm_file->f_mapping; -spin_lock (&mapping->i_mmap_lock); +down_write (&mapping->i_mmap_sem);if (new_vma->vm_truncate_count && new_vma->vm_truncate_count != vma->vm_truncate_count) new_vma->vm_truncate_count =0 ;
@@ -121,7 +121,7 @@ static void move_ptes(struct vm_area_strpte_unmap_nested (new_pte -1 );pte_unmap_unlock (old_pte -1 , old_ptl);if (mapping) -spin_unlock (&mapping->i_mmap_lock); +up_write (&mapping->i_mmap_sem); }#define LATENCY_LIMIT (64 * PAGE_SIZE)
mm/rmap.c +9 -11
@@ -24,7 +24,7 @@* inode->i_alloc_sem (vmtruncate_range) * mm->mmap_sem * page->flags PG_locked (lock_page) -* mapping->i_mmap_lock +* mapping->i_mmap_sem * anon_vma->lock * mm->page_table_lock or pte_lock * zone->lru_lock (in mark_page_accessed, isolate_lru_page)
@@ -372,14 +372,14 @@ static int page_referenced_file(struct p* The page lock not only makes sure that page->mapping cannot * suddenly be NULLified by truncation, it makes sure that the * structure at mapping cannot be freed and reused yet, -* so we can safely take mapping->i_mmap_lock. +* so we can safely take mapping->i_mmap_sem. */ BUG_ON (!PageLocked (page)); -spin_lock (&mapping->i_mmap_lock); +down_read (&mapping->i_mmap_sem);/* -* i_mmap_lock does not stabilize mapcount at all, but mapcount +* i_mmap_sem does not stabilize mapcount at all, but mapcount * is more likely to be accurate if we note it after spinning. */ mapcount =page_mapcount (page);
@@ -402,7 +402,7 @@ static int page_referenced_file(struct pbreak ; } -spin_unlock (&mapping->i_mmap_lock); +up_read (&mapping->i_mmap_sem);return referenced; }
@@ -487,12 +487,12 @@ static int page_mkclean_file(struct addrBUG_ON (PageAnon (page)); -spin_lock (&mapping->i_mmap_lock); +down_read (&mapping->i_mmap_sem); vma_prio_tree_foreach(vma, &iter, &mapping->i_mmap, pgoff, pgoff) {if (vma->vm_flags &VM_SHARED ) ret +=page_mkclean_one (page, vma); } -spin_unlock (&mapping->i_mmap_lock); +up_read (&mapping->i_mmap_sem);return ret; }
@@ -924,7 +924,7 @@ static int try_to_unmap_file(struct pageunsigned long max_nl_size =0 ;unsigned int mapcount; -spin_lock (&mapping->i_mmap_lock); +down_read (&mapping->i_mmap_sem); vma_prio_tree_foreach(vma, &iter, &mapping->i_mmap, pgoff, pgoff) { ret =try_to_unmap_one (page, vma, migration);if (ret ==SWAP_FAIL || !page_mapped (page))
@@ -961,7 +961,6 @@ static int try_to_unmap_file(struct page mapcount =page_mapcount (page);if (!mapcount) goto out; -cond_resched_lock (&mapping->i_mmap_lock); max_nl_size = (max_nl_size +CLUSTER_SIZE -1 ) &CLUSTER_MASK ;if (max_nl_cursor ==0 )
@@ -983,7 +982,6 @@ static int try_to_unmap_file(struct page } vma->vm_private_data = (void *) max_nl_cursor; } -cond_resched_lock (&mapping->i_mmap_lock); max_nl_cursor +=CLUSTER_SIZE ; }while (max_nl_cursor <= max_nl_size);
@@ -995,7 +993,7 @@ static int try_to_unmap_file(struct pagelist_for_each_entry (vma, &mapping->i_mmap_nonlinear, shared.vm_set.list) vma->vm_private_data =NULL ; out: -spin_unlock (&mapping->i_mmap_lock); +up_write (&mapping->i_mmap_sem);return ret; }
Re: [PATCH] 4/4 i_mmap_lock spinlock2rwsem (#v9 was 1/4)
Quoted 2 lines · Andrea Arcangeli
I didn't look into this but it shows how it would be risky to make this change in .25. It's a bit strange that the bugcheck triggers
Yes this was never intended for .25. I think we need to split this into a copule of patches. One needs to get rid of the spinlock dropping, then one that deals with the read concurrency issues and finally one that converts the spinlock. Thanks for looking at it.
-------------------------------------------------------------------------
This SF.net email is sponsored by: Microsoft Defy all challenges. Microsoft(R) Visual Studio 2008.
http://clk.atdmt.com/MRT/go/vse0120000070mrt/direct/01/
Re: [PATCH] 4/4 i_mmap_lock spinlock2rwsem (#v9 was 1/4)
In reply to Andrea ArcangeliYou need this patch to address the issues (that I already mentioned when I sent the patch to you). New EMM notifier patch with sleeping coming soon.
From: Christoph Lameter <[email protected]>
Subject: Move tlb flushing into free_pgtables
Move the tlb flushing into free_pgtables. The conversion of the locks taken for reverse map scanning would require taking sleeping locks in free_pgtables. Moving the tlb flushing into free_pgtables allows sleeping in part of free_pgtables().
--- include/linux/mm.h | 4 ++-- mm/memory.c | 14 ++++++++++---- mm/mmap.c | 6 +++--- 3 files changed, 15 insertions(+), 9 deletions(-)
include/linux/mm.h +2 -2
@@ -751,8 +751,8 @@ int walk_page_range(const struct mm_stru void *private);void free_pgd_range (struct mmu_gather **tlb,unsigned long addr,unsigned long end,unsigned long floor,unsigned long ceiling); -void free_pgtables (struct mmu_gather **tlb,struct vm_area_struct *start_vma, -unsigned long floor,unsigned long ceiling); +void free_pgtables (struct vm_area_struct *start_vma,unsigned long floor, +unsigned long ceiling);int copy_page_range (struct mm_struct *dst,struct mm_struct *src,struct vm_area_struct *vma); void unmap_mapping_range(struct address_space *mapping,
mm/memory.c +10 -4
@@ -271,9 +271,11 @@ void free_pgd_range(struct mmu_gather ** }while (pgd++, addr = next, addr != end); } -void free_pgtables (struct mmu_gather **tlb,struct vm_area_struct *vma, -unsigned long floor,unsigned long ceiling) +void free_pgtables (struct vm_area_struct *vma,unsigned long floor, +unsigned long ceiling) { +struct mmu_gather *tlb; +while (vma) {struct vm_area_struct *next = vma->vm_next;unsigned long addr = vma->vm_start;
@@ -285,8 +287,10 @@ void free_pgtables(struct mmu_gather **tunlink_file_vma (vma);if (is_vm_hugetlb_page (vma)) { -hugetlb_free_pgd_range (tlb, addr, vma->vm_end, + tlb =tlb_gather_mmu (vma->vm_mm,0 ); +hugetlb_free_pgd_range (&tlb, addr, vma->vm_end, floor, next? next->vm_start: ceiling); +tlb_finish_mmu (tlb, addr, vma->vm_end); }else {/* * Optimization: gather nearby vmas into one call down
@@ -298,8 +302,10 @@ void free_pgtables(struct mmu_gather **tanon_vma_unlink (vma);unlink_file_vma (vma); } -free_pgd_range (tlb, addr, vma->vm_end, + tlb =tlb_gather_mmu (vma->vm_mm,0 ); +free_pgd_range (&tlb, addr, vma->vm_end, floor, next? next->vm_start: ceiling); +tlb_finish_mmu (tlb, addr, vma->vm_end); } vma = next; }
mm/mmap.c +3 -3
@@ -1750,9 +1750,9 @@ static void unmap_region(struct mm_strucupdate_hiwater_rss (mm);unmap_vmas (&tlb, vma, start, end, &nr_accounted,NULL );vm_unacct_memory (nr_accounted); -free_pgtables (&tlb, vma, prev? prev->vm_end:FIRST_USER_ADDRESS , - next? next->vm_start:0 );tlb_finish_mmu (tlb, start, end); +free_pgtables (vma, prev? prev->vm_end:FIRST_USER_ADDRESS , + next? next->vm_start:0 );emm_notify (mm, emm_invalidate_end, start, end); }
@@ -2049,8 +2049,8 @@ void exit_mmap(struct mm_struct *mm)/* Use -1 here to ensure all VMAs in the mm are unmapped */ end =unmap_vmas (&tlb, vma,0 ,-1 , &nr_accounted,NULL );vm_unacct_memory (nr_accounted); -free_pgtables (&tlb, vma,FIRST_USER_ADDRESS ,0 );tlb_finish_mmu (tlb,0 , end); +free_pgtables (vma,FIRST_USER_ADDRESS ,0 );/* * Walk the list again, actually closing and freeing it,
-------------------------------------------------------------------------
This SF.net email is sponsored by: Microsoft Defy all challenges. Microsoft(R) Visual Studio 2008.
http://clk.atdmt.com/MRT/go/vse0120000070mrt/direct/01/
Re: [PATCH] 3/4 combine RCU with seqlock to allow mmu notifier methods to sleep (#v9 was 1/4)
In reply to Andrea Arcangelimm/mmu_notifier.c int __mmu_notifier_clear_flush_young(str · line 54
Quoted 12 lines · Andrea Arcangeli
mm/mmu_notifier.c +4
-1@@ -42,11 +45,19 @@ int __mmu_notifier_clear_flush_young(strstruct mmu_notifier *mn;struct hlist_node *n;int young =0 ; +unsigned seq;rcu_read_lock (); +restart: + seq =read_seqbegin (&mm->mmu_notifier_lock);hlist_for_each_entry_rcu (mn, n, &mm->mmu_notifier_list, hlist) { -if (mn->ops->clear_flush_young) +if (mn->ops->clear_flush_young) {
hlist_del_rcu(&mn->hlist)
Review int __mmu_notifier_clear_flush_young(str · line 55
Quoted 1 line
(quoted diff) +1
-0+ rcu_read_unlock();
kfree(mn);
mm/mmu_notifier.c int __mmu_notifier_clear_flush_young(str · line 56
Quoted 1 line
mm/mmu_notifier.c +0
-0young |= mn->ops->clear_flush_young (mn, mm, address);
*BANG*
Quoted 6 lines
mm/mmu_notifier.c +4
-0+rcu_read_lock (); + } +if (read_seqretry (&mm->mmu_notifier_lock, seq)) + goto restart; }rcu_read_unlock ();
Re: [PATCH] 3/4 combine RCU with seqlock to allow mmu notifier methods to sleep (#v9 was 1/4)
Quoted 9 lines · Peter Zijlstra
hlist_del_rcu(&mn->hlist)Review
Quoted 1 line
(quoted diff) +1
-0+ rcu_read_unlock();kfree(mn);
Quoted 1 line
young |=
mn->ops->clear_flush_young(mn, mm, address);*BANG*
My objective was to allow mmu_notifier_register/unregister to be called with the same mmu notifier object, I didn't mean the object could have been freed until ->release is called. However you reminded me that after unregistering ->release won't be called so unregister isn't very useful and I doubt we can keep it ;).
In the meantime I've also been thinking that we could need the write_seqlock in mmu_notifier_register, to know when to restart the loop if somebody does a mmu_notifier_register; synchronize_rcu(). Otherwise there's no way to be sure the mmu notifier will start firing immediately after synchronize_rcu. I'm unsure if it's acceptable that in-progress mmu notifier invocations, don't need to notice the fact that somebody did mmu_notifier_register; synchronize_rcu. If they don't need to notice, then we can just drop unregister and all rcu_read_lock()s instead of adding write_seqlock to the register operation.
Overall my effort is to try to avoid expand the list walk with explicit memory barriers like in EMM while trying to be equally efficient.
Another issue is that the _begin/_end logic doesn't provide any guarantee that the _begin will start firing before _end, if a kernel module is loaded while another cpu is already running inside some munmap operation etc.. The KVM usage of mmu notifier has no problem with that detail, but KVM doesn't use _begin at all, I wonder if others would have problems. This is a kind of a separate problem, but quite related to the question if the notifiers must be guaranteed to start firing immediately after mmu_notifier_unregister;synchronize_rcu or not, that's why I mentioned it here.
Once I get comments on the suggested direction for these details, I'll quickly repost a replacement patch for 3/4.
Thanks Peter!
Re: [PATCH] 3/4 combine RCU with seqlock to allow mmu notifier methods to sleep (#v9 was 1/4)
Quoted 3 lines · Andrea Arcangeli
Overall my effort is to try to avoid expand the list walk with explicit memory barriers like in EMM while trying to be equally efficient.
I think we can do with a smb_wmb(); like Christoph (and like hlist_add_rcu()), but replace the smb_rmb() Christoph has with a smp_read_barrier_depends().
That should give much the same results.
The reason Christoph can do without RCU is because he doesn't allow unregister, and as soon as you drop that you'll end up with something similar.
Quoted 9 lines
Another issue is that the _begin/_end logic doesn't provide any guarantee that the
_beginwill start firing before_end, if a kernel module is loaded while another cpu is already running inside some munmap operation etc.. The KVM usage of mmu notifier has no problem with that detail, but KVM doesn't use_beginat all, I wonder if others would have problems. This is a kind of a separate problem, but quite related to the question if the notifiers must be guaranteed to start firing immediately after mmu_notifier_unregister;synchronize_rcu or not, that's why I mentioned it here.
Curious problem indeed. Would it make sense to require registering these MMU notifiers when the process is still single threaded along with the requirement that they can never be removed again from a running process?
For KVM this should be quite doable, but I must admit I haven't been paying enough attention to know if its possible for these other users.
Re: [PATCH] 3/4 combine RCU with seqlock to allow mmu notifier methods to sleep (#v9 was 1/4)
Quoted 2 lines · Peter Zijlstra
The reason Christoph can do without RCU is because he doesn't allow unregister, and as soon as you drop that you'll end up with something
Not sure to follow, what do you mean "he doesn't allow"? We'll also have to rip unregister regardless after you pointed out the ->release won't be called after calling my mmu_notifier_unregister in 3/4. If you figured out how to retain mmu_notifier_unregister I'm not seeing it anymore.
Quoted 3 lines
Curious problem indeed. Would it make sense to require registering these MMU notifiers when the process is still single threaded along with the requirement that they can never be removed again from a running process?
I'm afraid that won't help much (even if the mmu notifiers users could cope with that restriction like KVM can) because the VM will run concurrently in another CPU despite the task is single threaded. See 2/4 in try_to_unmap_cluster: _start/end are not only invoked in the context of the current task.
PS. this problem I pointed out of _end possibly called before _begin is the same for #v9 and EMM V1 as far as I can tell.
-------------------------------------------------------------------------
This SF.net email is sponsored by: Microsoft Defy all challenges. Microsoft(R) Visual Studio 2008.
http://clk.atdmt.com/MRT/go/vse0120000070mrt/direct/01/
Re: [PATCH] 3/4 combine RCU with seqlock to allow mmu notifier methods to sleep (#v9 was 1/4)
Quoted 9 lines · Andrea Arcangeli
Quoted 2 lines · Peter Zijlstra
The reason Christoph can do without RCU is because he doesn't allow unregister, and as soon as you drop that you'll end up with something
Not sure to follow, what do you mean "he doesn't allow"? We'll also have to rip unregister regardless after you pointed out the ->release won't be called after calling my
mmu_notifier_unregisterin 3/4. If you figured out how to retainmmu_notifier_unregisterI'm not seeing it anymore.
Given I don't see other (buggy ;) ways anymore to retain mmu_notifier_unregister, I did like in EMM and I dropped the unregister function.
To me it looks like this will be enough and equally efficient as the expanded version in EMM that is not using the highlevel hlist_rcu macros. If you can see any pitfall let me know! Thanks a lot for the help.
------
This is a replacement for the previously posted 3/4, one of the pieces to allow the mmu notifier methods to sleep.
include/linux/mmu_notifier.h +0 -11
@@ -70,17 +70,6 @@ static inline int mm_has_notifiers(struc*/ extern void mmu_notifier_register (struct mmu_notifier *mn,struct mm_struct *mm); -/* -* Must hold the mmap_sem for write. -* -* RCU is used to traverse the list. A quiescent period needs to pass -* before the "struct mmu_notifier" can be freed. Alternatively it -* can be synchronously freed inside ->release when the list can't -* change anymore and nobody could possibly walk it. -*/ -extern void mmu_notifier_unregister (struct mmu_notifier *mn, -struct mm_struct *mm); -extern void __mmu_notifier_release (struct mm_struct *mm);extern int __mmu_notifier_clear_flush_young (struct mm_struct *mm,unsigned long address);
mm/mmu_notifier.c +0 -14
@@ -43,12 +43,10 @@ int __mmu_notifier_clear_flush_young(strstruct hlist_node *n;int young =0 ; -rcu_read_lock ();hlist_for_each_entry_rcu (mn, n, &mm->mmu_notifier_list, hlist) {if (mn->ops->clear_flush_young) young |= mn->ops->clear_flush_young (mn, mm, address); } -rcu_read_unlock ();return young; }
@@ -59,12 +57,10 @@ void __mmu_notifier_invalidate_page(strustruct mmu_notifier *mn;struct hlist_node *n; -rcu_read_lock ();hlist_for_each_entry_rcu (mn, n, &mm->mmu_notifier_list, hlist) {if (mn->ops->invalidate_page) mn->ops->invalidate_page (mn, mm, address); } -rcu_read_unlock (); } void __mmu_notifier_invalidate_range_begin(struct mm_struct *mm,
@@ -73,12 +69,10 @@ void __mmu_notifier_invalidate_range_begstruct mmu_notifier *mn;struct hlist_node *n; -rcu_read_lock ();hlist_for_each_entry_rcu (mn, n, &mm->mmu_notifier_list, hlist) {if (mn->ops->invalidate_range_begin) mn->ops->invalidate_range_begin (mn, mm, start, end); } -rcu_read_unlock (); } void __mmu_notifier_invalidate_range_end(struct mm_struct *mm,
@@ -87,12 +81,10 @@ void __mmu_notifier_invalidate_range_endstruct mmu_notifier *mn;struct hlist_node *n; -rcu_read_lock ();hlist_for_each_entry_rcu (mn, n, &mm->mmu_notifier_list, hlist) {if (mn->ops->invalidate_range_end) mn->ops->invalidate_range_end (mn, mm, start, end); } -rcu_read_unlock (); }/*
@@ -106,9 +98,3 @@ void mmu_notifier_register(struct mmu_nohlist_add_head_rcu (&mn->hlist, &mm->mmu_notifier_list); }EXPORT_SYMBOL_GPL (mmu_notifier_register); - -void mmu_notifier_unregister (struct mmu_notifier *mn,struct mm_struct *mm) -{ -hlist_del_rcu (&mn->hlist); -} -EXPORT_SYMBOL_GPL (mmu_notifier_unregister);
Re: [PATCH] 3/4 combine RCU with seqlock to allow mmu notifier methods to sleep (#v9 was 1/4)
Quoted 2 lines · Andrea Arcangeli
This is a replacement for the previously posted 3/4, one of the pieces to allow the mmu notifier methods to sleep.
Looks good. That is what we talked about last week. What guarantees now that we see the cacheline referenced after the cacheline that contains the pointer that was changed? hlist_for_reach does a rcu_dereference with implied memory barrier? So its like EMM?
-------------------------------------------------------------------------
This SF.net email is sponsored by: Microsoft Defy all challenges. Microsoft(R) Visual Studio 2008.
http://clk.atdmt.com/MRT/go/vse0120000070mrt/direct/01/
Re: [PATCH] 3/4 combine RCU with seqlock to allow mmu notifier methods to sleep (#v9 was 1/4)
In reply to Andrea ArcangeliQuoted 2 lines · Andrea Arcangeli
PS. this problem I pointed out of
_endpossibly called before_beginis the same for #v9 and EMM V1 as far as I can tell.
Hmmm.. We could just push that on the driver saying that is has to tolerate it. Otherwise how can we solve this?
Re: [PATCH] 3/4 combine RCU with seqlock to allow mmu notifier methods to sleep (#v9 was 1/4)
In reply to Andrea ArcangeliQuoted 10 lines · Andrea Arcangeli
In the meantime I've also been thinking that we could need the
write_seqlockinmmu_notifier_register, to know when to restart the loop if somebody does ammu_notifier_register;synchronize_rcu(). Otherwise there's no way to be sure the mmu notifier will start firing immediately aftersynchronize_rcu. I'm unsure if it's acceptable that in-progress mmu notifier invocations, don't need to notice the fact that somebody didmmu_notifier_register;synchronize_rcu. If they don't need to notice, then we can just drop unregister and all rcu_read_lock()s instead of addingwrite_seqlockto the register operation.
This is all getting into some very complicated issues.....
Quoted 3 lines
Overall my effort is to try to avoid expand the list walk with explicit memory barriers like in EMM while trying to be equally efficient.
The smp_rmb is such a big problem? You have seqlock, rcu etc all in there as well. I doubt that this is more efficient.
Quoted 9 lines
Another issue is that the _begin/_end logic doesn't provide any guarantee that the
_beginwill start firing before_end, if a kernel module is loaded while another cpu is already running inside some munmap operation etc.. The KVM usage of mmu notifier has no problem with that detail, but KVM doesn't use_beginat all, I wonder if others would have problems. This is a kind of a separate problem, but quite related to the question if the notifiers must be guaranteed to start firing immediately after mmu_notifier_unregister;synchronize_rcu or not, that's why I mentioned it here.
Ahh. Yes that is an interesting issue. If a device driver cannot handle this then _begin must prohibit module loading. That means not allowing stop_machine_run I guess which should not be that difficult.
Re: [PATCH] 3/4 combine RCU with seqlock to allow mmu notifier methods to sleep (#v9 was 1/4)
In reply to Andrea ArcangeliQuoted 10 lines · Andrea Arcangeli
This combines the non-sleep-capable RCU locking of #v9 with a seqlock so the mmu notifier fast path will require zero cacheline writes/bouncing while still providing
mmu_notifier_unregisterand allowing to schedule inside the mmu notifier methods. If we dropmmu_notifier_unregisterwe can as well drop all seqlock and rcu_read_lock()s. But this locking scheme combination is sexy enough and 100% scalable (themmu_notifier_listcacheline will be preloaded anyway and that will most certainly include the sequence number value in l1 for free even in Christoph's NUMA systems) so IMHO it worth to keepmmu_notifier_unregister.
Well its adds lots of processing. Not sure if its really worth it. Seems that this scheme cannot work since the existence of the structure passed to the callbacks is not guaranteed since the RCU locks are not held. You need some kind of a refcount to give the existence guarantee.
Quoted 14 lines
mm/mmu_notifier.c +2
-0@@ -20,7 +20,9 @@ void __mmu_notifier_release(struct mm_stvoid __mmu_notifier_release (struct mm_struct *mm) {struct mmu_notifier *mn; +unsigned seq; + seq =read_seqbegin (&mm->mmu_notifier_lock);while (unlikely (!hlist_empty (&mm->mmu_notifier_list))) { mn = hlist_entry(mm->mmu_notifier_list.first, struct mmu_notifier,
mm/mmu_notifier.c void __mmu_notifier_release(struct mm_st · line 35
Quoted 6 lines
mm/mmu_notifier.c +1
-0@@ -28,6 +30,7 @@ void __mmu_notifier_release(struct mm_sthlist_del (&mn->hlist);if (mn->ops->release) mn->ops->release (mn, mm); +BUG_ON (read_seqretry (&mm->mmu_notifier_lock, seq)); } }
So this is only for sanity checking? The BUG_ON detects concurrent operations that should not happen? Need a comment here.
Quoted 8 lines
mm/mmu_notifier.c +2
-0@@ -42,11 +45,19 @@ int __mmu_notifier_clear_flush_young(strstruct mmu_notifier *mn;struct hlist_node *n;int young =0 ; +unsigned seq;rcu_read_lock (); +restart:
mm/mmu_notifier.c int __mmu_notifier_clear_flush_young(str · line 60
Quoted 10 lines
mm/mmu_notifier.c +7
-1+ seq =read_seqbegin (&mm->mmu_notifier_lock);hlist_for_each_entry_rcu (mn, n, &mm->mmu_notifier_list, hlist) { -if (mn->ops->clear_flush_young) +if (mn->ops->clear_flush_young) { +rcu_read_unlock (); young |= mn->ops->clear_flush_young (mn, mm, address); +rcu_read_lock (); + } +if (read_seqretry (&mm->mmu_notifier_lock, seq)) + goto restart;
Great innovative idea of the seqlock for versioning checks.
mm/mmu_notifier.c int __mmu_notifier_clear_flush_young(str · line 63
Quoted 3 lines
mm/mmu_notifier.c +0
-0}rcu_read_unlock ();
Well that gets pretty sophisticated here. If you drop the rcu lock then the entity pointed to by mn can go away right? So how can you pass that structure to clear_flush_young? What is guaranteeing the existence of the structure?
Quoted 4 lines
mm/mmu_notifier.c +0
-0@@ -58,11 +69,19 @@ void __mmu_notifier_invalidate_page(stru {struct mmu_notifier *mn;struct hlist_node *n;
mm/mmu_notifier.c void __mmu_notifier_invalidate_page(stru · line 80
Quoted 10 lines
mm/mmu_notifier.c +5
-1+unsigned seq;rcu_read_lock (); +restart: + seq =read_seqbegin (&mm->mmu_notifier_lock);hlist_for_each_entry_rcu (mn, n, &mm->mmu_notifier_list, hlist) { -if (mn->ops->invalidate_page) +if (mn->ops->invalidate_page) { +rcu_read_unlock (); mn->ops->invalidate_page (mn, mm, address);
Ditto structure can vanish since no existence guarantee exists.
Re: [PATCH] 2/4 move all invalidate_page outside of PT lock (#v9 was 1/4)
In reply to Andrea ArcangeliQuoted 4 lines · Andrea Arcangeli
This below simple patch invalidates the "
invalidate_page" part, the next patch will invalidate the RCU part, and btw in a way that doesn't forbid unregistering the mmu notifiers at runtime (like your brand new EMM does).
Sounds good.
Quoted 5 lines
The reason I keep this incremental (unlike your EMM that does everything all at the same time mixed in a single patch) is to decrease the non obviously safe mangling over
mm/*during .25. The below patch is simple, but not as obviously safe as s/ptep_clear_flush/ptep_clear_flush_notify/.
There was never a chance to merge for .25. Lets drop that and focus on a solution that is good for all.
Quoted 9 lines
#endif /*
_LINUX_MMU_NOTIFIER_H*/mm/filemap_xip.c +0
-1@@ -194,11 +194,13 @@ __xip_unmap (struct address_space * mappif (pte) {/* Nuke the page table entry. */ flush_cache_page (vma, address,pte_pfn (*pte)); - pteval =ptep_clear_flush_notify (vma, address, pte);
mm/filemap_xip.c __xip_unmap (struct address_space * mapp · line 206
Quoted 10 lines
mm/filemap_xip.c +3
-0+ pteval =ptep_clear_flush (vma, address, pte);page_remove_rmap (page, vma);dec_mm_counter (mm, file_rss);BUG_ON (pte_dirty (pteval));pte_unmap_unlock (pte, ptl); +/* must invalidate_page _before_ freeing the page */ +mmu_notifier_invalidate_page (mm, address);page_cache_release (page); } }
Ok but we still hold the i_mmap_lock here.
mm/filemap_xip.c static void try_to_unmap_cluster(unsigne · line 850
Quoted 6 lines
mm/filemap_xip.c +2
-0@@ -834,6 +846,8 @@ static void try_to_unmap_cluster(unsigneif (!pmd_present (*pmd))return ; + start = address; +mmu_notifier_invalidate_range_begin (mm, start, end);
Hmmmm.. Okay you going for range invalidate here like EMM but there are still some invalidate_pages() left.
-------------------------------------------------------------------------
This SF.net email is sponsored by: Microsoft Defy all challenges. Microsoft(R) Visual Studio 2008.
http://clk.atdmt.com/MRT/go/vse0120000070mrt/direct/01/
Re: [PATCH] KVM swapping with mmu notifiers #v9
In reply to izik eidusHello Izik,
Quoted 1 line · Izik Eidus
i wrote to you about this before (i didnt get answer for this so i write
Ouch I must have lost your previous comment with a too-fast pgdown in the full quoting of the patch sorry.
Quoted 2 lines
again)
with large pages support i think we need to use hereput_page
Right, thanks!!
-------------------------------------------------------------------------
This SF.net email is sponsored by: Microsoft Defy all challenges. Microsoft(R) Visual Studio 2008.
http://clk.atdmt.com/MRT/go/vse0120000070mrt/direct/01/
Quoted 7 lines · Andrea Arcangeli
Quoted 3 lines · Nick Piggin
Sorry, I realise I still didn't get this through my head yet (and also have not seen your patch recently). So I don't know exactly what you are doing...
The last version was posted here:
Quoted 8 lines
http://marc.info/?l=kvm-devel&m=120321732521533&w=2
Quoted 3 lines
But why does _anybody_ (why does Christoph's patches) need to invalidate when they are going to be more permissive? This should be done lazily by the driver, I would have thought.
This can be done lazily by the driver yes. The place where I've an
invalidate_pagesin mprotect however can also become less permissive.
That's OK, because we have to flush tlbs there too.
Quoted 3 lines
It's simpler to invalidate always and it's not guaranteed the secondary mmu page fault is capable of refreshing the spte across a writeprotect fault.
I think we just have to make sure that it _can_ do writeprotect faults. AFAIKS, that will be possible if the driver registers a .page_mkwrite handler (actually not quite -- page_mkwrite is fairly crap, so I have a patch to merge it together with .fault so we get address information as well). Anyway, I really think we should do it that way.
Quoted 3 lines
In the future this can be changed to
mprotect_pagesthough, so no page fault will happen in the secondary mmu.
Possibly, but hopefully not needed for performance. Let's wait and see.
Quoted 12 lines · Nick Piggin
Quoted 10 lines · Andrea Arcangeli
Quoted 1 line · Nick Piggin
anything when changing the pte to be _more_ permissive, and I don't
Note that in my patch the
invalidate_pagesin mprotect can be trivially switched to amprotect_pageswith proper params. This will prevent page faults completely in the secondary MMU (there will only be tlb misses after the tlb flush just like for the core linux pte), and it'll allow all the secondary MMU pte blocks (512/1024 at time with my PT lock design) to be updated to have proper permissions matching the core linux pte.
Quoted 7 lines
Sorry, I realise I still didn't get this through my head yet (and also have not seen your patch recently). So I don't know exactly what you are doing...
But why does _anybody_ (why does Christoph's patches) need to invalidate when they are going to be more permissive? This should be done lazily by the driver, I would have thought.
I don't believe it should, but it probably does right now. I do know the case where a write fault where there is no need for a COW does not call out on the PTE change. I see no reason the others should not handle this as well. Just off the top of my head, I can only think of the mprotect case needing to special case the more permissive state and I don't think that changes PTEs at all, merely updates the VMA.
Thanks,
Robin
-------------------------------------------------------------------------
This SF.net email is sponsored by: Microsoft Defy all challenges. Microsoft(R) Visual Studio 2008.
http://clk.atdmt.com/MRT/go/vse0120000070mrt/direct/01/
Quoted 3 lines · Nick Piggin
But why does _anybody_ (why does Christoph's patches) need to invalidate when they are going to be more permissive? This should be done lazily by the driver, I would have thought.
Correct. If you find such places then we can avoid the invalidates there.
-------------------------------------------------------------------------
This SF.net email is sponsored by: Microsoft Defy all challenges. Microsoft(R) Visual Studio 2008.
http://clk.atdmt.com/MRT/go/vse0120000070mrt/direct/01/