#define JEMALLOC_CHUNK_DSS_C_ #include "internal/jemalloc_internal.h" #ifdef JEMALLOC_DSS /******************************************************************************/ /* Data. */ malloc_mutex_t dss_mtx; /* Base address of the DSS. */ static void *dss_base; /* Current end of the DSS, or ((void *)-1) if the DSS is exhausted. */ static void *dss_prev; /* Current upper limit on DSS addresses. */ static void *dss_max; /* * Trees of chunks that were previously allocated (trees differ only in node * ordering). These are used when allocating chunks, in an attempt to re-use * address space. Depending on function, different tree orderings are needed, * which is why there are two trees with the same contents. */ static extent_tree_t dss_chunks_szad; static extent_tree_t dss_chunks_ad; /******************************************************************************/ /* Function prototypes for non-inline static functions. */ static void *chunk_recycle_dss(size_t size, bool *zero); static extent_node_t *chunk_dealloc_dss_record(void *chunk, size_t size); /******************************************************************************/ static void * chunk_recycle_dss(size_t size, bool *zero) { extent_node_t *node, key; key.addr = NULL; key.size = size; malloc_mutex_lock(&dss_mtx); node = extent_tree_szad_nsearch(&dss_chunks_szad, &key); if (node != NULL) { void *ret = node->addr; /* Remove node from the tree. */ extent_tree_szad_remove(&dss_chunks_szad, node); if (node->size == size) { extent_tree_ad_remove(&dss_chunks_ad, node); base_node_dealloc(node); } else { /* * Insert the remainder of node's address range as a * smaller chunk. Its position within dss_chunks_ad * does not change. */ assert(node->size > size); node->addr = (void *)((uintptr_t)node->addr + size); node->size -= size; extent_tree_szad_insert(&dss_chunks_szad, node); } malloc_mutex_unlock(&dss_mtx); if (*zero) memset(ret, 0, size); return (ret); } malloc_mutex_unlock(&dss_mtx); return (NULL); } void * chunk_alloc_dss(size_t size, bool *zero) { void *ret; ret = chunk_recycle_dss(size, zero); if (ret != NULL) return (ret); /* * sbrk() uses a signed increment argument, so take care not to * interpret a huge allocation request as a negative increment. */ if ((intptr_t)size < 0) return (NULL); malloc_mutex_lock(&dss_mtx); if (dss_prev != (void *)-1) { intptr_t incr; /* * The loop is necessary to recover from races with other * threads that are using the DSS for something other than * malloc. */ do { /* Get the current end of the DSS. */ dss_max = sbrk(0); /* * Calculate how much padding is necessary to * chunk-align the end of the DSS. */ incr = (intptr_t)size - (intptr_t)CHUNK_ADDR2OFFSET(dss_max); if (incr == (intptr_t)size) ret = dss_max; else { ret = (void *)((intptr_t)dss_max + incr); incr += size; } dss_prev = sbrk(incr); if (dss_prev == dss_max) { /* Success. */ dss_max = (void *)((intptr_t)dss_prev + incr); malloc_mutex_unlock(&dss_mtx); *zero = true; return (ret); } } while (dss_prev != (void *)-1); } malloc_mutex_unlock(&dss_mtx); return (NULL); } static extent_node_t * chunk_dealloc_dss_record(void *chunk, size_t size) { extent_node_t *xnode, *node, *prev, key; xnode = NULL; while (true) { key.addr = (void *)((uintptr_t)chunk + size); node = extent_tree_ad_nsearch(&dss_chunks_ad, &key); /* Try to coalesce forward. */ if (node != NULL && node->addr == key.addr) { /* * Coalesce chunk with the following address range. * This does not change the position within * dss_chunks_ad, so only remove/insert from/into * dss_chunks_szad. */ extent_tree_szad_remove(&dss_chunks_szad, node); node->addr = chunk; node->size += size; extent_tree_szad_insert(&dss_chunks_szad, node); break; } else if (xnode == NULL) { /* * It is possible that base_node_alloc() will cause a * new base chunk to be allocated, so take care not to * deadlock on dss_mtx, and recover if another thread * deallocates an adjacent chunk while this one is busy * allocating xnode. */ malloc_mutex_unlock(&dss_mtx); xnode = base_node_alloc(); malloc_mutex_lock(&dss_mtx); if (xnode == NULL) return (NULL); } else { /* Coalescing forward failed, so insert a new node. */ node = xnode; xnode = NULL; node->addr = chunk; node->size = size; extent_tree_ad_insert(&dss_chunks_ad, node); extent_tree_szad_insert(&dss_chunks_szad, node); break; } } /* Discard xnode if it ended up unused do to a race. */ if (xnode != NULL) base_node_dealloc(xnode); /* Try to coalesce backward. */ prev = extent_tree_ad_prev(&dss_chunks_ad, node); if (prev != NULL && (void *)((uintptr_t)prev->addr + prev->size) == chunk) { /* * Coalesce chunk with the previous address range. This does * not change the position within dss_chunks_ad, so only * remove/insert node from/into dss_chunks_szad. */ extent_tree_szad_remove(&dss_chunks_szad, prev); extent_tree_ad_remove(&dss_chunks_ad, prev); extent_tree_szad_remove(&dss_chunks_szad, node); node->addr = prev->addr; node->size += prev->size; extent_tree_szad_insert(&dss_chunks_szad, node); base_node_dealloc(prev); } return (node); } bool chunk_dealloc_dss(void *chunk, size_t size) { bool ret; malloc_mutex_lock(&dss_mtx); if ((uintptr_t)chunk >= (uintptr_t)dss_base && (uintptr_t)chunk < (uintptr_t)dss_max) { extent_node_t *node; /* Try to coalesce with other unused chunks. */ node = chunk_dealloc_dss_record(chunk, size); if (node != NULL) { chunk = node->addr; size = node->size; } /* Get the current end of the DSS. */ dss_max = sbrk(0); /* * Try to shrink the DSS if this chunk is at the end of the * DSS. The sbrk() call here is subject to a race condition * with threads that use brk(2) or sbrk(2) directly, but the * alternative would be to leak memory for the sake of poorly * designed multi-threaded programs. */ if ((void *)((uintptr_t)chunk + size) == dss_max && (dss_prev = sbrk(-(intptr_t)size)) == dss_max) { /* Success. */ dss_max = (void *)((intptr_t)dss_prev - (intptr_t)size); if (node != NULL) { extent_tree_szad_remove(&dss_chunks_szad, node); extent_tree_ad_remove(&dss_chunks_ad, node); base_node_dealloc(node); } } else madvise(chunk, size, MADV_DONTNEED); ret = false; goto RETURN; } ret = true; RETURN: malloc_mutex_unlock(&dss_mtx); return (ret); } bool chunk_dss_boot(void) { if (malloc_mutex_init(&dss_mtx)) return (true); dss_base = sbrk(0); dss_prev = dss_base; dss_max = dss_base; extent_tree_szad_new(&dss_chunks_szad); extent_tree_ad_new(&dss_chunks_ad); return (false); } /******************************************************************************/ #endif /* JEMALLOC_DSS */