Skip to content

Commit

Permalink
slub: support for bulk free with SLUB freelists
Browse files Browse the repository at this point in the history
Make it possible to free a freelist with several objects by adjusting API
of slab_free() and __slab_free() to have head, tail and an objects counter
(cnt).

Tail being NULL indicate single object free of head object.  This allow
compiler inline constant propagation in slab_free() and
slab_free_freelist_hook() to avoid adding any overhead in case of single
object free.

This allows a freelist with several objects (all within the same
slab-page) to be free'ed using a single locked cmpxchg_double in
__slab_free() and with an unlocked cmpxchg_double in slab_free().

Object debugging on the free path is also extended to handle these
freelists.  When CONFIG_SLUB_DEBUG is enabled it will also detect if
objects don't belong to the same slab-page.

These changes are needed for the next patch to bulk free the detached
freelists it introduces and constructs.

Micro benchmarking showed no performance reduction due to this change,
when debugging is turned off (compiled with CONFIG_SLUB_DEBUG).

Signed-off-by: Jesper Dangaard Brouer <[email protected]>
Signed-off-by: Alexander Duyck <[email protected]>
Acked-by: Christoph Lameter <[email protected]>
Cc: Pekka Enberg <[email protected]>
Cc: David Rientjes <[email protected]>
Cc: Joonsoo Kim <[email protected]>
Signed-off-by: Andrew Morton <[email protected]>
Signed-off-by: Linus Torvalds <[email protected]>
  • Loading branch information
netoptimizer authored and torvalds committed Nov 22, 2015
1 parent b4a6471 commit 8108465
Showing 1 changed file with 67 additions and 18 deletions.
85 changes: 67 additions & 18 deletions mm/slub.c
Original file line number Diff line number Diff line change
Expand Up @@ -1065,18 +1065,25 @@ static noinline int alloc_debug_processing(struct kmem_cache *s,
return 0;
}

