diff --git a/mm/huge_memory.c b/mm/huge_memory.c index ff95a802d158..7ab677984924 100644 --- a/mm/huge_memory.c +++ b/mm/huge_memory.c @@ -38,6 +38,7 @@ #include #include #include +#include #include #include @@ -69,6 +70,7 @@ unsigned long transparent_hugepage_flags __read_mostly = static struct shrinker deferred_split_shrinker; static atomic_t huge_zero_refcount; +static DEFINE_SPINLOCK(huge_zero_lock); struct page *huge_zero_page __read_mostly; unsigned long huge_zero_pfn __read_mostly = ~0UL; @@ -144,7 +146,8 @@ bool hugepage_vma_check(struct vm_area_struct *vma, unsigned long vm_flags, static bool get_huge_zero_page(void) { struct page *zero_page; -retry: + + /* Paired with atomic_set_release(). */ if (likely(atomic_inc_not_zero(&huge_zero_refcount))) return true; @@ -154,17 +157,22 @@ retry: count_vm_event(THP_ZERO_PAGE_ALLOC_FAILED); return false; } - preempt_disable(); - if (cmpxchg(&huge_zero_page, NULL, zero_page)) { - preempt_enable(); - __free_pages(zero_page, compound_order(zero_page)); - goto retry; - } - WRITE_ONCE(huge_zero_pfn, page_to_pfn(zero_page)); - /* We take additional reference here. It will be put back by shrinker */ - atomic_set(&huge_zero_refcount, 2); - preempt_enable(); + /* Paired with critical section in shrink_huge_zero_page_scan(). */ + spin_lock(&huge_zero_lock); + if (huge_zero_page) { + /* Somebody else already installed it. */ + atomic_inc(&huge_zero_refcount); + spin_unlock(&huge_zero_lock); + __free_pages(zero_page, compound_order(zero_page)); + return true; + } + WRITE_ONCE(huge_zero_page, zero_page); + WRITE_ONCE(huge_zero_pfn, page_to_pfn(zero_page)); + /* Paired with atomic_inc_not_zero(). +1 for shrinker pin. */ + atomic_set_release(&huge_zero_refcount, 2); + spin_unlock(&huge_zero_lock); + count_vm_event(THP_ZERO_PAGE_ALLOC); return true; } @@ -208,15 +216,22 @@ static unsigned long shrink_huge_zero_page_count(struct shrinker *shrink, static unsigned long shrink_huge_zero_page_scan(struct shrinker *shrink, struct shrink_control *sc) { - if (atomic_cmpxchg(&huge_zero_refcount, 1, 0) == 1) { - struct page *zero_page = xchg(&huge_zero_page, NULL); - BUG_ON(zero_page == NULL); + struct page *zero_page; + + /* Paired with critical section in get_huge_zero_page(). */ + scoped_guard(spinlock, &huge_zero_lock) { + /* Paired with atomic_inc_not_zero() in get_huge_zero_page(). */ + if (atomic_cmpxchg(&huge_zero_refcount, 1, 0) != 1) + return 0; + + zero_page = huge_zero_page; + VM_WARN_ON_ONCE(!zero_page); + WRITE_ONCE(huge_zero_page, NULL); WRITE_ONCE(huge_zero_pfn, ~0UL); - __free_pages(zero_page, compound_order(zero_page)); - return HPAGE_PMD_NR; } - return 0; + __free_pages(zero_page, compound_order(zero_page)); + return HPAGE_PMD_NR; } static struct shrinker huge_zero_page_shrinker = {