mksquashfs was reported in openSUSE to be causing segmentation faults when
creating installation images. Testing showed that mksquashfs sometimes
failed and could be reproduced within 10 attempts. The core dump looked
like the heap top was corrupted and was pointing to an unmapped area. In
other cases, this has been due to an application corrupting glibc structures
but mksquashfs appears to be fine in this regard.
The problem is that heap_trim is "growing" the top into unmapped space.
If the top chunk == MINSIZE then top_area is -1 and this check does not
behave as expected due to a signed/unsigned comparison
if (top_area <= pad)
return 0;
The next calculation extra = ALIGN_DOWN(top_area - pad, pagesz) calculates
extra as a negative number which also is unnoticed due to a signed/unsigned
comparison. We then call shrink_heap(heap, negative_number) which crashes
later. This patch adds a simple check against MINSIZE to make sure extra
does not become negative. It adds a cast to hint to the reader that this
is a signed vs unsigned issue.
Without the patch, mksquash fails within 10 attempts. With it applied, it
completed 1000 times without error. The standard test suite "make check"
showed no changes in the summary of test results.
When the malloc subsystem detects some kind of memory corruption,
depending on the configuration it prints the error, a backtrace, a
memory map and then aborts the process. In this process, the
backtrace() call may result in a call to malloc, resulting in
various kinds of problematic behavior.
In one case, the malloc it calls may detect a corruption and call
backtrace again, and a stack overflow may result due to the infinite
recursion. In another case, the malloc it calls may deadlock on an
arena lock with the malloc (or free, realloc, etc.) that detected the
corruption. In yet another case, if the program is linked with
pthreads, backtrace may do a pthread_once initialization, which
deadlocks on itself.
In all these cases, the program exit is not as intended. This is
avoidable by marking the arena that malloc detected a corruption on,
as unusable. The following patch does that. Features of this patch
are as follows:
- A flag is added to the mstate struct of the arena to indicate if the
arena is corrupt.
- The flag is checked whenever malloc functions try to get a lock on
an arena. If the arena is unusable, a NULL is returned, causing the
malloc to use mmap or try the next arena.
- malloc_printerr sets the corrupt flag on the arena when it detects a
corruption
- free does not concern itself with the flag at all. It is not
important since the backtrace workflow does not need free. A free
in a parallel thread may cause another corruption, but that's not
new
- The flag check and set are not atomic and may race. This is fine
since we don't care about contention during the flag check. We want
to make sure that the malloc call in the backtrace does not trip on
itself and all that action happens in the same thread and not across
threads.
I verified that the test case does not show any regressions due to
this patch. I also ran the malloc benchmarks and found an
insignificant difference in timings (< 2%).
* malloc/Makefile (tests): New test case tst-malloc-backtrace.
* malloc/arena.c (arena_lock): Check if arena is corrupt.
(reused_arena): Find a non-corrupt arena.
(heap_trim): Pass arena to unlink.
* malloc/hooks.c (malloc_check_get_size): Pass arena to
malloc_printerr.
(top_check): Likewise.
(free_check): Likewise.
(realloc_check): Likewise.
* malloc/malloc.c (malloc_printerr): Add arena argument.
(unlink): Likewise.
(munmap_chunk): Adjust.
(ARENA_CORRUPTION_BIT): New macro.
(arena_is_corrupt): Likewise.
(set_arena_corrupt): Likewise.
(sysmalloc): Use mmap if there are no usable arenas.
(_int_malloc): Likewise.
(__libc_malloc): Don't fail if arena_get returns NULL.
(_mid_memalign): Likewise.
(__libc_calloc): Likewise.
(__libc_realloc): Adjust for additional argument to
malloc_printerr.
(_int_free): Likewise.
(malloc_consolidate): Likewise.
(_int_realloc): Likewise.
(_int_memalign): Don't touch corrupt arenas.
* malloc/tst-malloc-backtrace.c: New test case.
Trimming heaps is a balance between saving memory and the system overhead
required to update page tables and discard allocated pages. The malloc
option M_TRIM_THRESHOLD is a tunable that users are meant to use to decide
where this balance point is but it is only applied to the main arena.
For scalability reasons, glibc malloc has per-thread heaps but these are
shrunk with madvise() if there is one page free at the top of the heap.
In some circumstances this can lead to high system overhead if a thread
has a control flow like
while (data_to_process) {
buf = malloc(large_size);
do_stuff();
free(buf);
}
For a large size, the free() will call madvise (pagetable teardown, page
free and TLB flush) every time followed immediately by a malloc (fault,
kernel page alloc, zeroing and charge accounting). The kernel overhead
can dominate such a workload.
This patch allows the user to tune when madvise gets called by applying
the trim threshold to the per-thread heaps and using similar logic to the
main arena when deciding whether to shrink. Alternatively if the dynamic
brk/mmap threshold gets adjusted then the new values will be obeyed by
the per-thread heaps.
Bug 17195 was a test case motivated by a problem encountered in scientific
applications written in python that performance badly due to high page fault
overhead. The basic operation of such a program was posted by Julian Taylor
https://sourceware.org/ml/libc-alpha/2015-02/msg00373.html
With this patch applied, the overhead is eliminated. All numbers in this
report are in seconds and were recorded by running Julian's program 30
times.
pyarray
glibc madvise
2.21 v2
System min 1.81 ( 0.00%) 0.00 (100.00%)
System mean 1.93 ( 0.00%) 0.02 ( 99.20%)
System stddev 0.06 ( 0.00%) 0.01 ( 88.99%)
System max 2.06 ( 0.00%) 0.03 ( 98.54%)
Elapsed min 3.26 ( 0.00%) 2.37 ( 27.30%)
Elapsed mean 3.39 ( 0.00%) 2.41 ( 28.84%)
Elapsed stddev 0.14 ( 0.00%) 0.02 ( 82.73%)
Elapsed max 4.05 ( 0.00%) 2.47 ( 39.01%)
glibc madvise
2.21 v2
User 141.86 142.28
System 57.94 0.60
Elapsed 102.02 72.66
Note that almost a minutes worth of system time is eliminted and the
program completes 28% faster on average.
To illustrate the problem without python this is a basic test-case for
the worst case scenario where every free is a madvise followed by a an alloc
/* gcc bench-free.c -lpthread -o bench-free */
static int num = 1024;
void __attribute__((noinline,noclone)) dostuff (void *p)
{
}
void *worker (void *data)
{
int i;
for (i = num; i--;)
{
void *m = malloc (48*4096);
dostuff (m);
free (m);
}
return NULL;
}
int main()
{
int i;
pthread_t t;
void *ret;
if (pthread_create (&t, NULL, worker, NULL))
exit (2);
if (pthread_join (t, &ret))
exit (3);
return 0;
}
Before the patch, this resulted in 1024 calls to madvise. With the patch applied,
madvise is called twice because the default trim threshold is high enough to avoid
this.
This a more complex case where there is a mix of frees. It's simply a different worker
function for the test case above
void *worker (void *data)
{
int i;
int j = 0;
void *free_index[num];
for (i = num; i--;)
{
void *m = malloc ((i % 58) *4096);
dostuff (m);
if (i % 2 == 0) {
free (m);
} else {
free_index[j++] = m;
}
}
for (; j >= 0; j--)
{
free(free_index[j]);
}
return NULL;
}
glibc 2.21 calls malloc 90305 times but with the patch applied, it's
called 13438. Increasing the trim threshold will decrease the number of
times it's called with the option of eliminating the overhead.
ebizzy is meant to generate a workload resembling common web application
server workloads. It is threaded with a large working set that at its core
has an allocation, do_stuff, free loop that also hits this case. The primary
metric of the benchmark is records processed per second. This is running on
my desktop which is a single socket machine with an I7-4770 and 8 cores.
Each thread count was run for 30 seconds. It was only run once as the
performance difference is so high that the variation is insignificant.
glibc 2.21 patch
threads 1 10230 44114
threads 2 19153 84925
threads 4 34295 134569
threads 8 51007 183387
Note that the saving happens to be a concidence as the size allocated
by ebizzy was less than the default threshold. If a different number of
chunks were specified then it may also be necessary to tune the threshold
to compensate
This is roughly quadrupling the performance of this benchmark. The difference in
system CPU usage illustrates why.
ebizzy running 1 thread with glibc 2.21
10230 records/s 306904
real 30.00 s
user 7.47 s
sys 22.49 s
22.49 seconds was spent in the kernel for a workload runinng 30 seconds. With the
patch applied
ebizzy running 1 thread with patch applied
44126 records/s 1323792
real 30.00 s
user 29.97 s
sys 0.00 s
system CPU usage was zero with the patch applied. strace shows that glibc
running this workload calls madvise approximately 9000 times a second. With
the patch applied madvise was called twice during the workload (or 0.06
times per second).
2015-02-10 Mel Gorman <mgorman@suse.de>
[BZ #17195]
* malloc/arena.c (free): Apply trim threshold to per-thread heaps
as well as the main arena.
We are replacing all of the bespoke alignment code with
ALIGN_UP, ALIGN_DOWN, PTR_ALIGN_UP, and PTR_ALIGN_DOWN.
This cleans up malloc/malloc.c, malloc/arena.c, and
elf/dl-reloc.c. It also makes all the code consistently
use pagesize, and powerof2 as required.
Code size is reduced with the removal of precomputed
pagemask, and use of pagesize instead. No measurable
difference in performance.
No regressions on x86_64.
for ChangeLog
* malloc/arena.c (new_heap): New memory_heap_new probe.
(grow_heap): New memory_heap_more probe.
(shrink_heap): New memory_heap_less probe.
(heap_trim): New memory_heap_free probe.
* malloc/malloc.c (sysmalloc): New memory_sbrk_more probe.
(systrim): New memory_sbrk_less probe.
* manual/probes.texi: Document them.
Introduce (only on Linux) and use a HAVE_MREMAP symbol to advertize mremap
availability.
Move the malloc-sysdep.h include from arena.c to malloc.c, since what is
provided by malloc-sysdep.h is needed earlier in malloc.c, before the inclusion
of arena.c.
Using madvise with MADV_DONTNEED to release memory back to the kernel
is not sufficient to change the commit charge accounted against the
process on Linux. It is OK however, when overcommit is enabled or is
heuristic. However, when overcommit is restricted to a percentage of
memory setting the contents of /proc/sys/vm/overcommit_memory as 2, it
makes a difference since memory requests will fail. Hence, we do what
we do with secure exec binaries, which is to call mmap on the region
to be dropped with MAP_FIXED. This internally unmaps the pages in
question and reduces the amount of memory accounted against the
process.
* malloc.c/arena.c (reused_arena): New parameter, avoid_arena.
When avoid_arena is set, don't retry in the that arena. Pick the
next one, whatever it might be.
(arena_get2): New parameter avoid_arena, pass through to reused_arena.
(arena_lock): Pass in new parameter to arena_get2.
* malloc/malloc.c (__libc_memalign): Pass in new parameter to
arena_get2.
(__libc_malloc): Unify retrying after main arena failure with
__libc_memalign version.
(__libc_valloc, __libc_pvalloc, __libc_calloc): Likewise.
2009-04-16 Ulrich Drepper <drepper@redhat.com>
[BZ #9957]
* malloc/malloc.c (force_reg): Define.
(sYSMALLOc): Load hook variable into variable
before test and force into register.
(sYSTRIm): Likewise.
(public_mALLOc): Force hook value into register.
(public_fREe): Likewise.
(public_rEALLOc): Likewise.
(public_mEMALIGn): Likewise.
(public_vALLOc): Likewise.
(public_pVALLOc): Likewise.
(public_cALLOc): Likewise.
(__posix_memalign): Likewise.
* malloc/arena.c (ptmalloc_init): Load hook variable into variable
before test and force into register.
* malloc/hooks.c (top_check): Likewise.
(public_s_ET_STATe): Pretty printing.
* resolv/res_send.c (send_dg): Don't just ignore the result we got
in case we only receive one reply in single-request mode.
Change all callers.
(_int_realloc): Likewise.
All _int_* functions are now static.
* malloc/hooks.c: Change all callers to _int_free and _int_realloc.
* malloc/arena.c: Likewise.
* include/malloc.h: Remove now unnecessary declarations of the _int_*
functions.
(public_sET_STATe): If ms->version < 3, put all chunks into
unsorted chunks and clear {fd,bk}_nextsize fields of largebin
chunks.
* malloc/malloc.c [MALLOC_DEBUG]: Revert 2007-05-13 changes.
* malloc/hooks.c: Likewise.
* malloc/arena.c: Likewise.
* malloc/malloc.c (do_check_malloc_state): Don't assert
n_mmaps is not greater than n_mmaps_max. This removes the need
for the previous change.
* malloc/Makefile (CFLAGS-malloc.c): Revert accidental
2007-05-07 commit.
(new_heap): Initialize mprotect_size.
(grow_heap): When growing, only mprotect from mprotect_size till
new_size if mprotect_size is smaller. When shrinking, use PROT_NONE
MMAP for __libc_enable_secure only, otherwise use MADV_DONTNEED.
2007-05-07 Ulrich Drepper <drepper@redhat.com>
Jakub Jelinek <jakub@redhat.com>
* malloc/arena.c (heap_info): Add mprotect_size field, adjust pad.
(new_heap): Initialize mprotect_size.
(grow_heap): When growing, only mprotect from mprotect_size till
new_size if mprotect_size is smaller. When shrinking, use PROT_NONE
MMAP for __libc_enable_secure only, otherwise use MADV_DONTNEED.
* malloc/malloc.c (sYSMALLOc): Only call grow_heap if
(long) (MINSIZE + nb - old_size) is positive.
* malloc/arena.c (grow_heap): When growing bail even if new_size
is negative.
platforms define as 1MB. For 64-bit platforms as 32MB. The lower
limit is needed to avoid the exploding of the address space
requirement for secondary heaps.
* malloc/arena.c (HEAP_MAX_SIZE): Define using
DEFAULT_MMAP_THRESHOLD_MAX if it is defined.
The correct value differs only on powerpc32, and for now changing it
there is causing more trouble than it's worth.
* malloc/arena.c: Add compile-time sanity check on padding calculation.
2006-03-05 Jakub Jelinek <jakub@redhat.com>
* malloc/arena.c (heap_info): Adjust the padding size if
MALLOC_ALIGNMENT > 2 * SIZE_SZ.
* malloc/arena.c (ptmalloc_lock_all): If global lock already taken
by the same thread, just bump the counter.
(ptmalloc_unlock_all): If counter for recursive locks hasn't reached
zero, don't do anything else.
* malloc/Makefile (tests): Add tst-mallocfork.
* malloc/tst-mallocfork.c: New file.
2004-10-18 Jakub Jelinek <jakub@redhat.com>
* elf/dl-libc.c (__libc_dlsym_private, __libc_register_dl_open_hook):
New functions.
(__libc_dlopen_mode): Call __libc_register_dl_open_hook and
__libc_register_dlfcn_hook.
* dlfcn/Makefile (routines, elide-routines.os): Set.
Add rules to build and test tststatic2.
* dlfcn/tststatic2.c: New test.
* dlfcn/modstatic2.c: New test module.
* dlfcn/dladdr.c: Call _dlfcn_hook from libdl.so if not NULL.
Define __ prefixed routine in libc.a and in libdl.a just call it.
* dlfcn/dladdr1.c: Likewise.
* dlfcn/dlclose.c: Likewise.
* dlfcn/dlerror.c: Likewise.
* dlfcn/dlinfo.c: Likewise.
* dlfcn/dlmopen.c: Likewise.
* dlfcn/dlopen.c: Likewise.
* dlfcn/dlopenold.c: Likewise.
* dlfcn/dlsym.c: Likewise.
* dlfcn/dlvsym.c: Likewise.
* dlfcn/sdladdr.c: New file.
* dlfcn/sdladdr1.c: New file.
* dlfcn/sdlclose.c: New file.
* dlfcn/sdlerror.c: New file.
* dlfcn/sdlinfo.c: New file.
* dlfcn/sdlopen.c: New file.
* dlfcn/sdlsym.c: New file.
* dlfcn/sdlvsym.c: New file.
* dlfcn/Versions (libdl): Export _dlfcn_hook@GLIBC_PRIVATE.
* include/dlfcn.h (DL_CALLER_DECL, DL_CALLER RETURN_ADDRESS): Define.
(struct dlfcn_hook): New type.
(_dlfcn_hook): New extern decl.
(__dlopen, __dlclose, __dlsym, __dlerror, __dladdr, __dladdr1,
__dlinfo, __dlmopen, __libc_dlsym_private,
__libc_register_dl_open_hook, __libc_register_dlfcn_hook): New
prototypes.
(__dlvsym): Use DL_CALLER_DECL.
* include/libc-symbols.h: Define libdl_hidden_proto and friends.
* malloc/arena.c (_dl_open_hook): Extern decl.
(ptmalloc_init): Don't call _dl_addr when dlopened from statically
linked programs but don't use brk for them either.
Update.
Add support for namespaces in the dynamic linker.
* dlfcn/Makefile (libdl-routines): Add dlmopen.
* dlfcn/Versions [libdl, GLIBC_2.3.4]: Add dlmopen.
* dlfcn/dlfcn.h: Define Lmid_t, LM_ID_BASE, and LM_ID_NEWLM.
Declare dlmopen. Document RTLD_DI_LMID.
* dlfcn/dlinfo.c: Handle RTLD_DI_LMID.
* dlfcn/dlmopen.c: New file.
* dlfcn/dlopen.c: Pass new parameter to _dl_open.
* dlfcn/dlopenold.c: Likewise.
* elf/dl-addr.c: Adjust for removal of GL(dl_loaded).
* elf/dl-caller.c: Likewise.
* elf/dl-close.c: Likewise.
* elf/dl-conflict.c: Likewise.
* elf/dl-debug.c: Likewise.
* elf/dl-lookup.c: Likewise.
* elf/dl-sym.c: Likewise.
* elf/dl-version.c: Likewise.
* elf/do-lookup.h: Likewise.
* elf/rtld.c: Likewise.
* sysdeps/unix/sysv/linux/i386/dl-librecon.h: Likewise.
* elf/dl-depsc: Likewise. Add new parameter to _dl_map_object.
* elf/dl-fini.c: Call destructors in all namespaces.
* elf/dl-iteratephdr.c: Compute total nloaded. Adjust for removal of
GL(dl_loaded).
* elf/dl-libc.c: Pass new parameter to _dl_open. Adjust for removal
of GL(dl_loaded).
* elf/dl-load.c (_dl_map_object_from_fd): Don't load ld.so a second
time. Reuse the one from the main namespace in all others.
Pass new parameter to _dl_new_object.
Adjust for removal of GL(dl_loaded).
* elf/dl-object.c: Take new parameter. Use it to initialize l_ns.
Adjust for removal of GL(dl_loaded).
* elf/dl-open.c (_dl_open): Take new parameter.
Adjust for removal of GL(dl_loaded).
* elf/dl-support.c: Replace global _dl_loaded etc variables with
_dl_ns variable.
* include/dlfcn.h: Adjust prototype of _dl_open.
Define __LM_ID_CALLER.
* include/link.h: Add l_real, l_ns, and l_direct_opencount elements.
* sysdeps/generic/dl-tls.c: Bump TLS_STATIC_SURPLUS. Since libc is
using TLS we need memory appropriate to the number of namespaces.
* sysdeps/generic/ldsodefs.h (struct rtld_global): Replace _dl_loaded,
_dl_nloaded, _dl_global_scope, _dl_main_searchlist, and
_dl_global_scope_alloc with _dl_ns element. Define DL_NNS.
Adjust prototypes of _dl_map_object and member in rtld_global_ro.
* malloc/malloc.c: Include <dlfcn.h>.
* malloc/arena.c (ptmalloc_init): If libc is not in primary namespace,
never use brk.
* elf/Makefile: Add rules to build and run tst-dlmopen1 and
tst-dlmopen2.
* elf/tst-dlmopen1.c: New file.
* elf/tst-dlmopen1mod.c: New file.
* elf/tst-dlmopen2.c: New file.
* elf/dl-close.c: Improve reference counting by tracking direct loads.
* elf/dl-lookup.c (add_dependency): Likewise.
* elf/dl-open.c (dl_open_worker): Likewise.
* elf/rtld.c (dl_main): Likewise.
2004-09-09 GOTO Masanori <gotom@debian.or.jp>
[BZ #77]
* elf/dl-close.c: Count down l_opencount to check not only for
l_reldeps, but also l_initfini.
2004-10-13 Ulrich Drepper <drepper@redhat.com>
2004-03-18 Jakub Jelinek <jakub@redhat.com>
* malloc/arena.c (aligned_heap_area): New variable.
(new_heap): If aligned_heap_area != NULL, attempt to use that
first. If HEAP_MAX_SIZE << 1 area is already HEAP_MAX_SIZE bytes
aligned, remember the second half in aligned_heap_area.
(delete_heap): Clear aligned_heap_area if deleting the area right
before aligned_heap_area.
* malloc/hooks.c [_LIBC && (USE___THREAD || (USE_TLS && !SHARED))]
(malloc_starter, memalign_starter, free_starter): Don't define these.
* malloc/malloc.c [_LIBC && (USE___THREAD || (USE_TLS && !SHARED))]:
Don't declare them either.
* malloc/arena.c (ptmalloc_init) [_LIBC && USE_TLS]: Don't call
__pthread_initialize, so no need to set hooks to *_starter.
(ptmalloc_init_minimal): New function, broken out of ptmalloc_init.
[_LIBC && SHARED && USE_TLS && !USE___THREAD]
(__libc_malloc_pthread_startup): New function.
* malloc/Versions (libc: GLIBC_PRIVATE): New set, add that function.
* malloc/hooks.c (memalign_starter): New function.
* malloc/malloc.c: Declare it.
* malloc/arena.c (save_memalign_hook): New variable.
(ptmalloc_init): Set __memalign_hook to memalign_starter.
* elf/dl-minimal.c (free): Clear the memory.
(calloc): Just call malloc, knowing all memory it returns is cleared.
* sysdeps/generic/dl-tls.c (allocate_dtv): Use calloc instead of
malloc and memset; calloc can avoid the zeroing when redundant.
(_dl_tls_setup): Likewise.
* elf/dl-load.c (decompose_rpath): Likewise.
* sysdeps/generic/libc-tls.c (__libc_setup_tls): Comment out memset
call, since memory from sbrk at startup is already zero.
* elf/rtld.c (_dl_start, dl_main): TLS_INIT_TP macro now returns an
error string for failure, null for success. Update callers.
* sysdeps/generic/libc-tls.c (__libc_setup_tls): Likewise.
* elf/dl-load.c (_dl_map_object_from_fd): Likewise.
* malloc/arena.c
(ptmalloc_lock_all, ptmalloc_unlock_all, ptmalloc_unlock_all2): Do
nothing if not initialized. Bug report from Marcus Brinkmann
<Marcus.Brinkmann@ruhr-uni-bochum.de>.
2002-01-18 Wolfram Gloger <wg@malloc.de>
* malloc/malloc.c: Rewrite, adapted from Doug Lea's malloc-2.7.0.c.
* malloc/malloc.h: Likewise.
* malloc/arena.c: New file.
* malloc/hooks.c: New file.
* malloc/tst-mallocstate.c: New file.
* malloc/Makefile: Add new testcase tst-mallocstate.
Add arena.c and hooks.c to distribute. Fix commented CPPFLAGS.
2002-01-28 Ulrich Drepper <drepper@redhat.com>
* stdlib/msort.c: Remove last patch. The optimization violates the
same rule which qsort.c had problems with.
2002-01-27 Paul Eggert <eggert@twinsun.com>
* stdlib/qsort.c (_quicksort): Do not apply the comparison function
to a pivot element that lies outside the array to be sorted, as
ISO C99 requires that the comparison function be called only with
addresses of array elements [PR libc/2880].