Skip to contentSkip to conversation

mm: allow to decrease swap.max below actual swap usage

RFC In review Roman Gushchin 1 participant 2 messages Mar 20, 2018 – Apr 5, 2018

Subsystems (4)
lore
Share & tools
lore
#0 lore raw

RFC 1/1 mm: allow to decrease swap.max below actual swap usage

Currently an attempt to set swap.max into a value lower than the actual swap usage fails. And a user can't do much with it, except turning off swap globally (using swapoff).

This patch aims to fix this issue by allowing setting swap.max into any value (which corresponds to cgroup v2 API design), and schedule a background job to fit swap size into the new limit.

The following script can be used to test the memory.swap behavior:
#!/bin/bash

mkdir -p /sys/fs/cgroup/test_swap
echo 100M > /sys/fs/cgroup/test_swap/memory.max echo max > /sys/fs/cgroup/test_swap/memory.swap.max

mkdir -p /sys/fs/cgroup/test_swap_2
echo 100M > /sys/fs/cgroup/test_swap_2/memory.max echo max > /sys/fs/cgroup/test_swap_2/memory.swap.max

echo $$ > /sys/fs/cgroup/test_swap/cgroup.procs
allocate 200M &

echo $$ > /sys/fs/cgroup/test_swap_2/cgroup.procs allocate 200M &

sleep 2

cat /sys/fs/cgroup/test_swap/memory.swap.current cat /sys/fs/cgroup/test_swap_2/memory.swap.current

echo max > /sys/fs/cgroup/test_swap/memory.max echo 50M > /sys/fs/cgroup/test_swap/memory.swap.max

sleep 10

cat /sys/fs/cgroup/test_swap/memory.swap.current cat /sys/fs/cgroup/test_swap_2/memory.swap.current

pkill allocate

Original test results:
106024960
106348544
./swap.sh: line 23: echo: write error: Device or resource busy 106024960
106348544

With this patch applied:
106045440
106352640
52428800
106201088

---
 include/linux/memcontrol.h |  1 +
 include/linux/swap.h       |  9 +++++++
 include/linux/swapfile.h   |  3 ++-
 mm/frontswap.c             |  2 +-
 mm/memcontrol.c            | 27 +++++++++++++++----
 mm/swapfile.c              | 64 ++++++++++++++++++++++++++++++++++++++++++----
 6 files changed, 94 insertions(+), 12 deletions(-)
include/linux/memcontrol.h +1 -0
index 4525b4404a9e..0c1508503422 100644
@@ -184,6 +184,7 @@ struct mem_cgroup {
 
 	/* Range enforcement for interrupt charges */
 	struct work_struct high_work;
+	struct work_struct swap_work;
 
 	unsigned long soft_limit;
 
include/linux/swap.h +9 -0
index 1985940af479..878f111d0603 100644
@@ -650,6 +650,8 @@ extern int mem_cgroup_try_charge_swap(struct page *page, swp_entry_t entry);
 extern void mem_cgroup_uncharge_swap(swp_entry_t entry, unsigned int nr_pages);
 extern long mem_cgroup_get_nr_swap_pages(struct mem_cgroup *memcg);
 extern bool mem_cgroup_swap_full(struct page *page);
+extern int mem_cgroup_shrink_swap(struct mem_cgroup *memcg,
+				  unsigned long nr_pages);
 #else
 static inline void mem_cgroup_swapout(struct page *page, swp_entry_t entry)
 {
@@ -675,6 +677,13 @@ static inline bool mem_cgroup_swap_full(struct page *page)
 {
 	return vm_swap_full();
 }
+
+static inline int mem_cgroup_shrink_swap(struct mem_cgroup *memcg,
+					 unsigned long nr_pages)
+{
+	return 0;
+}
+
 #endif
 
 #endif /* __KERNEL__*/
include/linux/swapfile.h +2 -1
index 06bd7b096167..16844259e802 100644
@@ -9,6 +9,7 @@
 extern spinlock_t swap_lock;
 extern struct plist_head swap_active_head;
 extern struct swap_info_struct *swap_info[];
-extern int try_to_unuse(unsigned int, bool, unsigned long);
+extern int try_to_unuse(unsigned int type, bool fronstswap,
+			unsigned long pages_to_unuse, struct mem_cgroup *memcg);
 
 #endif /* _LINUX_SWAPFILE_H */
mm/frontswap.c +1 -1
index fec8b5044040..f7cb2e802fce 100644
@@ -458,7 +458,7 @@ void frontswap_shrink(unsigned long target_pages)
 	ret = __frontswap_shrink(target_pages, &pages_to_unuse, &type);
 	spin_unlock(&swap_lock);
 	if (ret == 0)
-		try_to_unuse(type, true, pages_to_unuse);
+		try_to_unuse(type, true, pages_to_unuse, NULL);
 	return;
 }
 EXPORT_SYMBOL(frontswap_shrink);
mm/memcontrol.c +22 -5
index d1a917b5b7b7..2150f15a7345 100644
@@ -60,6 +60,7 @@
 #include <linux/vmpressure.h>
 #include <linux/mm_inline.h>
 #include <linux/swap_cgroup.h>
+#include <linux/swapfile.h>
 #include <linux/cpu.h>
 #include <linux/oom.h>
 #include <linux/lockdep.h>
@@ -1879,6 +1880,23 @@ static void high_work_func(struct work_struct *work)
 	reclaim_high(memcg, MEMCG_CHARGE_BATCH, GFP_KERNEL);
 }
 
