[linux-mm-cc] [PATCH 09/12] avoid OOM : shrink ccache and eat-ccache-allocator

IKEDA Munehiro m-ikeda at ds.jp.nec.com
Fri Jul 20 06:45:26 EDT 2007


If the eat-ccache-allocator fails to allocate memory,
it frees the oldest fs_backed chunk_head(s) and retry.
If fail to allocate memory under lock situation,
release the lock once, schedule, try to lock,
free chunk_head(s), and retry to allocate again and again.


Signed-off-by: IKEDA, Munehiro <m-ikeda at ds.jp.nec.com>
---
 mm/ccache.c |  208 +++++++++++++++++++++++++++++++++++++++++++++++++++++------
 1 files changed, 188 insertions(+), 20 deletions(-)

diff --git a/mm/ccache.c b/mm/ccache.c
index 316e686..790027a 100644
--- a/mm/ccache.c
+++ b/mm/ccache.c
@@ -325,6 +325,76 @@ static int free_chunk_head(struct chunk_head *ch)
 	return freed;
 }
 
+static int shrink_anon_ccache(void)
+{
+	/* temporarily do nothing */
+	return 0;
+}
+
+static int shrink_fs_backed_ccache(void)
+{
+	struct chunk_head *ch;
+	struct list_head *p;
+	int freed;
+
+	spin_lock(&ccache_lock);
+	if (list_empty(&lru_fs_backed)){
+		CC_DEBUG("no fs_backed_ccache");
+		spin_unlock(&ccache_lock);
+		return 0;
+	}
+
+	list_for_each_prev(p, &lru_fs_backed) {
+		ch = list_entry(p, struct chunk_head, lru);
+		if (bit_spin_trylock(PG_locked, &ch->flags))
+			break;
+	}
+	if (p == &lru_fs_backed){
+		CC_DEBUG("all pages locked");
+		spin_unlock(&ccache_lock);
+		return -EAGAIN;
+	}
+		
+	list_del_init(&ch->lru);
+	spin_unlock(&ccache_lock);
+
+	write_lock_irq(&ch->mapping->tree_lock);
+	if (radix_tree_delete(&ch->mapping->page_tree, ch->offset))
+		ch->mapping->nrpages--;
+	else
+		CC_DEBUG("try to delete from radix tree, but no slot");
+	write_unlock_irq(&ch->mapping->tree_lock);
+	
+	freed = __free_chunk_head(ch);
+	bit_spin_unlock(PG_locked, &ch->flags);
+	release_chunk_head(ch);
+	atomic_dec(&fs_backed_cc_size);
+
+	return freed;
+}
+
+/*
+ * Try to shrink ccache
+ */
+static int shrink_ccache(int goal)
+{
+	int reclaim = 0;
+	int ret;
+
+	while (reclaim < goal){
+		ret = shrink_fs_backed_ccache();
+		if (ret < 0)
+			return ret;
+		reclaim += ret;
+	
+		ret = shrink_anon_ccache();
+		if (ret < 0)
+			return ret;
+		reclaim += ret;
+	}
+	return reclaim;
+}
+
 /*
  * Get a no. of chunks from free list for 'total_size'.
  * Allocate more chunks if required.
@@ -333,6 +403,7 @@ static int free_chunk_head(struct chunk_head *ch)
 static struct chunk_head* get_enough_chunks(unsigned int total_size)
 {
 	int ret = -ENOMEM, rem = total_size;
+	unsigned long cc_size;
 	struct chunk *chunk, *tail = NULL;
 	/* take this from slab */
 	struct chunk_head *ch = kmalloc(sizeof(struct chunk_head), GFP_KERNEL);
@@ -376,10 +447,20 @@ repeat:
 	spin_unlock(&ccache_lock);
 
 	/* Free list didn't have enough chunks. Get more! */
-	ret = expand_ccache();
-	if (ret)
-		goto out;
-
+	cc_size = (unsigned long)(atomic_read(&anon_cc_size) +
+				  atomic_read(&fs_backed_cc_size));
+	if (cc_size >= max_anon_cc_size + max_fs_backed_cc_size){
+		if ((ret = shrink_ccache(rem))<rem){
+			CC_DEBUG("shrink_ccache(%d) returned %d", rem, ret);
+			goto out;
+		}
+	}
+	else{
+		if ((ret = expand_ccache())<0){
+			CC_DEBUG("expand_ccache() returned %d", ret);
+			goto out;
+		}
+	}
 	goto repeat;
 out:
 	CC_INFO("function failed!!!");
@@ -616,6 +697,91 @@ int should_add_to_ccache(struct page *page)
 	return 0;
 }
 