/* Supports checking bulk free of a constructed freelist */
static noinline struct kmem_cache_node *free_debug_processing(
struct kmem_cache *s, struct page *page, void *object,
struct kmem_cache *s, struct page *page,
void *head, void *tail, int bulk_cnt,
unsigned long addr, unsigned long *flags)
{
struct kmem_cache_node *n = get_node(s, page_to_nid(page));
void *object = head;
int cnt = 0;

spin_lock_irqsave(&n->list_lock, *flags);
slab_lock(page);

if (!check_slab(s, page))
goto fail;

next_object:
cnt++;

if (!check_valid_pointer(s, page, object)) {
slab_err(s, page, "Invalid object pointer 0x%p", object);
goto fail;
Expand Down Expand Up @@ -1107,8 +1114,19 @@ static noinline struct kmem_cache_node *free_debug_processing(
if (s->flags & SLAB_STORE_USER)
set_track(s, object, TRACK_FREE, addr);
trace(s, page, object, 0);
/* Freepointer not overwritten by init_object(), SLAB_POISON moved it */
init_object(s, object, SLUB_RED_INACTIVE);

/* Reached end of constructed freelist yet? */
if (object != tail) {
object = get_freepointer(s, object);
goto next_object;
}
out:
if (cnt != bulk_cnt)
slab_err(s, page, "Bulk freelist count(%d) invalid(%d)\n",
bulk_cnt, cnt);

slab_unlock(page);
/*
* Keep node_lock to preserve integrity
Expand Down Expand Up @@ -1212,7 +1230,8 @@ static inline int alloc_debug_processing(struct kmem_cache *s,
struct page *page, void *object, unsigned long addr) { return 0; }

static inline struct kmem_cache_node *free_debug_processing(
struct kmem_cache *s, struct page *page, void *object,
struct kmem_cache *s, struct page *page,
void *head, void *tail, int bulk_cnt,
unsigned long addr, unsigned long *flags) { return NULL; }

static inline int slab_pad_check(struct kmem_cache *s, struct page *page)
Expand Down Expand Up @@ -1308,6 +1327,29 @@ static inline void slab_free_hook(struct kmem_cache *s, void *x)
kasan_slab_free(s, x);
}

static inline void slab_free_freelist_hook(struct kmem_cache *s,
void *head, void *tail)
{
/*
* Compiler cannot detect this function can be removed if slab_free_hook()
* evaluates to nothing. Thus, catch all relevant config debug options here.
*/
#if defined(CONFIG_KMEMCHECK) || \
defined(CONFIG_LOCKDEP) || \
defined(CONFIG_DEBUG_KMEMLEAK) || \
defined(CONFIG_DEBUG_OBJECTS_FREE) || \
defined(CONFIG_KASAN)

void *object = head;
void *tail_obj = tail ? : head;

do {
slab_free_hook(s, object);
} while ((object != tail_obj) &&
(object = get_freepointer(s, object)));
#endif
}

static void setup_object(struct kmem_cache *s, struct page *page,
void *object)
{
Expand Down Expand Up @@ -2583,10 +2625,11 @@ EXPORT_SYMBOL(kmem_cache_alloc_node_trace);
* handling required then we can return immediately.
*/
static void __slab_free(struct kmem_cache *s, struct page *page,
void *x, unsigned long addr)
void *head, void *tail, int cnt,
unsigned long addr)

{
void *prior;
void **object = (void *)x;
int was_frozen;
struct page new;
unsigned long counters;
Expand All @@ -2596,7 +2639,8 @@ static void __slab_free(struct kmem_cache *s, struct page *page,
stat(s, FREE_SLOWPATH);

if (kmem_cache_debug(s) &&
!(n = free_debug_processing(s, page, x, addr, &flags)))
!(n = free_debug_processing(s, page, head, tail, cnt,
addr, &flags)))
return;

do {
Expand All @@ -2606,10 +2650,10 @@ static void __slab_free(struct kmem_cache *s, struct page *page,
}
prior = page->freelist;
counters = page->counters;
set_freepointer(s, object, prior);
set_freepointer(s, tail, prior);
new.counters = counters;
was_frozen = new.frozen;
new.inuse--;
new.inuse -= cnt;
if ((!new.inuse || !prior) && !was_frozen) {

if (kmem_cache_has_cpu_partial(s) && !prior) {
Expand Down Expand Up @@ -2640,7 +2684,7 @@ static void __slab_free(struct kmem_cache *s, struct page *page,

} while (!cmpxchg_double_slab(s, page,
prior, counters,
object, new.counters,
head, new.counters,
"__slab_free"));

if (likely(!n)) {
Expand Down Expand Up @@ -2705,15 +2749,20 @@ static void __slab_free(struct kmem_cache *s, struct page *page,
*
* If fastpath is not possible then fall back to __slab_free where we deal
* with all sorts of special processing.
*
* Bulk free of a freelist with several objects (all pointing to the
* same page) possible by specifying head and tail ptr, plus objects
* count (cnt). Bulk free indicated by tail pointer being set.
*/
static __always_inline void slab_free(struct kmem_cache *s,
struct page *page, void *x, unsigned long addr)
static __always_inline void slab_free(struct kmem_cache *s, struct page *page,
void *head, void *tail, int cnt,
unsigned long addr)
{
void **object = (void *)x;
void *tail_obj = tail ? : head;
struct kmem_cache_cpu *c;
unsigned long tid;

slab_free_hook(s, x);
slab_free_freelist_hook(s, head, tail);

redo:
/*
Expand All @@ -2732,19 +2781,19 @@ static __always_inline void slab_free(struct kmem_cache *s,
barrier();

if (likely(page == c->page)) {
set_freepointer(s, object, c->freelist);
set_freepointer(s, tail_obj, c->freelist);

if (unlikely(!this_cpu_cmpxchg_double(
s->cpu_slab->freelist, s->cpu_slab->tid,
c->freelist, tid,
object, next_tid(tid)))) {
head, next_tid(tid)))) {

note_cmpxchg_failure("slab_free", s, tid);
goto redo;
}
stat(s, FREE_FASTPATH);
} else
__slab_free(s, page, x, addr);
__slab_free(s, page, head, tail_obj, cnt, addr);

}

Expand All @@ -2753,7 +2802,7 @@ void kmem_cache_free(struct kmem_cache *s, void *x)
s = cache_from_obj(s, x);
if (!s)
return;
slab_free(s, virt_to_head_page(x), x, _RET_IP_);
slab_free(s, virt_to_head_page(x), x, NULL, 1, _RET_IP_);
trace_kmem_cache_free(_RET_IP_, x);
}
EXPORT_SYMBOL(kmem_cache_free);
Expand Down Expand Up @@ -2788,7 +2837,7 @@ void kmem_cache_free_bulk(struct kmem_cache *s, size_t size, void **p)
c->tid = next_tid(c->tid);
local_irq_enable();
/* Slowpath: overhead locked cmpxchg_double_slab */
__slab_free(s, page, object, _RET_IP_);
__slab_free(s, page, object, object, 1, _RET_IP_);
local_irq_disable();
c = this_cpu_ptr(s->cpu_slab);
}
Expand Down Expand Up @@ -3523,7 +3572,7 @@ void kfree(const void *x)
__free_kmem_pages(page, compound_order(page));
return;
}
slab_free(page->slab_cache, page, object, _RET_IP_);
slab_free(page->slab_cache, page, object, NULL, 1, _RET_IP_);
}
EXPORT_SYMBOL(kfree);

Expand Down

0 comments on commit 8108465

Please sign in to comment.