+static void swap_work_func(struct work_struct *work)
+{
+	struct mem_cgroup *memcg;
+
+	memcg = container_of(work, struct mem_cgroup, swap_work);
+
+	for (;;) {
+		unsigned long usage = page_counter_read(&memcg->swap);
+
+		if (usage <= memcg->swap.limit)
+			break;
+
+		if (mem_cgroup_shrink_swap(memcg, usage - memcg->swap.limit))
+			break;
+	}
+}
+
 /*
  * Scheduled by try_charge() to be executed from the userland return path
  * and reclaims memory over the high limit.
@@ -4391,6 +4409,7 @@ static struct mem_cgroup *mem_cgroup_alloc(void)
 		goto fail;
 
 	INIT_WORK(&memcg->high_work, high_work_func);
+	INIT_WORK(&memcg->swap_work, swap_work_func);
 	memcg->last_scanned_node = MAX_NUMNODES;
 	INIT_LIST_HEAD(&memcg->oom_notify);
 	mutex_init(&memcg->thresholds_lock);
@@ -4526,6 +4545,7 @@ static void mem_cgroup_css_free(struct cgroup_subsys_state *css)
 
 	vmpressure_cleanup(&memcg->vmpressure);
 	cancel_work_sync(&memcg->high_work);
+	cancel_work_sync(&memcg->swap_work);
 	mem_cgroup_remove_from_trees(memcg);
 	memcg_free_kmem(memcg);
 	mem_cgroup_free(memcg);
@@ -6394,11 +6414,8 @@ static ssize_t swap_max_write(struct kernfs_open_file *of,
 	if (err)
 		return err;
 
-	mutex_lock(&memcg_limit_mutex);
-	err = page_counter_limit(&memcg->swap, max);
-	mutex_unlock(&memcg_limit_mutex);
-	if (err)
-		return err;
+	xchg(&memcg->swap.limit, max);
+	schedule_work(&memcg->swap_work);
 
 	return nbytes;
 }
mm/swapfile.c +59 -5

96 lines not on this page · raw message

Signature · 1 line
2.14.3
#1 lore raw
Quoted 47 lines · Roman Gushchin

Currently an attempt to set swap.max into a value lower than the actual swap usage fails. And a user can't do much with it, except turning off swap globally (using swapoff).

This patch aims to fix this issue by allowing setting swap.max into any value (which corresponds to cgroup v2 API design), and schedule a background job to fit swap size into the new limit.

The following script can be used to test the memory.swap behavior:
#!/bin/bash

mkdir -p /sys/fs/cgroup/test_swap
echo 100M > /sys/fs/cgroup/test_swap/memory.max echo max > /sys/fs/cgroup/test_swap/memory.swap.max

mkdir -p /sys/fs/cgroup/test_swap_2
echo 100M > /sys/fs/cgroup/test_swap_2/memory.max echo max > /sys/fs/cgroup/test_swap_2/memory.swap.max

echo $$ > /sys/fs/cgroup/test_swap/cgroup.procs
allocate 200M &

echo $$ > /sys/fs/cgroup/test_swap_2/cgroup.procs allocate 200M &

sleep 2

cat /sys/fs/cgroup/test_swap/memory.swap.current cat /sys/fs/cgroup/test_swap_2/memory.swap.current

echo max > /sys/fs/cgroup/test_swap/memory.max echo 50M > /sys/fs/cgroup/test_swap/memory.swap.max

sleep 10

cat /sys/fs/cgroup/test_swap/memory.swap.current cat /sys/fs/cgroup/test_swap_2/memory.swap.current

pkill allocate

Original test results:
106024960
106348544
./swap.sh: line 23: echo: write error: Device or resource busy 106024960
106348544

Quoted 5 lines

With this patch applied:
106045440
106352640
52428800
106201088

Any comments, thoughts, feedback?

Rebased version below.

---

From a0811f1d094b3103e50c005586471afbe15e9113 Mon Sep 17 00:00:00 2001
From: Roman Gushchin <[email protected]>
Date: Thu, 5 Apr 2018 15:46:50 +0100
Subject: [PATCH v2] mm: allow to decrease swap.max below actual swap usage

Currently an attempt to set swap.max into a value lower than the actual swap usage fails. And a user can't do much with it, except turning off swap globally (using swapoff).

This patch aims to fix this issue by allowing setting swap.max into any value (which corresponds to cgroup v2 API design), and schedule a background job to fit swap size into the new limit.

The following script can be used to test the memory.swap behavior:
#!/bin/bash

mkdir -p /sys/fs/cgroup/test_swap
echo 100M > /sys/fs/cgroup/test_swap/memory.max echo max > /sys/fs/cgroup/test_swap/memory.swap.max

mkdir -p /sys/fs/cgroup/test_swap_2
echo 100M > /sys/fs/cgroup/test_swap_2/memory.max echo max > /sys/fs/cgroup/test_swap_2/memory.swap.max

echo $$ > /sys/fs/cgroup/test_swap/cgroup.procs
allocate 200M &

echo $$ > /sys/fs/cgroup/test_swap_2/cgroup.procs allocate 200M &

sleep 2

cat /sys/fs/cgroup/test_swap/memory.swap.current cat /sys/fs/cgroup/test_swap_2/memory.swap.current

echo max > /sys/fs/cgroup/test_swap/memory.max echo 50M > /sys/fs/cgroup/test_swap/memory.swap.max

sleep 10

cat /sys/fs/cgroup/test_swap/memory.swap.current cat /sys/fs/cgroup/test_swap_2/memory.swap.current

pkill allocate

Original test results:
106024960
106348544
./swap.sh: line 23: echo: write error: Device or resource busy 106024960
106348544

With this patch applied:
106045440
106352640
52428800
106201088

---
 include/linux/memcontrol.h |  1 +
 include/linux/swap.h       |  9 +++++++
 include/linux/swapfile.h   |  3 ++-
 mm/frontswap.c             |  2 +-
 mm/memcontrol.c            | 27 +++++++++++++++----
 mm/swapfile.c              | 64 ++++++++++++++++++++++++++++++++++++++++++----
 6 files changed, 94 insertions(+), 12 deletions(-)
include/linux/memcontrol.h +1 -0
index 44422e1d3def..788e374274d3 100644
@@ -184,6 +184,7 @@ struct mem_cgroup {
 
 	/* Range enforcement for interrupt charges */
 	struct work_struct high_work;
