Re: [PATCH] mm: migrate: Fix THP's mapcount on isolation

From: Gavin Shan
Date: Wed Nov 23 2022 - 19:15:59 EST


On 11/23/22 4:56 PM, David Hildenbrand wrote:
On 23.11.22 06:14, Hugh Dickins wrote:
On Wed, 23 Nov 2022, Gavin Shan wrote:

The issue is reported when removing memory through virtio_mem device.
The transparent huge page, experienced copy-on-write fault, is wrongly
regarded as pinned. The transparent huge page is escaped from being
isolated in isolate_migratepages_block(). The transparent huge page
can't be migrated and the corresponding memory block can't be put
into offline state.

Fix it by replacing page_mapcount() with total_mapcount(). With this,
the transparent huge page can be isolated and migrated, and the memory
block can be put into offline state.

Fixes: 3917c80280c9 ("thp: change CoW semantics for anon-THP")
Cc: stable@xxxxxxxxxxxxxxx   # v5.8+
Reported-by: Zhenyu Zhang <zhenyzha@xxxxxxxxxx>
Suggested-by: David Hildenbrand <david@xxxxxxxxxx>
Signed-off-by: Gavin Shan <gshan@xxxxxxxxxx>

Interesting, good catch, looked right to me: except for the Fixes line
and mention of v5.8.  That CoW change may have added a case which easily
demonstrates the problem, but it would have been the wrong test on a THP
for long before then - but only in v5.7 were compound pages allowed
through at all to reach that test, so I think it should be

Fixes: 1da2f328fa64 ("mm,thp,compaction,cma: allow THP migration for CMA allocations")
Cc: stable@xxxxxxxxxxxxxxx   # v5.7+


Right, commit 1da2f328fa64 looks more accurate in this particular
case, I will fix it up in next revision.

Oh, no, stop: this is not so easy, even in the latest tree.

Because at the time of that "admittedly racy check", we have no hold
at all on the page in question: and if it's PageLRU or PageCompound
at one instant, it may be different the next instant.  Which leaves it
vulnerable to whatever BUG_ON()s there may be in the total_mapcount()
path - needs research.  *Perhaps* there are no more BUG_ON()s in the
total_mapcount() path than in the existing page_mapcount() path.

I suspect that for this to be safe (before your patch and more so after),
it will be necessary to shift the "admittedly racy check" down after the
get_page_unless_zero() (and check the sequence of operations when a
compound page is initialized).

Grabbing a reference first sounds like the right approach to me.


Yeah, it sounds reasonable to me to grab a page->__refcount in the
first place. Looking at isolate_migratepages_block(), the page's refcount
is increased by get_page_unless_zero(), but it's too late. To increase
the page's refcount at the first place in the function will be conflicting
with hugetlb page and non-LRU page. I mean there will be a series to refactor
the code so that the page's refcount can be grabbed in the first place.

So I plan to post a followup series to refactor the code and grab
the page's refcount in the first place. In this way, the fix can be
merged as soon as possible. David and Hugh, please let me know if
it's reasonable plan? :)

static int isolate_migratepages_block()
{
for (; low_pfn < end_pfn; low_pfn++) {
:
page = pfn_to_page(low_pfn);
if (unlikely(!get_page_unless_zero(page))) // grab page's refcount in the first place
goto isolate_fail_put;
:
if (PageHuge(page) && cc->alloc_contig) {
ret = isolate_or_dissolve_huge_page(page, &cc->migratepages); // refcount is increased by this function
:
}
:
if (!PageLRU(page)) {
if (unlikely(__PageMovable(page)) &&
!PageIsolated(page)) {
if (!isolate_movable_page(page, mode))) // refcunt is increased here too
goto isolate_success;
}
}
:
mapping = page_mapping(page);
if (!mapping && page_count(page) > total_mapcount(page))
goto isolate_fail;
:
if (unlikely(!get_page_unless_zero(page))) // too late to grab the refcount?
goto isolate_fail;
:
}
}


[...]

Thanks,
Gavin