diff options
| author | Andrew Morton <akpm@digeo.com> | 2002-10-28 16:22:13 -0800 |
|---|---|---|
| committer | Jens Axboe <axboe@suse.de> | 2002-10-28 16:22:13 -0800 |
| commit | caa2f8074a57dba5f7c8d3b7ad8db412ca54c3bc (patch) | |
| tree | 5b087081d02b4e1e0497e95154c688adbb879ef6 | |
| parent | 303c9cf648d3e5f648afe89d624cc3e3c8d5ce71 (diff) | |
[PATCH] invalidate_inode_pages fixes
Two fixes here.
First:
Fixes a BUG() which occurs if you try to perform O_DIRECT IO against a
blockdev which has an fs mounted on it. (We should be able to do
that).
What happens is that do_invalidatepage() ends up calling
discard_buffer() on buffers which it couldn't strip. That clears
buffer_mapped() against useful things like the superblock buffer_head.
The next submit_bh() goes BUG over the write of an unmapped buffer.
So just run try_to_release_page() (aka try_to_free_buffers()) on the
invalidate path.
Second:
The invalidate_inode_pages() functions are best-effort pagecache
shrinkers. They are used against pages inside i_size and are not
supposed to throw away dirty data.
However it is possible for another CPU to run set_page_dirty() against
one of these pages after invalidate_inode_pages() has decided that it
is clean. This could happen if someone was performing O_DIRECT IO
against a file which was also mapped with MAP_SHARED.
So recheck the dirty state of the page inside the mapping->page_lock
and back out if the page has just been marked dirty.
This will also prevent the remove_from_page_cache() BUG which will occur
if someone marks the page dirty between the clear_page_dirty() and
remove_from_page_cache() calls in truncate_complete_page().
| -rw-r--r-- | mm/truncate.c | 35 |
1 files changed, 30 insertions, 5 deletions
diff --git a/mm/truncate.c b/mm/truncate.c index 5db64d815b04..cdf08bd59f41 100644 --- a/mm/truncate.c +++ b/mm/truncate.c @@ -53,7 +53,34 @@ truncate_complete_page(struct address_space *mapping, struct page *page) clear_page_dirty(page); ClearPageUptodate(page); remove_from_page_cache(page); - page_cache_release(page); + page_cache_release(page); /* pagecache ref */ +} + +/* + * This is for invalidate_inode_pages(). That function can be called at + * any time, and is not supposed to throw away dirty pages. But pages can + * be marked dirty at any time too. So we re-check the dirtiness inside + * ->page_lock. That provides exclusion against the __set_page_dirty + * functions. + */ +static void +invalidate_complete_page(struct address_space *mapping, struct page *page) +{ + if (page->mapping != mapping) + return; + + if (PagePrivate(page) && !try_to_release_page(page, 0)) + return; + + write_lock(&mapping->page_lock); + if (PageDirty(page)) { + write_unlock(&mapping->page_lock); + } else { + __remove_from_page_cache(page); + write_unlock(&mapping->page_lock); + ClearPageUptodate(page); + page_cache_release(page); /* pagecache ref */ + } } /** @@ -172,11 +199,9 @@ void invalidate_inode_pages(struct address_space *mapping) next++; if (PageDirty(page) || PageWriteback(page)) goto unlock; - if (PagePrivate(page) && !try_to_release_page(page, 0)) - goto unlock; if (page_mapped(page)) goto unlock; - truncate_complete_page(mapping, page); + invalidate_complete_page(mapping, page); unlock: unlock_page(page); } @@ -213,7 +238,7 @@ void invalidate_inode_pages2(struct address_space *mapping) if (page_mapped(page)) clear_page_dirty(page); else - truncate_complete_page(mapping, page); + invalidate_complete_page(mapping, page); } unlock_page(page); } |
