| |
| /* |
| * Copyright (C) Igor Sysoev |
| * Copyright (C) NGINX, Inc. |
| */ |
| |
| |
| #include <njs_main.h> |
| |
| |
| /* |
| * A memory cache pool allocates memory in clusters of specified size and |
| * aligned to page_alignment. A cluster is divided on pages of specified |
| * size. Page size must be a power of 2. A page can be used entirely or |
| * can be divided on chunks of equal size. Chunk size must be a power of 2. |
| * A cluster can contains pages with different chunk sizes. Cluster size |
| * must be a multiple of page size and may be not a power of 2. Allocations |
| * greater than page are allocated outside clusters. Start addresses and |
| * sizes of the clusters and large allocations are stored in rbtree blocks |
| * to find them on free operations. The rbtree nodes are sorted by start |
| * addresses. |
| */ |
| |
| |
| typedef struct { |
| /* |
| * Used to link pages with free chunks in pool chunk slot list |
| * or to link free pages in clusters. |
| */ |
| njs_queue_link_t link; |
| |
| /* |
| * Size of chunks or page shifted by mp->chunk_size_shift. |
| * Zero means that page is free. |
| */ |
| uint8_t size; |
| |
| /* |
| * Page number in page cluster. |
| * There can be no more than 256 pages in a cluster. |
| */ |
| uint8_t number; |
| |
| /* Number of free chunks of a chunked page. */ |
| uint8_t chunks; |
| |
| uint8_t _unused; |
| |
| /* Chunk bitmap. There can be no more than 32 chunks in a page. */ |
| uint8_t map[4]; |
| } njs_mp_page_t; |
| |
| |
| typedef enum { |
| /* Block of cluster. The block is allocated apart of the cluster. */ |
| NJS_MP_CLUSTER_BLOCK = 0, |
| /* |
| * Block of large allocation. |
| * The block is allocated apart of the allocation. |
| */ |
| NJS_MP_DISCRETE_BLOCK, |
| /* |
| * Block of large allocation. |
| * The block is allocated just after of the allocation. |
| */ |
| NJS_MP_EMBEDDED_BLOCK, |
| } njs_mp_block_type_t; |
| |
| |
| typedef struct { |
| NJS_RBTREE_NODE (node); |
| njs_mp_block_type_t type:8; |
| |
| /* Block size must be less than 4G. */ |
| uint32_t size; |
| |
| u_char *start; |
| njs_mp_page_t pages[]; |
| } njs_mp_block_t; |
| |
| |
| typedef struct { |
| njs_queue_t pages; |
| |
| /* Size of page chunks. */ |
| #if (NJS_64BIT) |
| uint32_t size; |
| #else |
| uint16_t size; |
| #endif |
| |
| /* Maximum number of free chunks in chunked page. */ |
| uint8_t chunks; |
| } njs_mp_slot_t; |
| |
| |
| struct njs_mp_s { |
| /* rbtree of njs_mp_block_t. */ |
| njs_rbtree_t blocks; |
| |
| njs_queue_t free_pages; |
| |
| uint8_t chunk_size_shift; |
| uint8_t page_size_shift; |
| uint32_t page_size; |
| uint32_t page_alignment; |
| uint32_t cluster_size; |
| |
| njs_mp_cleanup_t *cleanup; |
| |
| njs_mp_slot_t slots[]; |
| }; |
| |
| |
| #define njs_mp_chunk_is_free(map, chunk) \ |
| ((map[chunk / 8] & (0x80 >> (chunk & 7))) == 0) |
| |
| |
| #define njs_mp_chunk_set_free(map, chunk) \ |
| map[chunk / 8] &= ~(0x80 >> (chunk & 7)) |
| |
| |
| #define njs_mp_free_junk(p, size) \ |
| njs_memset((p), 0x5A, size) |
| |
| |
| #define njs_is_power_of_two(value) \ |
| ((((value) - 1) & (value)) == 0) |
| |
| |
| static njs_uint_t njs_mp_shift(njs_uint_t n); |
| #if !(NJS_DEBUG_MEMORY) |
| static void *njs_mp_alloc_small(njs_mp_t *mp, size_t size); |
| static njs_uint_t njs_mp_alloc_chunk(u_char *map, njs_uint_t size); |
| static njs_mp_page_t *njs_mp_alloc_page(njs_mp_t *mp); |
| static njs_mp_block_t *njs_mp_alloc_cluster(njs_mp_t *mp); |
| #endif |
| static void *njs_mp_alloc_large(njs_mp_t *mp, size_t alignment, size_t size); |
| static intptr_t njs_mp_rbtree_compare(njs_rbtree_node_t *node1, |
| njs_rbtree_node_t *node2); |
| static njs_mp_block_t *njs_mp_find_block(njs_rbtree_t *tree, |
| u_char *p); |
| static const char *njs_mp_chunk_free(njs_mp_t *mp, njs_mp_block_t *cluster, |
| u_char *p); |
| |
| |
| njs_mp_t * |
| njs_mp_create(size_t cluster_size, size_t page_alignment, size_t page_size, |
| size_t min_chunk_size) |
| { |
| /* Alignment and sizes must be a power of 2. */ |
| |
| if (njs_slow_path(!njs_is_power_of_two(page_alignment) |
| || !njs_is_power_of_two(page_size) |
| || !njs_is_power_of_two(min_chunk_size))) |
| { |
| return NULL; |
| } |
| |
| page_alignment = njs_max(page_alignment, NJS_MAX_ALIGNMENT); |
| |
| if (njs_slow_path(page_size < 64 |
| || page_size < page_alignment |
| || page_size < min_chunk_size |
| || min_chunk_size * 32 < page_size |
| || cluster_size < page_size |
| || cluster_size / page_size > 256 |
| || cluster_size % page_size != 0)) |
| { |
| return NULL; |
| } |
| |
| return njs_mp_fast_create(cluster_size, page_alignment, page_size, |
| min_chunk_size); |
| } |
| |
| |
| njs_mp_t * |
| njs_mp_fast_create(size_t cluster_size, size_t page_alignment, size_t page_size, |
| size_t min_chunk_size) |
| { |
| njs_mp_t *mp; |
| njs_uint_t slots, chunk_size; |
| njs_mp_slot_t *slot; |
| |
| slots = 0; |
| chunk_size = page_size; |
| |
| do { |
| slots++; |
| chunk_size /= 2; |
| } while (chunk_size > min_chunk_size); |
| |
| mp = njs_zalloc(sizeof(njs_mp_t) + slots * sizeof(njs_mp_slot_t)); |
| |
| if (njs_fast_path(mp != NULL)) { |
| mp->page_size = page_size; |
| mp->page_alignment = njs_max(page_alignment, NJS_MAX_ALIGNMENT); |
| mp->cluster_size = cluster_size; |
| |
| slot = mp->slots; |
| |
| do { |
| njs_queue_init(&slot->pages); |
| |
| slot->size = chunk_size; |
| /* slot->chunks should be one less than actual number of chunks. */ |
| slot->chunks = (page_size / chunk_size) - 1; |
| |
| slot++; |
| chunk_size *= 2; |
| } while (chunk_size < page_size); |
| |
| mp->chunk_size_shift = njs_mp_shift(min_chunk_size); |
| mp->page_size_shift = njs_mp_shift(page_size); |
| |
| njs_rbtree_init(&mp->blocks, njs_mp_rbtree_compare); |
| |
| njs_queue_init(&mp->free_pages); |
| } |
| |
| return mp; |
| } |
| |
| |
| static njs_uint_t |
| njs_mp_shift(njs_uint_t n) |
| { |
| njs_uint_t shift; |
| |
| shift = 0; |
| n /= 2; |
| |
| do { |
| shift++; |
| n /= 2; |
| } while (n != 0); |
| |
| return shift; |
| } |
| |
| |
| njs_bool_t |
| njs_mp_is_empty(njs_mp_t *mp) |
| { |
| return (njs_rbtree_is_empty(&mp->blocks) |
| && njs_queue_is_empty(&mp->free_pages)); |
| } |
| |
| |
| void |
| njs_mp_destroy(njs_mp_t *mp) |
| { |
| void *p; |
| njs_mp_block_t *block; |
| njs_mp_cleanup_t *c; |
| njs_rbtree_node_t *node, *next; |
| |
| njs_debug_alloc("mp destroy\n"); |
| |
| for (c = mp->cleanup; c != NULL; c = c->next) { |
| if (c->handler != NULL) { |
| njs_debug_alloc("mp run cleanup: @%p\n", c); |
| c->handler(c->data); |
| } |
| } |
| |
| next = njs_rbtree_root(&mp->blocks); |
| |
| while (next != njs_rbtree_sentinel(&mp->blocks)) { |
| |
| node = njs_rbtree_destroy_next(&mp->blocks, &next); |
| block = (njs_mp_block_t *) node; |
| |
| p = block->start; |
| |
| if (block->type != NJS_MP_EMBEDDED_BLOCK) { |
| njs_free(block); |
| } |
| |
| njs_free(p); |
| } |
| |
| njs_free(mp); |
| } |
| |
| |
| void * |
| njs_mp_alloc(njs_mp_t *mp, size_t size) |
| { |
| njs_debug_alloc("mp alloc: %uz\n", size); |
| |
| #if !(NJS_DEBUG_MEMORY) |
| |
| if (size <= mp->page_size) { |
| return njs_mp_alloc_small(mp, size); |
| } |
| |
| #endif |
| |
| return njs_mp_alloc_large(mp, NJS_MAX_ALIGNMENT, size); |
| } |
| |
| |
| void * |
| njs_mp_zalloc(njs_mp_t *mp, size_t size) |
| { |
| void *p; |
| |
| p = njs_mp_alloc(mp, size); |
| |
| if (njs_fast_path(p != NULL)) { |
| njs_memzero(p, size); |
| } |
| |
| return p; |
| } |
| |
| |
| void * |
| njs_mp_align(njs_mp_t *mp, size_t alignment, size_t size) |
| { |
| njs_debug_alloc("mp align: @%uz:%uz\n", alignment, size); |
| |
| /* Alignment must be a power of 2. */ |
| |
| if (njs_fast_path(njs_is_power_of_two(alignment))) { |
| |
| #if !(NJS_DEBUG_MEMORY) |
| |
| if (size <= mp->page_size && alignment <= mp->page_alignment) { |
| size = njs_max(size, alignment); |
| |
| if (size <= mp->page_size) { |
| return njs_mp_alloc_small(mp, size); |
| } |
| } |
| |
| #endif |
| |
| return njs_mp_alloc_large(mp, alignment, size); |
| } |
| |
| return NULL; |
| } |
| |
| |
| void * |
| njs_mp_zalign(njs_mp_t *mp, size_t alignment, size_t size) |
| { |
| void *p; |
| |
| p = njs_mp_align(mp, alignment, size); |
| |
| if (njs_fast_path(p != NULL)) { |
| njs_memzero(p, size); |
| } |
| |
| return p; |
| } |
| |
| |
| #if !(NJS_DEBUG_MEMORY) |
| |
| njs_inline u_char * |
| njs_mp_page_addr(njs_mp_t *mp, njs_mp_page_t *page) |
| { |
| njs_mp_block_t *block; |
| |
| block = (njs_mp_block_t *) |
| ((u_char *) page - page->number * sizeof(njs_mp_page_t) |
| - offsetof(njs_mp_block_t, pages)); |
| |
| return block->start + (page->number << mp->page_size_shift); |
| } |
| |
| |
| static void * |
| njs_mp_alloc_small(njs_mp_t *mp, size_t size) |
| { |
| u_char *p; |
| njs_mp_page_t *page; |
| njs_mp_slot_t *slot; |
| njs_queue_link_t *link; |
| |
| p = NULL; |
| |
| if (size <= mp->page_size / 2) { |
| |
| /* Find a slot with appropriate chunk size. */ |
| for (slot = mp->slots; slot->size < size; slot++) { /* void */ } |
| |
| size = slot->size; |
| |
| if (njs_fast_path(!njs_queue_is_empty(&slot->pages))) { |
| |
| link = njs_queue_first(&slot->pages); |
| page = njs_queue_link_data(link, njs_mp_page_t, link); |
| |
| p = njs_mp_page_addr(mp, page); |
| p += njs_mp_alloc_chunk(page->map, size); |
| |
| page->chunks--; |
| |
| if (page->chunks == 0) { |
| /* |
| * Remove full page from the mp chunk slot list |
| * of pages with free chunks. |
| */ |
| njs_queue_remove(&page->link); |
| } |
| |
| } else { |
| page = njs_mp_alloc_page(mp); |
| |
| if (njs_fast_path(page != NULL)) { |
| |
| njs_queue_insert_head(&slot->pages, &page->link); |
| |
| /* Mark the first chunk as busy. */ |
| page->map[0] = 0x80; |
| page->map[1] = 0; |
| page->map[2] = 0; |
| page->map[3] = 0; |
| |
| /* slot->chunks are already one less. */ |
| page->chunks = slot->chunks; |
| page->size = size >> mp->chunk_size_shift; |
| |
| p = njs_mp_page_addr(mp, page); |
| } |
| } |
| |
| } else { |
| page = njs_mp_alloc_page(mp); |
| |
| if (njs_fast_path(page != NULL)) { |
| page->size = mp->page_size >> mp->chunk_size_shift; |
| |
| p = njs_mp_page_addr(mp, page); |
| } |
| |
| #if (NJS_DEBUG) |
| size = mp->page_size; |
| #endif |
| } |
| |
| njs_debug_alloc("mp chunk:%uz alloc: %p\n", size, p); |
| |
| return p; |
| } |
| |
| |
| static njs_uint_t |
| njs_mp_alloc_chunk(uint8_t *map, njs_uint_t size) |
| { |
| uint8_t mask; |
| njs_uint_t n, offset; |
| |
| offset = 0; |
| n = 0; |
| |
| /* The page must have at least one free chunk. */ |
| |
| for ( ;; ) { |
| if (map[n] != 0xff) { |
| |
| mask = 0x80; |
| |
| do { |
| if ((map[n] & mask) == 0) { |
| /* A free chunk is found. */ |
| map[n] |= mask; |
| return offset; |
| } |
| |
| offset += size; |
| mask >>= 1; |
| |
| } while (mask != 0); |
| |
| } else { |
| /* Fast-forward: all 8 chunks are occupied. */ |
| offset += size * 8; |
| } |
| |
| n++; |
| } |
| } |
| |
| |
| static njs_mp_page_t * |
| njs_mp_alloc_page(njs_mp_t *mp) |
| { |
| njs_mp_page_t *page; |
| njs_mp_block_t *cluster; |
| njs_queue_link_t *link; |
| |
| if (njs_queue_is_empty(&mp->free_pages)) { |
| cluster = njs_mp_alloc_cluster(mp); |
| if (njs_slow_path(cluster == NULL)) { |
| return NULL; |
| } |
| } |
| |
| link = njs_queue_first(&mp->free_pages); |
| njs_queue_remove(link); |
| |
| page = njs_queue_link_data(link, njs_mp_page_t, link); |
| |
| return page; |
| } |
| |
| |
| static njs_mp_block_t * |
| njs_mp_alloc_cluster(njs_mp_t *mp) |
| { |
| njs_uint_t n; |
| njs_mp_block_t *cluster; |
| |
| n = mp->cluster_size >> mp->page_size_shift; |
| |
| cluster = njs_zalloc(sizeof(njs_mp_block_t) + n * sizeof(njs_mp_page_t)); |
| |
| if (njs_slow_path(cluster == NULL)) { |
| return NULL; |
| } |
| |
| /* NJS_MP_CLUSTER_BLOCK type is zero. */ |
| |
| cluster->size = mp->cluster_size; |
| |
| cluster->start = njs_memalign(mp->page_alignment, mp->cluster_size); |
| if (njs_slow_path(cluster->start == NULL)) { |
| njs_free(cluster); |
| return NULL; |
| } |
| |
| n--; |
| cluster->pages[n].number = n; |
| njs_queue_insert_head(&mp->free_pages, &cluster->pages[n].link); |
| |
| while (n != 0) { |
| n--; |
| cluster->pages[n].number = n; |
| njs_queue_insert_before(&cluster->pages[n + 1].link, |
| &cluster->pages[n].link); |
| } |
| |
| njs_rbtree_insert(&mp->blocks, &cluster->node); |
| |
| return cluster; |
| } |
| |
| #endif |
| |
| |
| static void * |
| njs_mp_alloc_large(njs_mp_t *mp, size_t alignment, size_t size) |
| { |
| u_char *p; |
| size_t aligned_size; |
| uint8_t type; |
| njs_mp_block_t *block; |
| |
| /* Allocation must be less than 4G. */ |
| if (njs_slow_path(size >= UINT32_MAX)) { |
| return NULL; |
| } |
| |
| if (njs_is_power_of_two(size)) { |
| block = njs_malloc(sizeof(njs_mp_block_t)); |
| if (njs_slow_path(block == NULL)) { |
| return NULL; |
| } |
| |
| p = njs_memalign(alignment, size); |
| if (njs_slow_path(p == NULL)) { |
| njs_free(block); |
| return NULL; |
| } |
| |
| type = NJS_MP_DISCRETE_BLOCK; |
| |
| } else { |
| aligned_size = njs_align_size(size, sizeof(uintptr_t)); |
| |
| p = njs_memalign(alignment, aligned_size + sizeof(njs_mp_block_t)); |
| if (njs_slow_path(p == NULL)) { |
| return NULL; |
| } |
| |
| block = (njs_mp_block_t *) (p + aligned_size); |
| type = NJS_MP_EMBEDDED_BLOCK; |
| } |
| |
| block->type = type; |
| block->size = size; |
| block->start = p; |
| |
| njs_rbtree_insert(&mp->blocks, &block->node); |
| |
| return p; |
| } |
| |
| |
| static intptr_t |
| njs_mp_rbtree_compare(njs_rbtree_node_t *node1, njs_rbtree_node_t *node2) |
| { |
| njs_mp_block_t *block1, *block2; |
| |
| block1 = (njs_mp_block_t *) node1; |
| block2 = (njs_mp_block_t *) node2; |
| |
| return (uintptr_t) block1->start - (uintptr_t) block2->start; |
| } |
| |
| |
| njs_mp_cleanup_t * |
| njs_mp_cleanup_add(njs_mp_t *mp, size_t size) |
| { |
| njs_mp_cleanup_t *c; |
| |
| c = njs_mp_alloc(mp, sizeof(njs_mp_cleanup_t)); |
| if (njs_slow_path(c == NULL)) { |
| return NULL; |
| } |
| |
| if (size) { |
| c->data = njs_mp_alloc(mp, size); |
| if (njs_slow_path(c->data == NULL)) { |
| return NULL; |
| } |
| |
| } else { |
| c->data = NULL; |
| } |
| |
| c->handler = NULL; |
| c->next = mp->cleanup; |
| |
| mp->cleanup = c; |
| |
| njs_debug_alloc("mp add cleanup: @%p\n", c); |
| |
| return c; |
| } |
| |
| |
| void |
| njs_mp_free(njs_mp_t *mp, void *p) |
| { |
| const char *err; |
| njs_mp_block_t *block; |
| |
| njs_debug_alloc("mp free: @%p\n", p); |
| |
| block = njs_mp_find_block(&mp->blocks, p); |
| |
| if (njs_fast_path(block != NULL)) { |
| |
| if (block->type == NJS_MP_CLUSTER_BLOCK) { |
| err = njs_mp_chunk_free(mp, block, p); |
| |
| if (njs_fast_path(err == NULL)) { |
| return; |
| } |
| |
| } else if (njs_fast_path(p == block->start)) { |
| njs_rbtree_delete(&mp->blocks, &block->node); |
| |
| if (block->type == NJS_MP_DISCRETE_BLOCK) { |
| njs_free(block); |
| } |
| |
| njs_free(p); |
| |
| return; |
| |
| } else { |
| err = "freed pointer points to middle of block: %p\n"; |
| } |
| |
| } else { |
| err = "freed pointer is out of mp: %p\n"; |
| } |
| |
| njs_debug_alloc(err, p); |
| } |
| |
| |
| static njs_mp_block_t * |
| njs_mp_find_block(njs_rbtree_t *tree, u_char *p) |
| { |
| njs_mp_block_t *block; |
| njs_rbtree_node_t *node, *sentinel; |
| |
| node = njs_rbtree_root(tree); |
| sentinel = njs_rbtree_sentinel(tree); |
| |
| while (node != sentinel) { |
| |
| block = (njs_mp_block_t *) node; |
| |
| if (p < block->start) { |
| node = node->left; |
| |
| } else if (p >= block->start + block->size) { |
| node = node->right; |
| |
| } else { |
| return block; |
| } |
| } |
| |
| return NULL; |
| } |
| |
| |
| static const char * |
| njs_mp_chunk_free(njs_mp_t *mp, njs_mp_block_t *cluster, |
| u_char *p) |
| { |
| u_char *start; |
| uintptr_t offset; |
| njs_uint_t n, size, chunk; |
| njs_mp_page_t *page; |
| njs_mp_slot_t *slot; |
| |
| n = (p - cluster->start) >> mp->page_size_shift; |
| start = cluster->start + (n << mp->page_size_shift); |
| |
| page = &cluster->pages[n]; |
| |
| if (page->size == 0) { |
| return "freed pointer points to already free page: %p"; |
| } |
| |
| size = page->size << mp->chunk_size_shift; |
| |
| if (size != mp->page_size) { |
| |
| offset = (uintptr_t) (p - start) & (mp->page_size - 1); |
| chunk = offset / size; |
| |
| if (njs_slow_path(offset != chunk * size)) { |
| return "freed pointer points to wrong chunk: %p"; |
| } |
| |
| if (njs_slow_path(njs_mp_chunk_is_free(page->map, chunk))) { |
| return "freed pointer points to already free chunk: %p"; |
| } |
| |
| njs_mp_chunk_set_free(page->map, chunk); |
| |
| /* Find a slot with appropriate chunk size. */ |
| for (slot = mp->slots; slot->size < size; slot++) { /* void */ } |
| |
| if (page->chunks != slot->chunks) { |
| page->chunks++; |
| |
| if (page->chunks == 1) { |
| /* |
| * Add the page to the head of mp chunk slot list |
| * of pages with free chunks. |
| */ |
| njs_queue_insert_head(&slot->pages, &page->link); |
| } |
| |
| njs_mp_free_junk(p, size); |
| |
| return NULL; |
| |
| } else { |
| /* |
| * All chunks are free, remove the page from mp chunk slot |
| * list of pages with free chunks. |
| */ |
| njs_queue_remove(&page->link); |
| } |
| |
| } else if (njs_slow_path(p != start)) { |
| return "invalid pointer to chunk: %p"; |
| } |
| |
| /* Add the free page to the mp's free pages tree. */ |
| |
| page->size = 0; |
| njs_queue_insert_head(&mp->free_pages, &page->link); |
| |
| njs_mp_free_junk(p, size); |
| |
| /* Test if all pages in the cluster are free. */ |
| |
| page = cluster->pages; |
| n = mp->cluster_size >> mp->page_size_shift; |
| |
| do { |
| if (page->size != 0) { |
| return NULL; |
| } |
| |
| page++; |
| n--; |
| } while (n != 0); |
| |
| /* Free cluster. */ |
| |
| page = cluster->pages; |
| n = mp->cluster_size >> mp->page_size_shift; |
| |
| do { |
| njs_queue_remove(&page->link); |
| page++; |
| n--; |
| } while (n != 0); |
| |
| njs_rbtree_delete(&mp->blocks, &cluster->node); |
| |
| p = cluster->start; |
| |
| njs_free(cluster); |
| njs_free(p); |
| |
| return NULL; |
| } |