Skip to contentSkip to conversation

cgroup: limit the amount of dirty file pages

RFC PATCH In review Andrea Righi 6 participants 9 messages Sep 9, 2008 – Sep 10, 2008 2 messages not in the archive

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

RFC PATCH 1/1 cgroup: limit the amount of dirty file pages

This is a totally experimental patch against 2.6.27-rc5-mm1.

It allows to control how much dirty file pages a cgroup can have at any given time. This feature is supposed to be strictly connected to a generic cgroup IO controller (see below).

Interface: a new entry "filedirty" is added to the file memory.stat, reporting the number of dirty file pages (in pages), and a new file memory.file_dirty_limit_in_pages is added in the cgroup filesystem to show/set the current limit.

The overall design is the following.

The dirty file pages are accounted for each cgroup using the memory controller statistics. The memory controller also allows to define an upper bound on the number of dirty file pages. When this upper bound is exceeded the tasks in the cgroup are forced to writeback dirty pages to return within the cgroup limit.

With this functionality a generic cgroup IO controller can apply any kind of limitation or shaping policy directly to the IO requests (elevator, IO scheduler, ...) and it shouldn't care about how fast the userspace apps are dirtying pages in memory generating a lot of hard/slow to reclaim pages (or even potential OOM conditions), because the apps will be actually throttled by the IO controller when they'll start to writeback pages.

