blob: f26c8d492892b8bdd4c724ddc01625d14dfc3346 [file] [log] [blame]
/*
* SLOB Allocator: Simple List Of Blocks
*
* Matt Mackall <mpm@selenic.com> 12/30/03
*
* NUMA support by Paul Mundt, 2007.
*
* How SLOB works:
*
* The core of SLOB is a traditional K&R style heap allocator, with
* support for returning aligned objects. The granularity of this
* allocator is as little as 2 bytes, however typically most architectures
* will require 4 bytes on 32-bit and 8 bytes on 64-bit.
*
* The slob heap is a set of linked list of pages from alloc_pages(),
* and within each page, there is a singly-linked list of free blocks
* (slob_t). The heap is grown on demand. To reduce fragmentation,
* heap pages are segregated into three lists, with objects less than
* 256 bytes, objects less than 1024 bytes, and all other objects.
*
* Allocation from heap involves first searching for a page with
* sufficient free blocks (using a next-fit-like approach) followed by
* a first-fit scan of the page. Deallocation inserts objects back
* into the free list in address order, so this is effectively an
* address-ordered first fit.
*
* Above this is an implementation of kmalloc/kfree. Blocks returned
* from kmalloc are prepended with a 4-byte header with the kmalloc size.
* If kmalloc is asked for objects of PAGE_SIZE or larger, it calls
* alloc_pages() directly, allocating compound pages so the page order
* does not have to be separately tracked, and also stores the exact
* allocation size in page->private so that it can be used to accurately
* provide ksize(). These objects are detected in kfree() because slob_page()
* is false for them.
*
* SLAB is emulated on top of SLOB by simply calling constructors and
* destructors for every SLAB allocation. Objects are returned with the
* 4-byte alignment unless the SLAB_HWCACHE_ALIGN flag is set, in which
* case the low-level allocator will fragment blocks to create the proper
* alignment. Again, objects of page-size or greater are allocated by
* calling alloc_pages(). As SLAB objects know their size, no separate
* size bookkeeping is necessary and there is essentially no allocation
* space overhead, and compound pages aren't needed for multi-page
* allocations.
*
* NUMA support in SLOB is fairly simplistic, pushing most of the real
* logic down to the page allocator, and simply doing the node accounting
* on the upper levels. In the event that a node id is explicitly
* provided, alloc_pages_exact_node() with the specified node id is used
* instead. The common case (or when the node id isn't explicitly provided)
* will default to the current node, as per numa_node_id().
*
* Node aware pages are still inserted in to the global freelist, and
* these are scanned for by matching against the node id encoded in the
* page flags. As a result, block allocations that can be satisfied from
* the freelist will only be done so on pages residing on the same node,
* in order to prevent random node placement.
*/
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/mm.h>
#include <linux/swap.h> /* struct reclaim_state */
#include <linux/cache.h>
#include <linux/init.h>
#include <linux/export.h>
#include <linux/rcupdate.h>
#include <linux/list.h>
#include <linux/kmemleak.h>
#include <trace/events/kmem.h>
#include <linux/atomic.h>
#ifdef CONFIG_KMALLOC_TRACKER
#include <linux/mem_tracker_def.h>
#endif
#ifdef CONFIG_DEBUG_SLOB_MARK_HEAD
#define BYTES_PER_RECORD (sizeof(void *))
#define RECORD_MAGIC 0xdeaddead
#define RECORD_COUNT 4
static void **dbg_userrecord(void *mem,int index)
{
return (void **)(mem + index * BYTES_PER_RECORD);
}
static void **dbg_userhead(void *mem)
{
return (void **)(mem - RECORD_COUNT * BYTES_PER_RECORD);
}
#endif
/*
* slob_block has a field 'units', which indicates size of block if +ve,
* or offset of next block if -ve (in SLOB_UNITs).
*
* Free blocks of size 1 unit simply contain the offset of the next block.
* Those with larger size contain their size in the first SLOB_UNIT of
* memory, and the offset of the next free block in the second SLOB_UNIT.
*/
#if PAGE_SIZE <= (32767 * 2)
typedef s16 slobidx_t;
#else
typedef s32 slobidx_t;
#endif
struct slob_block {
slobidx_t units;
};
typedef struct slob_block slob_t;
/*
* We use struct page fields to manage some slob allocation aspects,
* however to avoid the horrible mess in include/linux/mm_types.h, we'll
* just define our own struct page type variant here.
*/
struct slob_page {
union {
struct {
unsigned long flags; /* mandatory */
unsigned long index; /* mandatory */
slobidx_t units; /* free units left in page */
unsigned long pad[2];
slob_t *free; /* first free slob_t in page */
struct list_head list; /* linked list of free pages */
};
struct page page;
};
};
unsigned int g_slob_kmalloc_pages;
unsigned int g_slob_kmem_cache_pages;
raw_spinlock_t g_slob_kmalloc_spin_lock = (raw_spinlock_t) __RAW_SPIN_LOCK_INITIALIZER (g_slob_kmalloc_spin_lock);
raw_spinlock_t g_slob_kmem_cache_spin_lock = (raw_spinlock_t) __RAW_SPIN_LOCK_INITIALIZER (g_slob_kmem_cache_spin_lock);
static inline void struct_slob_page_wrong_size(void)
{ BUILD_BUG_ON(sizeof(struct slob_page) != sizeof(struct page)); }
/*
* free_slob_page: call before a slob_page is returned to the page allocator.
*/
static inline void free_slob_page(struct slob_page *sp)
{
reset_page_mapcount(&sp->page);
sp->page.mapping = NULL;
}
/*
* All partially free slob pages go on these lists.
*/
#define SLOB_BREAK1 256
#define SLOB_BREAK2 1024
static LIST_HEAD(free_slob_small);
static LIST_HEAD(free_slob_medium);
static LIST_HEAD(free_slob_large);
/*
* is_slob_page: True for all slob pages (false for bigblock pages)
*/
static inline int is_slob_page(struct slob_page *sp)
{
return PageSlab((struct page *)sp);
}
static inline void set_slob_page(struct slob_page *sp)
{
__SetPageSlab((struct page *)sp);
}
static inline void clear_slob_page(struct slob_page *sp)
{
__ClearPageSlab((struct page *)sp);
}
static inline struct slob_page *slob_page(const void *addr)
{
return (struct slob_page *)virt_to_page(addr);
}
/*
* slob_page_free: true for pages on free_slob_pages list.
*/
static inline int slob_page_free(struct slob_page *sp)
{
return PageSlobFree((struct page *)sp);
}
static void set_slob_page_free(struct slob_page *sp, struct list_head *list)
{
list_add(&sp->list, list);
__SetPageSlobFree((struct page *)sp);
}
static inline void clear_slob_page_free(struct slob_page *sp)
{
list_del(&sp->list);
__ClearPageSlobFree((struct page *)sp);
}
#ifdef CONFIG_SLOB_OPT
#define SLOB_CHUNK_SHIFT (L1_CACHE_SHIFT)
#define SLOB_CHUNK_SIZE (1 << (SLOB_CHUNK_SHIFT))
#define SLOB_LIST_NUM (PAGE_SIZE >> (SLOB_CHUNK_SHIFT))
#define SLOB_LIST_LIMIT (0)
struct slob_item_info
{
struct list_head slob_list;
int count;
};
struct slob_item_info slob_sizes[SLOB_LIST_NUM];
#else
#define SLOB_CHUNK_SHIFT (L1_CACHE_SHIFT)
#define SLOB_CHUNK_SIZE (1 << (SLOB_CHUNK_SHIFT))
#define SLOB_LIST_NUM ((PAGE_SIZE >> (SLOB_CHUNK_SHIFT)))
struct list_head slob_sizes[SLOB_LIST_NUM];
#endif
static inline void set_slob_page_list_index(struct slob_page *sp, unsigned long index)
{
sp->index = index;
}
#ifdef CONFIG_SLOB_OPT
static inline struct slob_item_info *get_slob_page_list_head(struct slob_page *sp)
{
return &slob_sizes[sp->index];
}
#else
static inline struct list_head *get_slob_page_list_head(struct slob_page *sp)
{
return &slob_sizes[sp->index];
}
#endif
static inline void clear_slob_page_list_index(struct slob_page *sp)
{
sp->index = 0;
}
#define SLOB_UNIT sizeof(slob_t)
#define SLOB_UNITS(size) (((size) + SLOB_UNIT - 1)/SLOB_UNIT)
#define SLOB_ALIGN L1_CACHE_BYTES
/*
* struct slob_rcu is inserted at the tail of allocated slob blocks, which
* were created with a SLAB_DESTROY_BY_RCU slab. slob_rcu is used to free
* the block using call_rcu.
*/
struct slob_rcu {
struct rcu_head head;
int size;
};
/*
* slob_lock protects all slob allocator structures.
*/
static DEFINE_SPINLOCK(slob_lock);
/*
* Encode the given size and next info into a free slob block s.
*/
static void set_slob(slob_t *s, slobidx_t size, slob_t *next)
{
slob_t *base = (slob_t *)((unsigned long)s & PAGE_MASK);
slobidx_t offset = next - base;
if (size > 1) {
s[0].units = size;
s[1].units = offset;
} else
s[0].units = -offset;
}
/*
* Return the size of a slob block.
*/
static slobidx_t slob_units(slob_t *s)
{
if (s->units > 0)
return s->units;
return 1;
}
/*
* Return the next free slob block pointer after this one.
*/
static slob_t *slob_next(slob_t *s)
{
slob_t *base = (slob_t *)((unsigned long)s & PAGE_MASK);
slobidx_t next;
if (s[0].units < 0)
next = -s[0].units;
else
next = s[1].units;
return base+next;
}
/*
* Returns true if s is the last free block in its page.
*/
static int slob_last(slob_t *s)
{
return !((unsigned long)slob_next(s) & ~PAGE_MASK);
}
static void *slob_new_pages(gfp_t gfp, int order, int node)
{
void *page;
#ifdef CONFIG_NUMA
if (node != -1)
page = alloc_pages_exact_node(node, gfp, order);
else
#endif
page = alloc_pages(gfp, order);
if (!page)
return NULL;
return page_address(page);
}
static void slob_free_pages(void *b, int order)
{
if (current->reclaim_state)
current->reclaim_state->reclaimed_slab += 1 << order;
free_pages((unsigned long)b, order);
}
/*
* Allocate a slob block within a given slob_page sp.
*/
static void *slob_page_alloc(struct slob_page *sp, size_t size, int align)
{
slob_t *prev, *cur, *aligned = NULL;
int delta = 0, units = SLOB_UNITS(size);
for (prev = NULL, cur = sp->free; ; prev = cur, cur = slob_next(cur)) {
slobidx_t avail = slob_units(cur);
if (align) {
aligned = (slob_t *)ALIGN((unsigned long)cur, align);
delta = aligned - cur;
}
if (avail >= units + delta) { /* room enough? */
slob_t *next;
if (delta) { /* need to fragment head to align? */
next = slob_next(cur);
set_slob(aligned, avail - delta, next);
set_slob(cur, delta, aligned);
prev = cur;
cur = aligned;
avail = slob_units(cur);
}
next = slob_next(cur);
if (avail == units) { /* exact fit? unlink. */
if (prev)
set_slob(prev, slob_units(prev), next);
else
sp->free = next;
} else { /* fragment */
if (prev)
set_slob(prev, slob_units(prev), cur + units);
else
sp->free = cur + units;
set_slob(cur + units, avail - units, next);
}
sp->units -= units;
if (!sp->units
#ifdef CONFIG_SLOB_OPT
&& size != PAGE_SIZE
#endif
)
clear_slob_page_free(sp);
return cur;
}
if (slob_last(cur))
return NULL;
}
}
void slob_sizes_init(void)
{
int i;
for (i = 0; i < SLOB_LIST_NUM; i++)
{
#ifdef CONFIG_SLOB_OPT
INIT_LIST_HEAD(&slob_sizes[i].slob_list);
#else
INIT_LIST_HEAD(&slob_sizes[i]);
#endif
}
}
static struct list_head *find_general_slob_list(size_t size, size_t *general_size, unsigned long *index)
{
int slob_index = 0;
if (size & (SLOB_CHUNK_SIZE - 1))
slob_index = size >> SLOB_CHUNK_SHIFT;
else
slob_index = (size >> SLOB_CHUNK_SHIFT) - 1;
*general_size = (slob_index + 1) << SLOB_CHUNK_SHIFT;
*index = slob_index;
return &(slob_sizes[slob_index]);
}
/*
* slob_alloc: entry point into the slob allocator.
*/
static void *slob_alloc_general(size_t size, gfp_t gfp, int align, int node)
{
unsigned long index;
size_t general_size;
struct slob_page *sp;
struct list_head *prev;
#ifdef CONFIG_SLOB_OPT
struct slob_item_info *slob_list;
#else
struct list_head *slob_list;
#endif
slob_t *b = NULL;
unsigned long flags;
if (size < PAGE_SIZE)
slob_list = find_general_slob_list(size, &general_size, &index);
else
panic("slob alloc error");
size = general_size;
spin_lock_irqsave(&slob_lock, flags);
/* Iterate through each partially free page, try to find room */
#ifdef CONFIG_SLOB_OPT
if (size ==PAGE_SIZE && slob_list->count ==0 )
goto alloc_out;
list_for_each_entry(sp, &(slob_list->slob_list), list) {
#else
list_for_each_entry(sp, slob_list, list) {
#endif
#ifdef CONFIG_NUMA
/*
* If there's a node specification, search for a partial
* page with a matching node id in the freelist.
*/
if (node != -1 && page_to_nid(&sp->page) != node)
continue;
#endif
/* Enough room on this page? */
if (sp->units < SLOB_UNITS(size))
continue;
#ifdef CONFIG_SLOB_OPT
/*if it is a free block,count--*/
if (sp->units == SLOB_UNITS(PAGE_SIZE))
slob_list->count--;
#endif
/* Attempt to alloc */
prev = sp->list.prev;
b = slob_page_alloc(sp, size, align);
if (!b)
continue;
/* Improve fragment distribution and reduce our average
* search time by starting our next search here. (see
* Knuth vol 1, sec 2.5, pg 449) */
#ifdef CONFIG_SLOB_OPT
if (prev != slob_list->slob_list.prev &&
slob_list->slob_list.next != prev->next)
list_move_tail(&(slob_list->slob_list), prev->next);
#else
if (prev != slob_list->prev &&
slob_list->next != prev->next)
list_move_tail(slob_list, prev->next);
#endif
break;
}
alloc_out:
spin_unlock_irqrestore(&slob_lock, flags);
/* Not enough space: must allocate a new page */
if (!b) {
b = slob_new_pages(gfp & ~__GFP_ZERO, 0, node);
if (!b)
return NULL;
raw_spin_lock_irqsave(&g_slob_kmalloc_spin_lock, flags);
g_slob_kmalloc_pages++;
raw_spin_unlock_irqrestore(&g_slob_kmalloc_spin_lock, flags);
sp = slob_page(b);
set_slob_page(sp);
spin_lock_irqsave(&slob_lock, flags);
sp->units = SLOB_UNITS(PAGE_SIZE);
sp->free = b;
INIT_LIST_HEAD(&sp->list);
set_slob(b, SLOB_UNITS(PAGE_SIZE), b + SLOB_UNITS(PAGE_SIZE));
#ifdef CONFIG_SLOB_OPT
set_slob_page_free(sp, &(slob_list->slob_list));
#else
set_slob_page_free(sp, slob_list);
#endif
b = slob_page_alloc(sp, size, align);
BUG_ON(!b);
set_slob_page_list_index(sp, index);
spin_unlock_irqrestore(&slob_lock, flags);
}
if (unlikely((gfp & __GFP_ZERO) && b))
memset(b, 0, size);
return b;
}
static size_t slob_find_general_size(struct slob_page *sp)
{
return (sp->index + 1) << SLOB_CHUNK_SHIFT;
}
/*
* slob_free: entry point into the slob allocator.
*/
static void slob_free_general(const void *block, struct slob_page *sp)
{
size_t size;
slob_t *prev, *next, *b = (slob_t *)block;
slobidx_t units;
unsigned long flags;
#ifdef CONFIG_SLOB_OPT
struct slob_item_info *slob_list;
#else
struct list_head *slob_list;
#endif
if (unlikely(ZERO_OR_NULL_PTR(block)))
return;
slob_list = get_slob_page_list_head(sp);
size = slob_find_general_size(sp);
units = SLOB_UNITS(size);
spin_lock_irqsave(&slob_lock, flags);
if (sp->units + units == SLOB_UNITS(PAGE_SIZE)) {
#ifdef CONFIG_SLOB_OPT
if (slob_list->count < SLOB_LIST_LIMIT && slob_page_free(sp)){
sp->units += units;
sp->free = page_address((struct page *)sp);
set_slob(sp->free, SLOB_UNITS(PAGE_SIZE), sp->free + SLOB_UNITS(PAGE_SIZE));
slob_list->count++;
// if (slob_list->slob_list.next != &sp->list)
// list_move(&sp->list, &(slob_list->slob_list));
spin_unlock_irqrestore(&slob_lock, flags);
return;
}
#endif
/* Go directly to page allocator. Do not pass slob allocator */
if (slob_page_free(sp))
clear_slob_page_free(sp);
spin_unlock_irqrestore(&slob_lock, flags);
clear_slob_page(sp);
clear_slob_page_list_index(sp);
free_slob_page(sp);
slob_free_pages(b, 0);
raw_spin_lock_irqsave(&g_slob_kmalloc_spin_lock, flags);
g_slob_kmalloc_pages--;
raw_spin_unlock_irqrestore(&g_slob_kmalloc_spin_lock, flags);
return;
}
if (!slob_page_free(sp)) {
/* This slob page is about to become partially free. Easy! */
sp->units = units;
sp->free = b;
set_slob(b, units,
(void *)((unsigned long)(b +
SLOB_UNITS(PAGE_SIZE)) & PAGE_MASK));
#ifdef CONFIG_SLOB_OPT
set_slob_page_free(sp, &(slob_list->slob_list));
#else
set_slob_page_free(sp, slob_list);
#endif
goto out;
}
/*
* Otherwise the page is already partially free, so find reinsertion
* point.
*/
sp->units += units;
if (b < sp->free) {
if (b + units == sp->free) {
units += slob_units(sp->free);
sp->free = slob_next(sp->free);
}
set_slob(b, units, sp->free);
sp->free = b;
} else {
prev = sp->free;
next = slob_next(prev);
while (b > next) {
prev = next;
next = slob_next(prev);
}
if (unlikely((b == prev) || (b == next)))
panic("slob: double free error!\n");
if (!slob_last(prev) && b + units == next) {
units += slob_units(next);
set_slob(b, units, slob_next(next));
} else
set_slob(b, units, next);
if (prev + slob_units(prev) == b) {
units = slob_units(b) + slob_units(prev);
set_slob(prev, units, slob_next(b));
} else
set_slob(prev, slob_units(prev), b);
}
#ifdef CONFIG_SLOB_OPT
if (slob_list->slob_list.next != &sp->list)
list_move(&sp->list, &(slob_list->slob_list));
#else
if (slob_list->next != &sp->list)
list_move(&sp->list, slob_list);
#endif
out:
spin_unlock_irqrestore(&slob_lock, flags);
}
static void *slob_alloc(size_t size, gfp_t gfp, int align, int node)
{
struct slob_page *sp;
struct list_head *prev;
struct list_head *slob_list;
slob_t *b = NULL;
unsigned long flags;
if (size < SLOB_BREAK1)
slob_list = &free_slob_small;
else if (size < SLOB_BREAK2)
slob_list = &free_slob_medium;
else
slob_list = &free_slob_large;
spin_lock_irqsave(&slob_lock, flags);
/* Iterate through each partially free page, try to find room */
list_for_each_entry(sp, slob_list, list) {
#ifdef CONFIG_NUMA
/*
* If there's a node specification, search for a partial
* page with a matching node id in the freelist.
*/
if (node != -1 && page_to_nid(&sp->page) != node)
continue;
#endif
/* Enough room on this page? */
if (sp->units < SLOB_UNITS(size))
continue;
/* Attempt to alloc */
prev = sp->list.prev;
b = slob_page_alloc(sp, size, align);
if (!b)
continue;
/* Improve fragment distribution and reduce our average
* search time by starting our next search here. (see
* Knuth vol 1, sec 2.5, pg 449) */
if (prev != slob_list->prev &&
slob_list->next != prev->next)
list_move_tail(slob_list, prev->next);
break;
}
spin_unlock_irqrestore(&slob_lock, flags);
/* Not enough space: must allocate a new page */
if (!b) {
b = slob_new_pages(gfp & ~__GFP_ZERO, 0, node);
if (!b)
return NULL;
raw_spin_lock_irqsave(&g_slob_kmem_cache_spin_lock, flags);
g_slob_kmem_cache_pages++;
raw_spin_unlock_irqrestore(&g_slob_kmem_cache_spin_lock, flags);
sp = slob_page(b);
set_slob_page(sp);
spin_lock_irqsave(&slob_lock, flags);
sp->units = SLOB_UNITS(PAGE_SIZE);
sp->free = b;
INIT_LIST_HEAD(&sp->list);
set_slob(b, SLOB_UNITS(PAGE_SIZE), b + SLOB_UNITS(PAGE_SIZE));
set_slob_page_free(sp, slob_list);
b = slob_page_alloc(sp, size, align);
BUG_ON(!b);
spin_unlock_irqrestore(&slob_lock, flags);
}
if (unlikely((gfp & __GFP_ZERO) && b))
memset(b, 0, size);
return b;
}
/*
* slob_free: entry point into the slob allocator.
*/
static void slob_free(void *block, int size)
{
struct slob_page *sp;
slob_t *prev, *next, *b = (slob_t *)block;
slobidx_t units;
unsigned long flags;
struct list_head *slob_list;
if (unlikely(ZERO_OR_NULL_PTR(block)))
return;
BUG_ON(!size);
sp = slob_page(block);
units = SLOB_UNITS(size);
spin_lock_irqsave(&slob_lock, flags);
if (sp->units + units == SLOB_UNITS(PAGE_SIZE)) {
/* Go directly to page allocator. Do not pass slob allocator */
if (slob_page_free(sp))
clear_slob_page_free(sp);
spin_unlock_irqrestore(&slob_lock, flags);
clear_slob_page(sp);
free_slob_page(sp);
slob_free_pages(b, 0);
raw_spin_lock_irqsave(&g_slob_kmem_cache_spin_lock, flags);
g_slob_kmem_cache_pages--;
raw_spin_unlock_irqrestore(&g_slob_kmem_cache_spin_lock, flags);
return;
}
if (!slob_page_free(sp)) {
/* This slob page is about to become partially free. Easy! */
sp->units = units;
sp->free = b;
set_slob(b, units,
(void *)((unsigned long)(b +
SLOB_UNITS(PAGE_SIZE)) & PAGE_MASK));
if (size < SLOB_BREAK1)
slob_list = &free_slob_small;
else if (size < SLOB_BREAK2)
slob_list = &free_slob_medium;
else
slob_list = &free_slob_large;
set_slob_page_free(sp, slob_list);
goto out;
}
/*
* Otherwise the page is already partially free, so find reinsertion
* point.
*/
sp->units += units;
if (b < sp->free) {
if (b + units == sp->free) {
units += slob_units(sp->free);
sp->free = slob_next(sp->free);
}
set_slob(b, units, sp->free);
sp->free = b;
} else {
prev = sp->free;
next = slob_next(prev);
while (b > next) {
prev = next;
next = slob_next(prev);
}
if (!slob_last(prev) && b + units == next) {
units += slob_units(next);
set_slob(b, units, slob_next(next));
} else
set_slob(b, units, next);
if (prev + slob_units(prev) == b) {
units = slob_units(b) + slob_units(prev);
set_slob(prev, units, slob_next(b));
} else
set_slob(prev, slob_units(prev), b);
}
out:
spin_unlock_irqrestore(&slob_lock, flags);
}
/*
* End of slob allocator proper. Begin kmem_cache_alloc and kmalloc frontend.
*/
void *__kmalloc_node(size_t size, gfp_t gfp, int node)
{
int align = max(ARCH_KMALLOC_MINALIGN, ARCH_SLAB_MINALIGN);
void *ret;
unsigned long flags;
if (!size)
return ZERO_SIZE_PTR;
gfp &= gfp_allowed_mask;
lockdep_trace_alloc(gfp);
#ifdef CONFIG_DEBUG_SLOB_MARK_HEAD
if (size < PAGE_SIZE)
size += RECORD_COUNT * BYTES_PER_RECORD;
#endif
#ifdef CONFIG_KMALLOC_TRACKER
size += HEAP_SUFFIX_SIZE;
#endif
if (size < PAGE_SIZE) {
if (!size)
return ZERO_SIZE_PTR;
ret = slob_alloc_general(size, gfp, align, node);
trace_kmalloc_node(_RET_IP_, ret,
size, size, gfp, node);
#ifdef CONFIG_DEBUG_SLOB_MARK_HEAD
if (ret) {
*dbg_userrecord(ret,0) = (void *)RECORD_MAGIC;
*dbg_userrecord(ret,1) = __builtin_return_address(0);
*dbg_userrecord(ret,2) = current;
ret = (void *)dbg_userrecord(ret,RECORD_COUNT);
}
#endif
#ifdef CONFIG_KMALLOC_TRACKER
if (ret) {
kmalloc_alloc_tracker(ret, KMALLOC_ORIGINAL_SIZE(size));
return KMALLOC_SETUP(ret);
}
#endif
} else {
unsigned int order = get_order(size);
if (likely(order))
gfp |= __GFP_COMP;
ret = slob_new_pages(gfp, order, node);
if (ret) {
struct page *page;
page = virt_to_page(ret);
page->private = size;
raw_spin_lock_irqsave(&g_slob_kmalloc_spin_lock, flags);
g_slob_kmalloc_pages += (1 << order);
raw_spin_unlock_irqrestore(&g_slob_kmalloc_spin_lock, flags);
}
trace_kmalloc_node(_RET_IP_, ret,
size, PAGE_SIZE << order, gfp, node);
}
kmemleak_alloc(ret, size, 1, gfp);
return ret;
}
EXPORT_SYMBOL(__kmalloc_node);
void kfree(const void *block)
{
struct slob_page *sp;
unsigned long flags;
trace_kfree(_RET_IP_, block);
if (unlikely(ZERO_OR_NULL_PTR(block)))
return;
kmemleak_free(block);
#ifdef CONFIG_KMALLOC_TRACKER
int entry = 0;
void *mem = NULL;
sp = slob_page(block);
if (is_slob_page(sp)) {
mem = KMALLOC_BASE(block);
entry = *(size_t *)(mem);
if ((entry != 0) && (MEM_TRUE == check_node_entry(entry))) {
mem_free_tracker((void *)entry, MEM_TRACKER_TYPE_KMALLOC);
}else
panic("mem out!!");
slob_free_general(mem, sp);
} else {
struct page *page;
unsigned int order;
page = &sp->page;
order = get_order(page->private);
raw_spin_lock_irqsave(&g_slob_kmalloc_spin_lock, flags);
g_slob_kmalloc_pages -= (1 << order);
raw_spin_unlock_irqrestore(&g_slob_kmalloc_spin_lock, flags);
put_page(&sp->page);
}
#else
sp = slob_page(block);
if (is_slob_page(sp)) {
#ifdef CONFIG_DEBUG_SLOB_MARK_HEAD
if (*dbg_userhead(block) == (void *)RECORD_MAGIC) {
block = (void *)dbg_userhead(block);
} else
panic("memmory corruption!!");
#endif
slob_free_general(block, sp);
} else {
struct page *page;
unsigned int order;
page = &sp->page;
order = get_order(page->private);
raw_spin_lock_irqsave(&g_slob_kmalloc_spin_lock, flags);
g_slob_kmalloc_pages -= (1 << order);
raw_spin_unlock_irqrestore(&g_slob_kmalloc_spin_lock, flags);
put_page(&sp->page);
}
#endif
}
EXPORT_SYMBOL(kfree);
/* can't use ksize for kmem_cache_alloc memory, only kmalloc */
size_t ksize(const void *block)
{
struct slob_page *sp;
BUG_ON(!block);
if (unlikely(block == ZERO_SIZE_PTR))
return 0;
sp = slob_page(block);
if (is_slob_page(sp)) {
#ifdef CONFIG_SLOB_OPT
struct slob_item_info *slob_list;
#else
struct list_head *slob_list;
#endif
slob_list = get_slob_page_list_head(sp);
#ifdef CONFIG_KMALLOC_TRACKER
return (slob_find_general_size(sp) - 2 * sizeof(size_t));
#endif
#ifdef CONFIG_DEBUG_SLOB_MARK_HEAD
return (slob_find_general_size(sp) - RECORD_COUNT * BYTES_PER_RECORD);
#else
return slob_find_general_size(sp);
#endif
} else
return sp->page.private;
}
EXPORT_SYMBOL(ksize);
struct kmem_cache {
unsigned int size, align;
unsigned long flags;
const char *name;
void (*ctor)(void *);
};
struct kmem_cache *kmem_cache_create(const char *name, size_t size,
size_t align, unsigned long flags, void (*ctor)(void *))
{
struct kmem_cache *c;
c = slob_alloc(sizeof(struct kmem_cache),
GFP_KERNEL, ARCH_KMALLOC_MINALIGN, -1);
if (c) {
c->name = name;
c->size = size;
if (flags & SLAB_DESTROY_BY_RCU) {
/* leave room for rcu footer at the end of object */
c->size += sizeof(struct slob_rcu);
}
c->flags = flags;
c->ctor = ctor;
/* ignore alignment unless it's forced */
c->align = (flags & SLAB_HWCACHE_ALIGN) ? SLOB_ALIGN : 0;
if (c->align < ARCH_SLAB_MINALIGN)
c->align = ARCH_SLAB_MINALIGN;
if (c->align < align)
c->align = align;
} else if (flags & SLAB_PANIC)
panic("Cannot create slab cache %s\n", name);
kmemleak_alloc(c, sizeof(struct kmem_cache), 1, GFP_KERNEL);
return c;
}
EXPORT_SYMBOL(kmem_cache_create);
void kmem_cache_destroy(struct kmem_cache *c)
{
kmemleak_free(c);
if (c->flags & SLAB_DESTROY_BY_RCU)
rcu_barrier();
slob_free(c, sizeof(struct kmem_cache));
}
EXPORT_SYMBOL(kmem_cache_destroy);
static unsigned int align(unsigned int i)
{
return (i + 7) & ~7u;
}
void *kmem_cache_alloc_node(struct kmem_cache *c, gfp_t flags, int node)
{
void *b;
flags &= gfp_allowed_mask;
unsigned long spin_flags;
lockdep_trace_alloc(flags);
if (c->size < PAGE_SIZE) {
//b = slob_alloc(c->size, flags, c->align, node);
c->size = align(c->size);/*align 8*/
c->align = 0;
b = slob_alloc(c->size, flags, 0, node);/*already align 8*/
trace_kmem_cache_alloc_node(_RET_IP_, b, c->size,
SLOB_UNITS(c->size) * SLOB_UNIT,
flags, node);
} else {
b = slob_new_pages(flags, get_order(c->size), node);
if(b) {
raw_spin_lock_irqsave(&g_slob_kmem_cache_spin_lock, spin_flags);
g_slob_kmem_cache_pages += (1 << get_order(c->size));
raw_spin_unlock_irqrestore(&g_slob_kmem_cache_spin_lock, spin_flags);
}
trace_kmem_cache_alloc_node(_RET_IP_, b, c->size,
PAGE_SIZE << get_order(c->size),
flags, node);
}
if (c->ctor)
c->ctor(b);
kmemleak_alloc_recursive(b, c->size, 1, c->flags, flags);
return b;
}
EXPORT_SYMBOL(kmem_cache_alloc_node);
static void __kmem_cache_free(void *b, int size)
{
unsigned long flags;
if (size < PAGE_SIZE)
slob_free(b, size);
else {
slob_free_pages(b, get_order(size));
raw_spin_lock_irqsave(&g_slob_kmem_cache_spin_lock, flags);
g_slob_kmem_cache_pages -= (1 << get_order(size));
raw_spin_unlock_irqrestore(&g_slob_kmem_cache_spin_lock, flags);
}
}
static void kmem_rcu_free(struct rcu_head *head)
{
struct slob_rcu *slob_rcu = (struct slob_rcu *)head;
void *b = (void *)slob_rcu - (slob_rcu->size - sizeof(struct slob_rcu));
__kmem_cache_free(b, slob_rcu->size);
}
void kmem_cache_free(struct kmem_cache *c, void *b)
{
kmemleak_free_recursive(b, c->flags);
if (unlikely(c->flags & SLAB_DESTROY_BY_RCU)) {
struct slob_rcu *slob_rcu;
slob_rcu = b + (c->size - sizeof(struct slob_rcu));
slob_rcu->size = c->size;
call_rcu(&slob_rcu->head, kmem_rcu_free);
} else {
__kmem_cache_free(b, c->size);
}
trace_kmem_cache_free(_RET_IP_, b);
}
EXPORT_SYMBOL(kmem_cache_free);
unsigned int kmem_cache_size(struct kmem_cache *c)
{
return c->size;
}
EXPORT_SYMBOL(kmem_cache_size);
int kmem_cache_shrink(struct kmem_cache *d)
{
#ifdef CONFIG_SLOB_OPT
int i;
unsigned long flags;
struct slob_page *sp,*n;
for (i = 0; i < SLOB_LIST_NUM; i++){
if (slob_sizes[i].count == 0)
continue;
spin_lock_irqsave(&slob_lock, flags);
list_for_each_entry_safe(sp, n, &(slob_sizes[i].slob_list), list) {
if (sp->units == SLOB_UNITS(PAGE_SIZE)){
if (slob_page_free(sp))
clear_slob_page_free(sp);
clear_slob_page(sp);
clear_slob_page_list_index(sp);
free_slob_page(sp);
slob_free_pages(page_address((struct page*)sp), 0);
}
}
slob_sizes[i].count = 0;
spin_unlock_irqrestore(&slob_lock, flags);
}
#endif
return 0;
}
EXPORT_SYMBOL(kmem_cache_shrink);
static unsigned int slob_ready __read_mostly;
int slab_is_available(void)
{
return slob_ready;
}
void __init kmem_cache_init(void)
{
slob_ready = 1;
}
void __init kmem_cache_init_late(void)
{
/* Nothing to do */
}