mm: migrate: Check page_count of THP before migrating
Subsystems (3)
PATCH 1/1 mm: migrate: Check page_count of THP before migrating
Hugh Dickins pointed out that migrate_misplaced_transhuge_page() does not check page_count before migrating like base page migration and khugepage. He could not see why this was safe and he is right.
The potential impact of the bug is avoided due to the limitations of NUMA balancing. The page_mapcount() check ensures that only a single address space is using this page and as THPs are typically private it should not be possible for another address space to fault it in parallel. If the address space has one associated task then it's difficult to have both a GUP pin and be referencing the page at the same time. If there are multiple tasks then a buggy scenario requires that another thread be accessing the page while the direct IO is in flight. This is dodgy behaviour as there is a possibility of corruption with or without THP migration. It would be difficult to identify the corruption as being a migration bug.
While we happen to be safe for the most part it is shoddy to depend on such "safety" so this patch checks the page count similar to anonymous pages. Note that this does not mean that the page_mapcount() check can go away. If we were to remove the page_mapcount() check the the THP would have to be unmapped from all referencing PTEs, replaced with migration PTEs and restored properly afterwards.
--- mm/migrate.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-)
mm/migrate.c +10 -1
@@ -1679,9 +1679,18 @@ int migrate_misplaced_transhuge_page(struct mm_struct *mm,page_xchg_last_nid (new_page,page_last_nid (page)); isolated =numamigrate_isolate_page (pgdat, page); -if (!isolated) { + +/* +* Failing to isolate or a GUP pin prevents migration. The expected +* page count is 2. 1 for anonymous pages without a mapping and 1 +* for the callers pin. If the page was isolated, the page will +* need to be put back on the LRU. +*/ +if (!isolated ||page_count (page) !=2 ) {count_vm_events (PGMIGRATE_FAIL ,HPAGE_PMD_NR );put_page (new_page); +if (isolated) +putback_lru_page (page); goto out_keep_locked; }
--
To unsubscribe, send a message with 'unsubscribe linux-mm' in the body to [email protected]. For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"[email protected]"> [email protected] </a>
Quoted 22 lines · Mel Gorman
Hugh Dickins pointed out that
migrate_misplaced_transhuge_page()does not checkpage_countbefore migrating like base page migration and khugepage. He could not see why this was safe and he is right.The potential impact of the bug is avoided due to the limitations of NUMA balancing. The
page_mapcount()check ensures that only a single address space is using this page and as THPs are typically private it should not be possible for another address space to fault it in parallel. If the address space has one associated task then it's difficult to have both a GUP pin and be referencing the page at the same time. If there are multiple tasks then a buggy scenario requires that another thread be accessing the page while the direct IO is in flight. This is dodgy behaviour as there is a possibility of corruption with or without THP migration. It would be difficult to identify the corruption as being a migration bug.While we happen to be safe for the most part it is shoddy to depend on such "safety" so this patch checks the page count similar to anonymous pages. Note that this does not mean that the
page_mapcount()check can go away. If we were to remove thepage_mapcount()check the the THP would have to be unmapped from all referencing PTEs, replaced with migration PTEs and restored properly afterwards.
Quoted 2 lines
Reported-by Hugh Dickins <[email protected]>Signed-off-by Mel Gorman <[email protected]>
Sorry, Mel, it's a NAK: you will have expected an ack from me two weeks or more ago; but somehow I had an intuition that if I sat on it for long enough, a worm would crawl out. Got down to looking again today, and I notice that although the putback_lru_page() is right, NR_ISOLATED_ANON is not restored on this path, so that would leak.
I expect you'll want to do something like:
if (isolated) {
putback_lru_page(page);
isolated = 0;
goto out;
}and that may be the appropriate fix right now.
But I do still dislike the way you always put_page in numamigrate_isolate_page(): it makes sense in the case when isolate_lru_page() succeeds (I've long thought that weird both to insist on an existing page reference and add one of its own), but I find it very confusing on the failure paths, to have the put_page far away from the unlock_page - and I get worried when I see put_page followed by unlock_page rather than vice versa (it happens on !pmd_same
paths: if the pmd is not the same, then can we be sure that the put_page does not free the page?)
At the bottom I've put my own cleanup for this, which simplifies by doing the putback_lru_page() inside numamigrate_isolate_page(), and doesn't put_page when it doesn't isolate.
I think the only functional difference from yours (aside from fixing up NR_ISOLATED) is that migrate_misplaced_transhuge_page() doesn't have to pretend to its caller that it succeeded when actually it failed at the last hurdle (because it already did the unlock_page, which in yours the caller expects to do on failure). Oh, and I'm not holding page lock (sometimes) at clear_pmdnuma: I didn't see the reason for that, perhaps I'm missing something important there.
Maybe our tastes differ, and you won't see mine as an improvement. And I've hardly tested, so haven't signed off, and won't be surprised if its own worms crawl out.
Hugh
Quoted 17 lines
--- mm/migrate.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-)mm/migrate.c +4
-1@@ -1679,9 +1679,18 @@ int migrate_misplaced_transhuge_page(struct mm_struct *mm,page_xchg_last_nid (new_page,page_last_nid (page)); isolated =numamigrate_isolate_page (pgdat, page); -if (!isolated) { + +/* +* Failing to isolate or a GUP pin prevents migration. The expected +* page count is 2. 1 for anonymous pages without a mapping and 1
mm/migrate.c int migrate_misplaced_transhuge_page(struct mm_struct *mm, · line 1695
Quoted 10 lines
mm/migrate.c +6
-0+* for the callers pin. If the page was isolated, the page will +* need to be put back on the LRU. +*/ +if (!isolated ||page_count (page) !=2 ) {count_vm_events (PGMIGRATE_FAIL ,HPAGE_PMD_NR );put_page (new_page); +if (isolated) +putback_lru_page (page); goto out_keep_locked; }
---
mm/huge_memory.c | 28 +++++---------- mm/migrate.c | 79 ++++++++++++++++++++++----------------------- 2 files changed, 48 insertions(+), 59 deletions(-)
mm/huge_memory.c +9 -19
@@ -1298,7 +1298,6 @@ int do_huge_pmd_numa_page(struct mm_struint target_nid;int current_nid =-1 ;bool migrated; -bool page_locked = false;spin_lock (&mm->page_table_lock);if (unlikely (!pmd_same (pmd, *pmdp)))
@@ -1320,7 +1319,6 @@ int do_huge_pmd_numa_page(struct mm_stru/* Acquire the page lock to serialise THP migrations */ spin_unlock (&mm->page_table_lock);lock_page (page); - page_locked = true;/* Confirm the PTE did not while locked */ spin_lock (&mm->page_table_lock);
@@ -1333,34 +1331,26 @@ int do_huge_pmd_numa_page(struct mm_stru/* Migrate the THP to the requested node */ migrated =migrate_misplaced_transhuge_page (mm, vma, - pmdp, pmd, addr, - page, target_nid); -if (migrated) - current_nid = target_nid; -else { -spin_lock (&mm->page_table_lock); -if (unlikely (!pmd_same (pmd, *pmdp))) { -unlock_page (page); - goto out_unlock; - } - goto clear_pmdnuma; - } + pmdp, pmd, addr, page, target_nid); +if (!migrated) + goto check_same; -task_numa_fault (current_nid,HPAGE_PMD_NR , migrated); +task_numa_fault (target_nid,HPAGE_PMD_NR , true);return 0 ; +check_same: +spin_lock (&mm->page_table_lock); +if (unlikely (!pmd_same (pmd, *pmdp))) + goto out_unlock; clear_pmdnuma: pmd =pmd_mknonnuma (pmd);set_pmd_at (mm, haddr, pmdp, pmd);VM_BUG_ON (pmd_numa (*pmdp));update_mmu_cache_pmd (vma, addr, pmdp); -if (page_locked) -unlock_page (page); - out_unlock:spin_unlock (&mm->page_table_lock);if (current_nid !=-1 ) -task_numa_fault (current_nid,HPAGE_PMD_NR , migrated); +task_numa_fault (current_nid,HPAGE_PMD_NR , false);return 0 ; }
mm/migrate.c +39 -40
145 lines not on this page · raw message
--
To unsubscribe, send a message with 'unsubscribe linux-mm' in the body to [email protected]. For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"[email protected]"> [email protected] </a>
PATCH 1/1 mm: migrate: Check page_count of THP before migrating accounting fix
As pointed out by Hugh Dickins, "mm: migrate: Check page_count of THP before migrating" can leave nr_isolated_anon elevated, correct it. This is a fix to mm-migrate-check-page_count-of-thp-before-migrating.patch
--- mm/migrate.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-)
mm/migrate.c +4 -1
@@ -1689,8 +1689,11 @@ int migrate_misplaced_transhuge_page(struct mm_struct *mm,if (!isolated ||page_count (page) !=2 ) {count_vm_events (PGMIGRATE_FAIL ,HPAGE_PMD_NR );put_page (new_page); -if (isolated) +if (isolated) {putback_lru_page (page); + isolated =0 ; + goto out; + } goto out_keep_locked; }
--
To unsubscribe, send a message with 'unsubscribe linux-mm' in the body to [email protected]. For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"[email protected]"> [email protected] </a>
Re: [PATCH] mm: migrate: Check page_count of THP before migrating accounting fix
Quoted 5 lines · Mel Gorman
As pointed out by Hugh Dickins, "mm: migrate: Check
page_countof THP before migrating" can leavenr_isolated_anonelevated, correct it. This is a fix to mm-migrate-check-page_count-of-thp-before-migrating.patchSigned-off-by Mel Gorman <[email protected]>
Thanks: to this and the one it's fixing (I expect akpm will merge)
Quoted 10 lines
--- mm/migrate.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-)mm/migrate.c +0
-0@@ -1689,8 +1689,11 @@ int migrate_misplaced_transhuge_page(struct mm_struct *mm,if (!isolated ||page_count (page) !=2 ) {
mm/migrate.c int migrate_misplaced_transhuge_page(struct mm_struct *mm, · line 1698
Quoted 10 lines
mm/migrate.c +4
-1count_vm_events (PGMIGRATE_FAIL ,HPAGE_PMD_NR );put_page (new_page); -if (isolated) +if (isolated) {putback_lru_page (page); + isolated =0 ; + goto out; + } goto out_keep_locked; }
--
To unsubscribe, send a message with 'unsubscribe linux-mm' in the body to [email protected]. For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"[email protected]"> [email protected] </a>
Quoted 24 lines · Hugh Dickins
Quoted 22 lines · Mel Gorman
Hugh Dickins pointed out that
migrate_misplaced_transhuge_page()does not checkpage_countbefore migrating like base page migration and khugepage. He could not see why this was safe and he is right.The potential impact of the bug is avoided due to the limitations of NUMA balancing. The
page_mapcount()check ensures that only a single address space is using this page and as THPs are typically private it should not be possible for another address space to fault it in parallel. If the address space has one associated task then it's difficult to have both a GUP pin and be referencing the page at the same time. If there are multiple tasks then a buggy scenario requires that another thread be accessing the page while the direct IO is in flight. This is dodgy behaviour as there is a possibility of corruption with or without THP migration. It would be difficult to identify the corruption as being a migration bug.While we happen to be safe for the most part it is shoddy to depend on such "safety" so this patch checks the page count similar to anonymous pages. Note that this does not mean that the
page_mapcount()check can go away. If we were to remove thepage_mapcount()check the the THP would have to be unmapped from all referencing PTEs, replaced with migration PTEs and restored properly afterwards.
Quoted 4 lines
Quoted 2 lines
Reported-by Hugh Dickins <[email protected]>Signed-off-by Mel Gorman <[email protected]>Sorry, Mel, it's a NAK:
Don't be sorry. It's not your fault I am a muppet.
Quoted 6 lines
you will have expected an ack from me two weeks
or more ago; but somehow I had an intuition that if I sat on it for long enough, a worm would crawl out. Got down to looking again today, and I notice that although theputback_lru_page()is right,NR_ISOLATED_ANONis not restored on this path, so that would leak.
Quoted 8 lines
I expect you'll want to do something like:
if (isolated) { putback_lru_page(page); isolated = 0; goto out; }and that may be the appropriate fix right now.
Yes, I sent what should be a correction so we can treat this cleanup as a potential replacemet for it.
Quoted 2 lines
But I do still dislike the way you always
put_pageinnumamigrate_isolate_page():
At one point there was a conditional put_page depending on different failures and it was a very difficult to follow. This was easier to follow but could still be improved.
Quoted 9 lines
it makes sense in the case when
isolate_lru_page()succeeds (I've long thought that weird both to insist on an existing page reference and add one of its own), but I find it very confusing on the failure paths, to have theput_pagefar away from theunlock_page- and I get worried when I seeput_pagefollowed byunlock_pagerather than vice versa (it happens on !pmd_same
paths: if the pmd is not the same, then can we be sure that theput_pagedoes not free the page?)
I'm depending on the page table reference to prevent the put_page() freeing the page. As mmap_sem is held, there cannot be an munmap() freeing it. As we hold the page lock there cannot be a parallel reclaim as trylock_page() in shrink_page_list() will fail.
Quoted 57 lines
At the bottom I've put my own cleanup for this, which simplifies by doing the
putback_lru_page()insidenumamigrate_isolate_page(), and doesn'tput_pagewhen it doesn't isolate.I think the only functional difference from yours (aside from fixing up
NR_ISOLATED) is thatmigrate_misplaced_transhuge_page()doesn't have to pretend to its caller that it succeeded when actually it failed at the last hurdle (because it already did theunlock_page, which in yours the caller expects to do on failure). Oh, and I'm not holding page lock (sometimes) atclear_pmdnuma: I didn't see the reason for that, perhaps I'm missing something important there.Maybe our tastes differ, and you won't see mine as an improvement. And I've hardly tested, so haven't signed off, and won't be surprised if its own worms crawl out.
<SNIP>
Not-signed-off-by Hugh Dickins <[email protected]>---
mm/huge_memory.c | 28 +++++---------- mm/migrate.c | 79 ++++++++++++++++++++++----------------------- 2 files changed, 48 insertions(+), 59 deletions(-)mm/huge_memory.c +0
-12@@ -1298,7 +1298,6 @@ int do_huge_pmd_numa_page(struct mm_struint target_nid;int current_nid =-1 ;bool migrated; -bool page_locked = false;spin_lock (&mm->page_table_lock);if (unlikely (!pmd_same (pmd, *pmdp)))@@ -1320,7 +1319,6 @@ int do_huge_pmd_numa_page(struct mm_stru/* Acquire the page lock to serialise THP migrations */ spin_unlock (&mm->page_table_lock);lock_page (page); - page_locked = true;/* Confirm the PTE did not while locked */ spin_lock (&mm->page_table_lock);@@ -1333,34 +1331,26 @@ int do_huge_pmd_numa_page(struct mm_stru/* Migrate the THP to the requested node */ migrated = migrate_misplaced_transhuge_page(mm, vma, - pmdp, pmd, addr, - page, target_nid); -if (migrated) - current_nid = target_nid; -else { -spin_lock (&mm->page_table_lock); -if (unlikely (!pmd_same (pmd, *pmdp))) { -unlock_page (page); - goto out_unlock; - }
mm/huge_memory.c int do_huge_pmd_numa_page(struct mm_stru · line 1340
Quoted 10 lines
mm/huge_memory.c +4
-3- goto clear_pmdnuma; - } + pmdp, pmd, addr, page, target_nid); +if (!migrated) + goto check_same; -task_numa_fault (current_nid,HPAGE_PMD_NR , migrated); +task_numa_fault (target_nid,HPAGE_PMD_NR , true);return 0 ;
Ok.
mm/huge_memory.c int do_huge_pmd_numa_page(struct mm_stru · line 1344
Quoted 4 lines
mm/huge_memory.c +4
-0+check_same: +spin_lock (&mm->page_table_lock); +if (unlikely (!pmd_same (pmd, *pmdp))) + goto out_unlock;
Ok.
mm/huge_memory.c int do_huge_pmd_numa_page(struct mm_stru · line 1359
Quoted 8 lines
mm/huge_memory.c +0
-3clear_pmdnuma: pmd =pmd_mknonnuma (pmd);set_pmd_at (mm, haddr, pmdp, pmd);VM_BUG_ON (pmd_numa (*pmdp));update_mmu_cache_pmd (vma, addr, pmdp); -if (page_locked) -unlock_page (page); -
This is ok too. The lock page is to prevent the page being reclaimed in parallel during migration. In the two cases you end up in clear_pmdnuma the page table lock is taken and you've done a pmd_same check and the page lock is not necessary.
Quoted 42 lines
mm/huge_memory.c +1
-1out_unlock:spin_unlock (&mm->page_table_lock);if (current_nid !=-1 ) -task_numa_fault (current_nid,HPAGE_PMD_NR , migrated); +task_numa_fault (current_nid,HPAGE_PMD_NR , false);return 0 ; }mm/migrate.c +8
-17@@ -1555,39 +1555,38 @@ bool numamigrate_update_ratelimit(pg_datint numamigrate_isolate_page (pg_data_t *pgdat,struct page *page) { -int ret =0 ; +int page_lru;/* Avoid migrating to a node that is nearly full */ -if (migrate_balanced_pgdat (pgdat,1 )) { -int page_lru; +if (!migrate_balanced_pgdat (pgdat,1 )) +return 0 ; -if (isolate_lru_page (page)) { -put_page (page); -return 0 ; - } - -/* Page is isolated */ - ret =1 ; - page_lru =page_is_file_cache (page); -if (!PageTransHuge (page)) -inc_zone_page_state (page,NR_ISOLATED_ANON + page_lru); -else -mod_zone_page_state (page_zone (page), -NR_ISOLATED_ANON + page_lru, -HPAGE_PMD_NR ); +if (isolate_lru_page (page)) +return 0 ; + +/* +* migrate_misplaced_transhuge_page() skips page migration's usual
mm/migrate.c bool numamigrate_update_ratelimit(pg_dat · line 1578
Quoted 10 lines
mm/migrate.c +8
-0+* check on page_count(), so we must do it here, now that the page +* has been isolated: a GUP pin, or any other pin, prevents migration. +* The expected page count is 3: 1 for page's mapcount and 1 for the +* caller's pin and 1 for the reference taken by isolate_lru_page(). +*/ +if (PageTransHuge (page) &&page_count (page) !=3 ) { +putback_lru_page (page); +return 0 ; }
Ok so you putback the page before the isolated count is updated. Makes sense.
Quoted 55 lines
mm/migrate.c +11
-15+ page_lru =page_is_file_cache (page); +mod_zone_page_state (page_zone (page),NR_ISOLATED_ANON + page_lru, +hpage_nr_pages (page)); +/* -* Page is either isolated or there is not enough space on the target -* node. If isolated, then it has taken a reference count and the -* callers reference can be safely dropped without the page -* disappearing underneath us during migration. Otherwise the page is -* not to be migrated but the callers reference should still be -* dropped so it does not leak. +* Isolating the page has taken another reference, so the +* caller's reference can be safely dropped without the page +* disappearing underneath us during migration. */ put_page (page); - -return ret; +return 1 ; }/* @@ -1598,7 +1597,7 @@ int numamigrate_isolate_page(pg_data_t *int migrate_misplaced_page (struct page *page,int node) {pg_data_t *pgdat =NODE_DATA (node); -int isolated =0 ; +int isolated;int nr_remaining;LIST_HEAD (migratepages);@@ -1606,20 +1605,16 @@ int migrate_misplaced_page(struct page ** Don't migrate pages that are mapped in multiple processes. * TODO: Handle false sharing detection instead of this hammer */ -if (page_mapcount (page) !=1 ) { -put_page (page); +if (page_mapcount (page) !=1 ) goto out; - }/* * Rate-limit the amount of data that is being migrated to a node. * Optimal placement is no good if the memory bus is saturated and * all the time is being spent migrating! */ -if (numamigrate_update_ratelimit (pgdat,1 )) { -put_page (page); +if (numamigrate_update_ratelimit (pgdat,1 )) goto out; - } isolated =numamigrate_isolate_page (pgdat, page);if (!isolated)
mm/migrate.c int migrate_misplaced_page(struct page * · line 1639
Quoted 10 lines
mm/migrate.c +4
-1@@ -1636,8 +1631,11 @@ int migrate_misplaced_page(struct page * }else count_vm_numa_event (NUMA_PAGE_MIGRATE );BUG_ON (!list_empty (&migratepages)); -out:return isolated; + +out: +put_page (page); +return 0 ; }
Ok.
Quoted 35 lines
mm/migrate.c +5
-6#endif /* CONFIG_NUMA_BALANCING */ @@ -1672,17 +1670,15 @@ int migrate_misplaced_transhuge_page(str new_page =alloc_pages_node (node, (GFP_TRANSHUGE |GFP_THISNODE ) & ~__GFP_WAIT,HPAGE_PMD_ORDER ); -if (!new_page) { -count_vm_events (PGMIGRATE_FAIL ,HPAGE_PMD_NR ); - goto out_dropref; - } +if (!new_page) + goto out_fail; +page_xchg_last_nid (new_page,page_last_nid (page)); isolated =numamigrate_isolate_page (pgdat, page);if (!isolated) { -count_vm_events (PGMIGRATE_FAIL ,HPAGE_PMD_NR );put_page (new_page); - goto out_keep_locked; + goto out_fail; }/* Prepare a page as a migration target */ @@ -1714,6 +1710,7 @@ int migrate_misplaced_transhuge_page(strputback_lru_page (page);count_vm_events (PGMIGRATE_FAIL ,HPAGE_PMD_NR ); + isolated =0 ; goto out; }@@ -1758,9 +1755,11 @@ out: -HPAGE_PMD_NR);return isolated;
mm/migrate.c out: · line 1765
Quoted 10 lines
mm/migrate.c +3
-1+out_fail: +count_vm_events (PGMIGRATE_FAIL ,HPAGE_PMD_NR ); out_dropref: +unlock_page (page);put_page (page); -out_keep_locked: return 0; }#endif /* CONFIG_NUMA_BALANCING */
I haven't tested this but I cannot see a problem with it either and the flow does look nicer. I'll test it this evening and look at it some more.
Signature · 8 lines
Mel Gorman SUSE Labs -- To unsubscribe, send a message with 'unsubscribe linux-mm' in the body to [email protected]. For more info on Linux MM, see: http://www.linux-mm.org/ . Don't email: <a href=mailto:"[email protected]"> [email protected] </a>