| #include "jemalloc/internal/jemalloc_preamble.h" |
| |
| #include "jemalloc/internal/arena.h" |
| #include "jemalloc/internal/arenas_management.h" |
| #include "jemalloc/internal/background_thread.h" |
| #include "jemalloc/internal/background_thread_inlines.h" |
| #include "jemalloc/internal/jemalloc_internal_inlines_a.h" |
| #include "jemalloc/internal/prof.h" |
| #include "jemalloc/internal/tcache.h" |
| #include "jemalloc/internal/witness.h" |
| #include "jemalloc/internal/conf.h" |
| #include "jemalloc/internal/ctl.h" |
| #include "jemalloc/internal/emap.h" |
| #include "jemalloc/internal/extent_dss.h" |
| #include "jemalloc/internal/jemalloc_fork.h" |
| #include "jemalloc/internal/jemalloc_init.h" |
| #include "jemalloc/internal/malloc_io.h" |
| #include "jemalloc/internal/mutex.h" |
| #include "jemalloc/internal/os.h" |
| #include "jemalloc/internal/san.h" |
| #include "jemalloc/internal/sc.h" |
| #include "jemalloc/internal/spin.h" |
| #include "jemalloc/internal/sz.h" |
| #include "jemalloc/internal/thread_event.h" |
| |
| #ifdef JEMALLOC_THREADED_INIT |
| /* Used to let the initializing thread recursively allocate. */ |
| # define NO_INITIALIZER ((unsigned long)0) |
| # define INITIALIZER pthread_self() |
| static pthread_t malloc_initializer = NO_INITIALIZER; |
| #else |
| # define NO_INITIALIZER false |
| # define INITIALIZER true |
| static bool malloc_initializer = NO_INITIALIZER; |
| #endif |
| |
| bool |
| malloc_is_initializer(void) { |
| #ifdef JEMALLOC_THREADED_INIT |
| return pthread_equal(malloc_initializer, pthread_self()); |
| #else |
| return malloc_initializer; |
| #endif |
| } |
| |
| #ifdef JEMALLOC_THREADED_INIT |
| static bool |
| malloc_initializer_is_set(void) { |
| return malloc_initializer != NO_INITIALIZER; |
| } |
| #endif |
| |
| static void |
| malloc_initializer_set(void) { |
| malloc_initializer = INITIALIZER; |
| } |
| |
| /* Used to avoid initialization races. */ |
| #if !OS_MUTEX_HAS_STATIC_INIT |
| /* |
| * This target's os_mutex_t has no static initializer (currently: pre-Vista |
| * Windows CRITICAL_SECTION; see os/windows/mutex.h), so MALLOC_MUTEX_ |
| * INITIALIZER is empty -- init_lock needs its own lazy-init workaround |
| * instead, being the one static malloc_mutex_t that must be usable before |
| * any other code runs. |
| */ |
| static malloc_mutex_t init_lock; |
| static bool init_lock_initialized = false; |
| |
| JEMALLOC_ATTR(constructor) |
| static void WINAPI |
| _init_init_lock(void) { |
| /* |
| * If another constructor in the same binary is using mallctl to e.g. |
| * set up extent hooks, it may end up running before this one, and |
| * malloc_init_hard will crash trying to lock the uninitialized lock. So |
| * we force an initialization of the lock in malloc_init_hard as well. |
| * We don't try to care about atomicity of the accessed to the |
| * init_lock_initialized boolean, since it really only matters early in |
| * the process creation, before any separate thread normally starts |
| * doing anything. |
| */ |
| if (!init_lock_initialized) { |
| malloc_mutex_init(&init_lock, "init", WITNESS_RANK_INIT, |
| malloc_mutex_rank_exclusive); |
| } |
| init_lock_initialized = true; |
| } |
| |
| # ifdef _MSC_VER |
| # pragma section(".CRT$XCU", read) |
| JEMALLOC_SECTION(".CRT$XCU") |
| JEMALLOC_ATTR(used) |
| static const void(WINAPI *init_init_lock)(void) = _init_init_lock; |
| # endif |
| #else |
| static malloc_mutex_t init_lock = MALLOC_MUTEX_INITIALIZER; |
| #endif |
| |
| malloc_init_t malloc_init_state = malloc_init_uninitialized; |
| |
| /* When malloc_slow is true, set the corresponding bits for sanity check. */ |
| enum { |
| flag_opt_junk_alloc = (1U), |
| flag_opt_junk_free = (1U << 1), |
| flag_opt_zero = (1U << 2), |
| flag_opt_utrace = (1U << 3), |
| flag_opt_xmalloc = (1U << 4) |
| }; |
| static uint8_t malloc_slow_flags; |
| |
| static void |
| malloc_slow_flag_init(void) { |
| /* |
| * Combine the runtime options into malloc_slow for fast path. Called |
| * after bootstrap is complete. |
| */ |
| malloc_slow_flags |= (opt_junk_alloc ? flag_opt_junk_alloc : 0) |
| | (opt_junk_free ? flag_opt_junk_free : 0) |
| | (opt_zero ? flag_opt_zero : 0) |
| | (opt_utrace ? flag_opt_utrace : 0) |
| | (opt_xmalloc ? flag_opt_xmalloc : 0); |
| |
| malloc_slow = (malloc_slow_flags != 0); |
| } |
| |
| static void stats_print_atexit(void); |
| |
| static bool |
| malloc_init_hard_needed(void) { |
| if (malloc_initialized() |
| || (malloc_is_initializer() |
| && malloc_init_state == malloc_init_recursible)) { |
| /* |
| * Another thread initialized the allocator before this one |
| * acquired init_lock, or this thread is the initializing |
| * thread, and it is recursively allocating. |
| */ |
| return false; |
| } |
| #ifdef JEMALLOC_THREADED_INIT |
| if (malloc_initializer_is_set() && !malloc_is_initializer()) { |
| /* Busy-wait until the initializing thread completes. */ |
| spin_t spinner = SPIN_INITIALIZER; |
| do { |
| malloc_mutex_unlock(TSDN_NULL, &init_lock); |
| spin_adaptive(&spinner); |
| malloc_mutex_lock(TSDN_NULL, &init_lock); |
| } while (!malloc_initialized()); |
| return false; |
| } |
| #endif |
| return true; |
| } |
| |
| static bool |
| malloc_init_hard_a0_locked(void) { |
| malloc_initializer_set(); |
| |
| JEMALLOC_DIAGNOSTIC_PUSH |
| JEMALLOC_DIAGNOSTIC_IGNORE_MISSING_STRUCT_FIELD_INITIALIZERS |
| sc_data_t sc_data = {0}; |
| JEMALLOC_DIAGNOSTIC_POP |
| |
| /* |
| * Ordering here is somewhat tricky; we need sc_boot() first, since that |
| * determines what the size classes will be, and then |
| * malloc_conf_init(), since any slab size tweaking will need to be done |
| * before sz_boot and bin_info_boot, which assume that the values they |
| * read out of sc_data_global are final. |
| */ |
| sc_boot(&sc_data); |
| unsigned bin_shard_sizes[SC_NBINS]; |
| bin_shard_sizes_boot(bin_shard_sizes); |
| /* |
| * prof_boot0 only initializes opt_prof_prefix. We need to do it before |
| * we parse malloc_conf options, in case malloc_conf parsing overwrites |
| * it. |
| */ |
| if (config_prof) { |
| prof_boot0(); |
| } |
| char readlink_buf[PATH_MAX + 1]; |
| readlink_buf[0] = '\0'; |
| malloc_conf_init(&sc_data, bin_shard_sizes, readlink_buf); |
| san_init(opt_lg_san_uaf_align); |
| sz_boot(&sc_data, opt_cache_oblivious); |
| bin_info_boot(&sc_data, bin_shard_sizes); |
| |
| if (opt_stats_print) { |
| /* Print statistics at exit. */ |
| if (atexit(stats_print_atexit) != 0) { |
| malloc_write("<jemalloc>: Error in atexit()\n"); |
| if (opt_abort) { |
| abort(); |
| } |
| } |
| } |
| |
| if (stats_boot()) { |
| return true; |
| } |
| if (pages_boot()) { |
| return true; |
| } |
| if (base_boot(TSDN_NULL)) { |
| return true; |
| } |
| /* emap_global is static, hence zeroed. */ |
| if (emap_init(&arena_emap_global, b0get(), /* zeroed */ true)) { |
| return true; |
| } |
| if (extent_boot()) { |
| return true; |
| } |
| if (ctl_boot()) { |
| return true; |
| } |
| if (config_prof) { |
| prof_boot1(); |
| } |
| if (opt_hpa && !hpa_supported()) { |
| malloc_printf( |
| "<jemalloc>: HPA not supported in the current " |
| "configuration; %s.", |
| opt_abort_conf ? "aborting" : "disabling"); |
| if (opt_abort_conf) { |
| malloc_abort_invalid_conf(); |
| } else { |
| opt_hpa = false; |
| } |
| } |
| if (arena_boot(&sc_data, b0get(), opt_hpa)) { |
| return true; |
| } |
| if (tcache_boot(TSDN_NULL, b0get())) { |
| return true; |
| } |
| if (arenas_management_boot()) { |
| return true; |
| } |
| experimental_thread_events_boot(); |
| /* |
| * Create enough scaffolding to allow recursive allocation in |
| * os_cpu_ncpus(). |
| */ |
| narenas_auto_set(1); |
| manual_arena_base_set(narenas_auto + 1); |
| memset(arenas, 0, sizeof(arena_t *) * narenas_auto); |
| /* |
| * Initialize one arena here. The rest are lazily created in |
| * arena_choose_hard(). |
| */ |
| if (arena_init(TSDN_NULL, 0, &arena_config_default) == NULL) { |
| return true; |
| } |
| |
| if (opt_hpa && !hpa_supported()) { |
| malloc_printf( |
| "<jemalloc>: HPA not supported in the current " |
| "configuration; %s.", |
| opt_abort_conf ? "aborting" : "disabling"); |
| if (opt_abort_conf) { |
| malloc_abort_invalid_conf(); |
| } else { |
| opt_hpa = false; |
| } |
| } |
| |
| malloc_init_state = malloc_init_a0_initialized; |
| |
| size_t buf_len = strlen(readlink_buf); |
| if (buf_len > 0) { |
| void *readlink_allocated = a0malloc(buf_len + 1); |
| if (readlink_allocated != NULL) { |
| memcpy(readlink_allocated, readlink_buf, buf_len + 1); |
| opt_malloc_conf_symlink = readlink_allocated; |
| } |
| } |
| |
| return false; |
| } |
| |
| bool |
| malloc_init_hard_a0(void) { |
| bool ret; |
| |
| malloc_mutex_lock(TSDN_NULL, &init_lock); |
| ret = malloc_init_hard_a0_locked(); |
| malloc_mutex_unlock(TSDN_NULL, &init_lock); |
| return ret; |
| } |
| |
| static void |
| stats_print_atexit(void) { |
| if (config_stats) { |
| tsdn_t *tsdn; |
| unsigned narenas, i; |
| |
| tsdn = tsdn_fetch(); |
| |
| /* |
| * Merge stats from extant threads. This is racy, since |
| * individual threads do not lock when recording tcache stats |
| * events. As a consequence, the final stats may be slightly |
| * out of date by the time they are reported, if other threads |
| * continue to allocate. |
| */ |
| for (i = 0, narenas = narenas_total_get(); i < narenas; i++) { |
| arena_t *arena = arena_get(tsdn, i, false); |
| if (arena != NULL) { |
| arena_cache_bins_stats_merge(tsdn, arena); |
| } |
| } |
| } |
| je_malloc_stats_print(NULL, NULL, opt_stats_print_opts); |
| } |
| |
| /* Initialize data structures which may trigger recursive allocation. */ |
| static bool |
| malloc_init_hard_recursible(void) { |
| malloc_init_state = malloc_init_recursible; |
| |
| ncpus = os_cpu_ncpus(); |
| if (opt_percpu_arena != percpu_arena_disabled) { |
| bool cpu_count_is_deterministic = |
| os_cpu_count_is_deterministic(); |
| if (!cpu_count_is_deterministic) { |
| /* |
| * If # of CPU is not deterministic, and narenas not |
| * specified, disables per cpu arena since it may not |
| * detect CPU IDs properly. |
| */ |
| if (opt_narenas == 0) { |
| opt_percpu_arena = percpu_arena_disabled; |
| malloc_write( |
| "<jemalloc>: Number of CPUs " |
| "detected is not deterministic. Per-CPU " |
| "arena disabled.\n"); |
| if (opt_abort_conf) { |
| malloc_abort_invalid_conf(); |
| } |
| if (opt_abort) { |
| abort(); |
| } |
| } |
| } |
| } |
| |
| #if (defined(JEMALLOC_HAVE_PTHREAD_ATFORK) && !defined(JEMALLOC_MUTEX_INIT_CB) \ |
| && !defined(JEMALLOC_ZONE) && !defined(_WIN32) \ |
| && !defined(__native_client__)) |
| /* LinuxThreads' pthread_atfork() allocates. */ |
| if (pthread_atfork(jemalloc_prefork, jemalloc_postfork_parent, |
| jemalloc_postfork_child) |
| != 0) { |
| malloc_write("<jemalloc>: Error in pthread_atfork()\n"); |
| if (opt_abort) { |
| abort(); |
| } |
| return true; |
| } |
| #endif |
| |
| if (background_thread_boot0()) { |
| return true; |
| } |
| |
| return false; |
| } |
| |
| static unsigned |
| malloc_narenas_default(void) { |
| assert(ncpus > 0); |
| /* |
| * For SMP systems, create more than one arena per CPU by |
| * default. |
| */ |
| if (ncpus > 1) { |
| fxp_t fxp_ncpus = FXP_INIT_INT(ncpus); |
| fxp_t goal = fxp_mul(fxp_ncpus, opt_narenas_ratio); |
| uint32_t int_goal = fxp_round_nearest(goal); |
| if (int_goal == 0) { |
| return 1; |
| } |
| return int_goal; |
| } else { |
| return 1; |
| } |
| } |
| |
| static percpu_arena_mode_t |
| percpu_arena_as_initialized(percpu_arena_mode_t mode) { |
| assert(!malloc_initialized()); |
| assert(mode <= percpu_arena_disabled); |
| |
| if (mode != percpu_arena_disabled) { |
| mode += percpu_arena_mode_enabled_base; |
| } |
| |
| return mode; |
| } |
| |
| static bool |
| malloc_init_narenas(tsdn_t *tsdn) { |
| assert(ncpus > 0); |
| |
| if (opt_percpu_arena != percpu_arena_disabled) { |
| if (!have_percpu_arena || malloc_getcpu() < 0) { |
| opt_percpu_arena = percpu_arena_disabled; |
| malloc_printf( |
| "<jemalloc>: perCPU arena getcpu() not " |
| "available. Setting narenas to %u.\n", |
| opt_narenas ? opt_narenas |
| : malloc_narenas_default()); |
| if (opt_abort) { |
| abort(); |
| } |
| } else { |
| if (ncpus >= MALLOCX_ARENA_LIMIT) { |
| malloc_printf( |
| "<jemalloc>: narenas w/ percpu" |
| "arena beyond limit (%d)\n", |
| ncpus); |
| if (opt_abort) { |
| abort(); |
| } |
| return true; |
| } |
| /* NB: opt_percpu_arena isn't fully initialized yet. */ |
| if (percpu_arena_as_initialized(opt_percpu_arena) |
| == per_phycpu_arena |
| && ncpus % 2 != 0) { |
| malloc_printf( |
| "<jemalloc>: invalid " |
| "configuration -- per physical CPU arena " |
| "with odd number (%u) of CPUs (no hyper " |
| "threading?).\n", |
| ncpus); |
| if (opt_abort) |
| abort(); |
| } |
| unsigned n = percpu_arena_ind_limit( |
| percpu_arena_as_initialized(opt_percpu_arena)); |
| if (opt_narenas < n) { |
| /* |
| * If narenas is specified with percpu_arena |
| * enabled, actual narenas is set as the greater |
| * of the two. percpu_arena_choose will be free |
| * to use any of the arenas based on CPU |
| * id. This is conservative (at a small cost) |
| * but ensures correctness. |
| * |
| * If for some reason the ncpus determined at |
| * boot is not the actual number (e.g. because |
| * of affinity setting from numactl), reserving |
| * narenas this way provides a workaround for |
| * percpu_arena. |
| */ |
| opt_narenas = n; |
| } |
| } |
| } |
| if (opt_narenas == 0) { |
| opt_narenas = malloc_narenas_default(); |
| } |
| assert(opt_narenas > 0); |
| |
| narenas_auto_set(opt_narenas); |
| /* |
| * Limit the number of arenas to the indexing range of MALLOCX_ARENA(). |
| */ |
| if (narenas_auto >= MALLOCX_ARENA_LIMIT) { |
| narenas_auto_set(MALLOCX_ARENA_LIMIT - 1); |
| malloc_printf("<jemalloc>: Reducing narenas to limit (%d)\n", |
| narenas_auto); |
| } |
| narenas_total_set(narenas_auto); |
| if (arena_init_huge(tsdn, arena_get(tsdn, 0, false))) { |
| narenas_total_inc(); |
| } |
| manual_arena_base_set(narenas_total_get()); |
| |
| return false; |
| } |
| |
| static void |
| malloc_init_percpu(void) { |
| opt_percpu_arena = percpu_arena_as_initialized(opt_percpu_arena); |
| } |
| |
| static bool |
| malloc_init_hard_finish(void) { |
| if (malloc_mutex_boot()) { |
| return true; |
| } |
| |
| malloc_init_state = malloc_init_initialized; |
| malloc_slow_flag_init(); |
| |
| return false; |
| } |
| |
| static void |
| malloc_init_hard_cleanup(tsdn_t *tsdn, bool reentrancy_set) { |
| malloc_mutex_assert_owner(tsdn, &init_lock); |
| malloc_mutex_unlock(tsdn, &init_lock); |
| if (reentrancy_set) { |
| assert(!tsdn_null(tsdn)); |
| tsd_t *tsd = tsdn_tsd(tsdn); |
| assert(tsd_reentrancy_level_get(tsd) > 0); |
| post_reentrancy(tsd); |
| } |
| } |
| |
| bool |
| malloc_init_hard(void) { |
| tsd_t *tsd; |
| |
| assert(TCACHE_MAXCLASS_LIMIT <= USIZE_GROW_SLOW_THRESHOLD); |
| assert(SC_LOOKUP_MAXCLASS <= USIZE_GROW_SLOW_THRESHOLD); |
| /* |
| * This asserts an extreme case where TINY_MAXCLASS is larger |
| * than LARGE_MINCLASS. It could only happen if some constants |
| * are configured miserably wrong. |
| */ |
| assert(SC_NTINY == 0 || SC_LG_TINY_MAXCLASS <= SC_LG_LARGE_MINCLASS); |
| |
| #if !OS_MUTEX_HAS_STATIC_INIT |
| _init_init_lock(); |
| #endif |
| malloc_mutex_lock(TSDN_NULL, &init_lock); |
| |
| #define UNLOCK_RETURN(tsdn, ret, reentrancy) \ |
| malloc_init_hard_cleanup(tsdn, reentrancy); \ |
| return ret; |
| |
| if (!malloc_init_hard_needed()) { |
| UNLOCK_RETURN(TSDN_NULL, false, false) |
| } |
| |
| if (malloc_init_state != malloc_init_a0_initialized |
| && malloc_init_hard_a0_locked()) { |
| UNLOCK_RETURN(TSDN_NULL, true, false) |
| } |
| |
| malloc_mutex_unlock(TSDN_NULL, &init_lock); |
| /* Recursive allocation relies on functional tsd. */ |
| tsd = malloc_tsd_boot0(); |
| if (tsd == NULL) { |
| return true; |
| } |
| if (malloc_init_hard_recursible()) { |
| return true; |
| } |
| |
| malloc_mutex_lock(tsd_tsdn(tsd), &init_lock); |
| /* Set reentrancy level to 1 during init. */ |
| pre_reentrancy(tsd, NULL); |
| /* Initialize narenas before prof_boot2 (for allocation). */ |
| if (malloc_init_narenas(tsd_tsdn(tsd)) |
| || background_thread_boot1(tsd_tsdn(tsd), b0get())) { |
| UNLOCK_RETURN(tsd_tsdn(tsd), true, true) |
| } |
| if (opt_hpa) { |
| /* |
| * We didn't initialize arena 0 hpa_shard in arena_new, because |
| * background_thread_enabled wasn't initialized yet, but we |
| * need it to set correct value for deferral_allowed. |
| */ |
| arena_t *a0 = arena_get(tsd_tsdn(tsd), 0, false); |
| hpa_shard_opts_t hpa_shard_opts = opt_hpa_opts; |
| hpa_shard_opts.deferral_allowed = background_thread_enabled(); |
| if (pa_shard_enable_hpa(tsd_tsdn(tsd), &a0->pa_shard, |
| &hpa_shard_opts, &opt_hpa_sec_opts)) { |
| UNLOCK_RETURN(tsd_tsdn(tsd), true, true) |
| } |
| } |
| if (config_prof && prof_boot2(tsd, b0get())) { |
| UNLOCK_RETURN(tsd_tsdn(tsd), true, true) |
| } |
| |
| malloc_init_percpu(); |
| |
| if (malloc_init_hard_finish()) { |
| UNLOCK_RETURN(tsd_tsdn(tsd), true, true) |
| } |
| post_reentrancy(tsd); |
| malloc_mutex_unlock(tsd_tsdn(tsd), &init_lock); |
| |
| witness_assert_lockless( |
| witness_tsd_tsdn(tsd_witness_tsdp_get_unsafe(tsd))); |
| malloc_tsd_boot1(); |
| /* Update TSD after tsd_boot1. */ |
| tsd = tsd_fetch(); |
| if (opt_background_thread) { |
| assert(have_background_thread); |
| /* |
| * Need to finish init & unlock first before creating background |
| * threads (pthread_create depends on malloc). ctl_init (which |
| * sets isthreaded) needs to be called without holding any lock. |
| */ |
| background_thread_ctl_init(tsd_tsdn(tsd)); |
| if (background_thread_create(tsd, 0)) { |
| return true; |
| } |
| } |
| #undef UNLOCK_RETURN |
| return false; |
| } |
| |
| /* |
| * If an application creates a thread before doing any allocation in the main |
| * thread, then calls fork(2) in the main thread followed by memory allocation |
| * in the child process, a race can occur that results in deadlock within the |
| * child: the main thread may have forked while the created thread had |
| * partially initialized the allocator. Ordinarily jemalloc prevents |
| * fork/malloc races via the following functions it registers during |
| * initialization using pthread_atfork(), but of course that does no good if |
| * the allocator isn't fully initialized at fork time. The following library |
| * constructor is a partial solution to this problem. It may still be possible |
| * to trigger the deadlock described above, but doing so would involve forking |
| * via a library constructor that runs before jemalloc's runs. |
| */ |
| #ifndef JEMALLOC_JET |
| JEMALLOC_ATTR(constructor) |
| static void |
| jemalloc_constructor(void) { |
| malloc_init(); |
| } |
| #endif |