+	struct work_struct swap_work;
 
 	unsigned long soft_limit;
 
include/linux/swap.h +9 -0
index 1985940af479..878f111d0603 100644
@@ -650,6 +650,8 @@ extern int mem_cgroup_try_charge_swap(struct page *page, swp_entry_t entry);
 extern void mem_cgroup_uncharge_swap(swp_entry_t entry, unsigned int nr_pages);
 extern long mem_cgroup_get_nr_swap_pages(struct mem_cgroup *memcg);
 extern bool mem_cgroup_swap_full(struct page *page);
+extern int mem_cgroup_shrink_swap(struct mem_cgroup *memcg,
+				  unsigned long nr_pages);
 #else
 static inline void mem_cgroup_swapout(struct page *page, swp_entry_t entry)
 {
@@ -675,6 +677,13 @@ static inline bool mem_cgroup_swap_full(struct page *page)
 {
 	return vm_swap_full();
 }
+
+static inline int mem_cgroup_shrink_swap(struct mem_cgroup *memcg,
+					 unsigned long nr_pages)
+{
+	return 0;
+}
+
 #endif
 
 #endif /* __KERNEL__*/
include/linux/swapfile.h +2 -1
index 06bd7b096167..16844259e802 100644
@@ -9,6 +9,7 @@
 extern spinlock_t swap_lock;
 extern struct plist_head swap_active_head;
 extern struct swap_info_struct *swap_info[];
-extern int try_to_unuse(unsigned int, bool, unsigned long);
+extern int try_to_unuse(unsigned int type, bool fronstswap,
+			unsigned long pages_to_unuse, struct mem_cgroup *memcg);
 
 #endif /* _LINUX_SWAPFILE_H */
mm/frontswap.c +1 -1
index fec8b5044040..f7cb2e802fce 100644
@@ -458,7 +458,7 @@ void frontswap_shrink(unsigned long target_pages)
 	ret = __frontswap_shrink(target_pages, &pages_to_unuse, &type);
 	spin_unlock(&swap_lock);
 	if (ret == 0)
-		try_to_unuse(type, true, pages_to_unuse);
+		try_to_unuse(type, true, pages_to_unuse, NULL);
 	return;
 }
 EXPORT_SYMBOL(frontswap_shrink);