[ Honestly, I don't like this implementation in memcgroup. I'm using the memcgroup statistics to account the dirty file pages, but I'm also adding a variable to struct mem_cgroup to implement the dirty file pages limit. So it seems it would be more appropriate a struct res_counter, but I can't use res_counter_[un]charge() interface, because the dirty file pages limit is a soft-limit and can be exceeded without any problem; if it's exceeded the task is simply forced to perform a writeback of the dirty pages to return back to the allowed dirty limit. Suggestions are welcome. ]

---
 fs/buffer.c                |    1 +
 fs/reiser4/as_ops.c        |    4 ++-
 fs/reiser4/page_cache.c    |    4 ++-
 include/linux/memcontrol.h |   12 ++++++
 mm/filemap.c               |    1 +
 mm/memcontrol.c            |   83 ++++++++++++++++++++++++++++++++++++++++++++
 mm/page-writeback.c        |   37 +++++++++++++++++++
 mm/truncate.c              |    2 +
 8 files changed, 142 insertions(+), 2 deletions(-)
fs/buffer.c +1 -0
index 8274f5e..9c40a16 100644
@@ -718,6 +718,7 @@ static int __set_page_dirty(struct page *page,
 		WARN_ON_ONCE(warn && !PageUptodate(page));
 
 		if (mapping_cap_account_dirty(mapping)) {
+			mem_cgroup_charge_file_dirty(page, 1);
 			__inc_zone_page_state(page, NR_FILE_DIRTY);
 			__inc_bdi_stat(mapping->backing_dev_info,
 					BDI_RECLAIMABLE);
fs/reiser4/as_ops.c +3 -1
index decb9eb..671432a 100644
@@ -82,9 +82,11 @@ int reiser4_set_page_dirty(struct page *page)
 			/* check for race with truncate */
 			if (page->mapping) {
 				assert("vs-1652", page->mapping == mapping);
-				if (mapping_cap_account_dirty(mapping))
+				if (mapping_cap_account_dirty(mapping)) {
+					mem_cgroup_charge_file_dirty(page, 1);
 					inc_zone_page_state(page,
 							NR_FILE_DIRTY);
+				}
 				radix_tree_tag_set(&mapping->page_tree,
 						   page->index,
 						   PAGECACHE_TAG_REISER4_MOVED);
fs/reiser4/page_cache.c +3 -1
index 654e7ae..2d5dfac 100644
@@ -467,8 +467,10 @@ int reiser4_set_page_dirty_internal(struct page *page)
 	BUG_ON(mapping == NULL);
 
 	if (!TestSetPageDirty(page)) {
-		if (mapping_cap_account_dirty(mapping))
+		if (mapping_cap_account_dirty(mapping)) {
+			mem_cgroup_charge_file_dirty(page, 1);
 			inc_zone_page_state(page, NR_FILE_DIRTY);
+		}
 
 		__mark_inode_dirty(mapping->host, I_DIRTY_PAGES);
 	}
include/linux/memcontrol.h +12 -0
index ee1b2fc..6677097 100644
@@ -29,6 +29,9 @@ struct mm_struct;
 
 #define page_reset_bad_cgroup(page)	((page)->page_cgroup = 0)
 
+extern void mem_cgroup_charge_file_dirty(struct page *page, int charge);
+extern s64 mem_cgroup_check_file_dirty(void);
+
 extern struct page_cgroup *page_get_page_cgroup(struct page *page);
 extern int mem_cgroup_charge(struct page *page, struct mm_struct *mm,
 				gfp_t gfp_mask);
@@ -132,6 +135,15 @@ static inline void mem_cgroup_end_migration(struct page *page)
 {
 }
 
+static inline void mem_cgroup_charge_file_dirty(struct page *page, int charge)
+{
+}
+
+static inline s64 mem_cgroup_check_file_dirty(void)
+{
+	return 0;
+}
+
 static inline int mem_cgroup_calc_mapped_ratio(struct mem_cgroup *mem)
 {
 	return 0;
mm/filemap.c +1 -0
index 0df6e1f..ca20f51 100644
@@ -131,6 +131,7 @@ void __remove_from_page_cache(struct page *page)
 	 * having removed the page entirely.
 	 */
 	if (PageDirty(page) && mapping_cap_account_dirty(mapping)) {
+		mem_cgroup_charge_file_dirty(page, -1);
 		dec_zone_page_state(page, NR_FILE_DIRTY);
 		dec_bdi_stat(mapping->backing_dev_info, BDI_RECLAIMABLE);
 	}
mm/memcontrol.c +83 -0

131 lines not on this page · raw message

mm/page-writeback.c +37 -0
index c6d6088..ae89950 100644
@@ -25,6 +25,7 @@
 #include <linux/task_io_accounting_ops.h>
 #include <linux/blkdev.h>
 #include <linux/mpage.h>
+#include <linux/memcontrol.h>
 #include <linux/rmap.h>
 #include <linux/percpu.h>
 #include <linux/notifier.h>
@@ -412,6 +413,34 @@ get_dirty_limits(long *pbackground, long *pdirty, long *pbdi_dirty,
 	}
 }
 
+static void mem_cgroup_balance_dirty_pages(struct address_space *mapping,
+				long long dirty_pages)
+{
+	struct writeback_control wbc = {
+		.bdi		= mapping->backing_dev_info,
+		.sync_mode	= WB_SYNC_NONE,
+		.older_than_this = NULL,
+		.nr_to_write	= 0,
+		.range_cyclic	= 1,
+	};
+	unsigned long write_chunk = sync_writeback_pages();
+
+	while (dirty_pages > 0) {
+		wbc.more_io = 0;
+		wbc.encountered_congestion = 0;
+		wbc.nr_to_write = write_chunk;
+		wbc.pages_skipped = 0;
+		writeback_inodes(&wbc);
+		dirty_pages -= write_chunk - wbc.nr_to_write;
+		if (wbc.nr_to_write > 0 || wbc.pages_skipped > 0) {
+			if (wbc.encountered_congestion || wbc.more_io)
+				congestion_wait(WRITE, HZ/10);
+			else
+				break;
+		}
+	}
+}
+
 /*
  * balance_dirty_pages() must be called by processes which are generating dirty
  * data.  It looks at the number of dirty pages in the machine and will force
@@ -556,6 +585,12 @@ void balance_dirty_pages_ratelimited_nr(struct address_space *mapping,
 	static DEFINE_PER_CPU(unsigned long, ratelimits) = 0;
 	unsigned long ratelimit;
 	unsigned long *p;
+	long long memcg_dirty_pages = mem_cgroup_check_file_dirty();
+
+	if (memcg_dirty_pages) {
+		mem_cgroup_balance_dirty_pages(mapping, memcg_dirty_pages);
+		return;
+	}
 
 	ratelimit = ratelimit_pages;
 	if (mapping->backing_dev_info->dirty_exceeded)
@@ -1090,6 +1125,7 @@ int __set_page_dirty_nobuffers(struct page *page)
 			BUG_ON(mapping2 != mapping);
 			WARN_ON_ONCE(!PagePrivate(page) && !PageUptodate(page));
 			if (mapping_cap_account_dirty(mapping)) {
+				mem_cgroup_charge_file_dirty(page, 1);
 				__inc_zone_page_state(page, NR_FILE_DIRTY);
 				__inc_bdi_stat(mapping->backing_dev_info,
 						BDI_RECLAIMABLE);
@@ -1234,6 +1270,7 @@ int clear_page_dirty_for_io(struct page *page)
 		 * for more comments.
 		 */
 		if (TestClearPageDirty(page)) {
+			mem_cgroup_charge_file_dirty(page, -1);
 			dec_zone_page_state(page, NR_FILE_DIRTY);
 			dec_bdi_stat(mapping->backing_dev_info,
 					BDI_RECLAIMABLE);
mm/truncate.c +2 -0
index e2bdd70..8ca6893 100644
@@ -72,7 +72,9 @@ void cancel_dirty_page(struct page *page, unsigned int account_size)
 {
 	if (TestClearPageDirty(page)) {
 		struct address_space *mapping = page->mapping;
+
 		if (mapping && mapping_cap_account_dirty(mapping)) {
+			mem_cgroup_charge_file_dirty(page, -1);
 			dec_zone_page_state(page, NR_FILE_DIRTY);
 			dec_bdi_stat(mapping->backing_dev_info,
 					BDI_RECLAIMABLE);

--
To unsubscribe, send a message with 'unsubscribe linux-mm' in the body to [email protected]. For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"[email protected]"> [email protected] </a>

#1 lore raw

----- Original Message -----

Quoted 11 lines

This is a totally experimental patch against 2.6.27-rc5-mm1.

It allows to control how much dirty file pages a cgroup can have at any given time. This feature is supposed to be strictly connected to a generic cgroup IO controller (see below).

Interface: a new entry "filedirty" is added to the file memory.stat, reporting the number of dirty file pages (in pages), and a new file memory.file_dirty_limit_in_pages is added in the cgroup filesystem to show/set the current limit.

Before staring patch review, why not dirty_ratio per memcg ? Is there difficult implementation issue ?

Thanks,
-Kame

--
To unsubscribe, send a message with 'unsubscribe linux-mm' in the body to [email protected]. For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"[email protected]"> [email protected] </a>

A message that is not in the archive (sent off-list or not yet received): 11118085.1220977593430.kamezawa.hiroyu-+CUm20s59erQFUHtdCDX3A@public.gmane.org

#3 lore raw
Quoted 7 lines · [email protected] wrote:

----- Original Message -----

Quoted 6 lines

This is a totally experimental patch against 2.6.27-rc5-mm1.

It allows to control how much dirty file pages a cgroup can have at any given time. This feature is supposed to be strictly connected to a generic cgroup IO controller (see below).

Quoted 7 lines
Quoted 5 lines

Interface: a new entry "filedirty" is added to the file memory.stat, reporting the number of dirty file pages (in pages), and a new file memory.file_dirty_limit_in_pages is added in the cgroup filesystem to show/set the current limit.

Before staring patch review, why not dirty_ratio per memcg ? Is there difficult implementation issue ?

mmmh.. maybe it's a bit more complex (would add some overhead?) to translate the limit from dirty_ratio into pages or bytes, because we need to evaluate it in function of the per-cgroup dirtyable memory (lru pages and free pages I suppose). Maybe it's enough to implement it directly in determine_dirtyable_memory().

I can try to implement it and post a new patch.

-Andrea

#4 lore raw
In reply to [email protected]
Quoted 7 lines · [email protected] wrote:

----- Original Message -----

Quoted 6 lines

This is a totally experimental patch against 2.6.27-rc5-mm1.

It allows to control how much dirty file pages a cgroup can have at any given time. This feature is supposed to be strictly connected to a generic cgroup IO controller (see below).

Quoted 7 lines
Quoted 5 lines

Interface: a new entry "filedirty" is added to the file memory.stat, reporting the number of dirty file pages (in pages), and a new file memory.file_dirty_limit_in_pages is added in the cgroup filesystem to show/set the current limit.

Before staring patch review, why not dirty_ratio per memcg ? Is there difficult implementation issue ?

mmmh.. maybe it's a bit more complex (would add some overhead?) to translate the limit from dirty_ratio into pages or bytes, because we need to evaluate it in function of the per-cgroup dirtyable memory (lru pages and free pages I suppose). Maybe it's enough to implement it directly in determine_dirtyable_memory().

I can try to implement it and post a new patch.

-Andrea

--
To unsubscribe, send a message with 'unsubscribe linux-mm' in the body to [email protected]. For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"[email protected]"> [email protected] </a>

A message that is not in the archive (sent off-list or not yet received): [email protected]

#6 lore raw

----- Original Message -----

Quoted 11 lines

This is a totally experimental patch against 2.6.27-rc5-mm1.

It allows to control how much dirty file pages a cgroup can have at any given time. This feature is supposed to be strictly connected to a generic cgroup IO controller (see below).

Interface: a new entry "filedirty" is added to the file memory.stat, reporting the number of dirty file pages (in pages), and a new file memory.file_dirty_limit_in_pages is added in the cgroup filesystem to show/set the current limit.

Before staring patch review, why not dirty_ratio per memcg ? Is there difficult implementation issue ?

Thanks,
-Kame

#7 lore raw
In reply to a message not in the archive
Quoted 3 lines · Andrea Righi

It allows to control how much dirty file pages a cgroup can have at any given time. This feature is supposed to be strictly connected to a generic cgroup IO controller (see below).

So, this functions similarly to our global dirty ratio? Is it just intended to keep a cgroup from wedging itself too hard with too many dirty pages, just like the global ratio?

-- Dave

#8 lore raw
In reply to Andrea Righi
Quoted 3 lines · Andrea Righi

It allows to control how much dirty file pages a cgroup can have at any given time. This feature is supposed to be strictly connected to a generic cgroup IO controller (see below).

So, this functions similarly to our global dirty ratio? Is it just intended to keep a cgroup from wedging itself too hard with too many dirty pages, just like the global ratio?

-- Dave

--
To unsubscribe, send a message with 'unsubscribe linux-mm' in the body to [email protected]. For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"[email protected]"> [email protected] </a>

#9 lore raw
Quoted 10 lines · Dave Hansen
Quoted 3 lines · Andrea Righi

It allows to control how much dirty file pages a cgroup can have at any given time. This feature is supposed to be strictly connected to a generic cgroup IO controller (see below).

So, this functions similarly to our global dirty ratio? Is it just intended to keep a cgroup from wedging itself too hard with too many dirty pages, just like the global ratio?

-- Dave

Correct, it's the same functionality provided by vm.dirty_ratio and vm.dirty_background_ratio, except that is intended to be per-cgroup.

Without this functionality, a cgroup can even dirty all its memory, allocated by the memory controller, since statistics and writeback configurations are global.

-Andrea

#10 lore raw
In reply to Dave Hansen
Quoted 10 lines · Dave Hansen
Quoted 3 lines · Andrea Righi

It allows to control how much dirty file pages a cgroup can have at any given time. This feature is supposed to be strictly connected to a generic cgroup IO controller (see below).

So, this functions similarly to our global dirty ratio? Is it just intended to keep a cgroup from wedging itself too hard with too many dirty pages, just like the global ratio?

-- Dave

Correct, it's the same functionality provided by vm.dirty_ratio and vm.dirty_background_ratio, except that is intended to be per-cgroup.

Without this functionality, a cgroup can even dirty all its memory, allocated by the memory controller, since statistics and writeback configurations are global.

-Andrea

--
To unsubscribe, send a message with 'unsubscribe linux-mm' in the body to [email protected]. For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"[email protected]"> [email protected] </a>