Slab Defrag / Slab Targeted Reclaim
Subsystems (3)
cover Slab Defrag / Slab Targeted Reclaim
Initial support for Slab defragmentation and targeted reclaim. The functionality here is minimal. We establish a slab API to allow removal or moving of objects between slabs.
The only user provided here is a dentry cache reclaim capability. This is limited to the removal of unused dentries for now. It is planned to later add a similar inode reclaim capability and then extend the move/reclaim to support moving of dentries and inodes.
Slab defragmentation is performed during kmem_cache_shrink. This is usually triggered through the slab shrinkers but can also be manually triggered through the slabinfo command.
Support is provided for antifrag/defrag to evict a specific slab page through the kmem_cache_vacate function call. Since we can only reclaim unused dentries for now that functionality is pretty limited (we need to put some work into making dentries and inode more reclaimable or movable) but we can increase the capabilities over time which will allow us to move slabs from the reclaimable area into the movable area. This will shrink the reclaimable area significantly. Since we can target the vacating of pages this may allow the antifrag code to remove a page that hinders the freeing of higher order page.
PATCH 1/3 SLUB: add support for kmem_cache_ops
We use the parameter formerly used by the destructor to pass an optional pointer to a kmem_cache_ops structure to kmem_cache_create.
kmem_cache_ops is created as empty. Later patches populate kmem_cache_ops.
Create a KMEM_CACHE_OPS macro that allows the specification of a the kmem_cache_ops.
Code to handle kmem_cache_ops is added to SLUB. SLAB and SLOB are updated to be able to take a kmem_cache_ops structure but will ignore it.
--- include/linux/slab.h | 13 +++++++++---- include/linux/slub_def.h | 1 + mm/slab.c | 6 +++--- mm/slob.c | 2 +- mm/slub.c | 44 ++++++++++++++++++++++++++++++-------------- 5 files changed, 44 insertions(+), 22 deletions(-)
include/linux/slab.h +9 -4
@@ -38,10 +38,13 @@ typedef struct kmem_cache kmem_cache_t _void __init kmem_cache_init (void );int slab_is_available (void ); +struct kmem_cache_ops { +}; +struct kmem_cache *kmem_cache_create (const char *,size_t ,size_t ,unsigned long ,void (*)(void *,struct kmem_cache *,unsigned long ), -void (*)(void *,struct kmem_cache *,unsigned long )); +const struct kmem_cache_ops *s);void kmem_cache_destroy (struct kmem_cache *);int kmem_cache_shrink (struct kmem_cache *);void *kmem_cache_alloc (struct kmem_cache *,gfp_t );
@@ -59,9 +62,11 @@ int kmem_ptr_validate(struct kmem_cache* f.e. add ____cacheline_aligned_in_smp to the struct declaration * then the objects will be properly aligned in SMP configurations. */ -#define KMEM_CACHE (__struct, __flags) kmem_cache_create(#__struct,\ - sizeof(struct __struct), __alignof__(struct __struct),\ - (__flags), NULL, NULL) +#define KMEM_CACHE_OPS (__struct, __flags, __ops) \ + kmem_cache_create(#__struct, sizeof(struct __struct), \ + __alignof__(struct __struct), (__flags), NULL, (__ops)) + +#define KMEM_CACHE (__struct, __flags) KMEM_CACHE_OPS(__struct, __flags, NULL)#ifdef CONFIG_NUMA extern void *kmem_cache_alloc_node (struct kmem_cache *,gfp_t flags,int node);
mm/slub.c +30 -14
115 lines not on this page · raw message
include/linux/slub_def.h +1 -0
@@ -40,6 +40,7 @@ struct kmem_cache {int objects;/* Number of objects in slab */ int refcount;/* Refcount for slab cache destroy */ void (*ctor)(void *,struct kmem_cache *,unsigned long ); +const struct kmem_cache_ops *ops;int inuse;/* Offset to metadata */ int align;/* Alignment */ const char *name;/* Name (only for display!) */
mm/slab.c +3 -3
@@ -2100,7 +2100,7 @@ static int setup_cpu_cache(struct kmem_c* @align: The required alignment for the objects. * @flags: SLAB flags * @ctor: A constructor for the objects. -* @dtor: A destructor for the objects (not implemented anymore). +* @ops: A kmem_cache_ops structure (ignored). * * Returns a ptr to the cache on success, NULL on failure. * Cannot be called within a int, but can be interrupted.
@@ -2126,7 +2126,7 @@ struct kmem_cache * kmem_cache_create (const char *name, size_t size, size_t align, unsigned long flags, void (*ctor)(void*, struct kmem_cache *, unsigned long), - void (*dtor)(void*, struct kmem_cache *, unsigned long)) + const struct kmem_cache_ops *ops) { size_t left_over, slab_size, ralign;struct kmem_cache *cachep =NULL , *pc;
@@ -2135,7 +2135,7 @@ kmem_cache_create (const char *name, siz* Sanity checks... these are all serious usage bugs. */ if (!name ||in_interrupt () || (size <BYTES_PER_WORD ) || - size >KMALLOC_MAX_SIZE || dtor) { + size >KMALLOC_MAX_SIZE ) {printk (KERN_ERR "%s: Early error in slab %s\n" , __FUNCTION__, name);BUG ();
mm/slob.c +1 -1
@@ -285,7 +285,7 @@ struct kmem_cache {struct kmem_cache *kmem_cache_create (const char *name,size_t size,size_t align,unsigned long flags,void (*ctor)(void *,struct kmem_cache *,unsigned long ), -void (*dtor)(void *,struct kmem_cache *,unsigned long )) +const struct kmem_cache_ops *o) {struct kmem_cache *c;
PATCH 2/3 SLUB: Implement targeted reclaim and partial list defragmentation
In reply to [email protected]Targeted reclaim allows to target a single slab for reclaim. This is done by calling
kmem_cache_vacate(page);
It will return 1 on success, 0 if the operation failed.
The vacate functionality is also used for slab shrinking. During the shrink operation SLUB will generate a list sorted by the number of objects in use.
We extract pages off that list that are only filled less than a quarter. These objects are then processed using kmem_cache_vacate.
In order for a slabcache to support this functionality a couple of functions must be defined via kmem_cache_ops. These are
int get(struct kmem_cache *s, void *)
Must obtain a reference to the indicated object. SLUB guarantees that the objects is still allocated. However, another thread may be blocked in slab_free attempting to free the same object. It may succeed as soon as get() returns to the slab allocator. The function must detect this situation and return 1 if that is the case. If the object cannot be freed then a negative -Exx code must be returned indicating the reason for the failure.
get() return 0 on success.
No slab operations may be performed in get_reference(). Interrupts are disabled. What can be done is very limited. The slab lock for the page with the object is taken. Any attempt to perform a slab operation may lead to a deadlock.
void put(struct kmem_cache *, void *)
Used to restore the reference count obtained by get() if the reclaim logic decides to abandon the attempt to vacate all objects in a slab. This is usually the case if get() indicates that an object is not freeable.
put() is optional. If it is not defined then it is assumed that we can simply abandon get()s on slab objects.
int kick(struct kmem_cache *, void *)
After SLUB has established references to the remaining objects in a slab it will drop all locks and then use kick() on each of the objects. The existence of the object is guaranteed by virtue of the earlier obtained reference. The callback may perform any slab operation since no locks are held at the time of call.
Function must return 0 if the object was successfully freed. Return -Exxx to indicate that the object is not freeable and to stop further attempt to free objects in this slab.
The callback should remove the object from the slab in some way. This may be accomplished by reclaiming the object and then running kmem_cache_free() or reallocating it and then running kmem_cache_free(). Reallocation is advantageous because the partial slabs were just sorted to have the partial slabs with the most objects first. Allocation is likely to result in filling up a slab so that it can be removed from the partial list.
void sync(void)
After all objects have been removed by kick()s this function will be called to ensure that all free operations have completed. Typically the function called here is synchronize_rcu() if the slab cache uses RCU to free objects. The function is optional. If it is not specified then no synchronization is done before removing the slab.
If a kmem_cache_vacate on a page fails then the slab has usually a pretty low usage ratio. Go through the slab and resequence the freelist so that object addresses increase as we allocate objects. This will trigger the cacheline prefetcher and increase allocations speed.
--- include/linux/slab.h | 34 +++++ mm/slab.c | 9 + mm/slob.c | 9 + mm/slub.c | 304 +++++++++++++++++++++++++++++++++++++++++++++++++-- 4 files changed, 346 insertions(+), 10 deletions(-)
include/linux/slab.h +34 -0
@@ -39,6 +39,39 @@ void __init kmem_cache_init(void);int slab_is_available (void );struct kmem_cache_ops { +/* +* Called with slab lock held and interrupts disabled. +* No slab operation may be performed. +* +* Return 0 if reference was successfully obtained +* Return 1 if a concurrent kmem_cache_free is waiting to free object +* Return -errcode if it is not possible to free the object. +* No reference was obtained. +*/ +int (*get)(struct kmem_cache *,void *); + +/* +* Use to restore the reference count if we abandon the +* attempt to vacate a slab page due to an unmovable +* object. +*/ +void (*put)(struct kmem_cache *,void *); + +/* +* Called with no locks held and interrupts enabled. +* Any operation may be performed in kick_object. +* +* Return 0 for success +* Return -errcode aborts further kicks to objects in the slab +*/ +int (*kick)(struct kmem_cache *,void *); + +/* +* Callback to make sure that all object freeing is complete. +* If the slab destroys objects by RCU then this needs to be +* set to synchronize_rcu(). +*/ +void (*sync)(void ); }; struct kmem_cache *kmem_cache_create(const char *, size_t, size_t,
@@ -53,6 +86,7 @@ void kmem_cache_free(struct kmem_cache *unsigned int kmem_cache_size (struct kmem_cache *);const char *kmem_cache_name (struct kmem_cache *);int kmem_ptr_validate (struct kmem_cache *cachep,const void *ptr); +int kmem_cache_vacate (struct page *);/* * Please use this macro to create slab caches. Simply specify the
mm/slub.c +294 -10
348 lines not on this page · raw message
mm/slab.c +9 -0
@@ -2516,6 +2516,15 @@ int kmem_cache_shrink(struct kmem_cache }EXPORT_SYMBOL (kmem_cache_shrink); +/* +* SLAB does not support slab defragmentation +*/ +int kmem_cache_vacate (struct page *page) +{ +return 0 ; +} +EXPORT_SYMBOL (kmem_cache_vacate); +/** * kmem_cache_destroy - delete a cache * @cachep: the cache to destroy
mm/slob.c +9 -0
@@ -394,6 +394,15 @@ int kmem_cache_shrink(struct kmem_cache }EXPORT_SYMBOL (kmem_cache_shrink); +/* +* SLOB does not support slab defragmentation +*/ +int kmem_cache_vacate (struct page *page) +{ +return 0 ; +} +EXPORT_SYMBOL (kmem_cache_vacate); +int kmem_ptr_validate (struct kmem_cache *a,const void *b) {return 0 ;
PATCH 3/3 Support removal of unused dentry entries via SLUB defrag interface
In reply to [email protected]This patch allows the removal of unused dentry entries in a partial populated slab page. Very limited (yet) in what it can do for reclaim but this catches bad cases in which we have a long list of partial slabs with a few entries in each of them. We can free up the slabs that have only unused dentry entries in them.
get_dentry() uses the dcache lock and then works with dget_locked to obtain a reference to the dentry. An additional complication is that the dentry may be in process of being freed or it may just have been allocated. In that case d_inode is NULL. If we discover this then we simply stay away from the object and return 1 to indicate to the defrag logic that this object will be free. Otherwise we increment the refcount and return success.
kick_dentry() is called after get_dentry_reference() has
been used and after the slab has dropped all of its own locks. The dentry pruning for unused entries works in a straighforward way.
Note: The code here could be significantly improved. If we could get to a point where all used dentries could be moved then full dentry slab defragmentation and vacating of dentry slab pages would become possible.
--- fs/dcache.c | 89 ++++++++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 81 insertions(+), 8 deletions(-)
fs/dcache.c +81 -8
@@ -2114,18 +2114,91 @@ static void __init dcache_init_early(voiINIT_HLIST_HEAD (&dentry_hashtable[loop]); } +/* +* The slab is holding off frees. Thus we can safely examine +* the object without the danger of it vanishing from under us. +*/ +static int get_dentry (struct kmem_cache *s,void *private) +{ +struct dentry *dentry = private; +int result =0 ; + +spin_lock (&dcache_lock); +/* +* dentry->d_inode is set to NULL when the dentry +* is freed. Use that as an indicator that we should +* not interfere with the freeing process. +*/ +if (dentry->d_inode) { +dget_locked (dentry); +if (atomic_read (&dentry->d_count) >2 ) +/* +* Moving of dentries in use not +* implemented yet. +*/ + result = -EINVAL ; + }else + result =1 ; +spin_unlock (&dcache_lock); +return result; +} + +static void put_dentry (struct kmem_cache *s,void *private) +{ +struct dentry *dentry = private; + +dput (dentry); +} + +/* +* Slab has dropped all the locks. Get rid of the +* refcount we obtained earlier and also rid of the +* object. +*/ +static int kick_dentry (struct kmem_cache *s,void *private) +{ +struct dentry *dentry = private; + +spin_lock (&dcache_lock); +spin_lock (&dentry->d_lock); +if (atomic_read (&dentry->d_count) >1 ) { +/* +* Reference count was increased. +* We need to abandon the freeing of +* objects. +*/ +spin_unlock (&dentry->d_lock); +spin_unlock (&dcache_lock); +dput (dentry); +return -EBUSY ; + } + +/* Remove from LRU */ +if (!list_empty (&dentry->d_lru)) { + dentry_stat.nr_unused--; +list_del_init (&dentry->d_lru); + } +/* Drop the entry */ +prune_one_dentry (dentry,1 ); +spin_unlock (&dcache_lock); +return 0 ; +} + +static struct kmem_cache_ops dentry_kmem_cache_ops = { + .get = get_dentry, + .put = put_dentry, + .kick = kick_dentry, + .sync = synchronize_rcu +}; +static void __init dcache_init (unsigned long mempages) {int loop; -/* -* A constructor could be added for stable state like the lists, -* but it is probably not worth it because of the cache nature -* of the dcache. -*/ - dentry_cache =KMEM_CACHE (dentry, -SLAB_RECLAIM_ACCOUNT |SLAB_PANIC |SLAB_MEM_SPREAD ); - + dentry_cache =KMEM_CACHE_OPS (dentry, +SLAB_RECLAIM_ACCOUNT |SLAB_PANIC |SLAB_MEM_SPREAD , + &dentry_kmem_cache_ops); +register_shrinker (&dcache_shrinker);/* Hash may have been set up in dcache_init_early */