2005-04-17 06:20:36 +08:00
|
|
|
/*
|
|
|
|
* Generic hugetlb support.
|
|
|
|
* (C) William Irwin, April 2004
|
|
|
|
*/
|
|
|
|
#include <linux/gfp.h>
|
|
|
|
#include <linux/list.h>
|
|
|
|
#include <linux/init.h>
|
|
|
|
#include <linux/module.h>
|
|
|
|
#include <linux/mm.h>
|
|
|
|
#include <linux/sysctl.h>
|
|
|
|
#include <linux/highmem.h>
|
|
|
|
#include <linux/nodemask.h>
|
2005-06-22 08:14:44 +08:00
|
|
|
#include <linux/pagemap.h>
|
2006-01-06 16:10:46 +08:00
|
|
|
#include <linux/mempolicy.h>
|
2006-01-08 17:00:57 +08:00
|
|
|
#include <linux/cpuset.h>
|
[PATCH] hugepage: serialize hugepage allocation and instantiation
Currently, no lock or mutex is held between allocating a hugepage and
inserting it into the pagetables / page cache. When we do go to insert the
page into pagetables or page cache, we recheck and may free the newly
allocated hugepage. However, since the number of hugepages in the system
is strictly limited, and it's usualy to want to use all of them, this can
still lead to spurious allocation failures.
For example, suppose two processes are both mapping (MAP_SHARED) the same
hugepage file, large enough to consume the entire available hugepage pool.
If they race instantiating the last page in the mapping, they will both
attempt to allocate the last available hugepage. One will fail, of course,
returning OOM from the fault and thus causing the process to be killed,
despite the fact that the entire mapping can, in fact, be instantiated.
The patch fixes this race by the simple method of adding a (sleeping) mutex
to serialize the hugepage fault path between allocation and insertion into
pagetables and/or page cache. It would be possible to avoid the
serialization by catching the allocation failures, waiting on some
condition, then rechecking to see if someone else has instantiated the page
for us. Given the likely frequency of hugepage instantiations, it seems
very doubtful it's worth the extra complexity.
This patch causes no regression on the libhugetlbfs testsuite, and one
test, which can trigger this race now passes where it previously failed.
Actually, the test still sometimes fails, though less often and only as a
shmat() failure, rather processes getting OOM killed by the VM. The dodgy
heuristic tests in fs/hugetlbfs/inode.c for whether there's enough hugepage
space aren't protected by the new mutex, and would be ugly to do so, so
there's still a race there. Another patch to replace those tests with
something saner for this reason as well as others coming...
Signed-off-by: David Gibson <dwg@au1.ibm.com>
Cc: William Lee Irwin III <wli@holomorphy.com>
Signed-off-by: Andrew Morton <akpm@osdl.org>
Signed-off-by: Linus Torvalds <torvalds@osdl.org>
2006-03-22 16:08:53 +08:00
|
|
|
#include <linux/mutex.h>
|
2006-01-06 16:10:46 +08:00
|
|
|
|
2005-06-22 08:14:44 +08:00
|
|
|
#include <asm/page.h>
|
|
|
|
#include <asm/pgtable.h>
|
|
|
|
|
|
|
|
#include <linux/hugetlb.h>
|
2006-03-22 16:08:40 +08:00
|
|
|
#include "internal.h"
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
const unsigned long hugetlb_zero = 0, hugetlb_infinity = ~0UL;
|
2006-06-23 17:03:15 +08:00
|
|
|
static unsigned long nr_huge_pages, free_huge_pages, resv_huge_pages;
|
2007-10-16 16:26:18 +08:00
|
|
|
static unsigned long surplus_huge_pages;
|
2005-04-17 06:20:36 +08:00
|
|
|
unsigned long max_huge_pages;
|
|
|
|
static struct list_head hugepage_freelists[MAX_NUMNODES];
|
|
|
|
static unsigned int nr_huge_pages_node[MAX_NUMNODES];
|
|
|
|
static unsigned int free_huge_pages_node[MAX_NUMNODES];
|
2007-10-16 16:26:18 +08:00
|
|
|
static unsigned int surplus_huge_pages_node[MAX_NUMNODES];
|
2007-07-17 19:03:13 +08:00
|
|
|
static gfp_t htlb_alloc_mask = GFP_HIGHUSER;
|
|
|
|
unsigned long hugepages_treat_as_movable;
|
|
|
|
|
[PATCH] hugepage: serialize hugepage allocation and instantiation
Currently, no lock or mutex is held between allocating a hugepage and
inserting it into the pagetables / page cache. When we do go to insert the
page into pagetables or page cache, we recheck and may free the newly
allocated hugepage. However, since the number of hugepages in the system
is strictly limited, and it's usualy to want to use all of them, this can
still lead to spurious allocation failures.
For example, suppose two processes are both mapping (MAP_SHARED) the same
hugepage file, large enough to consume the entire available hugepage pool.
If they race instantiating the last page in the mapping, they will both
attempt to allocate the last available hugepage. One will fail, of course,
returning OOM from the fault and thus causing the process to be killed,
despite the fact that the entire mapping can, in fact, be instantiated.
The patch fixes this race by the simple method of adding a (sleeping) mutex
to serialize the hugepage fault path between allocation and insertion into
pagetables and/or page cache. It would be possible to avoid the
serialization by catching the allocation failures, waiting on some
condition, then rechecking to see if someone else has instantiated the page
for us. Given the likely frequency of hugepage instantiations, it seems
very doubtful it's worth the extra complexity.
This patch causes no regression on the libhugetlbfs testsuite, and one
test, which can trigger this race now passes where it previously failed.
Actually, the test still sometimes fails, though less often and only as a
shmat() failure, rather processes getting OOM killed by the VM. The dodgy
heuristic tests in fs/hugetlbfs/inode.c for whether there's enough hugepage
space aren't protected by the new mutex, and would be ugly to do so, so
there's still a race there. Another patch to replace those tests with
something saner for this reason as well as others coming...
Signed-off-by: David Gibson <dwg@au1.ibm.com>
Cc: William Lee Irwin III <wli@holomorphy.com>
Signed-off-by: Andrew Morton <akpm@osdl.org>
Signed-off-by: Linus Torvalds <torvalds@osdl.org>
2006-03-22 16:08:53 +08:00
|
|
|
/*
|
|
|
|
* Protects updates to hugepage_freelists, nr_huge_pages, and free_huge_pages
|
|
|
|
*/
|
|
|
|
static DEFINE_SPINLOCK(hugetlb_lock);
|
2005-11-22 13:32:28 +08:00
|
|
|
|
2006-03-22 16:08:51 +08:00
|
|
|
static void clear_huge_page(struct page *page, unsigned long addr)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
|
|
|
|
might_sleep();
|
|
|
|
for (i = 0; i < (HPAGE_SIZE/PAGE_SIZE); i++) {
|
|
|
|
cond_resched();
|
2007-10-01 16:20:10 +08:00
|
|
|
clear_user_highpage(page + i, addr + i * PAGE_SIZE);
|
2006-03-22 16:08:51 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void copy_huge_page(struct page *dst, struct page *src,
|
2006-12-13 01:14:55 +08:00
|
|
|
unsigned long addr, struct vm_area_struct *vma)
|
2006-03-22 16:08:51 +08:00
|
|
|
{
|
|
|
|
int i;
|
|
|
|
|
|
|
|
might_sleep();
|
|
|
|
for (i = 0; i < HPAGE_SIZE/PAGE_SIZE; i++) {
|
|
|
|
cond_resched();
|
2006-12-13 01:14:55 +08:00
|
|
|
copy_user_highpage(dst + i, src + i, addr + i*PAGE_SIZE, vma);
|
2006-03-22 16:08:51 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
static void enqueue_huge_page(struct page *page)
|
|
|
|
{
|
|
|
|
int nid = page_to_nid(page);
|
|
|
|
list_add(&page->lru, &hugepage_freelists[nid]);
|
|
|
|
free_huge_pages++;
|
|
|
|
free_huge_pages_node[nid]++;
|
|
|
|
}
|
|
|
|
|
2006-01-06 16:10:46 +08:00
|
|
|
static struct page *dequeue_huge_page(struct vm_area_struct *vma,
|
|
|
|
unsigned long address)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2007-07-16 14:38:02 +08:00
|
|
|
int nid;
|
2005-04-17 06:20:36 +08:00
|
|
|
struct page *page = NULL;
|
Fix NUMA Memory Policy Reference Counting
This patch proposes fixes to the reference counting of memory policy in the
page allocation paths and in show_numa_map(). Extracted from my "Memory
Policy Cleanups and Enhancements" series as stand-alone.
Shared policy lookup [shmem] has always added a reference to the policy,
but this was never unrefed after page allocation or after formatting the
numa map data.
Default system policy should not require additional ref counting, nor
should the current task's task policy. However, show_numa_map() calls
get_vma_policy() to examine what may be [likely is] another task's policy.
The latter case needs protection against freeing of the policy.
This patch adds a reference count to a mempolicy returned by
get_vma_policy() when the policy is a vma policy or another task's
mempolicy. Again, shared policy is already reference counted on lookup. A
matching "unref" [__mpol_free()] is performed in alloc_page_vma() for
shared and vma policies, and in show_numa_map() for shared and another
task's mempolicy. We can call __mpol_free() directly, saving an admittedly
inexpensive inline NULL test, because we know we have a non-NULL policy.
Handling policy ref counts for hugepages is a bit trickier.
huge_zonelist() returns a zone list that might come from a shared or vma
'BIND policy. In this case, we should hold the reference until after the
huge page allocation in dequeue_hugepage(). The patch modifies
huge_zonelist() to return a pointer to the mempolicy if it needs to be
unref'd after allocation.
Kernel Build [16cpu, 32GB, ia64] - average of 10 runs:
w/o patch w/ refcount patch
Avg Std Devn Avg Std Devn
Real: 100.59 0.38 100.63 0.43
User: 1209.60 0.37 1209.91 0.31
System: 81.52 0.42 81.64 0.34
Signed-off-by: Lee Schermerhorn <lee.schermerhorn@hp.com>
Acked-by: Andi Kleen <ak@suse.de>
Cc: Christoph Lameter <clameter@sgi.com>
Acked-by: Mel Gorman <mel@csn.ul.ie>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2007-09-19 13:46:47 +08:00
|
|
|
struct mempolicy *mpol;
|
2007-07-17 19:03:13 +08:00
|
|
|
struct zonelist *zonelist = huge_zonelist(vma, address,
|
Fix NUMA Memory Policy Reference Counting
This patch proposes fixes to the reference counting of memory policy in the
page allocation paths and in show_numa_map(). Extracted from my "Memory
Policy Cleanups and Enhancements" series as stand-alone.
Shared policy lookup [shmem] has always added a reference to the policy,
but this was never unrefed after page allocation or after formatting the
numa map data.
Default system policy should not require additional ref counting, nor
should the current task's task policy. However, show_numa_map() calls
get_vma_policy() to examine what may be [likely is] another task's policy.
The latter case needs protection against freeing of the policy.
This patch adds a reference count to a mempolicy returned by
get_vma_policy() when the policy is a vma policy or another task's
mempolicy. Again, shared policy is already reference counted on lookup. A
matching "unref" [__mpol_free()] is performed in alloc_page_vma() for
shared and vma policies, and in show_numa_map() for shared and another
task's mempolicy. We can call __mpol_free() directly, saving an admittedly
inexpensive inline NULL test, because we know we have a non-NULL policy.
Handling policy ref counts for hugepages is a bit trickier.
huge_zonelist() returns a zone list that might come from a shared or vma
'BIND policy. In this case, we should hold the reference until after the
huge page allocation in dequeue_hugepage(). The patch modifies
huge_zonelist() to return a pointer to the mempolicy if it needs to be
unref'd after allocation.
Kernel Build [16cpu, 32GB, ia64] - average of 10 runs:
w/o patch w/ refcount patch
Avg Std Devn Avg Std Devn
Real: 100.59 0.38 100.63 0.43
User: 1209.60 0.37 1209.91 0.31
System: 81.52 0.42 81.64 0.34
Signed-off-by: Lee Schermerhorn <lee.schermerhorn@hp.com>
Acked-by: Andi Kleen <ak@suse.de>
Cc: Christoph Lameter <clameter@sgi.com>
Acked-by: Mel Gorman <mel@csn.ul.ie>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2007-09-19 13:46:47 +08:00
|
|
|
htlb_alloc_mask, &mpol);
|
2006-01-06 16:10:45 +08:00
|
|
|
struct zone **z;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2006-01-06 16:10:45 +08:00
|
|
|
for (z = zonelist->zones; *z; z++) {
|
2006-09-26 14:31:55 +08:00
|
|
|
nid = zone_to_nid(*z);
|
2007-07-17 19:03:13 +08:00
|
|
|
if (cpuset_zone_allowed_softwall(*z, htlb_alloc_mask) &&
|
2007-07-19 16:49:08 +08:00
|
|
|
!list_empty(&hugepage_freelists[nid])) {
|
|
|
|
page = list_entry(hugepage_freelists[nid].next,
|
|
|
|
struct page, lru);
|
|
|
|
list_del(&page->lru);
|
|
|
|
free_huge_pages--;
|
|
|
|
free_huge_pages_node[nid]--;
|
2007-10-16 16:26:19 +08:00
|
|
|
if (vma && vma->vm_flags & VM_MAYSHARE)
|
|
|
|
resv_huge_pages--;
|
2007-07-24 09:44:00 +08:00
|
|
|
break;
|
2007-07-19 16:49:08 +08:00
|
|
|
}
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
Fix NUMA Memory Policy Reference Counting
This patch proposes fixes to the reference counting of memory policy in the
page allocation paths and in show_numa_map(). Extracted from my "Memory
Policy Cleanups and Enhancements" series as stand-alone.
Shared policy lookup [shmem] has always added a reference to the policy,
but this was never unrefed after page allocation or after formatting the
numa map data.
Default system policy should not require additional ref counting, nor
should the current task's task policy. However, show_numa_map() calls
get_vma_policy() to examine what may be [likely is] another task's policy.
The latter case needs protection against freeing of the policy.
This patch adds a reference count to a mempolicy returned by
get_vma_policy() when the policy is a vma policy or another task's
mempolicy. Again, shared policy is already reference counted on lookup. A
matching "unref" [__mpol_free()] is performed in alloc_page_vma() for
shared and vma policies, and in show_numa_map() for shared and another
task's mempolicy. We can call __mpol_free() directly, saving an admittedly
inexpensive inline NULL test, because we know we have a non-NULL policy.
Handling policy ref counts for hugepages is a bit trickier.
huge_zonelist() returns a zone list that might come from a shared or vma
'BIND policy. In this case, we should hold the reference until after the
huge page allocation in dequeue_hugepage(). The patch modifies
huge_zonelist() to return a pointer to the mempolicy if it needs to be
unref'd after allocation.
Kernel Build [16cpu, 32GB, ia64] - average of 10 runs:
w/o patch w/ refcount patch
Avg Std Devn Avg Std Devn
Real: 100.59 0.38 100.63 0.43
User: 1209.60 0.37 1209.91 0.31
System: 81.52 0.42 81.64 0.34
Signed-off-by: Lee Schermerhorn <lee.schermerhorn@hp.com>
Acked-by: Andi Kleen <ak@suse.de>
Cc: Christoph Lameter <clameter@sgi.com>
Acked-by: Mel Gorman <mel@csn.ul.ie>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2007-09-19 13:46:47 +08:00
|
|
|
mpol_free(mpol); /* unref if mpol !NULL */
|
2005-04-17 06:20:36 +08:00
|
|
|
return page;
|
|
|
|
}
|
|
|
|
|
hugetlb: Move update_and_free_page
Dynamic huge page pool resizing.
In most real-world scenarios, configuring the size of the hugetlb pool
correctly is a difficult task. If too few pages are allocated to the pool,
applications using MAP_SHARED may fail to mmap() a hugepage region and
applications using MAP_PRIVATE may receive SIGBUS. Isolating too much memory
in the hugetlb pool means it is not available for other uses, especially those
programs not using huge pages.
The obvious answer is to let the hugetlb pool grow and shrink in response to
the runtime demand for huge pages. The work Mel Gorman has been doing to
establish a memory zone for movable memory allocations makes dynamically
resizing the hugetlb pool reliable within the limits of that zone. This patch
series implements dynamic pool resizing for private and shared mappings while
being careful to maintain existing semantics. Please reply with your comments
and feedback; even just to say whether it would be a useful feature to you.
Thanks.
How it works
============
Upon depletion of the hugetlb pool, rather than reporting an error immediately,
first try and allocate the needed huge pages directly from the buddy allocator.
Care must be taken to avoid unbounded growth of the hugetlb pool, so the
hugetlb filesystem quota is used to limit overall pool size.
The real work begins when we decide there is a shortage of huge pages. What
happens next depends on whether the pages are for a private or shared mapping.
Private mappings are straightforward. At fault time, if alloc_huge_page()
fails, we allocate a page from the buddy allocator and increment the source
node's surplus_huge_pages counter. When free_huge_page() is called for a page
on a node with a surplus, the page is freed directly to the buddy allocator
instead of the hugetlb pool.
Because shared mappings require all of the pages to be reserved up front, some
additional work must be done at mmap() to support them. We determine the
reservation shortage and allocate the required number of pages all at once.
These pages are then added to the hugetlb pool and marked reserved. Where that
is not possible the mmap() will fail. As with private mappings, the
appropriate surplus counters are updated. Since reserved huge pages won't
necessarily be used by the process, we can't be sure that free_huge_page() will
always be called to return surplus pages to the buddy allocator. To prevent
the huge page pool from bloating, we must free unused surplus pages when their
reservation has ended.
Controlling it
==============
With the entire patch series applied, pool resizing is off by default so unless
specific action is taken, the semantics are unchanged.
To take advantage of the flexibility afforded by this patch series one must
tolerate a change in semantics. To control hugetlb pool growth, the following
techniques can be employed:
* A sysctl tunable to enable/disable the feature entirely
* The size= mount option for hugetlbfs filesystems to limit pool size
Performance
===========
When contiguous memory is readily available, it is expected that the cost of
dynamicly resizing the pool will be small. This series has been performance
tested with 'stream' to measure this cost.
Stream (http://www.cs.virginia.edu/stream/) was linked with libhugetlbfs to
enable remapping of the text and data/bss segments into huge pages.
Stream with small array
-----------------------
Baseline: nr_hugepages = 0, No libhugetlbfs segment remapping
Preallocated: nr_hugepages = 5, Text and data/bss remapping
Dynamic: nr_hugepages = 0, Text and data/bss remapping
Rate (MB/s)
Function Baseline Preallocated Dynamic
Copy: 4695.6266 5942.8371 5982.2287
Scale: 4451.5776 5017.1419 5658.7843
Add: 5815.8849 7927.7827 8119.3552
Triad: 5949.4144 8527.6492 8110.6903
Stream with large array
-----------------------
Baseline: nr_hugepages = 0, No libhugetlbfs segment remapping
Preallocated: nr_hugepages = 67, Text and data/bss remapping
Dynamic: nr_hugepages = 0, Text and data/bss remapping
Rate (MB/s)
Function Baseline Preallocated Dynamic
Copy: 2227.8281 2544.2732 2546.4947
Scale: 2136.3208 2430.7294 2421.2074
Add: 2773.1449 4004.0021 3999.4331
Triad: 2748.4502 3777.0109 3773.4970
* All numbers are averages taken from 10 consecutive runs with a maximum
standard deviation of 1.3 percent noted.
This patch:
Simply move update_and_free_page() so that it can be reused later in this
patch series. The implementation is not changed.
Signed-off-by: Adam Litke <agl@us.ibm.com>
Acked-by: Andy Whitcroft <apw@shadowen.org>
Acked-by: Dave McCracken <dave.mccracken@oracle.com>
Acked-by: William Irwin <bill.irwin@oracle.com>
Cc: David Gibson <david@gibson.dropbear.id.au>
Cc: Ken Chen <kenchen@google.com>
Cc: Badari Pulavarty <pbadari@us.ibm.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2007-10-16 16:26:16 +08:00
|
|
|
static void update_and_free_page(struct page *page)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
nr_huge_pages--;
|
|
|
|
nr_huge_pages_node[page_to_nid(page)]--;
|
|
|
|
for (i = 0; i < (HPAGE_SIZE / PAGE_SIZE); i++) {
|
|
|
|
page[i].flags &= ~(1 << PG_locked | 1 << PG_error | 1 << PG_referenced |
|
|
|
|
1 << PG_dirty | 1 << PG_active | 1 << PG_reserved |
|
|
|
|
1 << PG_private | 1<< PG_writeback);
|
|
|
|
}
|
|
|
|
set_compound_page_dtor(page, NULL);
|
|
|
|
set_page_refcounted(page);
|
|
|
|
__free_pages(page, HUGETLB_PAGE_ORDER);
|
|
|
|
}
|
|
|
|
|
2006-03-22 16:08:56 +08:00
|
|
|
static void free_huge_page(struct page *page)
|
|
|
|
{
|
2007-10-16 16:26:18 +08:00
|
|
|
int nid = page_to_nid(page);
|
2006-03-22 16:08:56 +08:00
|
|
|
|
2007-10-16 16:26:18 +08:00
|
|
|
BUG_ON(page_count(page));
|
2006-03-22 16:08:56 +08:00
|
|
|
INIT_LIST_HEAD(&page->lru);
|
|
|
|
|
|
|
|
spin_lock(&hugetlb_lock);
|
2007-10-16 16:26:18 +08:00
|
|
|
if (surplus_huge_pages_node[nid]) {
|
|
|
|
update_and_free_page(page);
|
|
|
|
surplus_huge_pages--;
|
|
|
|
surplus_huge_pages_node[nid]--;
|
|
|
|
} else {
|
|
|
|
enqueue_huge_page(page);
|
|
|
|
}
|
2006-03-22 16:08:56 +08:00
|
|
|
spin_unlock(&hugetlb_lock);
|
|
|
|
}
|
|
|
|
|
2007-10-16 16:26:18 +08:00
|
|
|
/*
|
|
|
|
* Increment or decrement surplus_huge_pages. Keep node-specific counters
|
|
|
|
* balanced by operating on them in a round-robin fashion.
|
|
|
|
* Returns 1 if an adjustment was made.
|
|
|
|
*/
|
|
|
|
static int adjust_pool_surplus(int delta)
|
|
|
|
{
|
|
|
|
static int prev_nid;
|
|
|
|
int nid = prev_nid;
|
|
|
|
int ret = 0;
|
|
|
|
|
|
|
|
VM_BUG_ON(delta != -1 && delta != 1);
|
|
|
|
do {
|
|
|
|
nid = next_node(nid, node_online_map);
|
|
|
|
if (nid == MAX_NUMNODES)
|
|
|
|
nid = first_node(node_online_map);
|
|
|
|
|
|
|
|
/* To shrink on this node, there must be a surplus page */
|
|
|
|
if (delta < 0 && !surplus_huge_pages_node[nid])
|
|
|
|
continue;
|
|
|
|
/* Surplus cannot exceed the total number of pages */
|
|
|
|
if (delta > 0 && surplus_huge_pages_node[nid] >=
|
|
|
|
nr_huge_pages_node[nid])
|
|
|
|
continue;
|
|
|
|
|
|
|
|
surplus_huge_pages += delta;
|
|
|
|
surplus_huge_pages_node[nid] += delta;
|
|
|
|
ret = 1;
|
|
|
|
break;
|
|
|
|
} while (nid != prev_nid);
|
|
|
|
|
|
|
|
prev_nid = nid;
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2006-03-22 16:08:08 +08:00
|
|
|
static int alloc_fresh_huge_page(void)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2007-07-16 14:38:12 +08:00
|
|
|
static int prev_nid;
|
2005-04-17 06:20:36 +08:00
|
|
|
struct page *page;
|
2007-07-16 14:38:12 +08:00
|
|
|
int nid;
|
|
|
|
|
2007-07-19 16:49:11 +08:00
|
|
|
/*
|
|
|
|
* Copy static prev_nid to local nid, work on that, then copy it
|
|
|
|
* back to prev_nid afterwards: otherwise there's a window in which
|
|
|
|
* a racer might pass invalid nid MAX_NUMNODES to alloc_pages_node.
|
|
|
|
* But we don't need to use a spin_lock here: it really doesn't
|
|
|
|
* matter if occasionally a racer chooses the same nid as we do.
|
|
|
|
*/
|
2007-07-16 14:38:12 +08:00
|
|
|
nid = next_node(prev_nid, node_online_map);
|
2006-03-22 16:09:10 +08:00
|
|
|
if (nid == MAX_NUMNODES)
|
|
|
|
nid = first_node(node_online_map);
|
2007-07-16 14:38:12 +08:00
|
|
|
prev_nid = nid;
|
|
|
|
|
2007-07-17 19:03:13 +08:00
|
|
|
page = alloc_pages_node(nid, htlb_alloc_mask|__GFP_COMP|__GFP_NOWARN,
|
2007-07-16 14:38:12 +08:00
|
|
|
HUGETLB_PAGE_ORDER);
|
2005-04-17 06:20:36 +08:00
|
|
|
if (page) {
|
2006-12-07 12:33:32 +08:00
|
|
|
set_compound_page_dtor(page, free_huge_page);
|
2005-11-22 13:32:28 +08:00
|
|
|
spin_lock(&hugetlb_lock);
|
2005-04-17 06:20:36 +08:00
|
|
|
nr_huge_pages++;
|
|
|
|
nr_huge_pages_node[page_to_nid(page)]++;
|
2005-11-22 13:32:28 +08:00
|
|
|
spin_unlock(&hugetlb_lock);
|
2006-03-22 16:08:08 +08:00
|
|
|
put_page(page); /* free it into the hugepage allocator */
|
|
|
|
return 1;
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
2006-03-22 16:08:08 +08:00
|
|
|
return 0;
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
|
2007-10-16 16:26:18 +08:00
|
|
|
static struct page *alloc_buddy_huge_page(struct vm_area_struct *vma,
|
|
|
|
unsigned long address)
|
|
|
|
{
|
|
|
|
struct page *page;
|
|
|
|
|
|
|
|
page = alloc_pages(htlb_alloc_mask|__GFP_COMP|__GFP_NOWARN,
|
|
|
|
HUGETLB_PAGE_ORDER);
|
|
|
|
if (page) {
|
|
|
|
set_compound_page_dtor(page, free_huge_page);
|
|
|
|
spin_lock(&hugetlb_lock);
|
|
|
|
nr_huge_pages++;
|
|
|
|
nr_huge_pages_node[page_to_nid(page)]++;
|
|
|
|
surplus_huge_pages++;
|
|
|
|
surplus_huge_pages_node[page_to_nid(page)]++;
|
|
|
|
spin_unlock(&hugetlb_lock);
|
|
|
|
}
|
|
|
|
|
|
|
|
return page;
|
|
|
|
}
|
|
|
|
|
2007-10-16 16:26:19 +08:00
|
|
|
/*
|
|
|
|
* Increase the hugetlb pool such that it can accomodate a reservation
|
|
|
|
* of size 'delta'.
|
|
|
|
*/
|
|
|
|
static int gather_surplus_pages(int delta)
|
|
|
|
{
|
|
|
|
struct list_head surplus_list;
|
|
|
|
struct page *page, *tmp;
|
|
|
|
int ret, i;
|
|
|
|
int needed, allocated;
|
|
|
|
|
|
|
|
needed = (resv_huge_pages + delta) - free_huge_pages;
|
|
|
|
if (needed <= 0)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
allocated = 0;
|
|
|
|
INIT_LIST_HEAD(&surplus_list);
|
|
|
|
|
|
|
|
ret = -ENOMEM;
|
|
|
|
retry:
|
|
|
|
spin_unlock(&hugetlb_lock);
|
|
|
|
for (i = 0; i < needed; i++) {
|
|
|
|
page = alloc_buddy_huge_page(NULL, 0);
|
|
|
|
if (!page) {
|
|
|
|
/*
|
|
|
|
* We were not able to allocate enough pages to
|
|
|
|
* satisfy the entire reservation so we free what
|
|
|
|
* we've allocated so far.
|
|
|
|
*/
|
|
|
|
spin_lock(&hugetlb_lock);
|
|
|
|
needed = 0;
|
|
|
|
goto free;
|
|
|
|
}
|
|
|
|
|
|
|
|
list_add(&page->lru, &surplus_list);
|
|
|
|
}
|
|
|
|
allocated += needed;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* After retaking hugetlb_lock, we need to recalculate 'needed'
|
|
|
|
* because either resv_huge_pages or free_huge_pages may have changed.
|
|
|
|
*/
|
|
|
|
spin_lock(&hugetlb_lock);
|
|
|
|
needed = (resv_huge_pages + delta) - (free_huge_pages + allocated);
|
|
|
|
if (needed > 0)
|
|
|
|
goto retry;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* The surplus_list now contains _at_least_ the number of extra pages
|
|
|
|
* needed to accomodate the reservation. Add the appropriate number
|
|
|
|
* of pages to the hugetlb pool and free the extras back to the buddy
|
|
|
|
* allocator.
|
|
|
|
*/
|
|
|
|
needed += allocated;
|
|
|
|
ret = 0;
|
|
|
|
free:
|
|
|
|
list_for_each_entry_safe(page, tmp, &surplus_list, lru) {
|
|
|
|
list_del(&page->lru);
|
|
|
|
if ((--needed) >= 0)
|
|
|
|
enqueue_huge_page(page);
|
|
|
|
else
|
|
|
|
update_and_free_page(page);
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* When releasing a hugetlb pool reservation, any surplus pages that were
|
|
|
|
* allocated to satisfy the reservation must be explicitly freed if they were
|
|
|
|
* never used.
|
|
|
|
*/
|
|
|
|
void return_unused_surplus_pages(unsigned long unused_resv_pages)
|
|
|
|
{
|
|
|
|
static int nid = -1;
|
|
|
|
struct page *page;
|
|
|
|
unsigned long nr_pages;
|
|
|
|
|
|
|
|
nr_pages = min(unused_resv_pages, surplus_huge_pages);
|
|
|
|
|
|
|
|
while (nr_pages) {
|
|
|
|
nid = next_node(nid, node_online_map);
|
|
|
|
if (nid == MAX_NUMNODES)
|
|
|
|
nid = first_node(node_online_map);
|
|
|
|
|
|
|
|
if (!surplus_huge_pages_node[nid])
|
|
|
|
continue;
|
|
|
|
|
|
|
|
if (!list_empty(&hugepage_freelists[nid])) {
|
|
|
|
page = list_entry(hugepage_freelists[nid].next,
|
|
|
|
struct page, lru);
|
|
|
|
list_del(&page->lru);
|
|
|
|
update_and_free_page(page);
|
|
|
|
free_huge_pages--;
|
|
|
|
free_huge_pages_node[nid]--;
|
|
|
|
surplus_huge_pages--;
|
|
|
|
surplus_huge_pages_node[nid]--;
|
|
|
|
nr_pages--;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2006-03-22 16:08:56 +08:00
|
|
|
static struct page *alloc_huge_page(struct vm_area_struct *vma,
|
|
|
|
unsigned long addr)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2007-10-16 16:26:18 +08:00
|
|
|
struct page *page = NULL;
|
2007-10-16 16:26:19 +08:00
|
|
|
int use_reserved_page = vma->vm_flags & VM_MAYSHARE;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
spin_lock(&hugetlb_lock);
|
2007-10-16 16:26:19 +08:00
|
|
|
if (!use_reserved_page && (free_huge_pages <= resv_huge_pages))
|
2006-06-23 17:03:15 +08:00
|
|
|
goto fail;
|
[PATCH] hugepage: Strict page reservation for hugepage inodes
These days, hugepages are demand-allocated at first fault time. There's a
somewhat dubious (and racy) heuristic when making a new mmap() to check if
there are enough available hugepages to fully satisfy that mapping.
A particularly obvious case where the heuristic breaks down is where a
process maps its hugepages not as a single chunk, but as a bunch of
individually mmap()ed (or shmat()ed) blocks without touching and
instantiating the pages in between allocations. In this case the size of
each block is compared against the total number of available hugepages.
It's thus easy for the process to become overcommitted, because each block
mapping will succeed, although the total number of hugepages required by
all blocks exceeds the number available. In particular, this defeats such
a program which will detect a mapping failure and adjust its hugepage usage
downward accordingly.
The patch below addresses this problem, by strictly reserving a number of
physical hugepages for hugepage inodes which have been mapped, but not
instatiated. MAP_SHARED mappings are thus "safe" - they will fail on
mmap(), not later with an OOM SIGKILL. MAP_PRIVATE mappings can still
trigger an OOM. (Actually SHARED mappings can technically still OOM, but
only if the sysadmin explicitly reduces the hugepage pool between mapping
and instantiation)
This patch appears to address the problem at hand - it allows DB2 to start
correctly, for instance, which previously suffered the failure described
above.
This patch causes no regressions on the libhugetblfs testsuite, and makes a
test (designed to catch this problem) pass which previously failed (ppc64,
POWER5).
Signed-off-by: David Gibson <dwg@au1.ibm.com>
Cc: William Lee Irwin III <wli@holomorphy.com>
Signed-off-by: Andrew Morton <akpm@osdl.org>
Signed-off-by: Linus Torvalds <torvalds@osdl.org>
2006-03-22 16:08:55 +08:00
|
|
|
|
|
|
|
page = dequeue_huge_page(vma, addr);
|
|
|
|
if (!page)
|
|
|
|
goto fail;
|
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
spin_unlock(&hugetlb_lock);
|
2006-03-22 16:08:40 +08:00
|
|
|
set_page_refcounted(page);
|
2005-04-17 06:20:36 +08:00
|
|
|
return page;
|
[PATCH] hugepage: Strict page reservation for hugepage inodes
These days, hugepages are demand-allocated at first fault time. There's a
somewhat dubious (and racy) heuristic when making a new mmap() to check if
there are enough available hugepages to fully satisfy that mapping.
A particularly obvious case where the heuristic breaks down is where a
process maps its hugepages not as a single chunk, but as a bunch of
individually mmap()ed (or shmat()ed) blocks without touching and
instantiating the pages in between allocations. In this case the size of
each block is compared against the total number of available hugepages.
It's thus easy for the process to become overcommitted, because each block
mapping will succeed, although the total number of hugepages required by
all blocks exceeds the number available. In particular, this defeats such
a program which will detect a mapping failure and adjust its hugepage usage
downward accordingly.
The patch below addresses this problem, by strictly reserving a number of
physical hugepages for hugepage inodes which have been mapped, but not
instatiated. MAP_SHARED mappings are thus "safe" - they will fail on
mmap(), not later with an OOM SIGKILL. MAP_PRIVATE mappings can still
trigger an OOM. (Actually SHARED mappings can technically still OOM, but
only if the sysadmin explicitly reduces the hugepage pool between mapping
and instantiation)
This patch appears to address the problem at hand - it allows DB2 to start
correctly, for instance, which previously suffered the failure described
above.
This patch causes no regressions on the libhugetblfs testsuite, and makes a
test (designed to catch this problem) pass which previously failed (ppc64,
POWER5).
Signed-off-by: David Gibson <dwg@au1.ibm.com>
Cc: William Lee Irwin III <wli@holomorphy.com>
Signed-off-by: Andrew Morton <akpm@osdl.org>
Signed-off-by: Linus Torvalds <torvalds@osdl.org>
2006-03-22 16:08:55 +08:00
|
|
|
|
2006-06-23 17:03:15 +08:00
|
|
|
fail:
|
[PATCH] hugepage: Strict page reservation for hugepage inodes
These days, hugepages are demand-allocated at first fault time. There's a
somewhat dubious (and racy) heuristic when making a new mmap() to check if
there are enough available hugepages to fully satisfy that mapping.
A particularly obvious case where the heuristic breaks down is where a
process maps its hugepages not as a single chunk, but as a bunch of
individually mmap()ed (or shmat()ed) blocks without touching and
instantiating the pages in between allocations. In this case the size of
each block is compared against the total number of available hugepages.
It's thus easy for the process to become overcommitted, because each block
mapping will succeed, although the total number of hugepages required by
all blocks exceeds the number available. In particular, this defeats such
a program which will detect a mapping failure and adjust its hugepage usage
downward accordingly.
The patch below addresses this problem, by strictly reserving a number of
physical hugepages for hugepage inodes which have been mapped, but not
instatiated. MAP_SHARED mappings are thus "safe" - they will fail on
mmap(), not later with an OOM SIGKILL. MAP_PRIVATE mappings can still
trigger an OOM. (Actually SHARED mappings can technically still OOM, but
only if the sysadmin explicitly reduces the hugepage pool between mapping
and instantiation)
This patch appears to address the problem at hand - it allows DB2 to start
correctly, for instance, which previously suffered the failure described
above.
This patch causes no regressions on the libhugetblfs testsuite, and makes a
test (designed to catch this problem) pass which previously failed (ppc64,
POWER5).
Signed-off-by: David Gibson <dwg@au1.ibm.com>
Cc: William Lee Irwin III <wli@holomorphy.com>
Signed-off-by: Andrew Morton <akpm@osdl.org>
Signed-off-by: Linus Torvalds <torvalds@osdl.org>
2006-03-22 16:08:55 +08:00
|
|
|
spin_unlock(&hugetlb_lock);
|
2007-10-16 16:26:18 +08:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Private mappings do not use reserved huge pages so the allocation
|
|
|
|
* may have failed due to an undersized hugetlb pool. Try to grab a
|
|
|
|
* surplus huge page from the buddy allocator.
|
|
|
|
*/
|
2007-10-16 16:26:19 +08:00
|
|
|
if (!use_reserved_page)
|
2007-10-16 16:26:18 +08:00
|
|
|
page = alloc_buddy_huge_page(vma, addr);
|
|
|
|
|
|
|
|
return page;
|
[PATCH] hugepage: Strict page reservation for hugepage inodes
These days, hugepages are demand-allocated at first fault time. There's a
somewhat dubious (and racy) heuristic when making a new mmap() to check if
there are enough available hugepages to fully satisfy that mapping.
A particularly obvious case where the heuristic breaks down is where a
process maps its hugepages not as a single chunk, but as a bunch of
individually mmap()ed (or shmat()ed) blocks without touching and
instantiating the pages in between allocations. In this case the size of
each block is compared against the total number of available hugepages.
It's thus easy for the process to become overcommitted, because each block
mapping will succeed, although the total number of hugepages required by
all blocks exceeds the number available. In particular, this defeats such
a program which will detect a mapping failure and adjust its hugepage usage
downward accordingly.
The patch below addresses this problem, by strictly reserving a number of
physical hugepages for hugepage inodes which have been mapped, but not
instatiated. MAP_SHARED mappings are thus "safe" - they will fail on
mmap(), not later with an OOM SIGKILL. MAP_PRIVATE mappings can still
trigger an OOM. (Actually SHARED mappings can technically still OOM, but
only if the sysadmin explicitly reduces the hugepage pool between mapping
and instantiation)
This patch appears to address the problem at hand - it allows DB2 to start
correctly, for instance, which previously suffered the failure described
above.
This patch causes no regressions on the libhugetblfs testsuite, and makes a
test (designed to catch this problem) pass which previously failed (ppc64,
POWER5).
Signed-off-by: David Gibson <dwg@au1.ibm.com>
Cc: William Lee Irwin III <wli@holomorphy.com>
Signed-off-by: Andrew Morton <akpm@osdl.org>
Signed-off-by: Linus Torvalds <torvalds@osdl.org>
2006-03-22 16:08:55 +08:00
|
|
|
}
|
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
static int __init hugetlb_init(void)
|
|
|
|
{
|
|
|
|
unsigned long i;
|
|
|
|
|
2005-11-07 08:06:55 +08:00
|
|
|
if (HPAGE_SHIFT == 0)
|
|
|
|
return 0;
|
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
for (i = 0; i < MAX_NUMNODES; ++i)
|
|
|
|
INIT_LIST_HEAD(&hugepage_freelists[i]);
|
|
|
|
|
|
|
|
for (i = 0; i < max_huge_pages; ++i) {
|
2006-03-22 16:08:08 +08:00
|
|
|
if (!alloc_fresh_huge_page())
|
2005-04-17 06:20:36 +08:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
max_huge_pages = free_huge_pages = nr_huge_pages = i;
|
|
|
|
printk("Total HugeTLB memory allocated, %ld\n", free_huge_pages);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
module_init(hugetlb_init);
|
|
|
|
|
|
|
|
static int __init hugetlb_setup(char *s)
|
|
|
|
{
|
|
|
|
if (sscanf(s, "%lu", &max_huge_pages) <= 0)
|
|
|
|
max_huge_pages = 0;
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
__setup("hugepages=", hugetlb_setup);
|
|
|
|
|
2007-05-09 17:33:34 +08:00
|
|
|
static unsigned int cpuset_mems_nr(unsigned int *array)
|
|
|
|
{
|
|
|
|
int node;
|
|
|
|
unsigned int nr = 0;
|
|
|
|
|
|
|
|
for_each_node_mask(node, cpuset_current_mems_allowed)
|
|
|
|
nr += array[node];
|
|
|
|
|
|
|
|
return nr;
|
|
|
|
}
|
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
#ifdef CONFIG_SYSCTL
|
|
|
|
#ifdef CONFIG_HIGHMEM
|
|
|
|
static void try_to_free_low(unsigned long count)
|
|
|
|
{
|
2006-09-26 14:31:55 +08:00
|
|
|
int i;
|
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
for (i = 0; i < MAX_NUMNODES; ++i) {
|
|
|
|
struct page *page, *next;
|
|
|
|
list_for_each_entry_safe(page, next, &hugepage_freelists[i], lru) {
|
|
|
|
if (PageHighMem(page))
|
|
|
|
continue;
|
|
|
|
list_del(&page->lru);
|
|
|
|
update_and_free_page(page);
|
|
|
|
free_huge_pages--;
|
2006-09-26 14:31:55 +08:00
|
|
|
free_huge_pages_node[page_to_nid(page)]--;
|
2005-04-17 06:20:36 +08:00
|
|
|
if (count >= nr_huge_pages)
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#else
|
|
|
|
static inline void try_to_free_low(unsigned long count)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2007-10-16 16:26:18 +08:00
|
|
|
#define persistent_huge_pages (nr_huge_pages - surplus_huge_pages)
|
2005-04-17 06:20:36 +08:00
|
|
|
static unsigned long set_max_huge_pages(unsigned long count)
|
|
|
|
{
|
2007-10-16 16:26:18 +08:00
|
|
|
unsigned long min_count, ret;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2007-10-16 16:26:18 +08:00
|
|
|
/*
|
|
|
|
* Increase the pool size
|
|
|
|
* First take pages out of surplus state. Then make up the
|
|
|
|
* remaining difference by allocating fresh huge pages.
|
|
|
|
*/
|
2005-04-17 06:20:36 +08:00
|
|
|
spin_lock(&hugetlb_lock);
|
2007-10-16 16:26:18 +08:00
|
|
|
while (surplus_huge_pages && count > persistent_huge_pages) {
|
|
|
|
if (!adjust_pool_surplus(-1))
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
while (count > persistent_huge_pages) {
|
|
|
|
int ret;
|
|
|
|
/*
|
|
|
|
* If this allocation races such that we no longer need the
|
|
|
|
* page, free_huge_page will handle it by freeing the page
|
|
|
|
* and reducing the surplus.
|
|
|
|
*/
|
|
|
|
spin_unlock(&hugetlb_lock);
|
|
|
|
ret = alloc_fresh_huge_page();
|
|
|
|
spin_lock(&hugetlb_lock);
|
|
|
|
if (!ret)
|
|
|
|
goto out;
|
|
|
|
|
|
|
|
}
|
|
|
|
if (count >= persistent_huge_pages)
|
|
|
|
goto out;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Decrease the pool size
|
|
|
|
* First return free pages to the buddy allocator (being careful
|
|
|
|
* to keep enough around to satisfy reservations). Then place
|
|
|
|
* pages into surplus state as needed so the pool will shrink
|
|
|
|
* to the desired size as pages become free.
|
|
|
|
*/
|
|
|
|
min_count = max(count, resv_huge_pages);
|
|
|
|
try_to_free_low(min_count);
|
|
|
|
while (min_count < persistent_huge_pages) {
|
2006-01-06 16:10:46 +08:00
|
|
|
struct page *page = dequeue_huge_page(NULL, 0);
|
2005-04-17 06:20:36 +08:00
|
|
|
if (!page)
|
|
|
|
break;
|
|
|
|
update_and_free_page(page);
|
|
|
|
}
|
2007-10-16 16:26:18 +08:00
|
|
|
while (count < persistent_huge_pages) {
|
|
|
|
if (!adjust_pool_surplus(1))
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
out:
|
|
|
|
ret = persistent_huge_pages;
|
2005-04-17 06:20:36 +08:00
|
|
|
spin_unlock(&hugetlb_lock);
|
2007-10-16 16:26:18 +08:00
|
|
|
return ret;
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
int hugetlb_sysctl_handler(struct ctl_table *table, int write,
|
|
|
|
struct file *file, void __user *buffer,
|
|
|
|
size_t *length, loff_t *ppos)
|
|
|
|
{
|
|
|
|
proc_doulongvec_minmax(table, write, file, buffer, length, ppos);
|
|
|
|
max_huge_pages = set_max_huge_pages(max_huge_pages);
|
|
|
|
return 0;
|
|
|
|
}
|
2007-07-17 19:03:13 +08:00
|
|
|
|
|
|
|
int hugetlb_treat_movable_handler(struct ctl_table *table, int write,
|
|
|
|
struct file *file, void __user *buffer,
|
|
|
|
size_t *length, loff_t *ppos)
|
|
|
|
{
|
|
|
|
proc_dointvec(table, write, file, buffer, length, ppos);
|
|
|
|
if (hugepages_treat_as_movable)
|
|
|
|
htlb_alloc_mask = GFP_HIGHUSER_MOVABLE;
|
|
|
|
else
|
|
|
|
htlb_alloc_mask = GFP_HIGHUSER;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
#endif /* CONFIG_SYSCTL */
|
|
|
|
|
|
|
|
int hugetlb_report_meminfo(char *buf)
|
|
|
|
{
|
|
|
|
return sprintf(buf,
|
|
|
|
"HugePages_Total: %5lu\n"
|
|
|
|
"HugePages_Free: %5lu\n"
|
2006-06-23 17:03:15 +08:00
|
|
|
"HugePages_Rsvd: %5lu\n"
|
2007-10-16 16:26:18 +08:00
|
|
|
"HugePages_Surp: %5lu\n"
|
2005-04-17 06:20:36 +08:00
|
|
|
"Hugepagesize: %5lu kB\n",
|
|
|
|
nr_huge_pages,
|
|
|
|
free_huge_pages,
|
2006-06-23 17:03:15 +08:00
|
|
|
resv_huge_pages,
|
2007-10-16 16:26:18 +08:00
|
|
|
surplus_huge_pages,
|
2005-04-17 06:20:36 +08:00
|
|
|
HPAGE_SIZE/1024);
|
|
|
|
}
|
|
|
|
|
|
|
|
int hugetlb_report_node_meminfo(int nid, char *buf)
|
|
|
|
{
|
|
|
|
return sprintf(buf,
|
|
|
|
"Node %d HugePages_Total: %5u\n"
|
|
|
|
"Node %d HugePages_Free: %5u\n",
|
|
|
|
nid, nr_huge_pages_node[nid],
|
|
|
|
nid, free_huge_pages_node[nid]);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Return the number pages of memory we physically have, in PAGE_SIZE units. */
|
|
|
|
unsigned long hugetlb_total_pages(void)
|
|
|
|
{
|
|
|
|
return nr_huge_pages * (HPAGE_SIZE / PAGE_SIZE);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* We cannot handle pagefaults against hugetlb pages at all. They cause
|
|
|
|
* handle_mm_fault() to try to instantiate regular-sized pages in the
|
|
|
|
* hugegpage VMA. do_page_fault() is supposed to trap this, so BUG is we get
|
|
|
|
* this far.
|
|
|
|
*/
|
2007-07-19 16:47:03 +08:00
|
|
|
static int hugetlb_vm_op_fault(struct vm_area_struct *vma, struct vm_fault *vmf)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
|
|
|
BUG();
|
2007-07-19 16:47:03 +08:00
|
|
|
return 0;
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
struct vm_operations_struct hugetlb_vm_ops = {
|
2007-07-19 16:47:03 +08:00
|
|
|
.fault = hugetlb_vm_op_fault,
|
2005-04-17 06:20:36 +08:00
|
|
|
};
|
|
|
|
|
2006-01-06 16:10:44 +08:00
|
|
|
static pte_t make_huge_pte(struct vm_area_struct *vma, struct page *page,
|
|
|
|
int writable)
|
2005-06-22 08:14:44 +08:00
|
|
|
{
|
|
|
|
pte_t entry;
|
|
|
|
|
2006-01-06 16:10:44 +08:00
|
|
|
if (writable) {
|
2005-06-22 08:14:44 +08:00
|
|
|
entry =
|
|
|
|
pte_mkwrite(pte_mkdirty(mk_pte(page, vma->vm_page_prot)));
|
|
|
|
} else {
|
|
|
|
entry = pte_wrprotect(mk_pte(page, vma->vm_page_prot));
|
|
|
|
}
|
|
|
|
entry = pte_mkyoung(entry);
|
|
|
|
entry = pte_mkhuge(entry);
|
|
|
|
|
|
|
|
return entry;
|
|
|
|
}
|
|
|
|
|
2006-01-06 16:10:44 +08:00
|
|
|
static void set_huge_ptep_writable(struct vm_area_struct *vma,
|
|
|
|
unsigned long address, pte_t *ptep)
|
|
|
|
{
|
|
|
|
pte_t entry;
|
|
|
|
|
|
|
|
entry = pte_mkwrite(pte_mkdirty(*ptep));
|
2007-06-17 01:16:12 +08:00
|
|
|
if (ptep_set_access_flags(vma, address, ptep, entry, 1)) {
|
|
|
|
update_mmu_cache(vma, address, entry);
|
|
|
|
}
|
2006-01-06 16:10:44 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2005-06-22 08:14:44 +08:00
|
|
|
int copy_hugetlb_page_range(struct mm_struct *dst, struct mm_struct *src,
|
|
|
|
struct vm_area_struct *vma)
|
|
|
|
{
|
|
|
|
pte_t *src_pte, *dst_pte, entry;
|
|
|
|
struct page *ptepage;
|
2005-10-20 12:23:43 +08:00
|
|
|
unsigned long addr;
|
2006-01-06 16:10:44 +08:00
|
|
|
int cow;
|
|
|
|
|
|
|
|
cow = (vma->vm_flags & (VM_SHARED | VM_MAYWRITE)) == VM_MAYWRITE;
|
2005-06-22 08:14:44 +08:00
|
|
|
|
2005-10-20 12:23:43 +08:00
|
|
|
for (addr = vma->vm_start; addr < vma->vm_end; addr += HPAGE_SIZE) {
|
2005-10-30 09:16:23 +08:00
|
|
|
src_pte = huge_pte_offset(src, addr);
|
|
|
|
if (!src_pte)
|
|
|
|
continue;
|
2005-06-22 08:14:44 +08:00
|
|
|
dst_pte = huge_pte_alloc(dst, addr);
|
|
|
|
if (!dst_pte)
|
|
|
|
goto nomem;
|
2005-10-30 09:16:23 +08:00
|
|
|
spin_lock(&dst->page_table_lock);
|
2005-10-20 12:23:43 +08:00
|
|
|
spin_lock(&src->page_table_lock);
|
2005-10-30 09:16:23 +08:00
|
|
|
if (!pte_none(*src_pte)) {
|
2006-01-06 16:10:44 +08:00
|
|
|
if (cow)
|
|
|
|
ptep_set_wrprotect(src, addr, src_pte);
|
2005-10-20 12:23:43 +08:00
|
|
|
entry = *src_pte;
|
|
|
|
ptepage = pte_page(entry);
|
|
|
|
get_page(ptepage);
|
|
|
|
set_huge_pte_at(dst, addr, dst_pte, entry);
|
|
|
|
}
|
|
|
|
spin_unlock(&src->page_table_lock);
|
2005-10-30 09:16:23 +08:00
|
|
|
spin_unlock(&dst->page_table_lock);
|
2005-06-22 08:14:44 +08:00
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
nomem:
|
|
|
|
return -ENOMEM;
|
|
|
|
}
|
|
|
|
|
2006-10-11 16:20:46 +08:00
|
|
|
void __unmap_hugepage_range(struct vm_area_struct *vma, unsigned long start,
|
|
|
|
unsigned long end)
|
2005-06-22 08:14:44 +08:00
|
|
|
{
|
|
|
|
struct mm_struct *mm = vma->vm_mm;
|
|
|
|
unsigned long address;
|
2005-08-06 02:59:35 +08:00
|
|
|
pte_t *ptep;
|
2005-06-22 08:14:44 +08:00
|
|
|
pte_t pte;
|
|
|
|
struct page *page;
|
2006-10-04 17:15:24 +08:00
|
|
|
struct page *tmp;
|
2006-12-07 12:31:39 +08:00
|
|
|
/*
|
|
|
|
* A page gathering list, protected by per file i_mmap_lock. The
|
|
|
|
* lock is used to avoid list corruption from multiple unmapping
|
|
|
|
* of the same page since we are using page->lru.
|
|
|
|
*/
|
2006-10-04 17:15:24 +08:00
|
|
|
LIST_HEAD(page_list);
|
2005-06-22 08:14:44 +08:00
|
|
|
|
|
|
|
WARN_ON(!is_vm_hugetlb_page(vma));
|
|
|
|
BUG_ON(start & ~HPAGE_MASK);
|
|
|
|
BUG_ON(end & ~HPAGE_MASK);
|
|
|
|
|
2005-10-30 09:16:30 +08:00
|
|
|
spin_lock(&mm->page_table_lock);
|
2005-06-22 08:14:44 +08:00
|
|
|
for (address = start; address < end; address += HPAGE_SIZE) {
|
2005-08-06 02:59:35 +08:00
|
|
|
ptep = huge_pte_offset(mm, address);
|
2005-10-30 09:16:46 +08:00
|
|
|
if (!ptep)
|
2005-08-06 02:59:35 +08:00
|
|
|
continue;
|
|
|
|
|
2006-12-07 12:32:03 +08:00
|
|
|
if (huge_pmd_unshare(mm, &address, ptep))
|
|
|
|
continue;
|
|
|
|
|
2005-08-06 02:59:35 +08:00
|
|
|
pte = huge_ptep_get_and_clear(mm, address, ptep);
|
2005-06-22 08:14:44 +08:00
|
|
|
if (pte_none(pte))
|
|
|
|
continue;
|
2005-08-06 02:59:35 +08:00
|
|
|
|
2005-06-22 08:14:44 +08:00
|
|
|
page = pte_page(pte);
|
2007-02-09 06:20:27 +08:00
|
|
|
if (pte_dirty(pte))
|
|
|
|
set_page_dirty(page);
|
2006-10-04 17:15:24 +08:00
|
|
|
list_add(&page->lru, &page_list);
|
2005-06-22 08:14:44 +08:00
|
|
|
}
|
2005-04-17 06:20:36 +08:00
|
|
|
spin_unlock(&mm->page_table_lock);
|
2005-10-30 09:16:30 +08:00
|
|
|
flush_tlb_range(vma, start, end);
|
2006-10-04 17:15:24 +08:00
|
|
|
list_for_each_entry_safe(page, tmp, &page_list, lru) {
|
|
|
|
list_del(&page->lru);
|
|
|
|
put_page(page);
|
|
|
|
}
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
2005-06-22 08:14:44 +08:00
|
|
|
|
2006-10-11 16:20:46 +08:00
|
|
|
void unmap_hugepage_range(struct vm_area_struct *vma, unsigned long start,
|
|
|
|
unsigned long end)
|
|
|
|
{
|
|
|
|
/*
|
|
|
|
* It is undesirable to test vma->vm_file as it should be non-null
|
|
|
|
* for valid hugetlb area. However, vm_file will be NULL in the error
|
|
|
|
* cleanup path of do_mmap_pgoff. When hugetlbfs ->mmap method fails,
|
|
|
|
* do_mmap_pgoff() nullifies vma->vm_file before calling this function
|
|
|
|
* to clean up. Since no pte has actually been setup, it is safe to
|
|
|
|
* do nothing in this case.
|
|
|
|
*/
|
|
|
|
if (vma->vm_file) {
|
|
|
|
spin_lock(&vma->vm_file->f_mapping->i_mmap_lock);
|
|
|
|
__unmap_hugepage_range(vma, start, end);
|
|
|
|
spin_unlock(&vma->vm_file->f_mapping->i_mmap_lock);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2006-01-06 16:10:44 +08:00
|
|
|
static int hugetlb_cow(struct mm_struct *mm, struct vm_area_struct *vma,
|
|
|
|
unsigned long address, pte_t *ptep, pte_t pte)
|
|
|
|
{
|
|
|
|
struct page *old_page, *new_page;
|
2006-03-22 16:08:51 +08:00
|
|
|
int avoidcopy;
|
2006-01-06 16:10:44 +08:00
|
|
|
|
|
|
|
old_page = pte_page(pte);
|
|
|
|
|
|
|
|
/* If no-one else is actually using this page, avoid the copy
|
|
|
|
* and just make the page writable */
|
|
|
|
avoidcopy = (page_count(old_page) == 1);
|
|
|
|
if (avoidcopy) {
|
|
|
|
set_huge_ptep_writable(vma, address, ptep);
|
2007-07-19 16:47:05 +08:00
|
|
|
return 0;
|
2006-01-06 16:10:44 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
page_cache_get(old_page);
|
2006-01-06 16:10:46 +08:00
|
|
|
new_page = alloc_huge_page(vma, address);
|
2006-01-06 16:10:44 +08:00
|
|
|
|
|
|
|
if (!new_page) {
|
|
|
|
page_cache_release(old_page);
|
2006-02-08 04:58:30 +08:00
|
|
|
return VM_FAULT_OOM;
|
2006-01-06 16:10:44 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
spin_unlock(&mm->page_table_lock);
|
2006-12-13 01:14:55 +08:00
|
|
|
copy_huge_page(new_page, old_page, address, vma);
|
2006-01-06 16:10:44 +08:00
|
|
|
spin_lock(&mm->page_table_lock);
|
|
|
|
|
|
|
|
ptep = huge_pte_offset(mm, address & HPAGE_MASK);
|
|
|
|
if (likely(pte_same(*ptep, pte))) {
|
|
|
|
/* Break COW */
|
|
|
|
set_huge_pte_at(mm, address, ptep,
|
|
|
|
make_huge_pte(vma, new_page, 1));
|
|
|
|
/* Make the old page be freed below */
|
|
|
|
new_page = old_page;
|
|
|
|
}
|
|
|
|
page_cache_release(new_page);
|
|
|
|
page_cache_release(old_page);
|
2007-07-19 16:47:05 +08:00
|
|
|
return 0;
|
2006-01-06 16:10:44 +08:00
|
|
|
}
|
|
|
|
|
2007-07-17 19:03:33 +08:00
|
|
|
static int hugetlb_no_page(struct mm_struct *mm, struct vm_area_struct *vma,
|
2006-01-06 16:10:44 +08:00
|
|
|
unsigned long address, pte_t *ptep, int write_access)
|
2005-10-20 23:24:28 +08:00
|
|
|
{
|
|
|
|
int ret = VM_FAULT_SIGBUS;
|
2005-10-30 09:16:46 +08:00
|
|
|
unsigned long idx;
|
|
|
|
unsigned long size;
|
|
|
|
struct page *page;
|
|
|
|
struct address_space *mapping;
|
2006-01-06 16:10:44 +08:00
|
|
|
pte_t new_pte;
|
2005-10-30 09:16:46 +08:00
|
|
|
|
|
|
|
mapping = vma->vm_file->f_mapping;
|
|
|
|
idx = ((address - vma->vm_start) >> HPAGE_SHIFT)
|
|
|
|
+ (vma->vm_pgoff >> (HPAGE_SHIFT - PAGE_SHIFT));
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Use page lock to guard against racing truncation
|
|
|
|
* before we get page_table_lock.
|
|
|
|
*/
|
2006-01-06 16:10:49 +08:00
|
|
|
retry:
|
|
|
|
page = find_lock_page(mapping, idx);
|
|
|
|
if (!page) {
|
2006-10-29 01:38:43 +08:00
|
|
|
size = i_size_read(mapping->host) >> HPAGE_SHIFT;
|
|
|
|
if (idx >= size)
|
|
|
|
goto out;
|
2006-01-06 16:10:49 +08:00
|
|
|
if (hugetlb_get_quota(mapping))
|
|
|
|
goto out;
|
|
|
|
page = alloc_huge_page(vma, address);
|
|
|
|
if (!page) {
|
|
|
|
hugetlb_put_quota(mapping);
|
2006-02-08 04:58:30 +08:00
|
|
|
ret = VM_FAULT_OOM;
|
2006-01-06 16:10:49 +08:00
|
|
|
goto out;
|
|
|
|
}
|
2006-03-22 16:08:51 +08:00
|
|
|
clear_huge_page(page, address);
|
2005-10-20 23:24:28 +08:00
|
|
|
|
2006-01-06 16:10:49 +08:00
|
|
|
if (vma->vm_flags & VM_SHARED) {
|
|
|
|
int err;
|
|
|
|
|
|
|
|
err = add_to_page_cache(page, mapping, idx, GFP_KERNEL);
|
|
|
|
if (err) {
|
|
|
|
put_page(page);
|
|
|
|
hugetlb_put_quota(mapping);
|
|
|
|
if (err == -EEXIST)
|
|
|
|
goto retry;
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
} else
|
|
|
|
lock_page(page);
|
|
|
|
}
|
2006-01-06 16:10:44 +08:00
|
|
|
|
2005-10-20 23:24:28 +08:00
|
|
|
spin_lock(&mm->page_table_lock);
|
2005-10-30 09:16:46 +08:00
|
|
|
size = i_size_read(mapping->host) >> HPAGE_SHIFT;
|
|
|
|
if (idx >= size)
|
|
|
|
goto backout;
|
|
|
|
|
2007-07-19 16:47:05 +08:00
|
|
|
ret = 0;
|
2006-01-06 16:10:43 +08:00
|
|
|
if (!pte_none(*ptep))
|
2005-10-30 09:16:46 +08:00
|
|
|
goto backout;
|
|
|
|
|
2006-01-06 16:10:44 +08:00
|
|
|
new_pte = make_huge_pte(vma, page, ((vma->vm_flags & VM_WRITE)
|
|
|
|
&& (vma->vm_flags & VM_SHARED)));
|
|
|
|
set_huge_pte_at(mm, address, ptep, new_pte);
|
|
|
|
|
|
|
|
if (write_access && !(vma->vm_flags & VM_SHARED)) {
|
|
|
|
/* Optimization, do the COW without a second fault */
|
|
|
|
ret = hugetlb_cow(mm, vma, address, ptep, new_pte);
|
|
|
|
}
|
|
|
|
|
2005-10-20 23:24:28 +08:00
|
|
|
spin_unlock(&mm->page_table_lock);
|
2005-10-30 09:16:46 +08:00
|
|
|
unlock_page(page);
|
|
|
|
out:
|
2005-10-20 23:24:28 +08:00
|
|
|
return ret;
|
2005-10-30 09:16:46 +08:00
|
|
|
|
|
|
|
backout:
|
|
|
|
spin_unlock(&mm->page_table_lock);
|
|
|
|
hugetlb_put_quota(mapping);
|
|
|
|
unlock_page(page);
|
|
|
|
put_page(page);
|
|
|
|
goto out;
|
2005-10-20 23:24:28 +08:00
|
|
|
}
|
|
|
|
|
2006-01-06 16:10:43 +08:00
|
|
|
int hugetlb_fault(struct mm_struct *mm, struct vm_area_struct *vma,
|
|
|
|
unsigned long address, int write_access)
|
|
|
|
{
|
|
|
|
pte_t *ptep;
|
|
|
|
pte_t entry;
|
2006-01-06 16:10:44 +08:00
|
|
|
int ret;
|
[PATCH] hugepage: serialize hugepage allocation and instantiation
Currently, no lock or mutex is held between allocating a hugepage and
inserting it into the pagetables / page cache. When we do go to insert the
page into pagetables or page cache, we recheck and may free the newly
allocated hugepage. However, since the number of hugepages in the system
is strictly limited, and it's usualy to want to use all of them, this can
still lead to spurious allocation failures.
For example, suppose two processes are both mapping (MAP_SHARED) the same
hugepage file, large enough to consume the entire available hugepage pool.
If they race instantiating the last page in the mapping, they will both
attempt to allocate the last available hugepage. One will fail, of course,
returning OOM from the fault and thus causing the process to be killed,
despite the fact that the entire mapping can, in fact, be instantiated.
The patch fixes this race by the simple method of adding a (sleeping) mutex
to serialize the hugepage fault path between allocation and insertion into
pagetables and/or page cache. It would be possible to avoid the
serialization by catching the allocation failures, waiting on some
condition, then rechecking to see if someone else has instantiated the page
for us. Given the likely frequency of hugepage instantiations, it seems
very doubtful it's worth the extra complexity.
This patch causes no regression on the libhugetlbfs testsuite, and one
test, which can trigger this race now passes where it previously failed.
Actually, the test still sometimes fails, though less often and only as a
shmat() failure, rather processes getting OOM killed by the VM. The dodgy
heuristic tests in fs/hugetlbfs/inode.c for whether there's enough hugepage
space aren't protected by the new mutex, and would be ugly to do so, so
there's still a race there. Another patch to replace those tests with
something saner for this reason as well as others coming...
Signed-off-by: David Gibson <dwg@au1.ibm.com>
Cc: William Lee Irwin III <wli@holomorphy.com>
Signed-off-by: Andrew Morton <akpm@osdl.org>
Signed-off-by: Linus Torvalds <torvalds@osdl.org>
2006-03-22 16:08:53 +08:00
|
|
|
static DEFINE_MUTEX(hugetlb_instantiation_mutex);
|
2006-01-06 16:10:43 +08:00
|
|
|
|
|
|
|
ptep = huge_pte_alloc(mm, address);
|
|
|
|
if (!ptep)
|
|
|
|
return VM_FAULT_OOM;
|
|
|
|
|
[PATCH] hugepage: serialize hugepage allocation and instantiation
Currently, no lock or mutex is held between allocating a hugepage and
inserting it into the pagetables / page cache. When we do go to insert the
page into pagetables or page cache, we recheck and may free the newly
allocated hugepage. However, since the number of hugepages in the system
is strictly limited, and it's usualy to want to use all of them, this can
still lead to spurious allocation failures.
For example, suppose two processes are both mapping (MAP_SHARED) the same
hugepage file, large enough to consume the entire available hugepage pool.
If they race instantiating the last page in the mapping, they will both
attempt to allocate the last available hugepage. One will fail, of course,
returning OOM from the fault and thus causing the process to be killed,
despite the fact that the entire mapping can, in fact, be instantiated.
The patch fixes this race by the simple method of adding a (sleeping) mutex
to serialize the hugepage fault path between allocation and insertion into
pagetables and/or page cache. It would be possible to avoid the
serialization by catching the allocation failures, waiting on some
condition, then rechecking to see if someone else has instantiated the page
for us. Given the likely frequency of hugepage instantiations, it seems
very doubtful it's worth the extra complexity.
This patch causes no regression on the libhugetlbfs testsuite, and one
test, which can trigger this race now passes where it previously failed.
Actually, the test still sometimes fails, though less often and only as a
shmat() failure, rather processes getting OOM killed by the VM. The dodgy
heuristic tests in fs/hugetlbfs/inode.c for whether there's enough hugepage
space aren't protected by the new mutex, and would be ugly to do so, so
there's still a race there. Another patch to replace those tests with
something saner for this reason as well as others coming...
Signed-off-by: David Gibson <dwg@au1.ibm.com>
Cc: William Lee Irwin III <wli@holomorphy.com>
Signed-off-by: Andrew Morton <akpm@osdl.org>
Signed-off-by: Linus Torvalds <torvalds@osdl.org>
2006-03-22 16:08:53 +08:00
|
|
|
/*
|
|
|
|
* Serialize hugepage allocation and instantiation, so that we don't
|
|
|
|
* get spurious allocation failures if two CPUs race to instantiate
|
|
|
|
* the same page in the page cache.
|
|
|
|
*/
|
|
|
|
mutex_lock(&hugetlb_instantiation_mutex);
|
2006-01-06 16:10:43 +08:00
|
|
|
entry = *ptep;
|
[PATCH] hugepage: serialize hugepage allocation and instantiation
Currently, no lock or mutex is held between allocating a hugepage and
inserting it into the pagetables / page cache. When we do go to insert the
page into pagetables or page cache, we recheck and may free the newly
allocated hugepage. However, since the number of hugepages in the system
is strictly limited, and it's usualy to want to use all of them, this can
still lead to spurious allocation failures.
For example, suppose two processes are both mapping (MAP_SHARED) the same
hugepage file, large enough to consume the entire available hugepage pool.
If they race instantiating the last page in the mapping, they will both
attempt to allocate the last available hugepage. One will fail, of course,
returning OOM from the fault and thus causing the process to be killed,
despite the fact that the entire mapping can, in fact, be instantiated.
The patch fixes this race by the simple method of adding a (sleeping) mutex
to serialize the hugepage fault path between allocation and insertion into
pagetables and/or page cache. It would be possible to avoid the
serialization by catching the allocation failures, waiting on some
condition, then rechecking to see if someone else has instantiated the page
for us. Given the likely frequency of hugepage instantiations, it seems
very doubtful it's worth the extra complexity.
This patch causes no regression on the libhugetlbfs testsuite, and one
test, which can trigger this race now passes where it previously failed.
Actually, the test still sometimes fails, though less often and only as a
shmat() failure, rather processes getting OOM killed by the VM. The dodgy
heuristic tests in fs/hugetlbfs/inode.c for whether there's enough hugepage
space aren't protected by the new mutex, and would be ugly to do so, so
there's still a race there. Another patch to replace those tests with
something saner for this reason as well as others coming...
Signed-off-by: David Gibson <dwg@au1.ibm.com>
Cc: William Lee Irwin III <wli@holomorphy.com>
Signed-off-by: Andrew Morton <akpm@osdl.org>
Signed-off-by: Linus Torvalds <torvalds@osdl.org>
2006-03-22 16:08:53 +08:00
|
|
|
if (pte_none(entry)) {
|
|
|
|
ret = hugetlb_no_page(mm, vma, address, ptep, write_access);
|
|
|
|
mutex_unlock(&hugetlb_instantiation_mutex);
|
|
|
|
return ret;
|
|
|
|
}
|
2006-01-06 16:10:43 +08:00
|
|
|
|
2007-07-19 16:47:05 +08:00
|
|
|
ret = 0;
|
2006-01-06 16:10:44 +08:00
|
|
|
|
|
|
|
spin_lock(&mm->page_table_lock);
|
|
|
|
/* Check for a racing update before calling hugetlb_cow */
|
|
|
|
if (likely(pte_same(entry, *ptep)))
|
|
|
|
if (write_access && !pte_write(entry))
|
|
|
|
ret = hugetlb_cow(mm, vma, address, ptep, entry);
|
|
|
|
spin_unlock(&mm->page_table_lock);
|
[PATCH] hugepage: serialize hugepage allocation and instantiation
Currently, no lock or mutex is held between allocating a hugepage and
inserting it into the pagetables / page cache. When we do go to insert the
page into pagetables or page cache, we recheck and may free the newly
allocated hugepage. However, since the number of hugepages in the system
is strictly limited, and it's usualy to want to use all of them, this can
still lead to spurious allocation failures.
For example, suppose two processes are both mapping (MAP_SHARED) the same
hugepage file, large enough to consume the entire available hugepage pool.
If they race instantiating the last page in the mapping, they will both
attempt to allocate the last available hugepage. One will fail, of course,
returning OOM from the fault and thus causing the process to be killed,
despite the fact that the entire mapping can, in fact, be instantiated.
The patch fixes this race by the simple method of adding a (sleeping) mutex
to serialize the hugepage fault path between allocation and insertion into
pagetables and/or page cache. It would be possible to avoid the
serialization by catching the allocation failures, waiting on some
condition, then rechecking to see if someone else has instantiated the page
for us. Given the likely frequency of hugepage instantiations, it seems
very doubtful it's worth the extra complexity.
This patch causes no regression on the libhugetlbfs testsuite, and one
test, which can trigger this race now passes where it previously failed.
Actually, the test still sometimes fails, though less often and only as a
shmat() failure, rather processes getting OOM killed by the VM. The dodgy
heuristic tests in fs/hugetlbfs/inode.c for whether there's enough hugepage
space aren't protected by the new mutex, and would be ugly to do so, so
there's still a race there. Another patch to replace those tests with
something saner for this reason as well as others coming...
Signed-off-by: David Gibson <dwg@au1.ibm.com>
Cc: William Lee Irwin III <wli@holomorphy.com>
Signed-off-by: Andrew Morton <akpm@osdl.org>
Signed-off-by: Linus Torvalds <torvalds@osdl.org>
2006-03-22 16:08:53 +08:00
|
|
|
mutex_unlock(&hugetlb_instantiation_mutex);
|
2006-01-06 16:10:44 +08:00
|
|
|
|
|
|
|
return ret;
|
2006-01-06 16:10:43 +08:00
|
|
|
}
|
|
|
|
|
2005-06-22 08:14:44 +08:00
|
|
|
int follow_hugetlb_page(struct mm_struct *mm, struct vm_area_struct *vma,
|
|
|
|
struct page **pages, struct vm_area_struct **vmas,
|
|
|
|
unsigned long *position, int *length, int i)
|
|
|
|
{
|
2006-03-22 16:09:03 +08:00
|
|
|
unsigned long pfn_offset;
|
|
|
|
unsigned long vaddr = *position;
|
2005-06-22 08:14:44 +08:00
|
|
|
int remainder = *length;
|
|
|
|
|
2005-10-20 12:23:43 +08:00
|
|
|
spin_lock(&mm->page_table_lock);
|
2005-06-22 08:14:44 +08:00
|
|
|
while (vaddr < vma->vm_end && remainder) {
|
2005-10-30 09:16:46 +08:00
|
|
|
pte_t *pte;
|
|
|
|
struct page *page;
|
2005-06-22 08:14:44 +08:00
|
|
|
|
2005-10-30 09:16:46 +08:00
|
|
|
/*
|
|
|
|
* Some archs (sparc64, sh*) have multiple pte_ts to
|
|
|
|
* each hugepage. We have to make * sure we get the
|
|
|
|
* first, for the page indexing below to work.
|
|
|
|
*/
|
|
|
|
pte = huge_pte_offset(mm, vaddr & HPAGE_MASK);
|
2005-06-22 08:14:44 +08:00
|
|
|
|
2005-10-30 09:16:46 +08:00
|
|
|
if (!pte || pte_none(*pte)) {
|
|
|
|
int ret;
|
2005-06-22 08:14:44 +08:00
|
|
|
|
2005-10-30 09:16:46 +08:00
|
|
|
spin_unlock(&mm->page_table_lock);
|
|
|
|
ret = hugetlb_fault(mm, vma, vaddr, 0);
|
|
|
|
spin_lock(&mm->page_table_lock);
|
2007-08-23 05:01:51 +08:00
|
|
|
if (!(ret & VM_FAULT_ERROR))
|
2005-10-30 09:16:46 +08:00
|
|
|
continue;
|
2005-06-22 08:14:44 +08:00
|
|
|
|
2005-10-30 09:16:46 +08:00
|
|
|
remainder = 0;
|
|
|
|
if (!i)
|
|
|
|
i = -EFAULT;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2006-03-22 16:09:03 +08:00
|
|
|
pfn_offset = (vaddr & ~HPAGE_MASK) >> PAGE_SHIFT;
|
|
|
|
page = pte_page(*pte);
|
|
|
|
same_page:
|
2006-03-31 18:29:57 +08:00
|
|
|
if (pages) {
|
|
|
|
get_page(page);
|
2006-03-22 16:09:03 +08:00
|
|
|
pages[i] = page + pfn_offset;
|
2006-03-31 18:29:57 +08:00
|
|
|
}
|
2005-06-22 08:14:44 +08:00
|
|
|
|
|
|
|
if (vmas)
|
|
|
|
vmas[i] = vma;
|
|
|
|
|
|
|
|
vaddr += PAGE_SIZE;
|
2006-03-22 16:09:03 +08:00
|
|
|
++pfn_offset;
|
2005-06-22 08:14:44 +08:00
|
|
|
--remainder;
|
|
|
|
++i;
|
2006-03-22 16:09:03 +08:00
|
|
|
if (vaddr < vma->vm_end && remainder &&
|
|
|
|
pfn_offset < HPAGE_SIZE/PAGE_SIZE) {
|
|
|
|
/*
|
|
|
|
* We use pfn_offset to avoid touching the pageframes
|
|
|
|
* of this compound page.
|
|
|
|
*/
|
|
|
|
goto same_page;
|
|
|
|
}
|
2005-06-22 08:14:44 +08:00
|
|
|
}
|
2005-10-20 12:23:43 +08:00
|
|
|
spin_unlock(&mm->page_table_lock);
|
2005-06-22 08:14:44 +08:00
|
|
|
*length = remainder;
|
|
|
|
*position = vaddr;
|
|
|
|
|
|
|
|
return i;
|
|
|
|
}
|
2006-03-22 16:08:50 +08:00
|
|
|
|
|
|
|
void hugetlb_change_protection(struct vm_area_struct *vma,
|
|
|
|
unsigned long address, unsigned long end, pgprot_t newprot)
|
|
|
|
{
|
|
|
|
struct mm_struct *mm = vma->vm_mm;
|
|
|
|
unsigned long start = address;
|
|
|
|
pte_t *ptep;
|
|
|
|
pte_t pte;
|
|
|
|
|
|
|
|
BUG_ON(address >= end);
|
|
|
|
flush_cache_range(vma, address, end);
|
|
|
|
|
2006-12-07 12:32:03 +08:00
|
|
|
spin_lock(&vma->vm_file->f_mapping->i_mmap_lock);
|
2006-03-22 16:08:50 +08:00
|
|
|
spin_lock(&mm->page_table_lock);
|
|
|
|
for (; address < end; address += HPAGE_SIZE) {
|
|
|
|
ptep = huge_pte_offset(mm, address);
|
|
|
|
if (!ptep)
|
|
|
|
continue;
|
2006-12-07 12:32:03 +08:00
|
|
|
if (huge_pmd_unshare(mm, &address, ptep))
|
|
|
|
continue;
|
2006-03-22 16:08:50 +08:00
|
|
|
if (!pte_none(*ptep)) {
|
|
|
|
pte = huge_ptep_get_and_clear(mm, address, ptep);
|
|
|
|
pte = pte_mkhuge(pte_modify(pte, newprot));
|
|
|
|
set_huge_pte_at(mm, address, ptep, pte);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
spin_unlock(&mm->page_table_lock);
|
2006-12-07 12:32:03 +08:00
|
|
|
spin_unlock(&vma->vm_file->f_mapping->i_mmap_lock);
|
2006-03-22 16:08:50 +08:00
|
|
|
|
|
|
|
flush_tlb_range(vma, start, end);
|
|
|
|
}
|
|
|
|
|
2006-06-23 17:03:15 +08:00
|
|
|
struct file_region {
|
|
|
|
struct list_head link;
|
|
|
|
long from;
|
|
|
|
long to;
|
|
|
|
};
|
|
|
|
|
|
|
|
static long region_add(struct list_head *head, long f, long t)
|
|
|
|
{
|
|
|
|
struct file_region *rg, *nrg, *trg;
|
|
|
|
|
|
|
|
/* Locate the region we are either in or before. */
|
|
|
|
list_for_each_entry(rg, head, link)
|
|
|
|
if (f <= rg->to)
|
|
|
|
break;
|
|
|
|
|
|
|
|
/* Round our left edge to the current segment if it encloses us. */
|
|
|
|
if (f > rg->from)
|
|
|
|
f = rg->from;
|
|
|
|
|
|
|
|
/* Check for and consume any regions we now overlap with. */
|
|
|
|
nrg = rg;
|
|
|
|
list_for_each_entry_safe(rg, trg, rg->link.prev, link) {
|
|
|
|
if (&rg->link == head)
|
|
|
|
break;
|
|
|
|
if (rg->from > t)
|
|
|
|
break;
|
|
|
|
|
|
|
|
/* If this area reaches higher then extend our area to
|
|
|
|
* include it completely. If this is not the first area
|
|
|
|
* which we intend to reuse, free it. */
|
|
|
|
if (rg->to > t)
|
|
|
|
t = rg->to;
|
|
|
|
if (rg != nrg) {
|
|
|
|
list_del(&rg->link);
|
|
|
|
kfree(rg);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
nrg->from = f;
|
|
|
|
nrg->to = t;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static long region_chg(struct list_head *head, long f, long t)
|
|
|
|
{
|
|
|
|
struct file_region *rg, *nrg;
|
|
|
|
long chg = 0;
|
|
|
|
|
|
|
|
/* Locate the region we are before or in. */
|
|
|
|
list_for_each_entry(rg, head, link)
|
|
|
|
if (f <= rg->to)
|
|
|
|
break;
|
|
|
|
|
|
|
|
/* If we are below the current region then a new region is required.
|
|
|
|
* Subtle, allocate a new region at the position but make it zero
|
|
|
|
* size such that we can guarentee to record the reservation. */
|
|
|
|
if (&rg->link == head || t < rg->from) {
|
|
|
|
nrg = kmalloc(sizeof(*nrg), GFP_KERNEL);
|
|
|
|
if (nrg == 0)
|
|
|
|
return -ENOMEM;
|
|
|
|
nrg->from = f;
|
|
|
|
nrg->to = f;
|
|
|
|
INIT_LIST_HEAD(&nrg->link);
|
|
|
|
list_add(&nrg->link, rg->link.prev);
|
|
|
|
|
|
|
|
return t - f;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Round our left edge to the current segment if it encloses us. */
|
|
|
|
if (f > rg->from)
|
|
|
|
f = rg->from;
|
|
|
|
chg = t - f;
|
|
|
|
|
|
|
|
/* Check for and consume any regions we now overlap with. */
|
|
|
|
list_for_each_entry(rg, rg->link.prev, link) {
|
|
|
|
if (&rg->link == head)
|
|
|
|
break;
|
|
|
|
if (rg->from > t)
|
|
|
|
return chg;
|
|
|
|
|
|
|
|
/* We overlap with this area, if it extends futher than
|
|
|
|
* us then we must extend ourselves. Account for its
|
|
|
|
* existing reservation. */
|
|
|
|
if (rg->to > t) {
|
|
|
|
chg += rg->to - t;
|
|
|
|
t = rg->to;
|
|
|
|
}
|
|
|
|
chg -= rg->to - rg->from;
|
|
|
|
}
|
|
|
|
return chg;
|
|
|
|
}
|
|
|
|
|
|
|
|
static long region_truncate(struct list_head *head, long end)
|
|
|
|
{
|
|
|
|
struct file_region *rg, *trg;
|
|
|
|
long chg = 0;
|
|
|
|
|
|
|
|
/* Locate the region we are either in or before. */
|
|
|
|
list_for_each_entry(rg, head, link)
|
|
|
|
if (end <= rg->to)
|
|
|
|
break;
|
|
|
|
if (&rg->link == head)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
/* If we are in the middle of a region then adjust it. */
|
|
|
|
if (end > rg->from) {
|
|
|
|
chg = rg->to - end;
|
|
|
|
rg->to = end;
|
|
|
|
rg = list_entry(rg->link.next, typeof(*rg), link);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Drop any remaining regions. */
|
|
|
|
list_for_each_entry_safe(rg, trg, rg->link.prev, link) {
|
|
|
|
if (&rg->link == head)
|
|
|
|
break;
|
|
|
|
chg += rg->to - rg->from;
|
|
|
|
list_del(&rg->link);
|
|
|
|
kfree(rg);
|
|
|
|
}
|
|
|
|
return chg;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int hugetlb_acct_memory(long delta)
|
|
|
|
{
|
|
|
|
int ret = -ENOMEM;
|
|
|
|
|
|
|
|
spin_lock(&hugetlb_lock);
|
2007-05-09 17:33:34 +08:00
|
|
|
/*
|
|
|
|
* When cpuset is configured, it breaks the strict hugetlb page
|
|
|
|
* reservation as the accounting is done on a global variable. Such
|
|
|
|
* reservation is completely rubbish in the presence of cpuset because
|
|
|
|
* the reservation is not checked against page availability for the
|
|
|
|
* current cpuset. Application can still potentially OOM'ed by kernel
|
|
|
|
* with lack of free htlb page in cpuset that the task is in.
|
|
|
|
* Attempt to enforce strict accounting with cpuset is almost
|
|
|
|
* impossible (or too ugly) because cpuset is too fluid that
|
|
|
|
* task or memory node can be dynamically moved between cpusets.
|
|
|
|
*
|
|
|
|
* The change of semantics for shared hugetlb mapping with cpuset is
|
|
|
|
* undesirable. However, in order to preserve some of the semantics,
|
|
|
|
* we fall back to check against current free page availability as
|
|
|
|
* a best attempt and hopefully to minimize the impact of changing
|
|
|
|
* semantics that cpuset has.
|
|
|
|
*/
|
2007-10-16 16:26:19 +08:00
|
|
|
if (delta > 0) {
|
|
|
|
if (gather_surplus_pages(delta) < 0)
|
|
|
|
goto out;
|
|
|
|
|
|
|
|
if (delta > cpuset_mems_nr(free_huge_pages_node))
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = 0;
|
|
|
|
resv_huge_pages += delta;
|
|
|
|
if (delta < 0)
|
|
|
|
return_unused_surplus_pages((unsigned long) -delta);
|
|
|
|
|
|
|
|
out:
|
|
|
|
spin_unlock(&hugetlb_lock);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
int hugetlb_reserve_pages(struct inode *inode, long from, long to)
|
|
|
|
{
|
|
|
|
long ret, chg;
|
|
|
|
|
|
|
|
chg = region_chg(&inode->i_mapping->private_list, from, to);
|
|
|
|
if (chg < 0)
|
|
|
|
return chg;
|
2007-05-09 17:33:34 +08:00
|
|
|
|
2006-06-23 17:03:15 +08:00
|
|
|
ret = hugetlb_acct_memory(chg);
|
|
|
|
if (ret < 0)
|
|
|
|
return ret;
|
|
|
|
region_add(&inode->i_mapping->private_list, from, to);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
void hugetlb_unreserve_pages(struct inode *inode, long offset, long freed)
|
|
|
|
{
|
|
|
|
long chg = region_truncate(&inode->i_mapping->private_list, offset);
|
|
|
|
hugetlb_acct_memory(freed - chg);
|
|
|
|
}
|