Skip to contentSkip to conversation

Slab Movable Objects (SMO)

RFC PATCH Superseded Tobin C. Harding 6 participants 28 messages Apr 3, 2019 – Apr 4, 2019

Subsystems (6)

v2 of 5 Superseded Latest is v5 →

lore
Share & tools
lore
#0 lore raw

v2 cover Slab Movable Objects (SMO)

Hi,

Version 2 re-structured to better follow the structure of Chirstoph's original patchset (linked below). Functions renamed and other suggestions on v1 from Roman implemented.

This version also adds an attempt at implementing object migration for the dcache, appropriate filesystem folk CC'd. Please see comments below in 'dcache' section.

Applies on top of Linus' tree (tag: v5.1-rc3).

This is a patch set implementing movable objects within the SLUB allocator. This is work based on Christopher's patch set:

https://lore.kernel.org/patchwork/project/lkml/list/?series=377335

The original code logic is from that set and implemented by Christopher. Clean up, refactoring, documentation, and additional features by myself. Blame for any bugs remaining falls solely with myself. Patches using Christopher's code use the Co-developed-by tag but do not currently have his SOB tag.

The core of the implementation is now contained within the first 4 patches (primarily patch #4).

Patches 7,8,10 add test code including a test modules to play around with this. With the series applied one can see functionality in action by using the slabinfo command on the xarray slab cache

slabinfo radix_tree_node -r

The NUMA stuff works as claimed with the radix_tree_node slab cache.

dcache
------

The dcache patches are my best effort on top of Christoph's original work. The dcache has changed a lot since then (2009). FTR one month ago was the first time I have ever opened fs/dcache.c.

I have been playing with this for at least two weeks without any functional changes to the dcache patches - I do not think me playing with it more is going to improve my understanding of the dcache so I am asking for help here. I've been testing in Qemu (both using ramdisk filesystem and a disk image filesystem) as well as on bare metal.

Shrinking the dcache with:

slabinfo dentry -s

produces _more_ partial slabs than before and repeated calls continue to increase the number of partial slabs. Although the initial calls do decrease the total number of cached objects. I cannot explain this.

During development I added a bunch of printks and the majority of dentry slab objects are skipped during the isolation function due to the following check from d_isolate():

	if (dentry->d_inode &&
	    !mapping_cap_writeback_dirty(dentry->d_inode->i_mapping))
	    ...
	    /* skip object*/

I cannot explain the large number of dentry objects skipped by this clause.

Any suggestions no matter how wild very much appreciated. Tips on files I should study or anything else I could do to better understand what is needed to understand to work with this. So far I have been primarily trying to grok the VFS and the dcache in particular via:

fs/dcache.c
include/linux/fs.h
include/linux/dcache.h
Documentation/filesystems/vfs.txt

I also tried using the cache shrinkers

echo 2 > /proc/sys/vm/drop_caches

Then shrinking the dentry slab cache. This resulted in a bunch of things disappearing e.g. sysfs gets unmounted, /home directory contents disappear. Again, I cannot explain this. Should this be doable if this series was implemented correctly?

Thanks for taking the time to look at this.

Tobin.

Tobin C. Harding (14):
slub: Add isolate() and migrate() methods tools/vm/slabinfo: Add support for -C and -M options
slub: Sort slab cache list
slub: Slab defrag core
tools/vm/slabinfo: Add remote node defrag ratio output tools/vm/slabinfo: Add defrag_used_ratio output tools/testing/slab: Add object migration test module tools/testing/slab: Add object migration test suite
xarray: Implement migration function for objects tools/testing/slab: Add XArray movable objects tests
slub: Enable moving objects to/from specific nodes
slub: Enable balancing slabs across nodes
dcache: Provide a dentry constructor
dcache: Implement object migration

 Documentation/ABI/testing/sysfs-kernel-slab |  14 +
 fs/dcache.c                                 | 124 ++-
 include/linux/slab.h                        |  71 ++
 include/linux/slub_def.h                    |  10 +
 lib/radix-tree.c                            |  13 +
 lib/xarray.c                                |  46 ++
 mm/Kconfig                                  |   7 +
 mm/slab_common.c                            |   2 +-
 mm/slub.c                                   | 819 ++++++++++++++++++--
 tools/testing/slab/Makefile                 |  10 +
 tools/testing/slab/slub_defrag.c            | 567 ++++++++++++++
 tools/testing/slab/slub_defrag.py           | 451 +++++++++++
 tools/testing/slab/slub_defrag_xarray.c     | 211 +++++
 tools/vm/slabinfo.c                         |  51 +-
 14 files changed, 2303 insertions(+), 93 deletions(-)
 create mode 100644 tools/testing/slab/Makefile
 create mode 100644 tools/testing/slab/slub_defrag.c
 create mode 100755 tools/testing/slab/slub_defrag.py
 create mode 100644 tools/testing/slab/slub_defrag_xarray.c
Signature · 1 line
2.21.0
#1 lore raw

RFC PATCH v2 1/14 slub: Add isolate() and migrate() methods

Add the two methods needed for moving objects and enable the display of the callbacks via the /sys/kernel/slab interface.

Add documentation explaining the use of these methods and the prototypes for slab.h. Add functions to setup the callbacks method for a slab cache.

Add empty functions for SLAB/SLOB. The API is generic so it could be theoretically implemented for these allocators as well.

Change sysfs 'ctor' field to be 'ops' to contain all the callback operations defined for a slab cache. Display the existing 'ctor' callback in the ops fields contents along with 'isolate' and 'migrate' callbacks.

---
 include/linux/slab.h     | 70 ++++++++++++++++++++++++++++++++++++++++
 include/linux/slub_def.h |  3 ++
 mm/slub.c                | 59 +++++++++++++++++++++++++++++----
 3 files changed, 126 insertions(+), 6 deletions(-)
include/linux/slab.h +70 -0
index 9449b19c5f10..886fc130334d 100644
@@ -154,6 +154,76 @@ void memcg_create_kmem_cache(struct mem_cgroup *, struct kmem_cache *);
 void memcg_deactivate_kmem_caches(struct mem_cgroup *);
 void memcg_destroy_kmem_caches(struct mem_cgroup *);
 
+/*
+ * Function prototypes passed to kmem_cache_setup_mobility() to enable
+ * mobile objects and targeted reclaim in slab caches.
+ */
+
+/**
+ * typedef kmem_cache_isolate_func - Object migration callback function.
+ * @s: The cache we are working on.
+ * @ptr: Pointer to an array of pointers to the objects to isolate.
+ * @nr: Number of objects in @ptr array.
+ *
+ * The purpose of kmem_cache_isolate_func() is to pin each object so that
+ * they cannot be freed until kmem_cache_migrate_func() has processed
+ * them. This may be accomplished by increasing the refcount or setting
+ * a flag.
+ *
+ * The object pointer array passed is also passed to
+ * kmem_cache_migrate_func().  The function may remove objects from the
+ * array by setting pointers to %NULL. This is useful if we can
+ * determine that an object is being freed because
+ * kmem_cache_isolate_func() was called when the subsystem was calling
+ * kmem_cache_free().  In that case it is not necessary to increase the
+ * refcount or specially mark the object because the release of the slab
+ * lock will lead to the immediate freeing of the object.
+ *
+ * Context: Called with locks held so that the slab objects cannot be
+ *          freed.  We are in an atomic context and no slab operations
+ *          may be performed.
+ * Return: A pointer that is passed to the migrate function. If any
+ *         objects cannot be touched at this point then the pointer may
+ *         indicate a failure and then the migration function can simply
+ *         remove the references that were already obtained. The private
+ *         data could be used to track the objects that were already pinned.
+ */
+typedef void *kmem_cache_isolate_func(struct kmem_cache *s, void **ptr, int nr);
+
+/**
+ * typedef kmem_cache_migrate_func - Object migration callback function.
+ * @s: The cache we are working on.
+ * @ptr: Pointer to an array of pointers to the objects to migrate.
+ * @nr: Number of objects in @ptr array.
+ * @node: The NUMA node where the object should be allocated.
+ * @private: The pointer returned by kmem_cache_isolate_func().
+ *
+ * This function is responsible for migrating objects.  Typically, for
+ * each object in the input array you will want to allocate an new
+ * object, copy the original object, update any pointers, and free the
+ * old object.
+ *
+ * After this function returns all pointers to the old object should now
+ * point to the new object.
+ *
+ * Context: Called with no locks held and interrupts enabled.  Sleeping
+ *          is possible.  Any operation may be performed.
+ */
+typedef void kmem_cache_migrate_func(struct kmem_cache *s, void **ptr,
+				     int nr, int node, void *private);
+
+/*
+ * kmem_cache_setup_mobility() is used to setup callbacks for a slab cache.
+ */
+#ifdef CONFIG_SLUB
+void kmem_cache_setup_mobility(struct kmem_cache *, kmem_cache_isolate_func,
+			       kmem_cache_migrate_func);
+#else
+static inline void
+kmem_cache_setup_mobility(struct kmem_cache *s, kmem_cache_isolate_func isolate,
+			  kmem_cache_migrate_func migrate) {}
+#endif
+
 /*
  * Please use this macro to create slab caches. Simply specify the
  * name of the structure and maybe some flags that are listed above.
include/linux/slub_def.h +3 -0
index d2153789bd9f..2879a2f5f8eb 100644
@@ -99,6 +99,9 @@ struct kmem_cache {
 	gfp_t allocflags;	/* gfp flags to use on each alloc */
 	int refcount;		/* Refcount for slab cache destroy */
 	void (*ctor)(void *);
+	kmem_cache_isolate_func *isolate;
+	kmem_cache_migrate_func *migrate;
+
 	unsigned int inuse;		/* Offset to metadata */
 	unsigned int align;		/* Alignment */
 	unsigned int red_left_pad;	/* Left redzone padding size */
mm/slub.c +53 -6

79 lines not on this page · raw message

Signature · 1 line
2.21.0
#2 lore raw

RFC PATCH v2 2/14 tools/vm/slabinfo: Add support for -C and -M options

In reply to Tobin C. Harding

-C lists caches that use a ctor.

-M lists caches that support object migration.

Add command line options to show caches with a constructor and caches that are movable (i.e. have migrate function).

---
 tools/vm/slabinfo.c | 40 ++++++++++++++++++++++++++++++++++++----
 1 file changed, 36 insertions(+), 4 deletions(-)
tools/vm/slabinfo.c +36 -4
index 73818f1b2ef8..cbfc56c44c2f 100644
@@ -33,6 +33,7 @@ struct slabinfo {
 	unsigned int hwcache_align, object_size, objs_per_slab;
 	unsigned int sanity_checks, slab_size, store_user, trace;
 	int order, poison, reclaim_account, red_zone;
+	int movable, ctor;
 	unsigned long partial, objects, slabs, objects_partial, objects_total;
 	unsigned long alloc_fastpath, alloc_slowpath;
 	unsigned long free_fastpath, free_slowpath;
@@ -67,6 +68,8 @@ int show_report;
 int show_alias;
 int show_slab;
 int skip_zero = 1;
+int show_movable;
+int show_ctor;
 int show_numa;
 int show_track;
 int show_first_alias;
@@ -109,11 +112,13 @@ static void fatal(const char *x, ...)
 
 static void usage(void)
 {
-	printf("slabinfo 4/15/2011. (c) 2007 sgi/(c) 2011 Linux Foundation.\n\n"
-		"slabinfo [-aADefhilnosrStTvz1LXBU] [N=K] [-dafzput] [slab-regexp]\n"
+	printf("slabinfo 4/15/2017. (c) 2007 sgi/(c) 2011 Linux Foundation/(c) 2017 Jump Trading LLC.\n\n"
+	       "slabinfo [-aACDefhilMnosrStTvz1LXBU] [N=K] [-dafzput] [slab-regexp]\n"
+
 		"-a|--aliases           Show aliases\n"
 		"-A|--activity          Most active slabs first\n"
 		"-B|--Bytes             Show size in bytes\n"
+		"-C|--ctor              Show slabs with ctors\n"
 		"-D|--display-active    Switch line format to activity\n"
 		"-e|--empty             Show empty slabs\n"
 		"-f|--first-alias       Show first alias\n"
@@ -121,6 +126,7 @@ static void usage(void)
 		"-i|--inverted          Inverted list\n"
 		"-l|--slabs             Show slabs\n"
 		"-L|--Loss              Sort by loss\n"
+		"-M|--movable           Show caches that support movable objects\n"
 		"-n|--numa              Show NUMA information\n"
 		"-N|--lines=K           Show the first K slabs\n"
 		"-o|--ops               Show kmem_cache_ops\n"
@@ -588,6 +594,12 @@ static void slabcache(struct slabinfo *s)
 	if (show_empty && s->slabs)
 		return;
 
+	if (show_ctor && !s->ctor)
+		return;
+
+	if (show_movable && !s->movable)
+		return;
+
 	if (sort_loss == 0)
 		store_size(size_str, slab_size(s));
 	else
@@ -602,6 +614,10 @@ static void slabcache(struct slabinfo *s)
 		*p++ = '*';
 	if (s->cache_dma)
 		*p++ = 'd';
+	if (s->ctor)
+		*p++ = 'C';
+	if (s->movable)
+		*p++ = 'M';
 	if (s->hwcache_align)
 		*p++ = 'A';
 	if (s->poison)
@@ -636,7 +652,8 @@ static void slabcache(struct slabinfo *s)
 		printf("%-21s %8ld %7d %15s %14s %4d %1d %3ld %3ld %s\n",
 			s->name, s->objects, s->object_size, size_str, dist_str,
 			s->objs_per_slab, s->order,
-			s->slabs ? (s->partial * 100) / s->slabs : 100,
+			s->slabs ? (s->partial * 100) /
+					(s->slabs * s->objs_per_slab) : 100,
 			s->slabs ? (s->objects * s->object_size * 100) /
 				(s->slabs * (page_size << s->order)) : 100,
 			flags);
@@ -1256,6 +1273,13 @@ static void read_slab_dir(void)
 			slab->alloc_node_mismatch = get_obj("alloc_node_mismatch");
 			slab->deactivate_bypass = get_obj("deactivate_bypass");
 			chdir("..");
+			if (read_slab_obj(slab, "ops")) {
+				if (strstr(buffer, "ctor :"))
+					slab->ctor = 1;
+				if (strstr(buffer, "migrate :"))
+					slab->movable = 1;
+			}
+
 			if (slab->name[0] == ':')
 				alias_targets++;
 			slab++;
@@ -1332,6 +1356,8 @@ static void xtotals(void)
 }
 
 struct option opts[] = {
+	{ "ctor", no_argument, NULL, 'C' },
+	{ "movable", no_argument, NULL, 'M' },
 	{ "aliases", no_argument, NULL, 'a' },
 	{ "activity", no_argument, NULL, 'A' },
 	{ "debug", optional_argument, NULL, 'd' },
@@ -1367,7 +1393,7 @@ int main(int argc, char *argv[])
 
 	page_size = getpagesize();
 
-	while ((c = getopt_long(argc, argv, "aAd::Defhil1noprstvzTSN:LXBU",
+	while ((c = getopt_long(argc, argv, "aACd::Defhil1MnoprstvzTSN:LXBU",
 						opts, NULL)) != -1)
 		switch (c) {
 		case '1':
@@ -1376,6 +1402,9 @@ int main(int argc, char *argv[])
 		case 'a':
 			show_alias = 1;
 			break;
+		case 'C':
+			show_ctor = 1;
+			break;
 		case 'A':
 			sort_active = 1;
 			break;
@@ -1399,6 +1428,9 @@ int main(int argc, char *argv[])
 		case 'i':
 			show_inverted = 1;
 			break;
+		case 'M':
+			show_movable = 1;
+			break;
 		case 'n':
 			show_numa = 1;
 			break;
Signature · 1 line
2.21.0
#3 lore raw

RFC PATCH v2 3/14 slub: Sort slab cache list

In reply to Tobin C. Harding

It is advantageous to have all defragmentable slabs together at the beginning of the list of slabs so that there is no need to scan the complete list. Put defragmentable caches first when adding a slab cache and others last.

---
 mm/slab_common.c | 2 +-
 mm/slub.c        | 6 ++++++
 2 files changed, 7 insertions(+), 1 deletion(-)
mm/slab_common.c +1 -1
index 58251ba63e4a..db5e9a0b1535 100644
@@ -393,7 +393,7 @@ static struct kmem_cache *create_cache(const char *name,
 		goto out_free_cache;
 
 	s->refcount = 1;
-	list_add(&s->list, &slab_caches);
+	list_add_tail(&s->list, &slab_caches);
 	memcg_link_cache(s);
 out:
 	if (err)
mm/slub.c +6 -0
index ae44d640b8c1..f6b0e4a395ef 100644
@@ -4342,6 +4342,8 @@ void kmem_cache_setup_mobility(struct kmem_cache *s,
 		return;
 	}
 
+	mutex_lock(&slab_mutex);
+
 	s->isolate = isolate;
 	s->migrate = migrate;
 
@@ -4350,6 +4352,10 @@ void kmem_cache_setup_mobility(struct kmem_cache *s,
 	 * to disable fast cmpxchg based processing.
 	 */
 	s->flags &= ~__CMPXCHG_DOUBLE;
+
+	list_move(&s->list, &slab_caches);	/* Move to top */
+
+	mutex_unlock(&slab_mutex);
 }
 EXPORT_SYMBOL(kmem_cache_setup_mobility);
 
Signature · 1 line
2.21.0
#4 lore raw

RFC PATCH v2 4/14 slub: Slab defrag core

In reply to Tobin C. Harding

Internal fragmentation can occur within pages used by the slub allocator. Under some workloads large numbers of pages can be used by partial slab pages. This under-utilisation is bad simply because it wastes memory but also because if the system is under memory pressure higher order allocations may become difficult to satisfy. If we can defrag slab caches we can alleviate these problems.

Implement Slab Movable Objects in order to defragment slab caches.

Slab defragmentation may occur:

1. Unconditionally when __kmem_cache_shrink() is called on a slab cache by the kernel calling kmem_cache_shrink().

2. Unconditionally through the use of the slabinfo command.

slabinfo <cache> -s

3. Conditionally via the use of kmem_cache_defrag()

- Use Slab Movable Objects when shrinking cache.

Currently when the kernel calls kmem_cache_shrink() we curate the partial slabs list. If object migration is not enabled for the cache we still do this, if however, SMO is enabled we attempt to move objects in partially full slabs in order to defragment the cache. Shrink attempts to move all objects in order to reduce the cache to a single partial slab for each node.

- Add conditional per node defrag via new function:

kmem_defrag_slabs(int node).

kmem_defrag_slabs() attempts to defragment all slab caches for node.
Defragmentation is done conditionally dependent on MAX_PARTIAL _AND_ defrag_used_ratio.

Caches are only considered for defragmentation if the number of partial slabs exceeds MAX_PARTIAL (per node).

Also, defragmentation only occurs if the usage ratio of the slab is lower than the configured percentage (sysfs field added in this patch). Fragmentation ratios are measured by calculating the percentage of objects in use compared to the total number of objects that the slab page can accommodate.

The scanning of slab caches is optimized because the defragmentable slabs come first on the list. Thus we can terminate scans on the first slab encountered that does not support defragmentation.

kmem_defrag_slabs() takes a node parameter. This can either be -1 if defragmentation should be performed on all nodes, or a node number.

Defragmentation may be disabled by setting defrag ratio to 0

echo 0 > /sys/kernel/slab/<cache>/defrag_used_ratio

- Add a defrag ratio sysfs field and set it to 30% by default. A limit of 30% specifies that more than 3 out of 10 available slots for objects need to be in use otherwise slab defragmentation will be attempted on the remaining objects.

In order for a cache to be defragmentable the cache must support object migration (SMO). Enabling SMO for a cache is done via a call to the recently added function:

	void kmem_cache_setup_mobility(struct kmem_cache *,
				       kmem_cache_isolate_func,
			               kmem_cache_migrate_func);
---
 Documentation/ABI/testing/sysfs-kernel-slab |  14 +
 include/linux/slab.h                        |   1 +
 include/linux/slub_def.h                    |   7 +
 mm/slub.c                                   | 385 ++++++++++++++++----
 4 files changed, 334 insertions(+), 73 deletions(-)
Documentation/ABI/testing/sysfs-kernel-slab +14 -0
index 29601d93a1c2..7770c03be6b4 100644
@@ -180,6 +180,20 @@ Description:
 		list.  It can be written to clear the current count.
 		Available when CONFIG_SLUB_STATS is enabled.
 
+What:		/sys/kernel/slab/cache/defrag_used_ratio
+Date:		February 2019
+KernelVersion:	5.0
+Contact:	Christoph Lameter <[email protected]>
+		Pekka Enberg <[email protected]>,
+Description:
+		The defrag_used_ratio file allows the control of how aggressive
+		slab fragmentation reduction works at reclaiming objects from
+		sparsely populated slabs. This is a percentage. If a slab has
+		less than this percentage of objects allocated then reclaim will
+		attempt to reclaim objects so that the whole slab page can be
+		freed. 0% specifies no reclaim attempt (defrag disabled), 100%
+		specifies attempt to reclaim all pages.  The default is 30%.
+
 What:		/sys/kernel/slab/cache/deactivate_to_tail
 Date:		February 2008
 KernelVersion:	2.6.25
include/linux/slab.h +1 -0
index 886fc130334d..4bf381b34829 100644
@@ -149,6 +149,7 @@ struct kmem_cache *kmem_cache_create_usercopy(const char *name,
 			void (*ctor)(void *));
 void kmem_cache_destroy(struct kmem_cache *);
 int kmem_cache_shrink(struct kmem_cache *);
+unsigned long kmem_defrag_slabs(int node);
 
 void memcg_create_kmem_cache(struct mem_cgroup *, struct kmem_cache *);
 void memcg_deactivate_kmem_caches(struct mem_cgroup *);
include/linux/slub_def.h +7 -0
index 2879a2f5f8eb..34c6f1250652 100644
@@ -107,6 +107,13 @@ struct kmem_cache {
 	unsigned int red_left_pad;	/* Left redzone padding size */
 	const char *name;	/* Name (only for display!) */
 	struct list_head list;	/* List of slab caches */
+	int defrag_used_ratio;	/*
+				 * Ratio used to check against the
+				 * percentage of objects allocated in a
+				 * slab page.  If less than this ratio
+				 * is allocated then reclaim attempts
+				 * are made.
+				 */
 #ifdef CONFIG_SYSFS
 	struct kobject kobj;	/* For sysfs */
 	struct work_struct kobj_remove_work;
mm/slub.c +312 -73

421 lines not on this page · raw message

Signature · 1 line
2.21.0
#5 lore raw

RFC PATCH v2 5/14 tools/vm/slabinfo: Add remote node defrag ratio output

In reply to Tobin C. Harding

Add output line for NUMA remote node defrag ratio.

---
 tools/vm/slabinfo.c | 7 +++++++
 1 file changed, 7 insertions(+)
tools/vm/slabinfo.c +7 -0
index cbfc56c44c2f..d2c22f9ee2d8 100644
@@ -34,6 +34,7 @@ struct slabinfo {
 	unsigned int sanity_checks, slab_size, store_user, trace;
 	int order, poison, reclaim_account, red_zone;
 	int movable, ctor;
+	int remote_node_defrag_ratio;
 	unsigned long partial, objects, slabs, objects_partial, objects_total;
 	unsigned long alloc_fastpath, alloc_slowpath;
 	unsigned long free_fastpath, free_slowpath;
@@ -377,6 +378,10 @@ static void slab_numa(struct slabinfo *s, int mode)
 	if (skip_zero && !s->slabs)
 		return;
 
+	if (mode) {
+		printf("\nNUMA remote node defrag ratio: %3d\n",
+		       s->remote_node_defrag_ratio);
+	}
 	if (!line) {
 		printf("\n%-21s:", mode ? "NUMA nodes" : "Slab");
 		for(node = 0; node <= highest_node; node++)
@@ -1272,6 +1277,8 @@ static void read_slab_dir(void)
 			slab->cpu_partial_free = get_obj("cpu_partial_free");
 			slab->alloc_node_mismatch = get_obj("alloc_node_mismatch");
 			slab->deactivate_bypass = get_obj("deactivate_bypass");
+			slab->remote_node_defrag_ratio =
+					get_obj("remote_node_defrag_ratio");
 			chdir("..");
 			if (read_slab_obj(slab, "ops")) {
 				if (strstr(buffer, "ctor :"))
Signature · 1 line
2.21.0
#6 lore raw

RFC PATCH v2 6/14 tools/vm/slabinfo: Add defrag_used_ratio output

In reply to Tobin C. Harding

Add output for the newly added defrag_used_ratio sysfs knob.

---
 tools/vm/slabinfo.c | 4 ++++
 1 file changed, 4 insertions(+)
tools/vm/slabinfo.c +4 -0
index d2c22f9ee2d8..ef4ff93df4cc 100644
@@ -34,6 +34,7 @@ struct slabinfo {
 	unsigned int sanity_checks, slab_size, store_user, trace;
 	int order, poison, reclaim_account, red_zone;
 	int movable, ctor;
+	int defrag_used_ratio;
 	int remote_node_defrag_ratio;
 	unsigned long partial, objects, slabs, objects_partial, objects_total;
 	unsigned long alloc_fastpath, alloc_slowpath;
@@ -549,6 +550,8 @@ static void report(struct slabinfo *s)
 		printf("** Slabs are destroyed via RCU\n");
 	if (s->reclaim_account)
 		printf("** Reclaim accounting active\n");
+	if (s->movable)
+		printf("** Defragmentation at %d%%\n", s->defrag_used_ratio);
 
 	printf("\nSizes (bytes)     Slabs              Debug                Memory\n");
 	printf("------------------------------------------------------------------------\n");
@@ -1279,6 +1282,7 @@ static void read_slab_dir(void)
 			slab->deactivate_bypass = get_obj("deactivate_bypass");
 			slab->remote_node_defrag_ratio =
 					get_obj("remote_node_defrag_ratio");
+			slab->defrag_used_ratio = get_obj("defrag_used_ratio");
 			chdir("..");
 			if (read_slab_obj(slab, "ops")) {
 				if (strstr(buffer, "ctor :"))
Signature · 1 line
2.21.0
#7 lore raw

RFC PATCH v2 7/14 tools/testing/slab: Add object migration test module

In reply to Tobin C. Harding

We just implemented slab movable objects for the SLUB allocator. We should test that code. In order to do so we need to be able to do a number of things

- Create a cache
- Enable Slab Movable Objects for the cache
- Allocate objects to the cache
- Free objects from within specific slabs of the cache

We can do all this via a loadable module.

Add a module that defines functions that can be triggered from userspace via a debugfs entry. From the source:

/*
* SLUB defragmentation a.k.a. Slab Movable Objects (SMO). *
* This module is used for testing the SLUB allocator. Enables
* userspace to run kernel functions via a debugfs file.
*
* debugfs: /sys/kernel/debugfs/smo/callfn (write only)
*
* String written to callfn is parsed by the module and associated
* function is called. See fn_tab for mapping of strings to functions. */

References to allocated objects are kept by the module in a linked list so that userspace can control which object to free.

We introduce the following four functions via the function table

"enable": Enables object migration for the test cache. "alloc X": Allocates X objects
"free X [Y]": Frees X objects starting at list position Y (default Y==0) "test": Runs [stress] tests from within the module (see below).

       {"enable", smo_enable_cache_mobility},
       {"alloc", smo_alloc_objects},
       {"free", smo_free_object},
       {"test", smo_run_module_tests},

Freeing from the start of the list creates a hole in the slab being freed from (i.e. creates a partial slab). The results of running these commands can be see using slabinfo (available in tools/vm/):

make -o slabinfo tools/vm/slabinfo.c

Stress tests can be run from within the module. These tests are internal to the module because we verify that object references are still good after object migration. These are called 'stress' tests because it is intended that they create/free a lot of objects. Userspace can control the number of objects to create, default is 1000.

Example test session
--------------------

Relevant /proc/slabinfo column headers:

name <active_objs> <num_objs> <objsize> <objperslab> <pagesperslab>

  # mount -t debugfs none /sys/kernel/debug/
  $ cd path/to/linux/tools/testing/slab; make
  ...
  # insmod slub_defrag.ko
  # cat /proc/slabinfo | grep smo_test | sed 's/:.*//'
  smo_test               0      0    392   20    2

From this we can see that the module created cache 'smo_test' with 20 objects per slab and 2 pages per slab (and cache is currently empty).

We can play with the slab allocator manually:

  # insmod slub_defrag.ko
  # echo 'alloc 21' > callfn
  # cat /proc/slabinfo | grep smo_test | sed 's/:.*//'
  smo_test              21     40    392   20    2

We see here that 21 active objects have been allocated creating 2 slabs (40 total objects).

  # slabinfo smo_test --report

Slabcache: smo_test Aliases: 0 Order : 1 Objects: 21

Sizes (bytes) Slabs Debug Memory
------------------------------------------------------------------------

  Object :      56  Total  :       2   Sanity Checks : On   Total:   16384
  SlabObj:     392  Full   :       1   Redzoning     : On   Used :    1176
  SlabSiz:    8192  Partial:       1   Poisoning     : On   Loss :   15208
  Loss   :     336  CpuSlab:       0   Tracking      : On   Lalig:    7056
  Align  :       8  Objects:      20   Tracing       : Off  Lpadd:     704

Now free an object from the first slot of the first slab

  # echo 'free 1' > callfn
  # cat /proc/slabinfo | grep smo_test | sed 's/:.*//'
  smo_test              20     40    392   20    2
  # slabinfo smo_test --report

Slabcache: smo_test Aliases: 0 Order : 1 Objects: 20

Sizes (bytes) Slabs Debug Memory
------------------------------------------------------------------------

  Object :      56  Total  :       2   Sanity Checks : On   Total:   16384
  SlabObj:     392  Full   :       0   Redzoning     : On   Used :    1120
  SlabSiz:    8192  Partial:       2   Poisoning     : On   Loss :   15264
  Loss   :     336  CpuSlab:       0   Tracking      : On   Lalig:    6720
  Align  :       8  Objects:      20   Tracing       : Off  Lpadd:     704

Calling shrink now on the cache does nothing because object migration is not enabled (output omitted). If we enable object migration then shrink the cache we expect the object from the second slab to me moved to the first slot in the first slab and the second slab to be removed from the partial list.

  # echo 'enable' > callfn
  # slabinfo smo_test --shrink
  # slabinfo smo_test --report

Slabcache: smo_test Aliases: 0 Order : 1 Objects: 20
** Defragmentation at 30%

Sizes (bytes) Slabs Debug Memory
------------------------------------------------------------------------

  Object :      56  Total  :       1   Sanity Checks : On   Total:    8192
  SlabObj:     392  Full   :       1   Redzoning     : On   Used :    1120
  SlabSiz:    8192  Partial:       0   Poisoning     : On   Loss :    7072
  Loss   :     336  CpuSlab:       0   Tracking      : On   Lalig:    6720
  Align  :       8  Objects:      20   Tracing       : Off  Lpadd:     352

We can run the stress tests (with the default number of objects):

  # cd /sys/kernel/debug/smo
  # echo 'test' > callfn
  [    3.576617] smo: test using nr_objs: 1000 keep: 10
  [    3.580169] smo: Module tests completed successfully
---
 tools/testing/slab/Makefile      |  10 +
 tools/testing/slab/slub_defrag.c | 566 +++++++++++++++++++++++++++++++
 2 files changed, 576 insertions(+)
 create mode 100644 tools/testing/slab/Makefile
 create mode 100644 tools/testing/slab/slub_defrag.c
tools/testing/slab/Makefile new file +10 -0
new file mode 100644
index 000000000000..440c2e3e356f
@@ -0,0 +1,10 @@
+obj-m += slub_defrag.o
+
+KTREE=../../..
+
+all:
+	make -C ${KTREE} M=$(PWD) modules
+
+clean:
+	make -C ${KTREE} M=$(PWD) clean
+
tools/testing/slab/slub_defrag.c new file +566 -0

566 lines not on this page · raw message

Signature · 1 line
2.21.0
#8 lore raw

RFC PATCH v2 8/14 tools/testing/slab: Add object migration test suite

In reply to Tobin C. Harding

We just added a module that enables testing the SLUB allocators ability to defrag/shrink caches via movable objects. Tests are better when they are automated.

Add automated testing via a python script for SLUB movable objects.

Example output:

  $ cd path/to/linux/tools/testing/slab
  $ /slub_defrag.py
  Please run script as root
  $ sudo ./slub_defrag.py
  <test are quiet, no output on success>
  $ sudo ./slub_defrag.py --debug
  Loading module ...
  Slab cache smo_test created
  Objects per slab: 20
  Running sanity checks ...

Running module stress test (see dmesg for additional test output) ... Removing module slub_defrag ...
Loading module ...
Slab cache smo_test created

Running test non-movable ...
testing slab 'smo_test' prior to enabling movable objects ... verified non-movable slabs are NOT shrinkable

Running test movable ...
testing slab 'smo_test' after enabling movable objects ... verified movable slabs are shrinkable

Removing module slub_defrag ...

---
 tools/testing/slab/slub_defrag.c  |   1 +
 tools/testing/slab/slub_defrag.py | 451 ++++++++++++++++++++++++++++++
 2 files changed, 452 insertions(+)
 create mode 100755 tools/testing/slab/slub_defrag.py
tools/testing/slab/slub_defrag.c +1 -0
index 4a5c24394b96..8332e69ee868 100644
@@ -337,6 +337,7 @@ static int smo_run_module_tests(int nr_objs, int keep)
 
 /*
  * struct functions() - Map command to a function pointer.
+ * If you update this please update the documentation in slub_defrag.py
  */
 struct functions {
 	char *fn_name;
tools/testing/slab/slub_defrag.py new file +451 -0

451 lines not on this page · raw message

Signature · 1 line
2.21.0
#9 lore raw

RFC PATCH v2 9/14 xarray: Implement migration function for objects

In reply to Tobin C. Harding

Implement functions to migrate objects. This is based on initial code by Matthew Wilcox and was modified to work with slab object migration.

---
 lib/radix-tree.c | 13 +++++++++++++
 lib/xarray.c     | 46 ++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 59 insertions(+)
lib/radix-tree.c +13 -0
index 14d51548bea6..9412c2853726 100644
@@ -1613,6 +1613,17 @@ static int radix_tree_cpu_dead(unsigned int cpu)
 	return 0;
 }
 
+extern void xa_object_migrate(void *tree_node, int numa_node);
+
+static void radix_tree_migrate(struct kmem_cache *s, void **objects, int nr,
+			       int node, void *private)
+{
+	int i;
+
+	for (i = 0; i < nr; i++)
+		xa_object_migrate(objects[i], node);
+}
+
 void __init radix_tree_init(void)
 {
 	int ret;
@@ -1627,4 +1638,6 @@ void __init radix_tree_init(void)
 	ret = cpuhp_setup_state_nocalls(CPUHP_RADIX_DEAD, "lib/radix:dead",
 					NULL, radix_tree_cpu_dead);
 	WARN_ON(ret < 0);
+	kmem_cache_setup_mobility(radix_tree_node_cachep, NULL,
+				  radix_tree_migrate);
 }
lib/xarray.c +46 -0

52 lines not on this page · raw message

Signature · 1 line
2.21.0
maintainer
#10 lore raw

Re: [RFC PATCH v2 09/14] xarray: Implement migration function for objects

Quoted 18 lines · Tobin C. Harding
lib/xarray.c +18 -0
+void xa_object_migrate(struct xa_node *node, int numa_node)
+{
+	struct xarray *xa = READ_ONCE(node->array);
+	void __rcu **slot;
+	struct xa_node *new_node;
+	int i;
+
+	/* Freed or not yet in tree then skip */
+	if (!xa || xa == XA_RCU_FREE)
+		return;
+
+	new_node = kmem_cache_alloc_node(radix_tree_node_cachep,
+					 GFP_KERNEL, numa_node);
+	if (!new_node)
+		return;
+
+	xa_lock_irq(xa);
+

lib/xarray.c void xa_destroy(struct xarray *xa) · line 2001

Quoted 10 lines
lib/xarray.c +10 -0
+	/* Check again..... */
+	if (xa != node->array || !list_empty(&node->private_list)) {
+		node = new_node;
+		goto unlock;
+	}
+
+	memcpy(new_node, node, sizeof(struct xa_node));
+
+	/* Move pointers to new node */
+	INIT_LIST_HEAD(&new_node->private_list);

Surely we can do something more clever, like ...

	if (xa != node->array) {
...
	if (list_empty(&node->private_list))
		INIT_LIST_HEAD(&new_node->private_list);
	else
		list_replace(&node->private_list, &new_node->private_list);

BTW, the raidx tree nodes / xa_nodes share the same slab cache; we need to finish converting all radix tree & IDR users to the XArray before this series can go in.

#11 lore raw

Re: [RFC PATCH v2 09/14] xarray: Implement migration function for objects

Quoted 32 lines · Matthew Wilcox
Quoted 18 lines · Tobin C. Harding
lib/xarray.c +18 -0
+void xa_object_migrate(struct xa_node *node, int numa_node)
+{
+	struct xarray *xa = READ_ONCE(node->array);
+	void __rcu **slot;
+	struct xa_node *new_node;
+	int i;
+
+	/* Freed or not yet in tree then skip */
+	if (!xa || xa == XA_RCU_FREE)
+		return;
+
+	new_node = kmem_cache_alloc_node(radix_tree_node_cachep,
+					 GFP_KERNEL, numa_node);
+	if (!new_node)
+		return;
+
+	xa_lock_irq(xa);
+

lib/xarray.c void xa_destroy(struct xarray *xa) · line 2001

Quoted 10 lines
lib/xarray.c +10 -0
+	/* Check again..... */
+	if (xa != node->array || !list_empty(&node->private_list)) {
+		node = new_node;
+		goto unlock;
+	}
+
+	memcpy(new_node, node, sizeof(struct xa_node));
+
+	/* Move pointers to new node */
+	INIT_LIST_HEAD(&new_node->private_list);

Surely we can do something more clever, like ...

Quoted 6 lines
	if (xa != node->array) {
...
	if (list_empty(&node->private_list))
		INIT_LIST_HEAD(&new_node->private_list);
	else
		list_replace(&node->private_list, &new_node->private_list);

Oh nice, thanks! I'll roll this into the next version.

Quoted 3 lines

BTW, the raidx tree nodes / xa_nodes share the same slab cache; we need to finish converting all radix tree & IDR users to the XArray before this series can go in.

Ok, I'll add this comment to the commit log for this patch on the next version so we don't forget. FTR complete conversion to the XArray is your goal isn't it (on the way to the Maple tree)?

thanks,
Tobin.

#12 lore raw

RFC PATCH v2 10/14 tools/testing/slab: Add XArray movable objects tests

In reply to Tobin C. Harding

We just implemented movable objects for the XArray. Let's test it intree.

Add test module for the XArray's movable objects implementation.

Functionality of the XArray Slab Movable Object implementation can usually be seen by simply by using slabinfo on a running machine since the radix tree is typically in use on a running machine and will have partial slabs. For repeated testing we can use the test module to run to simulate a workload on the XArray then use slabinfo to test object migration is functioning.

If testing on freshly spun up VM (low radix tree workload) it may be necessary to load/unload the module a number of times to create partial slabs.

Example test session
--------------------

Relevant /proc/slabinfo column headers:

name <active_objs> <num_objs> <objsize> <objperslab> <pagesperslab>

Prior to testing slabinfo report for radix_tree_node:

  # slabinfo radix_tree_node --report

Slabcache: radix_tree_node Aliases: 0 Order : 2 Objects: 8352 ** Reclaim accounting active
** Defragmentation at 30%

Sizes (bytes) Slabs Debug Memory
------------------------------------------------------------------------

  Object :     576  Total  :     497   Sanity Checks : On   Total: 8142848
  SlabObj:     912  Full   :     473   Redzoning     : On   Used : 4810752
  SlabSiz:   16384  Partial:      24   Poisoning     : On   Loss : 3332096
  Loss   :     336  CpuSlab:       0   Tracking      : On   Lalig: 2806272
  Align  :       8  Objects:      17   Tracing       : Off  Lpadd:  437360

Here you can see the kernel was built with Slab Movable Objects enabled for the XArray (XArray uses the radix tree below the surface).

After inserting the test module (note we have triggered allocation of a number of radix tree nodes increasing the object count but decreasing the number of partial slabs):

  # slabinfo radix_tree_node --report

Slabcache: radix_tree_node Aliases: 0 Order : 2 Objects: 8442 ** Reclaim accounting active
** Defragmentation at 30%

Sizes (bytes) Slabs Debug Memory
------------------------------------------------------------------------

  Object :     576  Total  :     499   Sanity Checks : On   Total: 8175616
  SlabObj:     912  Full   :     484   Redzoning     : On   Used : 4862592
  SlabSiz:   16384  Partial:      15   Poisoning     : On   Loss : 3313024
  Loss   :     336  CpuSlab:       0   Tracking      : On   Lalig: 2836512
  Align  :       8  Objects:      17   Tracing       : Off  Lpadd:  439120

Now we can shrink the radix_tree_node cache:

  # slabinfo radix_tree_node --shrink
  # slabinfo radix_tree_node --report

Slabcache: radix_tree_node Aliases: 0 Order : 2 Objects: 8515 ** Reclaim accounting active
** Defragmentation at 30%

Sizes (bytes) Slabs Debug Memory
------------------------------------------------------------------------

  Object :     576  Total  :     501   Sanity Checks : On   Total: 8208384
  SlabObj:     912  Full   :     500   Redzoning     : On   Used : 4904640
  SlabSiz:   16384  Partial:       1   Poisoning     : On   Loss : 3303744
  Loss   :     336  CpuSlab:       0   Tracking      : On   Lalig: 2861040
  Align  :       8  Objects:      17   Tracing       : Off  Lpadd:  440880

Note the single remaining partial slab.

---
 tools/testing/slab/Makefile             |   2 +-
 tools/testing/slab/slub_defrag_xarray.c | 211 ++++++++++++++++++++++++
 2 files changed, 212 insertions(+), 1 deletion(-)
 create mode 100644 tools/testing/slab/slub_defrag_xarray.c
tools/testing/slab/Makefile +1 -1
index 440c2e3e356f..44c18d9a4d52 100644
@@ -1,4 +1,4 @@
-obj-m += slub_defrag.o
+obj-m += slub_defrag.o slub_defrag_xarray.o
 
 KTREE=../../..
 
tools/testing/slab/slub_defrag_xarray.c new file +211 -0

211 lines not on this page · raw message

Signature · 1 line
2.21.0
#13 lore raw

RFC PATCH v2 11/14 slub: Enable moving objects to/from specific nodes

In reply to Tobin C. Harding

We have just implemented Slab Movable Objects (object migration). Currently object migration is used to defrag a cache. On NUMA systems it would be nice to be able to control the source and destination nodes when moving objects.

Add CONFIG_SMO_NODE to guard this feature. CONFIG_SMO_NODE depends on CONFIG_SLUB_DEBUG because we use the full list. Leave it like this for the RFC because the patch will be less cluttered to review, separate full list out of CONFIG_DEBUG before doing a PATCH version.

Implement moving all objects (including those in full slabs) to a specific node. Expose this functionality to userspace via a sysfs entry.

Add sysfs entry:

/sysfs/kernel/slab/<cache>/move

With this users get access to the following functionality:

- Move all objects to specified node.

echo "N1" > move

- Move all objects from specified node to other specified node (from N1 -> to N2):

echo "N1 N2" > move

This also enables shrinking slabs on a specific node:

echo "N1 N1" > move

---
 mm/Kconfig |   7 ++
 mm/slub.c  | 249 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 256 insertions(+)
mm/Kconfig +7 -0
index 25c71eb8a7db..47040d939f3b 100644
@@ -258,6 +258,13 @@ config ARCH_ENABLE_HUGEPAGE_MIGRATION
 config ARCH_ENABLE_THP_MIGRATION
 	bool
 
+config SMO_NODE
+       bool "Enable per node control of Slab Movable Objects"
+       depends on SLUB && SYSFS
+       select SLUB_DEBUG
+       help
+         On NUMA systems enable moving objects to and from a specified node.
+
 config PHYS_ADDR_T_64BIT
 	def_bool 64BIT
 
mm/slub.c +249 -0

273 lines not on this page · raw message

Signature · 1 line
2.21.0
#14 lore raw

RFC PATCH v2 12/14 slub: Enable balancing slabs across nodes

In reply to Tobin C. Harding

We have just implemented Slab Movable Objects (SMO). On NUMA systems slabs can become unbalanced i.e. many slabs on one node while other nodes have few slabs. Using SMO we can balance the slabs across all the nodes.

The algorithm used is as follows:

1. Move all objects to node 0 (this has the effect of defragmenting the

    cache).

2. Calculate the desired number of slabs for each node (this is done using the approximation nr_slabs / nr_nodes).

3. Loop over the nodes moving the desired number of slabs from node 0 to the node.

Feature is conditionally built in with CONFIG_SMO_NODE, this is because we need the full list (we enable SLUB_DEBUG to get this). Future version may separate final list out of SLUB_DEBUG.

Expose this functionality to userspace via a sysfs entry. Add sysfs entry:

/sysfs/kernel/slab/<cache>/balance

Write of '1' to this file triggers balance, no other value accepted.

This feature relies on SMO being enable for the cache, this is done with a call to, after the isolate/migrate functions have been defined.

kmem_cache_setup_mobility(s, isolate, migrate)

---
 mm/slub.c | 120 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 120 insertions(+)
mm/slub.c +120 -0
index e4f3dde443f5..a5c48c41d72b 100644
@@ -4583,6 +4583,109 @@ static unsigned long kmem_cache_move_to_node(struct kmem_cache *s, int node)
 
 	return left;
 }
+
+/*
+ * kmem_cache_move_slabs() - Attempt to move @num slabs to target_node,
+ * @s: The cache we are working on.
+ * @node: The node to move objects from.
+ * @target_node: The node to move objects to.
+ * @num: The number of slabs to move.
+ *
+ * Attempts to move @num slabs from @node to @target_node.  This is done
+ * by migrating objects from slabs on the full_list.
+ *
+ * Return: The number of slabs moved or error code.
+ */
+static long kmem_cache_move_slabs(struct kmem_cache *s,
+				  int node, int target_node, long num)
+{
+	struct kmem_cache_node *n = get_node(s, node);
+	LIST_HEAD(move_list);
+	struct page *page, *page2;
+	unsigned long flags;
+	void **scratch;
+	long done = 0;
+
+	if (node == target_node)
+		return -EINVAL;
+
+	scratch = alloc_scratch(s);
+	if (!scratch)
+		return -ENOMEM;
+
+	spin_lock_irqsave(&n->list_lock, flags);
+	list_for_each_entry_safe(page, page2, &n->full, lru) {
+		if (!slab_trylock(page))
+			/* Busy slab. Get out of the way */
+			continue;
+
+		list_move(&page->lru, &move_list);
+		page->frozen = 1;
+		slab_unlock(page);
+
+		if (++done >= num)
+			break;
+	}
+	spin_unlock_irqrestore(&n->list_lock, flags);
+
+	list_for_each_entry(page, &move_list, lru) {
+		if (page->inuse)
+			move_slab_page(page, scratch, target_node);
+	}
+	kfree(scratch);
+
+	/* Inspect results and dispose of pages */
+	spin_lock_irqsave(&n->list_lock, flags);
+	list_for_each_entry_safe(page, page2, &move_list, lru) {
+		list_del(&page->lru);
+		slab_lock(page);
+		page->frozen = 0;
+
+		if (page->inuse) {
+			/*
+			 * This is best effort only, if slab still has
+			 * objects just put it back on the partial list.
+			 */
+			n->nr_partial++;
+			list_add_tail(&page->lru, &n->partial);
+			slab_unlock(page);
+		} else {
+			slab_unlock(page);
+			discard_slab(s, page);
+		}
+	}
+	spin_unlock_irqrestore(&n->list_lock, flags);
+
+	return done;
+}
+
+/*
+ * kmem_cache_balance_nodes() - Balance slabs across nodes.
+ * @s: The cache we are working on.
+ */
+static void kmem_cache_balance_nodes(struct kmem_cache *s)
+{
+	struct kmem_cache_node *n = get_node(s, 0);
+	unsigned long desired_nr_slabs_per_node;
+	unsigned long nr_slabs;
+	int nr_nodes = 0;
+	int nid;
+
+	(void)kmem_cache_move_to_node(s, 0);
+
+	for_each_node_state(nid, N_NORMAL_MEMORY)
+		nr_nodes++;
+
+	nr_slabs = atomic_long_read(&n->nr_slabs);
+	desired_nr_slabs_per_node = nr_slabs / nr_nodes;
+
+	for_each_node_state(nid, N_NORMAL_MEMORY) {
+		if (nid == 0)
+			continue;
+
+		kmem_cache_move_slabs(s, 0, nid, desired_nr_slabs_per_node);
+	}
+}
 #endif
 
 /**
@@ -5847,6 +5950,22 @@ static ssize_t move_store(struct kmem_cache *s, const char *buf, size_t length)
 	return length;
 }
 SLAB_ATTR(move);
+
+static ssize_t balance_show(struct kmem_cache *s, char *buf)
+{
+	return 0;
+}
+
+static ssize_t balance_store(struct kmem_cache *s,
+			     const char *buf, size_t length)
+{
+	if (buf[0] == '1')
+		kmem_cache_balance_nodes(s);
+	else
+		return -EINVAL;
+	return length;
+}
+SLAB_ATTR(balance);
 #endif	/* CONFIG_SMO_NODE */
 
 #ifdef CONFIG_NUMA
@@ -5975,6 +6094,7 @@ static struct attribute *slab_attrs[] = {
 	&shrink_attr.attr,
 #ifdef CONFIG_SMO_NODE
 	&move_attr.attr,
+	&balance_attr.attr,
 #endif
 	&slabs_cpu_partial_attr.attr,
 #ifdef CONFIG_SLUB_DEBUG
Signature · 1 line
2.21.0
#15 lore raw

RFC PATCH v2 13/14 dcache: Provide a dentry constructor

In reply to Tobin C. Harding

In order to support object migration on the dentry cache we need to have a determined object state at all times. Without a constructor the object would have a random state after allocation.

Provide a dentry constructor.

---
 fs/dcache.c | 37 ++++++++++++++++++++++++++++---------
 1 file changed, 28 insertions(+), 9 deletions(-)
fs/dcache.c +28 -9
index aac41adf4743..606844ad5171 100644
@@ -1603,6 +1603,22 @@ void d_invalidate(struct dentry *dentry)
 }
 EXPORT_SYMBOL(d_invalidate);
 
+static void dcache_ctor(void *p)
+{
+	struct dentry *dentry = p;
+
+	dentry->d_lockref.count = 0;
+	dentry->d_inode = NULL;
+
+	spin_lock_init(&dentry->d_lock);
+
+	INIT_HLIST_BL_NODE(&dentry->d_hash);
+	INIT_LIST_HEAD(&dentry->d_lru);
+	INIT_LIST_HEAD(&dentry->d_subdirs);
+	INIT_HLIST_NODE(&dentry->d_u.d_alias);
+	INIT_LIST_HEAD(&dentry->d_child);
+}
+
 /**
  * __d_alloc	-	allocate a dcache entry
  * @sb: filesystem it will belong to
@@ -1658,7 +1674,7 @@ struct dentry *__d_alloc(struct super_block *sb, const struct qstr *name)
 
 	dentry->d_lockref.count = 1;
 	dentry->d_flags = 0;
-	spin_lock_init(&dentry->d_lock);
+
 	seqcount_init(&dentry->d_seq);
 	dentry->d_inode = NULL;
 	dentry->d_parent = dentry;
@@ -3091,14 +3107,17 @@ static void __init dcache_init_early(void)
 
 static void __init dcache_init(void)
 {
-	/*
-	 * 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_USERCOPY(dentry,
-		SLAB_RECLAIM_ACCOUNT|SLAB_PANIC|SLAB_MEM_SPREAD|SLAB_ACCOUNT,
-		d_iname);
+	slab_flags_t flags =
+		SLAB_RECLAIM_ACCOUNT | SLAB_PANIC | SLAB_MEM_SPREAD | SLAB_ACCOUNT;
+
+	dentry_cache =
+		kmem_cache_create_usercopy("dentry",
+					   sizeof(struct dentry),
+					   __alignof__(struct dentry),
+					   flags,
+					   offsetof(struct dentry, d_iname),
+					   sizeof_field(struct dentry, d_iname),
+					   dcache_ctor);
 
 	/* Hash may have been set up in dcache_init_early */
 	if (!hashdist)
Signature · 1 line
2.21.0
#16 lore raw

RFC PATCH v2 14/14 dcache: Implement object migration

In reply to Tobin C. Harding

The dentry slab cache is susceptible to internal fragmentation. Now that we have Slab Movable Objects we can defragment the dcache. Object migration is only possible for dentry objects that are not currently referenced by anyone, i.e. we are using the object migration infrastructure to free unused dentries.

Implement isolate and migrate functions for the dentry slab cache.

---
 fs/dcache.c | 87 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 87 insertions(+)
fs/dcache.c +87 -0
index 606844ad5171..4387715b7ebb 100644
@@ -30,6 +30,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"
 
@@ -3074,6 +3075,90 @@ 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 migrate.
+ * @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 dentry *dentry;
+	int i;
+
+	for (i = 0; i < nr; i++) {
+		dentry = v[i];
+		spin_lock(&dentry->d_lock);
+		/*
+		 * Three sorts of dentries cannot be reclaimed:
+		 *
+		 * 1. dentries that are in the process of being allocated
+		 *    or being freed. In that case the dentry is neither
+		 *    on the LRU nor hashed.
+		 *
+		 * 2. Fake hashed entries as used for anonymous dentries
+		 *    and pipe I/O. The fake hashed entries have d_flags
+		 *    set to indicate a hashed entry. However, the
+		 *    d_hash field indicates that the entry is not hashed.
+		 *
+		 * 3. dentries that have a backing store that is not
+		 *    writable. This is true for tmpsfs and other in
+		 *    memory filesystems. Removing dentries from them
+		 *    would loose dentries for good.
+		 */
+		if ((d_unhashed(dentry) && list_empty(&dentry->d_lru)) ||
+		    (!d_unhashed(dentry) && hlist_bl_unhashed(&dentry->d_hash)) ||
+		    (dentry->d_inode &&
+		     !mapping_cap_writeback_dirty(dentry->d_inode->i_mapping))) {
+			/* Ignore this dentry */
+			v[i] = NULL;
+		} else {
+			__dget_dlock(dentry);
+		}
+		spin_unlock(&dentry->d_lock);
+	}
+	return NULL;		/* No need for private data */
+}
+
+/*
+ * d_migrate() - Dentry migration callback function.
+ * @s: The dentry cache.
+ * @v: Vector of pointers to the objects to migrate.
+ * @nr: Number of objects in @v.
+ * @node: The NUMA node where new object should be allocated.
+ * @private: Returned by d_isolate() (currently %NULL).
+ *
+ * Slab has dropped all the locks. Get rid of the refcount obtained
+ * earlier and also free the object.
+ */
+static void d_migrate(struct kmem_cache *s, void **v, int nr,
+		      int node, void *_unused)
+{
+	struct dentry *dentry;
+	int i;
+
+	for (i = 0; i < nr; i++) {
+		dentry = v[i];
+		if (dentry)
+			d_invalidate(dentry);
+	}
+
+	for (i = 0; i < nr; i++) {
+		dentry = v[i];
+		if (dentry)
+			dput(dentry);
+	}
+
+	/*
+	 * dentries are freed using RCU so we need to wait until RCU
+	 * operations are complete.
+	 */
+	synchronize_rcu();
+}
+
 static __initdata unsigned long dhash_entries;
 static int __init set_dhash_entries(char *str)
 {
@@ -3119,6 +3204,8 @@ static void __init dcache_init(void)
 					   sizeof_field(struct dentry, d_iname),
 					   dcache_ctor);
 
+	kmem_cache_setup_mobility(dentry_cache, d_isolate, d_migrate);
+
 	/* Hash may have been set up in dcache_init_early */
 	if (!hashdist)
 		return;
Signature · 1 line
2.21.0
maintainer
#17 lore raw

Re: [RFC PATCH v2 14/14] dcache: Implement object migration

Quoted 50 lines · Tobin C. Harding

The dentry slab cache is susceptible to internal fragmentation. Now that we have Slab Movable Objects we can defragment the dcache. Object migration is only possible for dentry objects that are not currently referenced by anyone, i.e. we are using the object migration infrastructure to free unused dentries.

Implement isolate and migrate functions for the dentry slab cache.

fs/dcache.c +42 -0
+		/*
+		 * Three sorts of dentries cannot be reclaimed:
+		 *
+		 * 1. dentries that are in the process of being allocated
+		 *    or being freed. In that case the dentry is neither
+		 *    on the LRU nor hashed.
+		 *
+		 * 2. Fake hashed entries as used for anonymous dentries
+		 *    and pipe I/O. The fake hashed entries have d_flags
+		 *    set to indicate a hashed entry. However, the
+		 *    d_hash field indicates that the entry is not hashed.
+		 *
+		 * 3. dentries that have a backing store that is not
+		 *    writable. This is true for tmpsfs and other in
+		 *    memory filesystems. Removing dentries from them
+		 *    would loose dentries for good.
+		 */
+		if ((d_unhashed(dentry) && list_empty(&dentry->d_lru)) ||
+		    (!d_unhashed(dentry) && hlist_bl_unhashed(&dentry->d_hash)) ||
+		    (dentry->d_inode &&
+		     !mapping_cap_writeback_dirty(dentry->d_inode->i_mapping))) {
+			/* Ignore this dentry */
+			v[i] = NULL;
+		} else {
+			__dget_dlock(dentry);
+		}
+		spin_unlock(&dentry->d_lock);
+	}
+	return NULL;		/* No need for private data */
+}
+
+/*
+ * d_migrate() - Dentry migration callback function.
+ * @s: The dentry cache.
+ * @v: Vector of pointers to the objects to migrate.
+ * @nr: Number of objects in @v.
+ * @node: The NUMA node where new object should be allocated.
+ * @private: Returned by d_isolate() (currently %NULL).
+ *
+ * Slab has dropped all the locks. Get rid of the refcount obtained
+ * earlier and also free the object.
+ */

fs/dcache.c void d_tmpfile(struct dentry *dentry, struct inode *inode) · line 3146

Quoted 10 lines
fs/dcache.c +10 -0
+static void d_migrate(struct kmem_cache *s, void **v, int nr,
+		      int node, void *_unused)
+{
+	struct dentry *dentry;
+	int i;
+
+	for (i = 0; i < nr; i++) {
+		dentry = v[i];
+		if (dentry)
+			d_invalidate(dentry);

Oh, *brilliant*

Let's do d_invalidate() on random dentries and hope they go away. With convoluted and brittle logics for deciding which ones to spare, which is actually wrong. This will pick mountpoints and tear them out, to start with.

And this is a NAK for the entire approach; if it has a positive refcount, LEAVE IT ALONE. Period. Don't play this kind of games, they are wrong. d_invalidate() is not something that can be done to an arbitrary dentry.

maintainer
#18 lore raw

Re: [RFC PATCH v2 14/14] dcache: Implement object migration

Quoted 7 lines · Al Viro

Oh, *brilliant*

Let's do d_invalidate() on random dentries and hope they go away. With convoluted and brittle logics for deciding which ones to spare, which is actually wrong. This will pick mountpoints and tear them out, to start with.

Quoted 5 lines

And this is a NAK for the entire approach; if it has a positive refcount, LEAVE IT ALONE. Period. Don't play this kind of games, they are wrong. d_invalidate() is not something that can be done to an arbitrary dentry.

PS: "try to evict what can be evicted out of this set" can be done, but you want something like

	start with empty list
	go through your array of references
		grab dentry->d_lock
		if dentry->d_lockref.count is not zero
			unlock and continue
		if dentry->d_flags & DCACHE_SHRINK_LIST
			ditto, it's not for us to play with
                if (dentry->d_flags & DCACHE_LRU_LIST)
                        d_lru_del(dentry);
		d_shrink_add(dentry, &list);
		unlock

on the collection phase and

	if the list is not empty by the end of that loop
		shrink_dentry_list(&list);

on the disposal.

maintainer
#19 lore raw

Re: [RFC PATCH v2 14/14] dcache: Implement object migration

Quoted 29 lines · Al Viro
Quoted 7 lines · Al Viro

Oh, *brilliant*

Let's do d_invalidate() on random dentries and hope they go away. With convoluted and brittle logics for deciding which ones to spare, which is actually wrong. This will pick mountpoints and tear them out, to start with.

Quoted 5 lines

And this is a NAK for the entire approach; if it has a positive refcount, LEAVE IT ALONE. Period. Don't play this kind of games, they are wrong. d_invalidate() is not something that can be done to an arbitrary dentry.

PS: "try to evict what can be evicted out of this set" can be done, but you want something like

	start with empty list
	go through your array of references
		grab dentry->d_lock
		if dentry->d_lockref.count is not zero
			unlock and continue
		if dentry->d_flags & DCACHE_SHRINK_LIST
			ditto, it's not for us to play with
                if (dentry->d_flags & DCACHE_LRU_LIST)
                        d_lru_del(dentry);
		d_shrink_add(dentry, &list);
		unlock
Quoted 4 lines

on the collection phase and

	if the list is not empty by the end of that loop
		shrink_dentry_list(&list);

on the disposal.

Note, BTW, that your constructor is wrong - all it really needs to do is spin_lock_init() and setting ->d_lockref.count same as lockref_mark_dead() does, to match the state of dentries being torn down.

__d_alloc() is not holding ->d_lock, since the object is not visible to anybody else yet; with your changes it *is* visible. However, if the assignment to ->d_lockref.count in __d_alloc() is guaranteed to be non-zero to non-zero, the above should be safe.

#20 lore raw

Re: [RFC PATCH v2 14/14] dcache: Implement object migration

Quoted 30 lines · Al Viro
Quoted 29 lines · Al Viro
Quoted 7 lines · Al Viro

Oh, *brilliant*

Let's do d_invalidate() on random dentries and hope they go away. With convoluted and brittle logics for deciding which ones to spare, which is actually wrong. This will pick mountpoints and tear them out, to start with.

Quoted 5 lines

And this is a NAK for the entire approach; if it has a positive refcount, LEAVE IT ALONE. Period. Don't play this kind of games, they are wrong. d_invalidate() is not something that can be done to an arbitrary dentry.

PS: "try to evict what can be evicted out of this set" can be done, but you want something like

	start with empty list
	go through your array of references
		grab dentry->d_lock
		if dentry->d_lockref.count is not zero
			unlock and continue
		if dentry->d_flags & DCACHE_SHRINK_LIST
			ditto, it's not for us to play with
                if (dentry->d_flags & DCACHE_LRU_LIST)
                        d_lru_del(dentry);
		d_shrink_add(dentry, &list);
		unlock
Quoted 8 lines
Quoted 4 lines

on the collection phase and

	if the list is not empty by the end of that loop
		shrink_dentry_list(&list);

on the disposal.

Note, BTW, that your constructor is wrong - all it really needs to do is spin_lock_init() and setting ->d_lockref.count same as lockref_mark_dead() does, to match the state of dentries being torn down.

Thanks for looking at this Al.

Quoted 2 lines

__d_alloc() is not holding ->d_lock, since the object is not visible to anybody else yet; with your changes it *is* visible.

I don't quite understand this comment. How is the object visible? The constructor is only called when allocating a new page to the slab and this is done with interrupts disabled.

Quoted 3 lines

However, if the
assignment to ->d_lockref.count in __d_alloc() is guaranteed to be non-zero to non-zero, the above should be safe.

I've done as you suggest and set it to -128

Thanks for schooling me on the VFS stuff.

Tobin

maintainer
#21 lore raw

Re: [RFC PATCH v2 14/14] dcache: Implement object migration

Quoted 6 lines · Tobin C. Harding
Quoted 2 lines

__d_alloc() is not holding ->d_lock, since the object is not visible to anybody else yet; with your changes it *is* visible.

I don't quite understand this comment. How is the object visible? The constructor is only called when allocating a new page to the slab and this is done with interrupts disabled.

Not to constructor, to your try-to-evict code...

#22 lore raw

Re: [RFC PATCH v2 14/14] dcache: Implement object migration

In reply to Al Viro
Quoted 29 lines · Al Viro
Quoted 7 lines · Al Viro

Oh, *brilliant*

Let's do d_invalidate() on random dentries and hope they go away. With convoluted and brittle logics for deciding which ones to spare, which is actually wrong. This will pick mountpoints and tear them out, to start with.

Quoted 5 lines

And this is a NAK for the entire approach; if it has a positive refcount, LEAVE IT ALONE. Period. Don't play this kind of games, they are wrong. d_invalidate() is not something that can be done to an arbitrary dentry.

PS: "try to evict what can be evicted out of this set" can be done, but you want something like

	start with empty list
	go through your array of references
		grab dentry->d_lock
		if dentry->d_lockref.count is not zero
			unlock and continue
		if dentry->d_flags & DCACHE_SHRINK_LIST
			ditto, it's not for us to play with
                if (dentry->d_flags & DCACHE_LRU_LIST)
                        d_lru_del(dentry);
		d_shrink_add(dentry, &list);
		unlock
Quoted 4 lines

on the collection phase and

	if the list is not empty by the end of that loop
		shrink_dentry_list(&list);

on the disposal.

Implemented as suggested, thanks. RFCv3 to come when we have some stats for you :)

thanks,
Tobin.

#23 lore raw

Re: [RFC PATCH v2 14/14] dcache: Implement object migration

In reply to Al Viro
Quoted 10 lines · Al Viro

Let's do d_invalidate() on random dentries and hope they go away. With convoluted and brittle logics for deciding which ones to spare, which is actually wrong. This will pick mountpoints and tear them out, to start with.

And this is a NAK for the entire approach; if it has a positive refcount, LEAVE IT ALONE. Period. Don't play this kind of games, they are wrong. d_invalidate() is not something that can be done to an arbitrary dentry.

Well could you help us figure out how to do it the right way? We (the MM guys) are having a hard time not being familiar with the filesystem stuff.

This is an RFC and we want to know how to do this right.

maintainer
#24 lore raw

Re: [RFC PATCH v2 14/14] dcache: Implement object migration

Quoted 9 lines · Christopher Lameter
Quoted 7 lines · Al Viro

Let's do d_invalidate() on random dentries and hope they go away. With convoluted and brittle logics for deciding which ones to spare, which is actually wrong. This will pick mountpoints and tear them out, to start with.

Quoted 8 lines
Quoted 3 lines

And this is a NAK for the entire approach; if it has a positive refcount, LEAVE IT ALONE. Period. Don't play this kind of games, they are wrong. d_invalidate() is not something that can be done to an arbitrary dentry.

Well could you help us figure out how to do it the right way? We (the MM guys) are having a hard time not being familiar with the filesystem stuff.

This is an RFC and we want to know how to do this right.

If by "how to do it right" you mean "expedit kicking out something with non-zero refcount" - there's no way to do that. Nothing even remotely sane.

If you mean "kick out everything in this page with zero refcount" - that can be done (see further in the thread).

Look, dentries and inodes are really, really not relocatable. If they can be evicted by memory pressure - sure, we can do that for a given set (e.g. "everything in that page"). But that's it - if memory pressure would _not_ get rid of that one, there's nothing to be done. Again, all VM can do is to simulate shrinker hitting hard on given bunch (rather than buggering the entire cache). If filesystem (or something in VFS) says "it's busy", it bloody well _is_ busy and won't be going away until it ceases to be such.

maintainer
#25 lore raw

Re: [RFC PATCH v2 14/14] dcache: Implement object migration

Quoted 7 lines · Al Viro

If by "how to do it right" you mean "expedit kicking out something with non-zero refcount" - there's no way to do that. Nothing even remotely sane.

If you mean "kick out everything in this page with zero refcount" - that can be done (see further in the thread).

Quoted 8 lines

Look, dentries and inodes are really, really not relocatable. If they can be evicted by memory pressure - sure, we can do that for a given set (e.g. "everything in that page"). But that's it - if memory pressure would _not_ get rid of that one, there's nothing to be done. Again, all VM can do is to simulate shrinker hitting hard on given bunch (rather than buggering the entire cache). If filesystem (or something in VFS) says "it's busy", it bloody well _is_ busy and won't be going away until it ceases to be such.

FWIW, some theory: the only kind of long-term reference that can be killed off by memory pressure is that from child to parent. Anything else (e.g. an opened file, current directory, mountpoint, etc.) is out of limits - it either won't be going away until the thing is not pinned anymore (close, chdir, etc.) *or* it really shouldn't be ("VM wants this mountpoint dentry freed, so just dissolve the mount" is a bloody bad idea for obvious reasons).

Stuff in somebody's shrink list is none of our business - somebody else is going to try and evict it anyway; if it can be evicted, it will be.

Anything with zero refcount that isn't in somebody else's shrink list is fair game.

Directories with children could, in principle, be helped along - we could try shrink_dcache_parent() on them, which might end up leaving them with zero refcount. However, it's not cheap and if you pick the root dentry of a filesystem, it'll try to evict everything on it that can be evicted, be it in this page or not. And there's no promise that it will end up evictable after that.

So from the correctness POV
* you can kick out everything with zero refcount not
on shrink lists.
* you _might_ try shrink_dcache_parent() on directory
dentries, in hope to drive their refcount to zero. However, that's almost certainly going to hit too hard and be too costly.
* d_invalidate() is no-go; if anything, you want something
weaker than shrink_dcache_parent(), not stronger.

For anything beyond "just kick out everything in that page that happens to have zero refcount" I would really like to see the stats - how much does it help, how costly it is _and_ how much of the cache does it throw away (see above re running into a root dentry of some filesystem and essentially trimming dcache for that fs down to the unevictable stuff).

#26 lore raw

Re: [RFC PATCH v2 14/14] dcache: Implement object migration

Quoted 19 lines · Al Viro
Quoted 7 lines · Al Viro

If by "how to do it right" you mean "expedit kicking out something with non-zero refcount" - there's no way to do that. Nothing even remotely sane.

If you mean "kick out everything in this page with zero refcount" - that can be done (see further in the thread).

Quoted 8 lines

Look, dentries and inodes are really, really not relocatable. If they can be evicted by memory pressure - sure, we can do that for a given set (e.g. "everything in that page"). But that's it - if memory pressure would _not_ get rid of that one, there's nothing to be done. Again, all VM can do is to simulate shrinker hitting hard on given bunch (rather than buggering the entire cache). If filesystem (or something in VFS) says "it's busy", it bloody well _is_ busy and won't be going away until it ceases to be such.

Quoted 8 lines

FWIW, some theory: the only kind of long-term reference that can be killed off by memory pressure is that from child to parent. Anything else (e.g. an opened file, current directory, mountpoint, etc.) is out of limits - it either won't be going away until the thing is not pinned anymore (close, chdir, etc.) *or* it really shouldn't be ("VM wants this mountpoint dentry freed, so just dissolve the mount" is a bloody bad idea for obvious reasons).

Well, theoretically we could do two levels of references, where the long term reference is stable and contains an rcu protected unstable
reference to the real object. In the likely case when only read-only
access to the object is needed (d_lookup) then the cost is an extra dereference and the associated additional cache usage. If read-write access is needed to object, then extra locking is needed to protect against concurrent migration. So there's non-trivial cost in addition to the added complexity, and I don't see it actually making sense in
practice. But maybe someone can expand this idea to something
practicable...

Thanks,
Miklos

#27 lore raw

Re: [RFC PATCH v2 14/14] dcache: Implement object migration

In reply to Al Viro
Quoted 5 lines · Al Viro
Quoted 1 line

This is an RFC and we want to know how to do this right.

If by "how to do it right" you mean "expedit kicking out something with non-zero refcount" - there's no way to do that. Nothing even remotely sane.

Sure we know that.

Quoted 2 lines

If you mean "kick out everything in this page with zero refcount" - that can be done (see further in the thread).

Ok that would already be progress. If we can use this to liberate some slab pages with just a few dentry object then it may be worthwhile.

Quoted 8 lines

Look, dentries and inodes are really, really not relocatable. If they can be evicted by memory pressure - sure, we can do that for a given set (e.g. "everything in that page"). But that's it - if memory pressure would _not_ get rid of that one, there's nothing to be done. Again, all VM can do is to simulate shrinker hitting hard on given bunch (rather than buggering the entire cache). If filesystem (or something in VFS) says "it's busy", it bloody well _is_ busy and won't be going away until it ceases to be such.

Right. Thats why the patch attempted to check for these things to avoid touching such objects.