Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

mm: activate !PageLRU pages on mark_page_accessed if page is on local pagevec

If a page is on a pagevec then it is !PageLRU and mark_page_accessed()
may fail to move a page to the active list as expected. Now that the
LRU is selected at LRU drain time, mark pages PageActive if they are on
the local pagevec so it gets moved to the correct list at LRU drain
time. Using a debugging patch it was found that for a simple git
checkout based workload that pages were never added to the active file
list in practice but with this patch applied they are.

before after
LRU Add Active File 0 750583
LRU Add Active Anon 2640587 2702818
LRU Add Inactive File 8833662 8068353
LRU Add Inactive Anon 207 200

Note that only pages on the local pagevec are considered on purpose. A
!PageLRU page could be in the process of being released, reclaimed,
migrated or on a remote pagevec that is currently being drained.
Marking it PageActive is vunerable to races where PageLRU and Active
bits are checked at the wrong time. Page reclaim will trigger
VM_BUG_ONs but depending on when the race hits, it could also free a
PageActive page to the page allocator and trigger a bad_page warning.
Similarly a potential race exists between a per-cpu drain on a pagevec
list and an activation on a remote CPU.

lru_add_drain_cpu
__pagevec_lru_add
lru = page_lru(page);
mark_page_accessed
if (PageLRU(page))
activate_page
else
SetPageActive
SetPageLRU(page);
add_page_to_lru_list(page, lruvec, lru);

In this case a PageActive page is added to the inactivate list and later
the inactive/active stats will get skewed. While the PageActive checks
in vmscan could be removed and potentially dealt with, a skew in the
statistics would be very difficult to detect. Hence this patch deals
just with the common case where a page being marked accessed has just
been added to the local pagevec.

Signed-off-by: Mel Gorman <mgorman@suse.de>
Cc: Jan Kara <jack@suse.cz>
Cc: Rik van Riel <riel@redhat.com>
Acked-by: Johannes Weiner <hannes@cmpxchg.org>
Cc: Alexey Lyahkov <alexey.lyashkov@gmail.com>
Cc: Andrew Perepechko <anserper@ya.ru>
Cc: Robin Dong <sanbai@taobao.com>
Cc: Theodore Tso <tytso@mit.edu>
Cc: Hugh Dickins <hughd@google.com>
Cc: Rik van Riel <riel@redhat.com>
Cc: Bernd Schubert <bernd.schubert@fastmail.fm>
Cc: David Howells <dhowells@redhat.com>
Cc: Trond Myklebust <Trond.Myklebust@netapp.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>

authored by

Mel Gorman and committed by
Linus Torvalds
059285a2 13f7f789

+39 -2
+39 -2
mm/swap.c
··· 432 432 } 433 433 #endif 434 434 435 + static void __lru_cache_activate_page(struct page *page) 436 + { 437 + struct pagevec *pvec = &get_cpu_var(lru_add_pvec); 438 + int i; 439 + 440 + /* 441 + * Search backwards on the optimistic assumption that the page being 442 + * activated has just been added to this pagevec. Note that only 443 + * the local pagevec is examined as a !PageLRU page could be in the 444 + * process of being released, reclaimed, migrated or on a remote 445 + * pagevec that is currently being drained. Furthermore, marking 446 + * a remote pagevec's page PageActive potentially hits a race where 447 + * a page is marked PageActive just after it is added to the inactive 448 + * list causing accounting errors and BUG_ON checks to trigger. 449 + */ 450 + for (i = pagevec_count(pvec) - 1; i >= 0; i--) { 451 + struct page *pagevec_page = pvec->pages[i]; 452 + 453 + if (pagevec_page == page) { 454 + SetPageActive(page); 455 + break; 456 + } 457 + } 458 + 459 + put_cpu_var(lru_add_pvec); 460 + } 461 + 435 462 /* 436 463 * Mark a page as having seen activity. 437 464 * ··· 469 442 void mark_page_accessed(struct page *page) 470 443 { 471 444 if (!PageActive(page) && !PageUnevictable(page) && 472 - PageReferenced(page) && PageLRU(page)) { 473 - activate_page(page); 445 + PageReferenced(page)) { 446 + 447 + /* 448 + * If the page is on the LRU, queue it for activation via 449 + * activate_page_pvecs. Otherwise, assume the page is on a 450 + * pagevec, mark it active and it'll be moved to the active 451 + * LRU on the next drain. 452 + */ 453 + if (PageLRU(page)) 454 + activate_page(page); 455 + else 456 + __lru_cache_activate_page(page); 474 457 ClearPageReferenced(page); 475 458 } else if (!PageReferenced(page)) { 476 459 SetPageReferenced(page);