aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorOGAWA Hirofumi <hirofumi@mail.parknet.co.jp>2012-12-16 04:43:23 +0900
committerDaniel Phillips <daniel@tux3.org>2012-12-16 04:43:23 +0900
commit6fe71bac561a38ce5bab4738bcfbe702c6ded79c (patch)
treec595ef432287df3f256b7da453af7866f35a444c
parentf8387a5ab2f4fdaedd73dff82e7ca07ad3a2c6dd (diff)
downloadlinux-tux3-6fe71bac561a38ce5bab4738bcfbe702c6ded79c.tar.gz
tux3: Implement bufferfork_to_invalidate() for truncate(2)
We have to do buffer fork if we truncate the stabled buffers. This checks it in bufferfork_to_invalidate(). If buffer was already stabled, this deletes the page from radix-tree. With this, we don't need to wait I/O in truncate(2) path. Signed-off-by: OGAWA Hirofumi <hirofumi@mail.parknet.co.jp>
-rw-r--r--fs/tux3/buffer_fork.c180
1 files changed, 140 insertions, 40 deletions
diff --git a/fs/tux3/buffer_fork.c b/fs/tux3/buffer_fork.c
index 180d43d703b96f..97f5d3b640c53e 100644
--- a/fs/tux3/buffer_fork.c
+++ b/fs/tux3/buffer_fork.c
@@ -4,6 +4,7 @@
#include <linux/hugetlb.h> /* for PageHuge() */
#include <linux/swap.h> /* for __lru_cache_add() */
+#include <linux/cleancache.h>
/*
* Scanning the freeable forked page.
@@ -166,10 +167,9 @@ static struct buffer_head *page_buffer(struct page *page, unsigned which)
* Check if the page can be modified for delta. I.e. All buffers
* should be dirtied for delta, or clean.
*/
-static int page_can_modify(struct buffer_head *buffer, unsigned delta)
+static int page_can_modify(struct page *page, unsigned delta)
{
- struct page *page = buffer->b_page;
- struct buffer_head *head;
+ struct buffer_head *buffer, *head;
head = page_buffers(page);
buffer = head;
@@ -240,6 +240,56 @@ static void newpage_add_lru(struct page *page)
__lru_cache_add(page, LRU_INACTIVE_FILE);
}
+enum ret_needfork {
+ RET_FORKED = 1, /* Someone already forked */
+ RET_NEED_FORK, /* Need to fork to dirty */
+ RET_CAN_DIRTY, /* Can dirty without fork */
+ RET_ALREADY_DIRTY, /* Buffer is already dirtied for delta */
+};
+
+static enum ret_needfork
+need_fork(struct page *page, struct buffer_head *buffer, unsigned delta)
+{
+ /* Someone already forked this page. */
+ if (PageForked(page))
+ return RET_FORKED;
+ /* Page is under I/O, needs buffer fork */
+ if (PageWriteback(page))
+ return RET_NEED_FORK;
+ /*
+ * If page isn't dirty (and isn't writeback), this is clean
+ * page (and all buffers should be clean on this page). So we
+ * can just dirty the buffer for current delta.
+ */
+ if (!PageDirty(page)) {
+ assert(!buffer || !buffer_dirty(buffer));
+ return RET_CAN_DIRTY;
+ }
+
+ /*
+ * (Re-)check the partial dirtied page under lock_page. (We
+ * don't allow the buffer has different delta states on same
+ * page.)
+ */
+ if (buffer && buffer_dirty(buffer)) {
+ /* Buffer is dirtied by delta, just modify this buffer */
+ if (buffer_can_modify(buffer, delta))
+ return RET_ALREADY_DIRTY;
+
+ /* Buffer was dirtied by different delta, we need buffer fork */
+ } else {
+ /* Check other buffers sharing same page. */
+ if (page_can_modify(page, delta)) {
+ /* This page can be modified, dirty this buffer */
+ return RET_CAN_DIRTY;
+ }
+
+ /* The page can't be modified for delta */
+ }
+
+ return RET_NEED_FORK;
+}
+
struct buffer_head *blockdirty(struct buffer_head *buffer, unsigned newdelta)
{
struct page *oldpage = buffer->b_page;
@@ -263,49 +313,28 @@ struct buffer_head *blockdirty(struct buffer_head *buffer, unsigned newdelta)
lock_page(oldpage);
/* This happens on partially dirty page. */
-// assert(PageUptodate(oldpage));
+// assert(PageUptodate(page));
- /* Someone already forked this page. */
- if (PageForked(oldpage)) {
+ switch (need_fork(oldpage, buffer, newdelta)) {
+ case RET_FORKED:
+ /* This page was already forked. Retry from lookup page. */
buffer = ERR_PTR(-EAGAIN);
assert(0); /* FIXME: we have to handle -EAGAIN case */
+ /* FALLTHRU */
+ case RET_ALREADY_DIRTY:
+ /* This buffer was already dirtied. Done. */
goto out;
- }
- /* Page is under I/O, needs buffer fork */
- if (PageWriteback(oldpage))
- goto do_clone_page;
- /*
- * If page isn't dirty (and isn't writeback), this is clean
- * page (and all buffers should be clean on this page). So we
- * can just dirty the buffer for current delta.
- */
- if (!PageDirty(oldpage)) {
- assert(!buffer_dirty(buffer));
+ case RET_CAN_DIRTY:
+ /* We can dirty this buffer. */
goto dirty_buffer;
+ case RET_NEED_FORK:
+ /* We need to buffer fork. */
+ break;
+ default:
+ BUG();
+ break;
}
- /*
- * (Re-)check the partial dirtied page under lock_page. (We
- * don't allow the buffer has different delta states on same
- * page.)
- */
- if (buffer_dirty(buffer)) {
- /* Buffer is dirtied by newdelta, just modify this buffer */
- if (buffer_can_modify(buffer, newdelta))
- goto out;
-
- /* Buffer was dirtied by different delta, we need buffer fork */
- } else {
- /* Check other buffers sharing same page. */
- if (page_can_modify(buffer, newdelta)) {
- /* This page can be modified, dirty this buffer */
- goto dirty_buffer;
- }
-
- /* The page can't be modified for newdelta */
- }
-
-do_clone_page:
/* Clone the oldpage */
newpage = clone_page(oldpage, sb->blocksize);
if (IS_ERR(newpage)) {
@@ -389,6 +418,77 @@ out:
*/
int bufferfork_to_invalidate(struct address_space *mapping, struct page *page)
{
+ struct sb *sb = tux_sb(mapping->host->i_sb);
+ unsigned delta = tux3_inode_delta(mapping->host);
+
assert(PageLocked(page));
- return 0;
+
+ switch (need_fork(page, NULL, delta)) {
+ case RET_NEED_FORK:
+ /* Need to fork, then delete from radix-tree */
+ break;
+ case RET_CAN_DIRTY:
+ /* We can invalidate the page */
+ return 0;
+ case RET_FORKED:
+ case RET_ALREADY_DIRTY:
+ trace_on("mapping %p, page %p", mapping, page);
+ /* FALLTHRU */
+ default:
+ BUG();
+ break;
+ }
+
+ /*
+ * Similar to truncate_inode_page(), but the oldpage is for writeout.
+ * FIXME: we would have to add mmap handling (e.g. replace PTE)
+ */
+ /* Delete page from radix tree. */
+ spin_lock_irq(&mapping->tree_lock);
+ /*
+ * if we're uptodate, flush out into the cleancache, otherwise
+ * invalidate any existing cleancache entries. We can't leave
+ * stale data around in the cleancache once our page is gone
+ */
+ if (PageUptodate(page) && PageMappedToDisk(page))
+ cleancache_put_page(page);
+ else
+ cleancache_invalidate_page(mapping, page);
+
+ radix_tree_delete(&mapping->page_tree, page->index);
+#if 0 /* FIXME: backend is assuming page->mapping is available */
+ page->mapping = NULL;
+#endif
+ /* Leave page->index set: truncation lookup relies upon it */
+ mapping->nrpages--;
+ __dec_zone_page_state(page, NR_FILE_PAGES);
+
+ /*
+ * Inherit reference count of radix-tree, so referencer are
+ * radix-tree + ->private (plus other users and lru_cache).
+ *
+ * FIXME: We can't remove from LRU, because page can be on
+ * per-cpu lru cache at here. So, vmscan will try to free
+ * oldpage. We get refcount to pin oldpage to prevent vmscan
+ * releases oldpage.
+ */
+ trace("oldpage count %u", page_count(page));
+ assert(page_count(page) >= 2);
+ page_cache_get(page);
+ oldpage_try_remove_from_lru(page);
+ spin_unlock_irq(&mapping->tree_lock);
+#if 0 /* FIXME */
+ mem_cgroup_uncharge_cache_page(page);
+#endif
+
+ /*
+ * This prevents to re-fork the oldpage. And we guarantee the
+ * newpage is available on radix-tree here.
+ */
+ SetPageForked(page);
+
+ /* Register forked buffer to free forked page later */
+ forked_buffer_add(sb, page_buffers(page));
+
+ return 1;
}