| From: |
| Shantanu Goel <sgoel01@yahoo.com> |
| To: |
| linux-kernel@vger.kernel.org |
| Subject: |
| [VM PATCH] Faster reclamation of dirty pages and unused inode/dcache entries in 2.4.22 |
| Date: |
| Fri, 29 Aug 2003 08:01:11 -0700 (PDT) |
Hi kernel hackers,
The VM subsystem in Linux 2.4.22 can cause spurious
swapouts in the presence of lots of dirty pages.
Presently, as dirty pages are encountered,
shrink_cache() schedules a writepage() and moves the
page to the head of the inactive list. When a lot of
dirty pages are present, this can break the FIFO
ordering of the inactive list because clean pages
further down the list will be reclaimed first. The
following patch records the pages being laundered, and
once SWAP_CLUSTER_MAX pages have been accumulated or
the scan is complete, goes back and attempts to move
them back to the tail of the list.
The second part of the patch reclaims unused
inode/dentry/dquot entries more aggressively. I have
observed that on an NFS server where swap out activity
is low, the VM can shrink the page cache to the point
where most pages are used up by unused inode/dentry
entries. This is because page cache reclamation
succeeds most of the time except when a swap_out()
happens.
Feedback and comments are welcome.
Thanks,
Shantanu Goel
__________________________________
Do you Yahoo!?
Yahoo! SiteBuilder - Free, easy-to-use web site design software
http://sitebuilder.yahoo.com--- vmscan.c.~1~ 2002-11-28 18:53:15.000000000 -0500
+++ vmscan.c 2003-08-28 19:09:48.000000000 -0400
@@ -23,6 +23,11 @@
#include <linux/init.h>
#include <linux/highmem.h>
#include <linux/file.h>
+#include <linux/fs.h>
+#include <linux/dcache.h>
+#ifdef CONFIG_QUOTA
+#include <linux/quota.h>
+#endif
#include <asm/pgalloc.h>
@@ -334,12 +339,52 @@
return 0;
}
+/*
+ * Wait for pages being laundered and move them
+ * to tail of inactive list if they are still freeable.
+ */
+static void process_laundry(struct page **laundry, int nr_pages)
+{
+ int i;
+ struct page *page;
+
+ for (i = nr_pages - 1; i >= 0; i--) {
+ page = laundry[i];
+ spin_lock(&pagemap_lru_lock);
+
+ if (!PageLRU(page) || PageActive(page))
+ goto next_page;
+
+ if (TryLockPage(page)) {
+ if (PageLaunder(page)) {
+ spin_unlock(&pagemap_lru_lock);
+ wait_on_page(page);
+ i++;
+ continue;
+ }
+ goto next_page;
+ }
+
+ if (!PageDirty(page) && !PageError(page) && page->mapping && (page_count(page) - !!page->buffers) == 2) {
+ list_del(&page->lru);
+ list_add_tail(&page->lru, &inactive_list);
+ }
+
+ UnlockPage(page);
+next_page:
+ spin_unlock(&pagemap_lru_lock);
+ page_cache_release(page);
+ }
+}
+
static int FASTCALL(shrink_cache(int nr_pages, zone_t * classzone, unsigned int gfp_mask, int priority));
static int shrink_cache(int nr_pages, zone_t * classzone, unsigned int gfp_mask, int priority)
{
struct list_head * entry;
+ int nr_laundry = 0;
int max_scan = nr_inactive_pages / priority;
int max_mapped = min((nr_pages << (10 - priority)), max_scan / 10);
+ struct page *laundry[SWAP_CLUSTER_MAX];
spin_lock(&pagemap_lru_lock);
while (--max_scan >= 0 && (entry = inactive_list.prev) != &inactive_list) {
@@ -408,8 +453,14 @@
page_cache_get(page);
spin_unlock(&pagemap_lru_lock);
- writepage(page);
- page_cache_release(page);
+ if (!writepage(page)) {
+ laundry[nr_laundry++] = page;
+ if (nr_laundry == SWAP_CLUSTER_MAX) {
+ process_laundry(laundry, nr_laundry);
+ nr_laundry = 0;
+ }
+ } else
+ page_cache_release(page);
spin_lock(&pagemap_lru_lock);
continue;
@@ -483,7 +534,7 @@
*/
spin_unlock(&pagemap_lru_lock);
swap_out(priority, gfp_mask, classzone);
- return nr_pages;
+ goto out;
}
/*
@@ -519,6 +570,9 @@
break;
}
spin_unlock(&pagemap_lru_lock);
+out:
+ if (nr_laundry)
+ process_laundry(laundry, nr_laundry);
return nr_pages;
}
@@ -572,14 +626,22 @@
refill_inactive(ratio);
nr_pages = shrink_cache(nr_pages, classzone, gfp_mask, priority);
- if (nr_pages <= 0)
- return 0;
- shrink_dcache_memory(priority, gfp_mask);
- shrink_icache_memory(priority, gfp_mask);
+ /*
+ * Keep an eye on unused inode/dcache/quota entries.
+ */
+ ratio = (inodes_stat.nr_unused * sizeof(struct inode)) >> PAGE_SHIFT;
+ ratio += (dentry_stat.nr_unused * sizeof(struct dentry)) >> PAGE_SHIFT;
+#ifdef CONFIG_QUOTA
+ ratio += (dqstats.free_dquots * sizeof(struct dquot)) >> PAGE_SHIFT;
+#endif
+ if (nr_pages > 0 || ratio > SWAP_CLUSTER_MAX) {
+ shrink_dcache_memory(priority, gfp_mask);
+ shrink_icache_memory(priority, gfp_mask);
#ifdef CONFIG_QUOTA
- shrink_dqcache_memory(DEF_PRIORITY, gfp_mask);
+ shrink_dqcache_memory(priority, gfp_mask);
#endif
+ }
return nr_pages;
}