mirror of
git://sourceware.org/git/glibc.git
synced 2025-03-06 20:58:33 +01:00
malloc: Add Huge Page support to arenas
It is enabled as default for glibc.malloc.hugetlb set to 2 or higher. It also uses a non configurable minimum value and maximum value, currently set respectively to 1 and 4 selected huge page size. The arena allocation with huge pages does not use MAP_NORESERVE. As indicate by kernel internal documentation [1], the flag might trigger a SIGBUS on soft page faults if at memory access there is no left pages in the pool. On systems without a reserved huge pages pool, is just stress the mmap(MAP_HUGETLB) allocation failure. To improve test coverage it is required to create a pool with some allocated pages. Checked on x86_64-linux-gnu with no reserved pages, 10 reserved pages (which trigger mmap(MAP_HUGETBL) failures) and with 256 reserved pages (which does not trigger mmap(MAP_HUGETLB) failures). [1] https://www.kernel.org/doc/html/v4.18/vm/hugetlbfs_reserv.html#resv-map-modifications Reviewed-by: DJ Delorie <dj@redhat.com>
This commit is contained in:
parent
98d5fcb8d0
commit
c1beb51d08
3 changed files with 99 additions and 44 deletions
|
@ -91,10 +91,15 @@ tests-exclude-hugetlb1 = \
|
||||||
tst-malloc-usable \
|
tst-malloc-usable \
|
||||||
tst-malloc-usable-tunables \
|
tst-malloc-usable-tunables \
|
||||||
tst-mallocstate
|
tst-mallocstate
|
||||||
|
# The tst-free-errno relies on the used malloc page size to mmap an
|
||||||
|
# overlapping region.
|
||||||
|
tests-exclude-hugetlb2 = \
|
||||||
|
$(tests-exclude-hugetlb1) \
|
||||||
|
tst-free-errno
|
||||||
tests-malloc-hugetlb1 = \
|
tests-malloc-hugetlb1 = \
|
||||||
$(filter-out $(tests-exclude-hugetlb1), $(tests))
|
$(filter-out $(tests-exclude-hugetlb1), $(tests))
|
||||||
tests-malloc-hugetlb2 = \
|
tests-malloc-hugetlb2 = \
|
||||||
$(filter-out $(tests-exclude-hugetlb1), $(tests))
|
$(filter-out $(tests-exclude-hugetlb2), $(tests))
|
||||||
|
|
||||||
# -lmcheck needs __malloc_initialize_hook, which was deprecated in 2.24.
|
# -lmcheck needs __malloc_initialize_hook, which was deprecated in 2.24.
|
||||||
ifeq ($(have-GLIBC_2.23)$(build-shared),yesyes)
|
ifeq ($(have-GLIBC_2.23)$(build-shared),yesyes)
|
||||||
|
|
134
malloc/arena.c
134
malloc/arena.c
|
@ -41,6 +41,29 @@
|
||||||
mmap threshold, so that requests with a size just below that
|
mmap threshold, so that requests with a size just below that
|
||||||
threshold can be fulfilled without creating too many heaps. */
|
threshold can be fulfilled without creating too many heaps. */
|
||||||
|
|
||||||
|
/* When huge pages are used to create new arenas, the maximum and minumum
|
||||||
|
size are based on the runtime defined huge page size. */
|
||||||
|
|
||||||
|
static inline size_t
|
||||||
|
heap_min_size (void)
|
||||||
|
{
|
||||||
|
#if HAVE_TUNABLES
|
||||||
|
return mp_.hp_pagesize == 0 ? HEAP_MIN_SIZE : mp_.hp_pagesize;
|
||||||
|
#else
|
||||||
|
return HEAP_MIN_SIZE;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline size_t
|
||||||
|
heap_max_size (void)
|
||||||
|
{
|
||||||
|
#if HAVE_TUNABLES
|
||||||
|
return mp_.hp_pagesize == 0 ? HEAP_MAX_SIZE : mp_.hp_pagesize * 4;
|
||||||
|
#else
|
||||||
|
return HEAP_MAX_SIZE;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
/***************************************************************************/
|
/***************************************************************************/
|
||||||
|
|
||||||
#define top(ar_ptr) ((ar_ptr)->top)
|
#define top(ar_ptr) ((ar_ptr)->top)
|
||||||
|
@ -56,10 +79,11 @@ typedef struct _heap_info
|
||||||
size_t size; /* Current size in bytes. */
|
size_t size; /* Current size in bytes. */
|
||||||
size_t mprotect_size; /* Size in bytes that has been mprotected
|
size_t mprotect_size; /* Size in bytes that has been mprotected
|
||||||
PROT_READ|PROT_WRITE. */
|
PROT_READ|PROT_WRITE. */
|
||||||
|
size_t pagesize; /* Page size used when allocating the arena. */
|
||||||
/* Make sure the following data is properly aligned, particularly
|
/* Make sure the following data is properly aligned, particularly
|
||||||
that sizeof (heap_info) + 2 * SIZE_SZ is a multiple of
|
that sizeof (heap_info) + 2 * SIZE_SZ is a multiple of
|
||||||
MALLOC_ALIGNMENT. */
|
MALLOC_ALIGNMENT. */
|
||||||
char pad[-6 * SIZE_SZ & MALLOC_ALIGN_MASK];
|
char pad[-3 * SIZE_SZ & MALLOC_ALIGN_MASK];
|
||||||
} heap_info;
|
} heap_info;
|
||||||
|
|
||||||
/* Get a compile-time error if the heap_info padding is not correct
|
/* Get a compile-time error if the heap_info padding is not correct
|
||||||
|
@ -125,10 +149,18 @@ static bool __malloc_initialized = false;
|
||||||
|
|
||||||
/* find the heap and corresponding arena for a given ptr */
|
/* find the heap and corresponding arena for a given ptr */
|
||||||
|
|
||||||
#define heap_for_ptr(ptr) \
|
static inline heap_info *
|
||||||
((heap_info *) ((unsigned long) (ptr) & ~(HEAP_MAX_SIZE - 1)))
|
heap_for_ptr (void *ptr)
|
||||||
#define arena_for_chunk(ptr) \
|
{
|
||||||
(chunk_main_arena (ptr) ? &main_arena : heap_for_ptr (ptr)->ar_ptr)
|
size_t max_size = heap_max_size ();
|
||||||
|
return PTR_ALIGN_DOWN (ptr, max_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline struct malloc_state *
|
||||||
|
arena_for_chunk (mchunkptr ptr)
|
||||||
|
{
|
||||||
|
return chunk_main_arena (ptr) ? &main_arena : heap_for_ptr (ptr)->ar_ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**************************************************************************/
|
/**************************************************************************/
|
||||||
|
@ -443,71 +475,72 @@ static char *aligned_heap_area;
|
||||||
of the page size. */
|
of the page size. */
|
||||||
|
|
||||||
static heap_info *
|
static heap_info *
|
||||||
new_heap (size_t size, size_t top_pad)
|
alloc_new_heap (size_t size, size_t top_pad, size_t pagesize,
|
||||||
|
int mmap_flags)
|
||||||
{
|
{
|
||||||
size_t pagesize = GLRO (dl_pagesize);
|
|
||||||
char *p1, *p2;
|
char *p1, *p2;
|
||||||
unsigned long ul;
|
unsigned long ul;
|
||||||
heap_info *h;
|
heap_info *h;
|
||||||
|
size_t min_size = heap_min_size ();
|
||||||
|
size_t max_size = heap_max_size ();
|
||||||
|
|
||||||
if (size + top_pad < HEAP_MIN_SIZE)
|
if (size + top_pad < min_size)
|
||||||
size = HEAP_MIN_SIZE;
|
size = min_size;
|
||||||
else if (size + top_pad <= HEAP_MAX_SIZE)
|
else if (size + top_pad <= max_size)
|
||||||
size += top_pad;
|
size += top_pad;
|
||||||
else if (size > HEAP_MAX_SIZE)
|
else if (size > max_size)
|
||||||
return 0;
|
return 0;
|
||||||
else
|
else
|
||||||
size = HEAP_MAX_SIZE;
|
size = max_size;
|
||||||
size = ALIGN_UP (size, pagesize);
|
size = ALIGN_UP (size, pagesize);
|
||||||
|
|
||||||
/* A memory region aligned to a multiple of HEAP_MAX_SIZE is needed.
|
/* A memory region aligned to a multiple of max_size is needed.
|
||||||
No swap space needs to be reserved for the following large
|
No swap space needs to be reserved for the following large
|
||||||
mapping (on Linux, this is the case for all non-writable mappings
|
mapping (on Linux, this is the case for all non-writable mappings
|
||||||
anyway). */
|
anyway). */
|
||||||
p2 = MAP_FAILED;
|
p2 = MAP_FAILED;
|
||||||
if (aligned_heap_area)
|
if (aligned_heap_area)
|
||||||
{
|
{
|
||||||
p2 = (char *) MMAP (aligned_heap_area, HEAP_MAX_SIZE, PROT_NONE,
|
p2 = (char *) MMAP (aligned_heap_area, max_size, PROT_NONE, mmap_flags);
|
||||||
MAP_NORESERVE);
|
|
||||||
aligned_heap_area = NULL;
|
aligned_heap_area = NULL;
|
||||||
if (p2 != MAP_FAILED && ((unsigned long) p2 & (HEAP_MAX_SIZE - 1)))
|
if (p2 != MAP_FAILED && ((unsigned long) p2 & (max_size - 1)))
|
||||||
{
|
{
|
||||||
__munmap (p2, HEAP_MAX_SIZE);
|
__munmap (p2, max_size);
|
||||||
p2 = MAP_FAILED;
|
p2 = MAP_FAILED;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (p2 == MAP_FAILED)
|
if (p2 == MAP_FAILED)
|
||||||
{
|
{
|
||||||
p1 = (char *) MMAP (0, HEAP_MAX_SIZE << 1, PROT_NONE, MAP_NORESERVE);
|
p1 = (char *) MMAP (0, max_size << 1, PROT_NONE, mmap_flags);
|
||||||
if (p1 != MAP_FAILED)
|
if (p1 != MAP_FAILED)
|
||||||
{
|
{
|
||||||
p2 = (char *) (((unsigned long) p1 + (HEAP_MAX_SIZE - 1))
|
p2 = (char *) (((unsigned long) p1 + (max_size - 1))
|
||||||
& ~(HEAP_MAX_SIZE - 1));
|
& ~(max_size - 1));
|
||||||
ul = p2 - p1;
|
ul = p2 - p1;
|
||||||
if (ul)
|
if (ul)
|
||||||
__munmap (p1, ul);
|
__munmap (p1, ul);
|
||||||
else
|
else
|
||||||
aligned_heap_area = p2 + HEAP_MAX_SIZE;
|
aligned_heap_area = p2 + max_size;
|
||||||
__munmap (p2 + HEAP_MAX_SIZE, HEAP_MAX_SIZE - ul);
|
__munmap (p2 + max_size, max_size - ul);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
/* Try to take the chance that an allocation of only HEAP_MAX_SIZE
|
/* Try to take the chance that an allocation of only max_size
|
||||||
is already aligned. */
|
is already aligned. */
|
||||||
p2 = (char *) MMAP (0, HEAP_MAX_SIZE, PROT_NONE, MAP_NORESERVE);
|
p2 = (char *) MMAP (0, max_size, PROT_NONE, mmap_flags);
|
||||||
if (p2 == MAP_FAILED)
|
if (p2 == MAP_FAILED)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
if ((unsigned long) p2 & (HEAP_MAX_SIZE - 1))
|
if ((unsigned long) p2 & (max_size - 1))
|
||||||
{
|
{
|
||||||
__munmap (p2, HEAP_MAX_SIZE);
|
__munmap (p2, max_size);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (__mprotect (p2, size, mtag_mmap_flags | PROT_READ | PROT_WRITE) != 0)
|
if (__mprotect (p2, size, mtag_mmap_flags | PROT_READ | PROT_WRITE) != 0)
|
||||||
{
|
{
|
||||||
__munmap (p2, HEAP_MAX_SIZE);
|
__munmap (p2, max_size);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -516,22 +549,42 @@ new_heap (size_t size, size_t top_pad)
|
||||||
h = (heap_info *) p2;
|
h = (heap_info *) p2;
|
||||||
h->size = size;
|
h->size = size;
|
||||||
h->mprotect_size = size;
|
h->mprotect_size = size;
|
||||||
|
h->pagesize = pagesize;
|
||||||
LIBC_PROBE (memory_heap_new, 2, h, h->size);
|
LIBC_PROBE (memory_heap_new, 2, h, h->size);
|
||||||
return h;
|
return h;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static heap_info *
|
||||||
|
new_heap (size_t size, size_t top_pad)
|
||||||
|
{
|
||||||
|
#if HAVE_TUNABLES
|
||||||
|
if (__glibc_unlikely (mp_.hp_pagesize != 0))
|
||||||
|
{
|
||||||
|
/* MAP_NORESERVE is not used for huge pages because some kernel may
|
||||||
|
not reserve the mmap region and a subsequent access may trigger
|
||||||
|
a SIGBUS if there is no free pages in the pool. */
|
||||||
|
heap_info *h = alloc_new_heap (size, top_pad, mp_.hp_pagesize,
|
||||||
|
mp_.hp_flags);
|
||||||
|
if (h != NULL)
|
||||||
|
return h;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
return alloc_new_heap (size, top_pad, GLRO (dl_pagesize), MAP_NORESERVE);
|
||||||
|
}
|
||||||
|
|
||||||
/* Grow a heap. size is automatically rounded up to a
|
/* Grow a heap. size is automatically rounded up to a
|
||||||
multiple of the page size. */
|
multiple of the page size. */
|
||||||
|
|
||||||
static int
|
static int
|
||||||
grow_heap (heap_info *h, long diff)
|
grow_heap (heap_info *h, long diff)
|
||||||
{
|
{
|
||||||
size_t pagesize = GLRO (dl_pagesize);
|
size_t pagesize = h->pagesize;
|
||||||
|
size_t max_size = heap_max_size ();
|
||||||
long new_size;
|
long new_size;
|
||||||
|
|
||||||
diff = ALIGN_UP (diff, pagesize);
|
diff = ALIGN_UP (diff, pagesize);
|
||||||
new_size = (long) h->size + diff;
|
new_size = (long) h->size + diff;
|
||||||
if ((unsigned long) new_size > (unsigned long) HEAP_MAX_SIZE)
|
if ((unsigned long) new_size > (unsigned long) max_size)
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
if ((unsigned long) new_size > h->mprotect_size)
|
if ((unsigned long) new_size > h->mprotect_size)
|
||||||
|
@ -581,21 +634,14 @@ shrink_heap (heap_info *h, long diff)
|
||||||
|
|
||||||
/* Delete a heap. */
|
/* Delete a heap. */
|
||||||
|
|
||||||
#define delete_heap(heap) \
|
|
||||||
do { \
|
|
||||||
if ((char *) (heap) + HEAP_MAX_SIZE == aligned_heap_area) \
|
|
||||||
aligned_heap_area = NULL; \
|
|
||||||
__munmap ((char *) (heap), HEAP_MAX_SIZE); \
|
|
||||||
} while (0)
|
|
||||||
|
|
||||||
static int
|
static int
|
||||||
heap_trim (heap_info *heap, size_t pad)
|
heap_trim (heap_info *heap, size_t pad)
|
||||||
{
|
{
|
||||||
mstate ar_ptr = heap->ar_ptr;
|
mstate ar_ptr = heap->ar_ptr;
|
||||||
unsigned long pagesz = GLRO (dl_pagesize);
|
|
||||||
mchunkptr top_chunk = top (ar_ptr), p;
|
mchunkptr top_chunk = top (ar_ptr), p;
|
||||||
heap_info *prev_heap;
|
heap_info *prev_heap;
|
||||||
long new_size, top_size, top_area, extra, prev_size, misalign;
|
long new_size, top_size, top_area, extra, prev_size, misalign;
|
||||||
|
size_t max_size = heap_max_size ();
|
||||||
|
|
||||||
/* Can this heap go away completely? */
|
/* Can this heap go away completely? */
|
||||||
while (top_chunk == chunk_at_offset (heap, sizeof (*heap)))
|
while (top_chunk == chunk_at_offset (heap, sizeof (*heap)))
|
||||||
|
@ -612,19 +658,23 @@ heap_trim (heap_info *heap, size_t pad)
|
||||||
assert (new_size > 0 && new_size < (long) (2 * MINSIZE));
|
assert (new_size > 0 && new_size < (long) (2 * MINSIZE));
|
||||||
if (!prev_inuse (p))
|
if (!prev_inuse (p))
|
||||||
new_size += prev_size (p);
|
new_size += prev_size (p);
|
||||||
assert (new_size > 0 && new_size < HEAP_MAX_SIZE);
|
assert (new_size > 0 && new_size < max_size);
|
||||||
if (new_size + (HEAP_MAX_SIZE - prev_heap->size) < pad + MINSIZE + pagesz)
|
if (new_size + (max_size - prev_heap->size) < pad + MINSIZE
|
||||||
|
+ heap->pagesize)
|
||||||
break;
|
break;
|
||||||
ar_ptr->system_mem -= heap->size;
|
ar_ptr->system_mem -= heap->size;
|
||||||
LIBC_PROBE (memory_heap_free, 2, heap, heap->size);
|
LIBC_PROBE (memory_heap_free, 2, heap, heap->size);
|
||||||
delete_heap (heap);
|
if ((char *) heap + max_size == aligned_heap_area)
|
||||||
|
aligned_heap_area = NULL;
|
||||||
|
__munmap (heap, max_size);
|
||||||
heap = prev_heap;
|
heap = prev_heap;
|
||||||
if (!prev_inuse (p)) /* consolidate backward */
|
if (!prev_inuse (p)) /* consolidate backward */
|
||||||
{
|
{
|
||||||
p = prev_chunk (p);
|
p = prev_chunk (p);
|
||||||
unlink_chunk (ar_ptr, p);
|
unlink_chunk (ar_ptr, p);
|
||||||
}
|
}
|
||||||
assert (((unsigned long) ((char *) p + new_size) & (pagesz - 1)) == 0);
|
assert (((unsigned long) ((char *) p + new_size) & (heap->pagesize - 1))
|
||||||
|
== 0);
|
||||||
assert (((char *) p + new_size) == ((char *) heap + heap->size));
|
assert (((char *) p + new_size) == ((char *) heap + heap->size));
|
||||||
top (ar_ptr) = top_chunk = p;
|
top (ar_ptr) = top_chunk = p;
|
||||||
set_head (top_chunk, new_size | PREV_INUSE);
|
set_head (top_chunk, new_size | PREV_INUSE);
|
||||||
|
@ -644,7 +694,7 @@ heap_trim (heap_info *heap, size_t pad)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
/* Release in pagesize units and round down to the nearest page. */
|
/* Release in pagesize units and round down to the nearest page. */
|
||||||
extra = ALIGN_DOWN(top_area - pad, pagesz);
|
extra = ALIGN_DOWN(top_area - pad, heap->pagesize);
|
||||||
if (extra == 0)
|
if (extra == 0)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
|
|
|
@ -5302,7 +5302,7 @@ static __always_inline int
|
||||||
do_set_mmap_threshold (size_t value)
|
do_set_mmap_threshold (size_t value)
|
||||||
{
|
{
|
||||||
/* Forbid setting the threshold too high. */
|
/* Forbid setting the threshold too high. */
|
||||||
if (value <= HEAP_MAX_SIZE / 2)
|
if (value <= heap_max_size () / 2)
|
||||||
{
|
{
|
||||||
LIBC_PROBE (memory_mallopt_mmap_threshold, 3, value, mp_.mmap_threshold,
|
LIBC_PROBE (memory_mallopt_mmap_threshold, 3, value, mp_.mmap_threshold,
|
||||||
mp_.no_dyn_threshold);
|
mp_.no_dyn_threshold);
|
||||||
|
|
Loading…
Add table
Reference in a new issue