Patch "mm: migrate dirty page without clear_page_dirty_for_io etc" has been added to the 3.14-stable tree
Subsystems (3)
This is a note to let you know that I've just added the patch titled
mm: migrate dirty page without clear_page_dirty_for_io etc
to the 3.14-stable tree which can be found at:
http://www.kernel.org/git/?p=linux/kernel/git/stable/stable-queue.git;a=summary
The filename of the patch is:
mm-migrate-dirty-page-without-clear_page_dirty_for_io-etc.patch
and it can be found in the queue-3.14 subdirectory.
If you, or anyone else, feels it should not be added to the stable tree, please let <[email protected]> know about it.
Quoted 1 line
From
42cb14b110a5698ccf26ce59c4441722605a3743Mon Sep 17 00:00:00 2001
From: Hugh Dickins <[email protected]>
Date: Thu, 5 Nov 2015 18:50:05 -0800
Subject: mm: migrate dirty page without clear_page_dirty_for_io etc
From: Hugh Dickins <[email protected]>
commit 42cb14b110a5698ccf26ce59c4441722605a3743 upstream.
clear_page_dirty_for_io() has accumulated writeback and memcg subtleties since v2.6.16 first introduced page migration; and the set_page_dirty() which completed its migration of PageDirty, later had to be moderated to __set_page_dirty_nobuffers(); then PageSwapBacked had to skip that too.
No actual problems seen with this procedure recently, but if you look into what the clear_page_dirty_for_io(page)+set_page_dirty(newpage) is actually achieving, it turns out to be nothing more than moving the PageDirty flag, and its NR_FILE_DIRTY stat from one zone to another.
It would be good to avoid a pile of irrelevant decrementations and incrementations, and improper event counting, and unnecessary descent of the radix_tree under tree_lock (to set the PAGECACHE_TAG_DIRTY which radix_tree_replace_slot() left in place anyway).
Do the NR_FILE_DIRTY movement, like the other stats movements, while interrupts still disabled in migrate_page_move_mapping(); and don't even bother if the zone is the same. Do the PageDirty movement there under tree_lock too, where old page is frozen and newpage not yet visible:
bearing in mind that as soon as newpage becomes visible in radix_tree, an un-page-locked set_page_dirty() might interfere (or perhaps that's just not possible: anything doing so should already hold an additional reference to the old page, preventing its migration; but play safe).
But we do still need to transfer PageDirty in migrate_page_copy(), for those who don't go the mapping route through migrate_page_move_mapping().
--- mm/migrate.c | 51 +++++++++++++++++++++++++++++++-------------------- 1 file changed, 31 insertions(+), 20 deletions(-)
mm/migrate.c +31 -20
@@ -30,6 +30,7 @@#include <linux/mempolicy.h> #include <linux/vmalloc.h> #include <linux/security.h> +#include <linux/backing-dev.h> #include <linux/memcontrol.h> #include <linux/syscalls.h> #include <linux/hugetlb.h>
@@ -344,6 +345,8 @@ int migrate_page_move_mapping(struct add struct buffer_head *head, enum migrate_mode mode, int extra_count) { + struct zone *oldzone, *newzone; +int dirty;int expected_count =1 + extra_count;void **pslot;
@@ -354,6 +357,9 @@ int migrate_page_move_mapping(struct addreturn MIGRATEPAGE_SUCCESS ; } + oldzone =page_zone (page); + newzone =page_zone (newpage); +spin_lock_irq (&mapping->tree_lock); pslot = radix_tree_lookup_slot(&mapping->page_tree,
@@ -394,6 +400,13 @@ int migrate_page_move_mapping(struct addset_page_private (newpage,page_private (page)); } +/* Move dirty while page refs frozen and newpage not yet exposed */ + dirty =PageDirty (page); +if (dirty) { +ClearPageDirty (page); +SetPageDirty (newpage); + } +radix_tree_replace_slot (pslot, newpage);/*
@@ -403,6 +416,9 @@ int migrate_page_move_mapping(struct add*/ page_unfreeze_refs (page, expected_count -1 ); +spin_unlock (&mapping->tree_lock); +/* Leave irq disabled to prevent preemption while updating stats */ +/* * If moved to a different zone then also account * the page for that zone. Other VM counters will be
@@ -413,13 +429,19 @@ int migrate_page_move_mapping(struct add* via NR_FILE_PAGES and NR_ANON_PAGES if they * are mapped to swap space. */ -__dec_zone_page_state (page,NR_FILE_PAGES ); -__inc_zone_page_state (newpage,NR_FILE_PAGES ); -if (!PageSwapCache (page) &&PageSwapBacked (page)) { -__dec_zone_page_state (page,NR_SHMEM ); -__inc_zone_page_state (newpage,NR_SHMEM ); +if (newzone != oldzone) { +__dec_zone_state (oldzone,NR_FILE_PAGES ); +__inc_zone_state (newzone,NR_FILE_PAGES ); +if (PageSwapBacked (page) && !PageSwapCache (page)) { +__dec_zone_state (oldzone,NR_SHMEM ); +__inc_zone_state (newzone,NR_SHMEM ); + } +if (dirty &&mapping_cap_account_dirty (mapping)) { +__dec_zone_state (oldzone,NR_FILE_DIRTY ); +__inc_zone_state (newzone,NR_FILE_DIRTY ); + } } -spin_unlock_irq (&mapping->tree_lock); +local_irq_enable ();return MIGRATEPAGE_SUCCESS ; }
@@ -544,20 +566,9 @@ void migrate_page_copy(struct page *newpif (PageMappedToDisk (page))SetPageMappedToDisk (newpage); -if (PageDirty (page)) { -clear_page_dirty_for_io (page); -/* -* Want to mark the page and the radix tree as dirty, and -* redo the accounting that clear_page_dirty_for_io undid, -* but we can't use set_page_dirty because that function -* is actually a signal that all of the page has become dirty. -* Whereas only part of our page may be dirty. -*/ -if (PageSwapBacked (page)) -SetPageDirty (newpage); -else -__set_page_dirty_nobuffers (newpage); - } +/* Move dirty on pages not done by migrate_page_move_mapping() */ +if (PageDirty (page)) +SetPageDirty (newpage);/* * Copy NUMA information to the new page, to prevent over-eager
Patches currently in stable-queue which might be from [email protected] are
queue-3.14/mm-migrate-dirty-page-without-clear_page_dirty_for_io-etc.patch