mm/memcontrol.c +22 -5
index 18dda3f113bd..c1496453d3ef 100644
@@ -60,6 +60,7 @@
 #include <linux/vmpressure.h>
 #include <linux/mm_inline.h>
 #include <linux/swap_cgroup.h>
+#include <linux/swapfile.h>
 #include <linux/cpu.h>
 #include <linux/oom.h>
 #include <linux/lockdep.h>
@@ -1870,6 +1871,23 @@ static void high_work_func(struct work_struct *work)
 	reclaim_high(memcg, MEMCG_CHARGE_BATCH, GFP_KERNEL);
 }
 
+static void swap_work_func(struct work_struct *work)
+{
+	struct mem_cgroup *memcg;
+
+	memcg = container_of(work, struct mem_cgroup, swap_work);
+
+	for (;;) {
+		unsigned long usage = page_counter_read(&memcg->swap);
+
+		if (usage <= memcg->swap.limit)
+			break;
+
+		if (mem_cgroup_shrink_swap(memcg, usage - memcg->swap.limit))
+			break;
+	}
+}
+
 /*
  * Scheduled by try_charge() to be executed from the userland return path
  * and reclaims memory over the high limit.
@@ -4391,6 +4409,7 @@ static struct mem_cgroup *mem_cgroup_alloc(void)
 		goto fail;
 
 	INIT_WORK(&memcg->high_work, high_work_func);
+	INIT_WORK(&memcg->swap_work, swap_work_func);
 	memcg->last_scanned_node = MAX_NUMNODES;
 	INIT_LIST_HEAD(&memcg->oom_notify);
 	mutex_init(&memcg->thresholds_lock);
@@ -4526,6 +4545,7 @@ static void mem_cgroup_css_free(struct cgroup_subsys_state *css)
 
 	vmpressure_cleanup(&memcg->vmpressure);
 	cancel_work_sync(&memcg->high_work);
+	cancel_work_sync(&memcg->swap_work);
 	mem_cgroup_remove_from_trees(memcg);
 	memcg_free_kmem(memcg);
 	mem_cgroup_free(memcg);
@@ -6408,11 +6428,8 @@ static ssize_t swap_max_write(struct kernfs_open_file *of,
 	if (err)
 		return err;
 
-	mutex_lock(&memcg_limit_mutex);
-	err = page_counter_limit(&memcg->swap, max);
-	mutex_unlock(&memcg_limit_mutex);
-	if (err)
-		return err;
+	xchg(&memcg->swap.limit, max);
+	schedule_work(&memcg->swap_work);
 
 	return nbytes;
 }
mm/swapfile.c +59 -5

96 lines not on this page · raw message

Signature · 1 line
2.14.3