| // SPDX-License-Identifier: GPL-2.0 |
| #include <stdlib.h> |
| #include <string.h> |
| #include <malloc.h> |
| #include <pthread.h> |
| #include <unistd.h> |
| #include <assert.h> |
| |
| #include <linux/gfp.h> |
| #include <linux/poison.h> |
| #include <linux/slab.h> |
| #include <linux/radix-tree.h> |
| #include <urcu/uatomic.h> |
| |
| int nr_allocated; |
| int preempt_count; |
| int test_verbose; |
| |
| struct kmem_cache { |
| pthread_mutex_t lock; |
| unsigned int size; |
| unsigned int align; |
| int nr_objs; |
| void *objs; |
| void (*ctor)(void *); |
| unsigned int non_kernel; |
| unsigned long nr_allocated; |
| unsigned long nr_tallocated; |
| bool exec_callback; |
| void (*callback)(void *); |
| void *private; |
| }; |
| |
| void kmem_cache_set_callback(struct kmem_cache *cachep, void (*callback)(void *)) |
| { |
| cachep->callback = callback; |
| } |
| |
| void kmem_cache_set_private(struct kmem_cache *cachep, void *private) |
| { |
| cachep->private = private; |
| } |
| |
| void kmem_cache_set_non_kernel(struct kmem_cache *cachep, unsigned int val) |
| { |
| cachep->non_kernel = val; |
| } |
| |
| unsigned long kmem_cache_get_alloc(struct kmem_cache *cachep) |
| { |
| return cachep->size * cachep->nr_allocated; |
| } |
| |
| unsigned long kmem_cache_nr_allocated(struct kmem_cache *cachep) |
| { |
| return cachep->nr_allocated; |
| } |
| |
| unsigned long kmem_cache_nr_tallocated(struct kmem_cache *cachep) |
| { |
| return cachep->nr_tallocated; |
| } |
| |
| void kmem_cache_zero_nr_tallocated(struct kmem_cache *cachep) |
| { |
| cachep->nr_tallocated = 0; |
| } |
| |
| void *kmem_cache_alloc_lru(struct kmem_cache *cachep, struct list_lru *lru, |
| int gfp) |
| { |
| void *p; |
| |
| if (cachep->exec_callback) { |
| if (cachep->callback) |
| cachep->callback(cachep->private); |
| cachep->exec_callback = false; |
| } |
| |
| if (!(gfp & __GFP_DIRECT_RECLAIM)) { |
| if (!cachep->non_kernel) { |
| cachep->exec_callback = true; |
| return NULL; |
| } |
| |
| cachep->non_kernel--; |
| } |
| |
| pthread_mutex_lock(&cachep->lock); |
| if (cachep->nr_objs) { |
| struct radix_tree_node *node = cachep->objs; |
| cachep->nr_objs--; |
| cachep->objs = node->parent; |
| pthread_mutex_unlock(&cachep->lock); |
| node->parent = NULL; |
| p = node; |
| } else { |
| pthread_mutex_unlock(&cachep->lock); |
| if (cachep->align) |
| posix_memalign(&p, cachep->align, cachep->size); |
| else |
| p = malloc(cachep->size); |
| if (cachep->ctor) |
| cachep->ctor(p); |
| else if (gfp & __GFP_ZERO) |
| memset(p, 0, cachep->size); |
| } |
| |
| uatomic_inc(&cachep->nr_allocated); |
| uatomic_inc(&nr_allocated); |
| uatomic_inc(&cachep->nr_tallocated); |
| if (kmalloc_verbose) |
| printf("Allocating %p from slab\n", p); |
| return p; |
| } |
| |
| void __kmem_cache_free_locked(struct kmem_cache *cachep, void *objp) |
| { |
| assert(objp); |
| if (cachep->nr_objs > 10 || cachep->align) { |
| memset(objp, POISON_FREE, cachep->size); |
| free(objp); |
| } else { |
| struct radix_tree_node *node = objp; |
| cachep->nr_objs++; |
| node->parent = cachep->objs; |
| cachep->objs = node; |
| } |
| } |
| |
| void kmem_cache_free_locked(struct kmem_cache *cachep, void *objp) |
| { |
| uatomic_dec(&nr_allocated); |
| uatomic_dec(&cachep->nr_allocated); |
| if (kmalloc_verbose) |
| printf("Freeing %p to slab\n", objp); |
| __kmem_cache_free_locked(cachep, objp); |
| } |
| |
| void kmem_cache_free(struct kmem_cache *cachep, void *objp) |
| { |
| pthread_mutex_lock(&cachep->lock); |
| kmem_cache_free_locked(cachep, objp); |
| pthread_mutex_unlock(&cachep->lock); |
| } |
| |
| void kmem_cache_free_bulk(struct kmem_cache *cachep, size_t size, void **list) |
| { |
| if (kmalloc_verbose) |
| pr_debug("Bulk free %p[0-%lu]\n", list, size - 1); |
| |
| pthread_mutex_lock(&cachep->lock); |
| for (int i = 0; i < size; i++) |
| kmem_cache_free_locked(cachep, list[i]); |
| pthread_mutex_unlock(&cachep->lock); |
| } |
| |
| void kmem_cache_shrink(struct kmem_cache *cachep) |
| { |
| } |
| |
| int kmem_cache_alloc_bulk(struct kmem_cache *cachep, gfp_t gfp, size_t size, |
| void **p) |
| { |
| size_t i; |
| |
| if (kmalloc_verbose) |
| pr_debug("Bulk alloc %lu\n", size); |
| |
| pthread_mutex_lock(&cachep->lock); |
| if (cachep->nr_objs >= size) { |
| struct radix_tree_node *node; |
| |
| for (i = 0; i < size; i++) { |
| if (!(gfp & __GFP_DIRECT_RECLAIM)) { |
| if (!cachep->non_kernel) |
| break; |
| cachep->non_kernel--; |
| } |
| |
| node = cachep->objs; |
| cachep->nr_objs--; |
| cachep->objs = node->parent; |
| p[i] = node; |
| node->parent = NULL; |
| } |
| pthread_mutex_unlock(&cachep->lock); |
| } else { |
| pthread_mutex_unlock(&cachep->lock); |
| for (i = 0; i < size; i++) { |
| if (!(gfp & __GFP_DIRECT_RECLAIM)) { |
| if (!cachep->non_kernel) |
| break; |
| cachep->non_kernel--; |
| } |
| |
| if (cachep->align) { |
| posix_memalign(&p[i], cachep->align, |
| cachep->size); |
| } else { |
| p[i] = malloc(cachep->size); |
| if (!p[i]) |
| break; |
| } |
| if (cachep->ctor) |
| cachep->ctor(p[i]); |
| else if (gfp & __GFP_ZERO) |
| memset(p[i], 0, cachep->size); |
| } |
| } |
| |
| if (i < size) { |
| size = i; |
| pthread_mutex_lock(&cachep->lock); |
| for (i = 0; i < size; i++) |
| __kmem_cache_free_locked(cachep, p[i]); |
| pthread_mutex_unlock(&cachep->lock); |
| return 0; |
| } |
| |
| for (i = 0; i < size; i++) { |
| uatomic_inc(&nr_allocated); |
| uatomic_inc(&cachep->nr_allocated); |
| uatomic_inc(&cachep->nr_tallocated); |
| if (kmalloc_verbose) |
| printf("Allocating %p from slab\n", p[i]); |
| } |
| |
| return size; |
| } |
| |
| struct kmem_cache * |
| kmem_cache_create(const char *name, unsigned int size, unsigned int align, |
| unsigned int flags, void (*ctor)(void *)) |
| { |
| struct kmem_cache *ret = malloc(sizeof(*ret)); |
| |
| pthread_mutex_init(&ret->lock, NULL); |
| ret->size = size; |
| ret->align = align; |
| ret->nr_objs = 0; |
| ret->nr_allocated = 0; |
| ret->nr_tallocated = 0; |
| ret->objs = NULL; |
| ret->ctor = ctor; |
| ret->non_kernel = 0; |
| ret->exec_callback = false; |
| ret->callback = NULL; |
| ret->private = NULL; |
| return ret; |
| } |
| |
| /* |
| * Test the test infrastructure for kem_cache_alloc/free and bulk counterparts. |
| */ |
| void test_kmem_cache_bulk(void) |
| { |
| int i; |
| void *list[12]; |
| static struct kmem_cache *test_cache, *test_cache2; |
| |
| /* |
| * Testing the bulk allocators without aligned kmem_cache to force the |
| * bulk alloc/free to reuse |
| */ |
| test_cache = kmem_cache_create("test_cache", 256, 0, SLAB_PANIC, NULL); |
| |
| for (i = 0; i < 5; i++) |
| list[i] = kmem_cache_alloc(test_cache, __GFP_DIRECT_RECLAIM); |
| |
| for (i = 0; i < 5; i++) |
| kmem_cache_free(test_cache, list[i]); |
| assert(test_cache->nr_objs == 5); |
| |
| kmem_cache_alloc_bulk(test_cache, __GFP_DIRECT_RECLAIM, 5, list); |
| kmem_cache_free_bulk(test_cache, 5, list); |
| |
| for (i = 0; i < 12 ; i++) |
| list[i] = kmem_cache_alloc(test_cache, __GFP_DIRECT_RECLAIM); |
| |
| for (i = 0; i < 12; i++) |
| kmem_cache_free(test_cache, list[i]); |
| |
| /* The last free will not be kept around */ |
| assert(test_cache->nr_objs == 11); |
| |
| /* Aligned caches will immediately free */ |
| test_cache2 = kmem_cache_create("test_cache2", 128, 128, SLAB_PANIC, NULL); |
| |
| kmem_cache_alloc_bulk(test_cache2, __GFP_DIRECT_RECLAIM, 10, list); |
| kmem_cache_free_bulk(test_cache2, 10, list); |
| assert(!test_cache2->nr_objs); |
| |
| |
| } |