summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--fs/buffer.c16
1 files changed, 15 insertions, 1 deletions
diff --git a/fs/buffer.c b/fs/buffer.c
index 6f1ae3ac9789..2f8fcf7dd5c1 100644
--- a/fs/buffer.c
+++ b/fs/buffer.c
@@ -3250,7 +3250,7 @@ int try_to_free_buffers(struct page *page)
{
struct address_space * const mapping = page->mapping;
struct buffer_head *buffers_to_free = NULL;
- int ret = 0;
+ int ret = 0, extra;
BUG_ON(!PageLocked(page));
if (PageWriteback(page))
@@ -3261,6 +3261,20 @@ int try_to_free_buffers(struct page *page)
goto out;
}
+ /* Page has a mapping and buffer so 2 extra references. */
+ extra = 2;
+ /* If page is isolated from lru then it has an extra refcount. */
+ extra += PageLRU(page) ? 0 : 1;
+ /*
+ * We do not want to free buffers attach to a page that is pinned by a
+ * GUP (get_user_pages()) as the driver/code path that did GUP might do
+ * a set_page_dirty() on the page when releasing the page. To test for
+ * GUP we check the refcount, if it is bigger than mapcount it means
+ * that the page is pinned by some GUP and we should keep the buffers.
+ */
+ if ((page_count(page) - extra) > page_mapcount(page))
+ return 0;
+
spin_lock(&mapping->private_lock);
ret = drop_buffers(page, &buffers_to_free);