+static int free_ccache_pages(int pages)
+{
+	unsigned int oldp, bytes;
+	int ret;
+	
+	oldp = atomic_read(&cc_pages);
+	bytes = PAGE_SIZE * pages;
+	do {
+		ret = shrink_ccache(bytes);
+		if (ret < 0)
+			break;
+		ret = oldp - atomic_read(&cc_pages);
+	} while (ret < pages);
+
+	return ret;
+}
+
+/*
+ * Try to alloc a page or eat ccache itself if fail
+ */
+static struct page *alloc_or_eat_ccache(gfp_t gfp_mask, int order)
+{
+	struct page *page;
+	gfp_t mask;
+
+	/* __GFP_NOFAIL is handled in this function */
+	mask = (gfp_mask&~__GFP_NOFAIL)| __GFP_NOWARN;
+	page = alloc_pages(mask, order);
+	if (page)
+		return page;
+
+	for (;;){
+		if (free_ccache_pages(1 << order) >= 0) {
+			page = alloc_pages(mask, order);
+			if (page)
+				return page;
+		}
+		if (!(gfp_mask & __GFP_NOFAIL))
+			return NULL;
+		schedule();
+	}
+}
+
+/*
+ * Try to alloc a page or eat ccache itself with lock.
+ * If fail to allocate, release the lock, schedule,
+ * acruire the lock and retry.
+ * 
+ */
+static struct page *alloc_or_eat_ccache_lock(gfp_t gfp_mask,
+					int order, struct chunk_head *ch)
+{
+	struct page *page;
+	gfp_t mask;
+
+	/* __GFP_NOFAIL is handled in this function */
+	mask = (gfp_mask & ~__GFP_NOFAIL)| (GFP_NOWAIT|__GFP_NOWARN);
+
+	page = alloc_pages(mask, order);
+	if (page)
+		return page;
+
+	for (;;){
+		if (free_ccache_pages(1 << order) >= 0) {
+			page = alloc_pages(mask, order);
+			if (page)
+				return page;
+		}
+
+		/* can't allocate ... schedule and retry later */
+		bit_spin_unlock(PG_locked, &ch->flags);
+		if (!(gfp_mask & __GFP_NOFAIL))
+			break;
+		schedule();
+		if (!bit_spin_trylock(PG_locked, &ch->flags))
+			break;
+	}
+
+	/*
+	 * Someone locked and already decompressing.
+	 * Here, no page allocated, no lock aquired
+	 */
+	return NULL;
+}
+
 /*
  * given chunk_head, gather the chunks into a page,
  * decompress it, and return resulting page.
@@ -624,7 +790,8 @@ static struct page *cc_readpage(struct chunk_head *ch)
 {
 	int ret = -ENOMEM, algo_idx;
 	unsigned int comp_size=0;
-	struct page *decomp_page, *comp_page;
+	struct page *comp_page = NULL;
+	struct page *decomp_page = NULL;
 	void *comp_data;
 	struct chunk *chunk, *tmp;
 	CC_DEBUG2("start");
@@ -640,23 +807,23 @@ static struct page *cc_readpage(struct chunk_head *ch)
 	 * -- Doing GFP_KERNEL giver higher chances that alloc will
 	 * be successfull but it may sleep (and hence doesn't work)!
 	 * -- What to do??
+	 *
+	 * --> alloc_or_eat_ccache_lock() is the solution proposal
 	 */
-	comp_page = alloc_page(GFP_ATOMIC);
+	comp_page = alloc_or_eat_ccache_lock(__GFP_NOFAIL, 0, ch);
 	if (!comp_page) {
-		CC_INFO("comp_page alloc failed!!!\n");
-		BUG();
-		return NULL;
+		CC_INFO("duplicated ccache reading");
+		goto out;
 	}
 	comp_data = page_address(comp_page);
 #if 0
 	decomp_page = alloc_page(GFP_ATOMIC);
 #endif
 	/* same comments apply as for comp_page alloc */
-	decomp_page = alloc_page(GFP_ATOMIC);
+	decomp_page = alloc_or_eat_ccache_lock(__GFP_NOFAIL, 0, ch);
 	if (!decomp_page) {
-		CC_INFO("decomp_page alloc failed!!!\n");
-		BUG();	// we normally hit this after some OOM kills :)
-		return NULL;
+		CC_INFO("duplicated ccache reading");
+		goto out;
 	}
 
 	chunk = ch->chunk_list;
@@ -698,8 +865,10 @@ static struct page *cc_readpage(struct chunk_head *ch)
 	CC_DEBUG2("decomp_page->flags=0x%08lx", decomp_page->flags);
 
 	set_page_private(comp_page, 0);
-	__free_page(comp_page);
 
+out:
+	if (comp_page)
+		__free_page(comp_page);
 	return decomp_page;
 }
 
@@ -725,7 +894,8 @@ int cc_writepage(struct page *page)
 	CC_DEBUG2("mapping=%p, flags=0x%08lx, index=%lu",
 			mapping, page->flags, page->index);
 
-	tmp_page = alloc_pages(GFP_KERNEL, 1);	/* page may expand */
+	/* page may expand */
+	tmp_page = alloc_or_eat_ccache(GFP_KERNEL, 1);
 	if (!tmp_page)
 		goto out;
 	
@@ -896,11 +1066,9 @@ struct page *handle_ccache_fault(struct chunk_head *ch,
 		page = cc_readpage(ch);
 		CC_DEBUG2("after cc_readpage");
 
-		if (!page) {
-			CC_INFO("cc_readpage failed!!!");
-			bit_spin_unlock(PG_locked, &ch->flags);
-			return NULL;
-		}
+		if (!page)
+			return cleanup_dup_fault(ch, mapping);
+
 		page->mapping = mapping;
 
 		write_lock_irq(&mapping->tree_lock);
-- 
1.4.4.4





More information about the linux-mm-cc mailing list