[PATCH] hugetlb: fix race in set_max_huge_pages for multiple updaters of nr_huge_pages

If there are multiple updaters to /proc/sys/vm/nr_hugepages simultaneously
it is possible for the nr_huge_pages variable to become incorrect. There
is no locking in the set_max_huge_pages function around
alloc_fresh_huge_page which is able to update nr_huge_pages. Two callers
to alloc_fresh_huge_page could race against each other as could a call to
alloc_fresh_huge_page and a call to update_and_free_page. This patch just
expands the area covered by the hugetlb_lock to cover the call into
alloc_fresh_huge_page. I'm not sure how we could say that a sysctl section
is performance critical where more specific locking would be needed.

My reproducer was to run a couple copies of the following script
simultaneously

while [ true ]; do
echo 1000 > /proc/sys/vm/nr_hugepages
echo 500 > /proc/sys/vm/nr_hugepages
echo 750 > /proc/sys/vm/nr_hugepages
echo 100 > /proc/sys/vm/nr_hugepages
echo 0 > /proc/sys/vm/nr_hugepages
done

and then watch /proc/meminfo and eventually you will see things like

HugePages_Total: 100
HugePages_Free: 109

After applying the patch all seemed well.

Signed-off-by: Eric Paris <eparis@redhat.com>
Acked-by: William Irwin <wli@holomorphy.com>
Signed-off-by: Andrew Morton <akpm@osdl.org>
Signed-off-by: Linus Torvalds <torvalds@osdl.org>

authored by Eric Paris and committed by Linus Torvalds 0bd0f9fb 5ef897c7

+6
+6
mm/hugetlb.c
··· 22 static struct list_head hugepage_freelists[MAX_NUMNODES]; 23 static unsigned int nr_huge_pages_node[MAX_NUMNODES]; 24 static unsigned int free_huge_pages_node[MAX_NUMNODES]; 25 static DEFINE_SPINLOCK(hugetlb_lock); 26 27 static void enqueue_huge_page(struct page *page) ··· 65 HUGETLB_PAGE_ORDER); 66 nid = (nid + 1) % num_online_nodes(); 67 if (page) { 68 nr_huge_pages++; 69 nr_huge_pages_node[page_to_nid(page)]++; 70 } 71 return page; 72 }
··· 22 static struct list_head hugepage_freelists[MAX_NUMNODES]; 23 static unsigned int nr_huge_pages_node[MAX_NUMNODES]; 24 static unsigned int free_huge_pages_node[MAX_NUMNODES]; 25 + 26 + /* 27 + * Protects updates to hugepage_freelists, nr_huge_pages, and free_huge_pages 28 + */ 29 static DEFINE_SPINLOCK(hugetlb_lock); 30 31 static void enqueue_huge_page(struct page *page) ··· 61 HUGETLB_PAGE_ORDER); 62 nid = (nid + 1) % num_online_nodes(); 63 if (page) { 64 + spin_lock(&hugetlb_lock); 65 nr_huge_pages++; 66 nr_huge_pages_node[page_to_nid(page)]++; 67 + spin_unlock(&hugetlb_lock); 68 } 69 return page; 70 }