On Sun, Feb 03, 2019 at 09:21:33PM -0800, john.hubb...@gmail.com wrote:
> From: John Hubbard <jhubb...@nvidia.com>
> 

[snip]

>  
> +/*
> + * GUP_PIN_COUNTING_BIAS, and the associated functions that use it, overload
> + * the page's refcount so that two separate items are tracked: the original 
> page
> + * reference count, and also a new count of how many get_user_pages() calls 
> were
> + * made against the page. ("gup-pinned" is another term for the latter).
> + *
> + * With this scheme, get_user_pages() becomes special: such pages are marked
> + * as distinct from normal pages. As such, the new put_user_page() call (and
> + * its variants) must be used in order to release gup-pinned pages.
> + *
> + * Choice of value:
> + *
> + * By making GUP_PIN_COUNTING_BIAS a power of two, debugging of page 
> reference
> + * counts with respect to get_user_pages() and put_user_page() becomes 
> simpler,
> + * due to the fact that adding an even power of two to the page refcount has
> + * the effect of using only the upper N bits, for the code that counts up 
> using
> + * the bias value. This means that the lower bits are left for the exclusive
> + * use of the original code that increments and decrements by one (or at 
> least,
> + * by much smaller values than the bias value).
> + *
> + * Of course, once the lower bits overflow into the upper bits (and this is
> + * OK, because subtraction recovers the original values), then visual 
> inspection
> + * no longer suffices to directly view the separate counts. However, for 
> normal
> + * applications that don't have huge page reference counts, this won't be an
> + * issue.
> + *
> + * This has to work on 32-bit as well as 64-bit systems. In the more 
> constrained
> + * 32-bit systems, the 10 bit value of the bias value leaves 22 bits for the
> + * upper bits. Therefore, only about 4M calls to get_user_page() may occur 
> for
> + * a page.
> + *
> + * Locking: the lockless algorithm described in 
> page_cache_gup_pin_speculative()
> + * and page_cache_gup_pin_speculative() provides safe operation for

Did you mean:

page_cache_gup_pin_speculative and __ page_cache_get_speculative __?

Just found this while looking at your branch.

Sorry,
Ira

> + * get_user_pages and page_mkclean and other calls that race to set up page
> + * table entries.
> + */
> +#define GUP_PIN_COUNTING_BIAS (1UL << 10)
> +
> +int get_gup_pin_page(struct page *page);
> +
> +void put_user_page(struct page *page);
> +void put_user_pages_dirty(struct page **pages, unsigned long npages);
> +void put_user_pages_dirty_lock(struct page **pages, unsigned long npages);
> +void put_user_pages(struct page **pages, unsigned long npages);
> +
> +/**
> + * page_gup_pinned() - report if a page is gup-pinned (pinned by a call to
> + *                   get_user_pages).
> + * @page:    pointer to page to be queried.
> + * @Returns: True, if it is likely that the page has been "gup-pinned".
> + *           False, if the page is definitely not gup-pinned.
> + */
> +static inline bool page_gup_pinned(struct page *page)
> +{
> +     return (page_ref_count(page)) > GUP_PIN_COUNTING_BIAS;
> +}
> +
>  static inline void get_page(struct page *page)
>  {
>       page = compound_head(page);
> @@ -993,30 +1050,6 @@ static inline void put_page(struct page *page)
>               __put_page(page);
>  }
>  
> -/**
> - * put_user_page() - release a gup-pinned page
> - * @page:            pointer to page to be released
> - *
> - * Pages that were pinned via get_user_pages*() must be released via
> - * either put_user_page(), or one of the put_user_pages*() routines
> - * below. This is so that eventually, pages that are pinned via
> - * get_user_pages*() can be separately tracked and uniquely handled. In
> - * particular, interactions with RDMA and filesystems need special
> - * handling.
> - *
> - * put_user_page() and put_page() are not interchangeable, despite this early
> - * implementation that makes them look the same. put_user_page() calls must
> - * be perfectly matched up with get_user_page() calls.
> - */
> -static inline void put_user_page(struct page *page)
> -{
> -     put_page(page);
> -}
> -
> -void put_user_pages_dirty(struct page **pages, unsigned long npages);
> -void put_user_pages_dirty_lock(struct page **pages, unsigned long npages);
> -void put_user_pages(struct page **pages, unsigned long npages);
> -
>  #if defined(CONFIG_SPARSEMEM) && !defined(CONFIG_SPARSEMEM_VMEMMAP)
>  #define SECTION_IN_PAGE_FLAGS
>  #endif
> diff --git a/include/linux/pagemap.h b/include/linux/pagemap.h
> index 5c8a9b59cbdc..5f5b72ba595f 100644
> --- a/include/linux/pagemap.h
> +++ b/include/linux/pagemap.h
> @@ -209,6 +209,11 @@ static inline int page_cache_add_speculative(struct page 
> *page, int count)
>       return __page_cache_add_speculative(page, count);
>  }
>  
> +static inline int page_cache_gup_pin_speculative(struct page *page)
> +{
> +     return __page_cache_add_speculative(page, GUP_PIN_COUNTING_BIAS);
> +}
> +
>  #ifdef CONFIG_NUMA
>  extern struct page *__page_cache_alloc(gfp_t gfp);
>  #else
> diff --git a/mm/gup.c b/mm/gup.c
> index 05acd7e2eb22..3291da342f9c 100644
> --- a/mm/gup.c
> +++ b/mm/gup.c
> @@ -25,6 +25,26 @@ struct follow_page_context {
>       unsigned int page_mask;
>  };
>  
> +/**
> + * get_gup_pin_page() - mark a page as being used by get_user_pages().
> + * @page:    pointer to page to be marked
> + * @Returns: 0 for success, -EOVERFLOW if the page refcount would have
> + *           overflowed.
> + *
> + */
> +int get_gup_pin_page(struct page *page)
> +{
> +     page = compound_head(page);
> +
> +     if (page_ref_count(page) >= (UINT_MAX - GUP_PIN_COUNTING_BIAS)) {
> +             WARN_ONCE(1, "get_user_pages pin count overflowed");
> +             return -EOVERFLOW;
> +     }
> +
> +     page_ref_add(page, GUP_PIN_COUNTING_BIAS);
> +     return 0;
> +}
> +
>  static struct page *no_page_table(struct vm_area_struct *vma,
>               unsigned int flags)
>  {
> @@ -157,8 +177,14 @@ static struct page *follow_page_pte(struct 
> vm_area_struct *vma,
>               goto retry;
>       }
>  
> -     if (flags & FOLL_GET)
> -             get_page(page);
> +     if (flags & FOLL_GET) {
> +             int ret = get_gup_pin_page(page);
> +
> +             if (ret) {
> +                     page = ERR_PTR(ret);
> +                     goto out;
> +             }
> +     }
>       if (flags & FOLL_TOUCH) {
>               if ((flags & FOLL_WRITE) &&
>                   !pte_dirty(pte) && !PageDirty(page))
> @@ -497,7 +523,10 @@ static int get_gate_page(struct mm_struct *mm, unsigned 
> long address,
>               if (is_device_public_page(*page))
>                       goto unmap;
>       }
> -     get_page(*page);
> +
> +     ret = get_gup_pin_page(*page);
> +     if (ret)
> +             goto unmap;
>  out:
>       ret = 0;
>  unmap:
> @@ -1429,11 +1458,11 @@ static int gup_pte_range(pmd_t pmd, unsigned long 
> addr, unsigned long end,
>               page = pte_page(pte);
>               head = compound_head(page);
>  
> -             if (!page_cache_get_speculative(head))
> +             if (!page_cache_gup_pin_speculative(head))
>                       goto pte_unmap;
>  
>               if (unlikely(pte_val(pte) != pte_val(*ptep))) {
> -                     put_page(head);
> +                     put_user_page(head);
>                       goto pte_unmap;
>               }
>  
> @@ -1488,7 +1517,11 @@ static int __gup_device_huge(unsigned long pfn, 
> unsigned long addr,
>               }
>               SetPageReferenced(page);
>               pages[*nr] = page;
> -             get_page(page);
> +             if (get_gup_pin_page(page)) {
> +                     undo_dev_pagemap(nr, nr_start, pages);
> +                     return 0;
> +             }
> +
>               (*nr)++;
>               pfn++;
>       } while (addr += PAGE_SIZE, addr != end);
> @@ -1569,15 +1602,14 @@ static int gup_huge_pmd(pmd_t orig, pmd_t *pmdp, 
> unsigned long addr,
>       } while (addr += PAGE_SIZE, addr != end);
>  
>       head = compound_head(pmd_page(orig));
> -     if (!page_cache_add_speculative(head, refs)) {
> +     if (!page_cache_gup_pin_speculative(head)) {
>               *nr -= refs;
>               return 0;
>       }
>  
>       if (unlikely(pmd_val(orig) != pmd_val(*pmdp))) {
>               *nr -= refs;
> -             while (refs--)
> -                     put_page(head);
> +             put_user_page(head);
>               return 0;
>       }
>  
> @@ -1607,15 +1639,14 @@ static int gup_huge_pud(pud_t orig, pud_t *pudp, 
> unsigned long addr,
>       } while (addr += PAGE_SIZE, addr != end);
>  
>       head = compound_head(pud_page(orig));
> -     if (!page_cache_add_speculative(head, refs)) {
> +     if (!page_cache_gup_pin_speculative(head)) {
>               *nr -= refs;
>               return 0;
>       }
>  
>       if (unlikely(pud_val(orig) != pud_val(*pudp))) {
>               *nr -= refs;
> -             while (refs--)
> -                     put_page(head);
> +             put_user_page(head);
>               return 0;
>       }
>  
> @@ -1644,15 +1675,14 @@ static int gup_huge_pgd(pgd_t orig, pgd_t *pgdp, 
> unsigned long addr,
>       } while (addr += PAGE_SIZE, addr != end);
>  
>       head = compound_head(pgd_page(orig));
> -     if (!page_cache_add_speculative(head, refs)) {
> +     if (!page_cache_gup_pin_speculative(head)) {
>               *nr -= refs;
>               return 0;
>       }
>  
>       if (unlikely(pgd_val(orig) != pgd_val(*pgdp))) {
>               *nr -= refs;
> -             while (refs--)
> -                     put_page(head);
> +             put_user_page(head);
>               return 0;
>       }
>  
> diff --git a/mm/swap.c b/mm/swap.c
> index 7c42ca45bb89..39b0ddd35933 100644
> --- a/mm/swap.c
> +++ b/mm/swap.c
> @@ -133,6 +133,27 @@ void put_pages_list(struct list_head *pages)
>  }
>  EXPORT_SYMBOL(put_pages_list);
>  
> +/**
> + * put_user_page() - release a gup-pinned page
> + * @page:            pointer to page to be released
> + *
> + * Pages that were pinned via get_user_pages*() must be released via
> + * either put_user_page(), or one of the put_user_pages*() routines
> + * below. This is so that eventually, pages that are pinned via
> + * get_user_pages*() can be separately tracked and uniquely handled. In
> + * particular, interactions with RDMA and filesystems need special
> + * handling.
> + */
> +void put_user_page(struct page *page)
> +{
> +     page = compound_head(page);
> +
> +     VM_BUG_ON_PAGE(page_ref_count(page) < GUP_PIN_COUNTING_BIAS, page);
> +
> +     page_ref_sub(page, GUP_PIN_COUNTING_BIAS);
> +}
> +EXPORT_SYMBOL(put_user_page);
> +
>  typedef int (*set_dirty_func)(struct page *page);
>  
>  static void __put_user_pages_dirty(struct page **pages,
> -- 
> 2.20.1
> 

Reply via email to