[RFC PATCH v5 15/16] dcache: Implement partial shrink via Slab Movable Objects

From: Tobin C. Harding
Date: Mon May 20 2019 - 01:45:22 EST


The dentry slab cache is susceptible to internal fragmentation. Now
that we have Slab Movable Objects we can attempt to defragment the
dcache. Dentry objects are inherently _not_ relocatable however under
some conditions they can be free'd. This is the same as shrinking the
dcache but instead of shrinking the whole cache we only attempt to free
those objects that are located in partially full slab pages. There is
no guarantee that this will reduce the memory usage of the system, it is
a compromise between fragmented memory and total cache shrinkage with
the hope that some memory pressure can be alleviated.

This is implemented using the newly added Slab Movable Objects
infrastructure. The dcache 'migration' function is intentionally _not_
called 'd_migrate' because we only free, we do not migrate. Call it
'd_partial_shrink' to make explicit that no reallocation is done.

Implement isolate and 'migrate' functions for the dentry slab cache.

Signed-off-by: Tobin C. Harding <tobin@xxxxxxxxxx>
---
fs/dcache.c | 76 +++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 76 insertions(+)

diff --git a/fs/dcache.c b/fs/dcache.c
index b7318615979d..0dfe580c2d42 100644
--- a/fs/dcache.c
+++ b/fs/dcache.c
@@ -31,6 +31,7 @@
#include <linux/bit_spinlock.h>
#include <linux/rculist_bl.h>
#include <linux/list_lru.h>
+#include <linux/backing-dev.h>
#include "internal.h"
#include "mount.h"

@@ -3071,6 +3072,79 @@ void d_tmpfile(struct dentry *dentry, struct inode *inode)
}
EXPORT_SYMBOL(d_tmpfile);

+/*
+ * d_isolate() - Dentry isolation callback function.
+ * @s: The dentry cache.
+ * @v: Vector of pointers to the objects to isolate.
+ * @nr: Number of objects in @v.
+ *
+ * The slab allocator is holding off frees. We can safely examine
+ * the object without the danger of it vanishing from under us.
+ */
+static void *d_isolate(struct kmem_cache *s, void **v, int nr)
+{
+ struct list_head *dispose;
+ struct dentry *dentry;
+ int i;
+
+ dispose = kmalloc(sizeof(*dispose), GFP_KERNEL);
+ if (!dispose)
+ return NULL;
+
+ INIT_LIST_HEAD(dispose);
+
+ for (i = 0; i < nr; i++) {
+ dentry = v[i];
+ spin_lock(&dentry->d_lock);
+
+ if (dentry->d_lockref.count > 0 ||
+ dentry->d_flags & DCACHE_SHRINK_LIST) {
+ spin_unlock(&dentry->d_lock);
+ continue;
+ }
+
+ if (dentry->d_flags & DCACHE_LRU_LIST)
+ d_lru_del(dentry);
+
+ d_shrink_add(dentry, dispose);
+ spin_unlock(&dentry->d_lock);
+ }
+
+ return dispose;
+}
+
+/*
+ * d_partial_shrink() - Dentry migration callback function.
+ * @s: The dentry cache.
+ * @_unused: We do not access the vector.
+ * @__unused: No need for length of vector.
+ * @___unused: We do not do any allocation.
+ * @private: list_head pointer representing the shrink list.
+ *
+ * Dispose of the shrink list created during isolation function.
+ *
+ * Dentry objects can _not_ be relocated and shrinking the whole dcache
+ * can be expensive. This is an effort to free dentry objects that are
+ * stopping slab pages from being free'd without clearing the whole dcache.
+ *
+ * This callback is called from the SLUB allocator object migration
+ * infrastructure in attempt to free up slab pages by freeing dentry
+ * objects from partially full slabs.
+ */
+static void d_partial_shrink(struct kmem_cache *s, void **_unused, int __unused,
+ int ___unused, void *private)
+{
+ struct list_head *dispose = private;
+
+ if (!private) /* kmalloc error during isolate. */
+ return;
+
+ if (!list_empty(dispose))
+ shrink_dentry_list(dispose);
+
+ kfree(private);
+}
+
static __initdata unsigned long dhash_entries;
static int __init set_dhash_entries(char *str)
{
@@ -3116,6 +3190,8 @@ static void __init dcache_init(void)
sizeof_field(struct dentry, d_iname),
dcache_ctor);

+ kmem_cache_setup_mobility(dentry_cache, d_isolate, d_partial_shrink);
+
/* Hash may have been set up in dcache_init_early */
if (!hashdist)
return;
--
2.21.0