Commit ba3c4ce6 authored by Huang Ying's avatar Huang Ying Committed by Linus Torvalds

mm, THP, swap: make reuse_swap_page() works for THP swapped out

After supporting to delay THP (Transparent Huge Page) splitting after
swapped out, it is possible that some page table mappings of the THP are
turned into swap entries.  So reuse_swap_page() need to check the swap
count in addition to the map count as before.  This patch done that.

In the huge PMD write protect fault handler, in addition to the page map
count, the swap count need to be checked too, so the page lock need to
be acquired too when calling reuse_swap_page() in addition to the page
table lock.

[ying.huang@intel.com: silence a compiler warning]
  Link: http://lkml.kernel.org/r/87bmnzizjy.fsf@yhuang-dev.intel.com
Link: http://lkml.kernel.org/r/20170724051840.2309-4-ying.huang@intel.comSigned-off-by: default avatar"Huang, Ying" <ying.huang@intel.com>
Cc: Johannes Weiner <hannes@cmpxchg.org>
Cc: Minchan Kim <minchan@kernel.org>
Cc: Hugh Dickins <hughd@google.com>
Cc: Shaohua Li <shli@kernel.org>
Cc: Rik van Riel <riel@redhat.com>
Cc: Andrea Arcangeli <aarcange@redhat.com>
Cc: "Kirill A . Shutemov" <kirill.shutemov@linux.intel.com>
Cc: Dan Williams <dan.j.williams@intel.com>
Cc: Jens Axboe <axboe@kernel.dk>
Cc: Michal Hocko <mhocko@kernel.org>
Cc: Ross Zwisler <ross.zwisler@intel.com> [for brd.c, zram_drv.c, pmem.c]
Cc: Vishal L Verma <vishal.l.verma@intel.com>
Signed-off-by: default avatarAndrew Morton <akpm@linux-foundation.org>
Signed-off-by: default avatarLinus Torvalds <torvalds@linux-foundation.org>
parent e0709829
...@@ -510,8 +510,8 @@ static inline int swp_swapcount(swp_entry_t entry) ...@@ -510,8 +510,8 @@ static inline int swp_swapcount(swp_entry_t entry)
return 0; return 0;
} }
#define reuse_swap_page(page, total_mapcount) \ #define reuse_swap_page(page, total_map_swapcount) \
(page_trans_huge_mapcount(page, total_mapcount) == 1) (page_trans_huge_mapcount(page, total_map_swapcount) == 1)
static inline int try_to_free_swap(struct page *page) static inline int try_to_free_swap(struct page *page)
{ {
......
...@@ -1240,15 +1240,29 @@ int do_huge_pmd_wp_page(struct vm_fault *vmf, pmd_t orig_pmd) ...@@ -1240,15 +1240,29 @@ int do_huge_pmd_wp_page(struct vm_fault *vmf, pmd_t orig_pmd)
* We can only reuse the page if nobody else maps the huge page or it's * We can only reuse the page if nobody else maps the huge page or it's
* part. * part.
*/ */
if (page_trans_huge_mapcount(page, NULL) == 1) { if (!trylock_page(page)) {
get_page(page);
spin_unlock(vmf->ptl);
lock_page(page);
spin_lock(vmf->ptl);
if (unlikely(!pmd_same(*vmf->pmd, orig_pmd))) {
unlock_page(page);
put_page(page);
goto out_unlock;
}
put_page(page);
}
if (reuse_swap_page(page, NULL)) {
pmd_t entry; pmd_t entry;
entry = pmd_mkyoung(orig_pmd); entry = pmd_mkyoung(orig_pmd);
entry = maybe_pmd_mkwrite(pmd_mkdirty(entry), vma); entry = maybe_pmd_mkwrite(pmd_mkdirty(entry), vma);
if (pmdp_set_access_flags(vma, haddr, vmf->pmd, entry, 1)) if (pmdp_set_access_flags(vma, haddr, vmf->pmd, entry, 1))
update_mmu_cache_pmd(vma, vmf->address, vmf->pmd); update_mmu_cache_pmd(vma, vmf->address, vmf->pmd);
ret |= VM_FAULT_WRITE; ret |= VM_FAULT_WRITE;
unlock_page(page);
goto out_unlock; goto out_unlock;
} }
unlock_page(page);
get_page(page); get_page(page);
spin_unlock(vmf->ptl); spin_unlock(vmf->ptl);
alloc: alloc:
......
...@@ -2619,7 +2619,7 @@ static int do_wp_page(struct vm_fault *vmf) ...@@ -2619,7 +2619,7 @@ static int do_wp_page(struct vm_fault *vmf)
* not dirty accountable. * not dirty accountable.
*/ */
if (PageAnon(vmf->page) && !PageKsm(vmf->page)) { if (PageAnon(vmf->page) && !PageKsm(vmf->page)) {
int total_mapcount; int total_map_swapcount;
if (!trylock_page(vmf->page)) { if (!trylock_page(vmf->page)) {
get_page(vmf->page); get_page(vmf->page);
pte_unmap_unlock(vmf->pte, vmf->ptl); pte_unmap_unlock(vmf->pte, vmf->ptl);
...@@ -2634,8 +2634,8 @@ static int do_wp_page(struct vm_fault *vmf) ...@@ -2634,8 +2634,8 @@ static int do_wp_page(struct vm_fault *vmf)
} }
put_page(vmf->page); put_page(vmf->page);
} }
if (reuse_swap_page(vmf->page, &total_mapcount)) { if (reuse_swap_page(vmf->page, &total_map_swapcount)) {
if (total_mapcount == 1) { if (total_map_swapcount == 1) {
/* /*
* The page is all ours. Move it to * The page is all ours. Move it to
* our anon_vma so the rmap code will * our anon_vma so the rmap code will
......
...@@ -1405,9 +1405,89 @@ static bool page_swapped(struct page *page) ...@@ -1405,9 +1405,89 @@ static bool page_swapped(struct page *page)
return swap_page_trans_huge_swapped(si, entry); return swap_page_trans_huge_swapped(si, entry);
return false; return false;
} }
static int page_trans_huge_map_swapcount(struct page *page, int *total_mapcount,
int *total_swapcount)
{
int i, map_swapcount, _total_mapcount, _total_swapcount;
unsigned long offset = 0;
struct swap_info_struct *si;
struct swap_cluster_info *ci = NULL;
unsigned char *map = NULL;
int mapcount, swapcount = 0;
/* hugetlbfs shouldn't call it */
VM_BUG_ON_PAGE(PageHuge(page), page);
if (likely(!PageTransCompound(page))) {
mapcount = atomic_read(&page->_mapcount) + 1;
if (total_mapcount)
*total_mapcount = mapcount;
if (PageSwapCache(page))
swapcount = page_swapcount(page);
if (total_swapcount)
*total_swapcount = swapcount;
return mapcount + swapcount;
}
page = compound_head(page);
_total_mapcount = _total_swapcount = map_swapcount = 0;
if (PageSwapCache(page)) {
swp_entry_t entry;
entry.val = page_private(page);
si = _swap_info_get(entry);
if (si) {
map = si->swap_map;
offset = swp_offset(entry);
}
}
if (map)
ci = lock_cluster(si, offset);
for (i = 0; i < HPAGE_PMD_NR; i++) {
mapcount = atomic_read(&page[i]._mapcount) + 1;
_total_mapcount += mapcount;
if (map) {
swapcount = swap_count(map[offset + i]);
_total_swapcount += swapcount;
}
map_swapcount = max(map_swapcount, mapcount + swapcount);
}
unlock_cluster(ci);
if (PageDoubleMap(page)) {
map_swapcount -= 1;
_total_mapcount -= HPAGE_PMD_NR;
}
mapcount = compound_mapcount(page);
map_swapcount += mapcount;
_total_mapcount += mapcount;
if (total_mapcount)
*total_mapcount = _total_mapcount;
if (total_swapcount)
*total_swapcount = _total_swapcount;
return map_swapcount;
}
#else #else
#define swap_page_trans_huge_swapped(si, entry) swap_swapcount(si, entry) #define swap_page_trans_huge_swapped(si, entry) swap_swapcount(si, entry)
#define page_swapped(page) (page_swapcount(page) != 0) #define page_swapped(page) (page_swapcount(page) != 0)
static int page_trans_huge_map_swapcount(struct page *page, int *total_mapcount,
int *total_swapcount)
{
int mapcount, swapcount = 0;
/* hugetlbfs shouldn't call it */
VM_BUG_ON_PAGE(PageHuge(page), page);
mapcount = page_trans_huge_mapcount(page, total_mapcount);
if (PageSwapCache(page))
swapcount = page_swapcount(page);
if (total_swapcount)
*total_swapcount = swapcount;
return mapcount + swapcount;
}
#endif #endif
/* /*
...@@ -1416,23 +1496,27 @@ static bool page_swapped(struct page *page) ...@@ -1416,23 +1496,27 @@ static bool page_swapped(struct page *page)
* on disk will never be read, and seeking back there to write new content * on disk will never be read, and seeking back there to write new content
* later would only waste time away from clustering. * later would only waste time away from clustering.
* *
* NOTE: total_mapcount should not be relied upon by the caller if * NOTE: total_map_swapcount should not be relied upon by the caller if
* reuse_swap_page() returns false, but it may be always overwritten * reuse_swap_page() returns false, but it may be always overwritten
* (see the other implementation for CONFIG_SWAP=n). * (see the other implementation for CONFIG_SWAP=n).
*/ */
bool reuse_swap_page(struct page *page, int *total_mapcount) bool reuse_swap_page(struct page *page, int *total_map_swapcount)
{ {
int count; int count, total_mapcount, total_swapcount;
VM_BUG_ON_PAGE(!PageLocked(page), page); VM_BUG_ON_PAGE(!PageLocked(page), page);
if (unlikely(PageKsm(page))) if (unlikely(PageKsm(page)))
return false; return false;
count = page_trans_huge_mapcount(page, total_mapcount); count = page_trans_huge_map_swapcount(page, &total_mapcount,
if (count <= 1 && PageSwapCache(page)) { &total_swapcount);
count += page_swapcount(page); if (total_map_swapcount)
if (count != 1) *total_map_swapcount = total_mapcount + total_swapcount;
goto out; if (count == 1 && PageSwapCache(page) &&
(likely(!PageTransCompound(page)) ||
/* The remaining swap count will be freed soon */
total_swapcount == page_swapcount(page))) {
if (!PageWriteback(page)) { if (!PageWriteback(page)) {
page = compound_head(page);
delete_from_swap_cache(page); delete_from_swap_cache(page);
SetPageDirty(page); SetPageDirty(page);
} else { } else {
...@@ -1448,7 +1532,7 @@ bool reuse_swap_page(struct page *page, int *total_mapcount) ...@@ -1448,7 +1532,7 @@ bool reuse_swap_page(struct page *page, int *total_mapcount)
spin_unlock(&p->lock); spin_unlock(&p->lock);
} }
} }
out:
return count <= 1; return count <= 1;
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment