/* * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ static pthread_mutex_t malloc_disabled_lock = PTHREAD_MUTEX_INITIALIZER; static bool malloc_disabled_tcache; static void je_iterate_chunk(arena_chunk_t *chunk, void (*callback)(uintptr_t ptr, size_t size, void* arg), void* arg); static void je_iterate_small(arena_run_t *run, void (*callback)(uintptr_t ptr, size_t size, void* arg), void* arg); /* je_iterate calls callback for each allocation found in the memory region * between [base, base+size). base will be rounded down to by the jemalloc * chunk size, and base+size will be rounded up to the chunk size. If no memory * managed by jemalloc is found in the requested region, je_iterate returns -1 * and sets errno to EINVAL. * * je_iterate must be called when no allocations are in progress, either * when single-threaded (for example just after a fork), or between * jemalloc_prefork() and jemalloc_postfork_parent(). The callback must * not attempt to allocate with jemalloc. */ int je_iterate(uintptr_t base, size_t size, void (*callback)(uintptr_t ptr, size_t size, void* arg), void* arg) { int error = EINVAL; uintptr_t ptr = (uintptr_t)CHUNK_ADDR2BASE(base); uintptr_t end = CHUNK_CEILING(base + size); while (ptr < end) { assert(ptr == (uintptr_t)CHUNK_ADDR2BASE(ptr)); extent_node_t *node; node = chunk_lookup((void *)ptr, false); if (node == NULL) { ptr += chunksize; continue; } assert(extent_node_achunk_get(node) || (uintptr_t)extent_node_addr_get(node) == ptr); error = 0; if (extent_node_achunk_get(node)) { /* Chunk */ arena_chunk_t *chunk = (arena_chunk_t *)ptr; ptr += chunksize; if (&chunk->node != node) { /* Empty retained chunk */ continue; } je_iterate_chunk(chunk, callback, arg); } else if ((uintptr_t)extent_node_addr_get(node) == ptr) { /* Huge allocation */ callback(ptr, extent_node_size_get(node), arg); ptr = CHUNK_CEILING(ptr + extent_node_size_get(node)); } } if (error) { set_errno(error); return -1; } return 0; } /* Iterate over a valid jemalloc chunk, calling callback for each large * allocation run, and calling je_iterate_small for each small allocation run */ static void je_iterate_chunk(arena_chunk_t *chunk, void (*callback)(uintptr_t ptr, size_t size, void* arg), void* arg) { size_t pageind; pageind = map_bias; while (pageind < chunk_npages) { size_t mapbits; size_t size; mapbits = arena_mapbits_get(chunk, pageind); if (!arena_mapbits_allocated_get(chunk, pageind)) { /* Unallocated run */ size = arena_mapbits_unallocated_size_get(chunk, pageind); } else if (arena_mapbits_large_get(chunk, pageind)) { /* Large allocation run */ void *rpages; size = arena_mapbits_large_size_get(chunk, pageind); rpages = arena_miscelm_to_rpages(arena_miscelm_get_mutable(chunk, pageind)); callback((uintptr_t)rpages, size, arg); } else { /* Run of small allocations */ szind_t binind; arena_run_t *run; assert(arena_mapbits_small_runind_get(chunk, pageind) == pageind); binind = arena_mapbits_binind_get(chunk, pageind); run = &arena_miscelm_get_mutable(chunk, pageind)->run; assert(run->binind == binind); size = arena_bin_info[binind].run_size; je_iterate_small(run, callback, arg); } assert(size == PAGE_CEILING(size)); assert(size > 0); pageind += size >> LG_PAGE; } } /* Iterate over a valid jemalloc small allocation run, calling callback for each * active allocation. */ static void je_iterate_small(arena_run_t *run, void (*callback)(uintptr_t ptr, size_t size, void* arg), void* arg) { szind_t binind; const arena_bin_info_t *bin_info; uint32_t regind; uintptr_t ptr; void *rpages; binind = run->binind; bin_info = &arena_bin_info[binind]; rpages = arena_miscelm_to_rpages(arena_run_to_miscelm(run)); ptr = (uintptr_t)rpages + bin_info->reg0_offset; for (regind = 0; regind < bin_info->nregs; regind++) { if (bitmap_get(run->bitmap, &bin_info->bitmap_info, regind)) { callback(ptr, bin_info->reg_size, arg); } ptr += bin_info->reg_interval; } } static void je_malloc_disable_prefork() { pthread_mutex_lock(&malloc_disabled_lock); } static void je_malloc_disable_postfork_parent() { pthread_mutex_unlock(&malloc_disabled_lock); } static void je_malloc_disable_postfork_child() { pthread_mutex_init(&malloc_disabled_lock, NULL); } void je_malloc_disable_init() { if (pthread_atfork(je_malloc_disable_prefork, je_malloc_disable_postfork_parent, je_malloc_disable_postfork_child) != 0) { malloc_write(": Error in pthread_atfork()\n"); if (opt_abort) abort(); } } void je_malloc_disable() { static pthread_once_t once_control = PTHREAD_ONCE_INIT; pthread_once(&once_control, je_malloc_disable_init); pthread_mutex_lock(&malloc_disabled_lock); bool new_tcache = false; size_t old_len = sizeof(malloc_disabled_tcache); je_mallctl("thread.tcache.enabled", &malloc_disabled_tcache, &old_len, &new_tcache, sizeof(new_tcache)); jemalloc_prefork(); } void je_malloc_enable() { jemalloc_postfork_parent(); if (malloc_disabled_tcache) { je_mallctl("thread.tcache.enabled", NULL, NULL, &malloc_disabled_tcache, sizeof(malloc_disabled_tcache)); } pthread_mutex_unlock(&malloc_disabled_lock); }