mirror of
git://git.yoctoproject.org/linux-yocto.git
synced 2025-10-23 07:23:12 +02:00
mm, slub: refactor free debug processing
Since commit c7323a5ad0 ("mm/slub: restrict sysfs validation to debug
caches and make it safe"), caches with debugging enabled use the
free_debug_processing() function to do both freeing checks and actual
freeing to partial list under list_lock, bypassing the fast paths.
We will want to use the same path for CONFIG_SLUB_TINY, but without the
debugging checks, so refactor the code so that free_debug_processing()
does only the checks, while the freeing is handled by a new function
free_to_partial_list().
For consistency, change return parameter alloc_debug_processing() from
int to bool and correct the !SLUB_DEBUG variant to return true and not
false. This didn't matter until now, but will in the following changes.
Signed-off-by: Vlastimil Babka <vbabka@suse.cz>
Acked-by: Mike Rapoport <rppt@linux.ibm.com>
Reviewed-by: Christoph Lameter <cl@linux.com>
Reviewed-by: Hyeonggon Yoo <42.hyeyoo@gmail.com>
This commit is contained in:
parent
3d97d976e5
commit
fa9b88e459
154
mm/slub.c
154
mm/slub.c
|
|
@ -1368,7 +1368,7 @@ static inline int alloc_consistency_checks(struct kmem_cache *s,
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static noinline int alloc_debug_processing(struct kmem_cache *s,
|
static noinline bool alloc_debug_processing(struct kmem_cache *s,
|
||||||
struct slab *slab, void *object, int orig_size)
|
struct slab *slab, void *object, int orig_size)
|
||||||
{
|
{
|
||||||
if (s->flags & SLAB_CONSISTENCY_CHECKS) {
|
if (s->flags & SLAB_CONSISTENCY_CHECKS) {
|
||||||
|
|
@ -1380,7 +1380,7 @@ static noinline int alloc_debug_processing(struct kmem_cache *s,
|
||||||
trace(s, slab, object, 1);
|
trace(s, slab, object, 1);
|
||||||
set_orig_size(s, object, orig_size);
|
set_orig_size(s, object, orig_size);
|
||||||
init_object(s, object, SLUB_RED_ACTIVE);
|
init_object(s, object, SLUB_RED_ACTIVE);
|
||||||
return 1;
|
return true;
|
||||||
|
|
||||||
bad:
|
bad:
|
||||||
if (folio_test_slab(slab_folio(slab))) {
|
if (folio_test_slab(slab_folio(slab))) {
|
||||||
|
|
@ -1393,7 +1393,7 @@ bad:
|
||||||
slab->inuse = slab->objects;
|
slab->inuse = slab->objects;
|
||||||
slab->freelist = NULL;
|
slab->freelist = NULL;
|
||||||
}
|
}
|
||||||
return 0;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline int free_consistency_checks(struct kmem_cache *s,
|
static inline int free_consistency_checks(struct kmem_cache *s,
|
||||||
|
|
@ -1646,17 +1646,17 @@ static inline void setup_object_debug(struct kmem_cache *s, void *object) {}
|
||||||
static inline
|
static inline
|
||||||
void setup_slab_debug(struct kmem_cache *s, struct slab *slab, void *addr) {}
|
void setup_slab_debug(struct kmem_cache *s, struct slab *slab, void *addr) {}
|
||||||
|
|
||||||
static inline int alloc_debug_processing(struct kmem_cache *s,
|
static inline bool alloc_debug_processing(struct kmem_cache *s,
|
||||||
struct slab *slab, void *object, int orig_size) { return 0; }
|
struct slab *slab, void *object, int orig_size) { return true; }
|
||||||
|
|
||||||
static inline void free_debug_processing(
|
static inline bool free_debug_processing(struct kmem_cache *s,
|
||||||
struct kmem_cache *s, struct slab *slab,
|
struct slab *slab, void *head, void *tail, int *bulk_cnt,
|
||||||
void *head, void *tail, int bulk_cnt,
|
unsigned long addr, depot_stack_handle_t handle) { return true; }
|
||||||
unsigned long addr) {}
|
|
||||||
|
|
||||||
static inline void slab_pad_check(struct kmem_cache *s, struct slab *slab) {}
|
static inline void slab_pad_check(struct kmem_cache *s, struct slab *slab) {}
|
||||||
static inline int check_object(struct kmem_cache *s, struct slab *slab,
|
static inline int check_object(struct kmem_cache *s, struct slab *slab,
|
||||||
void *object, u8 val) { return 1; }
|
void *object, u8 val) { return 1; }
|
||||||
|
static inline depot_stack_handle_t set_track_prepare(void) { return 0; }
|
||||||
static inline void set_track(struct kmem_cache *s, void *object,
|
static inline void set_track(struct kmem_cache *s, void *object,
|
||||||
enum track_item alloc, unsigned long addr) {}
|
enum track_item alloc, unsigned long addr) {}
|
||||||
static inline void add_full(struct kmem_cache *s, struct kmem_cache_node *n,
|
static inline void add_full(struct kmem_cache *s, struct kmem_cache_node *n,
|
||||||
|
|
@ -2833,38 +2833,28 @@ static inline unsigned long node_nr_objs(struct kmem_cache_node *n)
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Supports checking bulk free of a constructed freelist */
|
/* Supports checking bulk free of a constructed freelist */
|
||||||
static noinline void free_debug_processing(
|
static inline bool free_debug_processing(struct kmem_cache *s,
|
||||||
struct kmem_cache *s, struct slab *slab,
|
struct slab *slab, void *head, void *tail, int *bulk_cnt,
|
||||||
void *head, void *tail, int bulk_cnt,
|
unsigned long addr, depot_stack_handle_t handle)
|
||||||
unsigned long addr)
|
|
||||||
{
|
{
|
||||||
struct kmem_cache_node *n = get_node(s, slab_nid(slab));
|
bool checks_ok = false;
|
||||||
struct slab *slab_free = NULL;
|
|
||||||
void *object = head;
|
void *object = head;
|
||||||
int cnt = 0;
|
int cnt = 0;
|
||||||
unsigned long flags;
|
|
||||||
bool checks_ok = false;
|
|
||||||
depot_stack_handle_t handle = 0;
|
|
||||||
|
|
||||||
if (s->flags & SLAB_STORE_USER)
|
|
||||||
handle = set_track_prepare();
|
|
||||||
|
|
||||||
spin_lock_irqsave(&n->list_lock, flags);
|
|
||||||
|
|
||||||
if (s->flags & SLAB_CONSISTENCY_CHECKS) {
|
if (s->flags & SLAB_CONSISTENCY_CHECKS) {
|
||||||
if (!check_slab(s, slab))
|
if (!check_slab(s, slab))
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (slab->inuse < bulk_cnt) {
|
if (slab->inuse < *bulk_cnt) {
|
||||||
slab_err(s, slab, "Slab has %d allocated objects but %d are to be freed\n",
|
slab_err(s, slab, "Slab has %d allocated objects but %d are to be freed\n",
|
||||||
slab->inuse, bulk_cnt);
|
slab->inuse, *bulk_cnt);
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
|
|
||||||
next_object:
|
next_object:
|
||||||
|
|
||||||
if (++cnt > bulk_cnt)
|
if (++cnt > *bulk_cnt)
|
||||||
goto out_cnt;
|
goto out_cnt;
|
||||||
|
|
||||||
if (s->flags & SLAB_CONSISTENCY_CHECKS) {
|
if (s->flags & SLAB_CONSISTENCY_CHECKS) {
|
||||||
|
|
@ -2886,57 +2876,18 @@ next_object:
|
||||||
checks_ok = true;
|
checks_ok = true;
|
||||||
|
|
||||||
out_cnt:
|
out_cnt:
|
||||||
if (cnt != bulk_cnt)
|
if (cnt != *bulk_cnt) {
|
||||||
slab_err(s, slab, "Bulk free expected %d objects but found %d\n",
|
slab_err(s, slab, "Bulk free expected %d objects but found %d\n",
|
||||||
bulk_cnt, cnt);
|
*bulk_cnt, cnt);
|
||||||
|
*bulk_cnt = cnt;
|
||||||
|
}
|
||||||
|
|
||||||
out:
|
out:
|
||||||
if (checks_ok) {
|
|
||||||
void *prior = slab->freelist;
|
|
||||||
|
|
||||||
/* Perform the actual freeing while we still hold the locks */
|
|
||||||
slab->inuse -= cnt;
|
|
||||||
set_freepointer(s, tail, prior);
|
|
||||||
slab->freelist = head;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* If the slab is empty, and node's partial list is full,
|
|
||||||
* it should be discarded anyway no matter it's on full or
|
|
||||||
* partial list.
|
|
||||||
*/
|
|
||||||
if (slab->inuse == 0 && n->nr_partial >= s->min_partial)
|
|
||||||
slab_free = slab;
|
|
||||||
|
|
||||||
if (!prior) {
|
|
||||||
/* was on full list */
|
|
||||||
remove_full(s, n, slab);
|
|
||||||
if (!slab_free) {
|
|
||||||
add_partial(n, slab, DEACTIVATE_TO_TAIL);
|
|
||||||
stat(s, FREE_ADD_PARTIAL);
|
|
||||||
}
|
|
||||||
} else if (slab_free) {
|
|
||||||
remove_partial(n, slab);
|
|
||||||
stat(s, FREE_REMOVE_PARTIAL);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (slab_free) {
|
|
||||||
/*
|
|
||||||
* Update the counters while still holding n->list_lock to
|
|
||||||
* prevent spurious validation warnings
|
|
||||||
*/
|
|
||||||
dec_slabs_node(s, slab_nid(slab_free), slab_free->objects);
|
|
||||||
}
|
|
||||||
|
|
||||||
spin_unlock_irqrestore(&n->list_lock, flags);
|
|
||||||
|
|
||||||
if (!checks_ok)
|
if (!checks_ok)
|
||||||
slab_fix(s, "Object at 0x%p not freed", object);
|
slab_fix(s, "Object at 0x%p not freed", object);
|
||||||
|
|
||||||
if (slab_free) {
|
return checks_ok;
|
||||||
stat(s, FREE_SLAB);
|
|
||||||
free_slab(s, slab_free);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
#endif /* CONFIG_SLUB_DEBUG */
|
#endif /* CONFIG_SLUB_DEBUG */
|
||||||
|
|
||||||
|
|
@ -3453,6 +3404,67 @@ void *kmem_cache_alloc_node(struct kmem_cache *s, gfp_t gfpflags, int node)
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL(kmem_cache_alloc_node);
|
EXPORT_SYMBOL(kmem_cache_alloc_node);
|
||||||
|
|
||||||
|
static noinline void free_to_partial_list(
|
||||||
|
struct kmem_cache *s, struct slab *slab,
|
||||||
|
void *head, void *tail, int bulk_cnt,
|
||||||
|
unsigned long addr)
|
||||||
|
{
|
||||||
|
struct kmem_cache_node *n = get_node(s, slab_nid(slab));
|
||||||
|
struct slab *slab_free = NULL;
|
||||||
|
int cnt = bulk_cnt;
|
||||||
|
unsigned long flags;
|
||||||
|
depot_stack_handle_t handle = 0;
|
||||||
|
|
||||||
|
if (s->flags & SLAB_STORE_USER)
|
||||||
|
handle = set_track_prepare();
|
||||||
|
|
||||||
|
spin_lock_irqsave(&n->list_lock, flags);
|
||||||
|
|
||||||
|
if (free_debug_processing(s, slab, head, tail, &cnt, addr, handle)) {
|
||||||
|
void *prior = slab->freelist;
|
||||||
|
|
||||||
|
/* Perform the actual freeing while we still hold the locks */
|
||||||
|
slab->inuse -= cnt;
|
||||||
|
set_freepointer(s, tail, prior);
|
||||||
|
slab->freelist = head;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If the slab is empty, and node's partial list is full,
|
||||||
|
* it should be discarded anyway no matter it's on full or
|
||||||
|
* partial list.
|
||||||
|
*/
|
||||||
|
if (slab->inuse == 0 && n->nr_partial >= s->min_partial)
|
||||||
|
slab_free = slab;
|
||||||
|
|
||||||
|
if (!prior) {
|
||||||
|
/* was on full list */
|
||||||
|
remove_full(s, n, slab);
|
||||||
|
if (!slab_free) {
|
||||||
|
add_partial(n, slab, DEACTIVATE_TO_TAIL);
|
||||||
|
stat(s, FREE_ADD_PARTIAL);
|
||||||
|
}
|
||||||
|
} else if (slab_free) {
|
||||||
|
remove_partial(n, slab);
|
||||||
|
stat(s, FREE_REMOVE_PARTIAL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (slab_free) {
|
||||||
|
/*
|
||||||
|
* Update the counters while still holding n->list_lock to
|
||||||
|
* prevent spurious validation warnings
|
||||||
|
*/
|
||||||
|
dec_slabs_node(s, slab_nid(slab_free), slab_free->objects);
|
||||||
|
}
|
||||||
|
|
||||||
|
spin_unlock_irqrestore(&n->list_lock, flags);
|
||||||
|
|
||||||
|
if (slab_free) {
|
||||||
|
stat(s, FREE_SLAB);
|
||||||
|
free_slab(s, slab_free);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Slow path handling. This may still be called frequently since objects
|
* Slow path handling. This may still be called frequently since objects
|
||||||
* have a longer lifetime than the cpu slabs in most processing loads.
|
* have a longer lifetime than the cpu slabs in most processing loads.
|
||||||
|
|
@ -3479,7 +3491,7 @@ static void __slab_free(struct kmem_cache *s, struct slab *slab,
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (kmem_cache_debug(s)) {
|
if (kmem_cache_debug(s)) {
|
||||||
free_debug_processing(s, slab, head, tail, cnt, addr);
|
free_to_partial_list(s, slab, head, tail, cnt, addr);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user