[COMMON] mm: fix cma page migration race condition
authorSunyoung Kang <sy0816.kang@samsung.com>
Tue, 14 Jun 2016 06:09:20 +0000 (15:09 +0900)
committerhskang <hs1218.kang@samsung.com>
Mon, 27 Aug 2018 07:21:26 +0000 (16:21 +0900)
The below callstack makes VM_BUG_ON in race condition.
So this fixes it by moving the page isolation into before
pte_unmap_unlock().

----------------------------------------------------------
CPU0 CPU1
__need_migrate_cma_page
   PageLRU(page)
pte_unmap_unlock
isolate_migrate_pages_range
migrate_pages # clear pageLRU
# _count == 0
__migrate_cma_pinpage
   __isolate_lru_page # failure on !PageLRU
get_page_foll # VM_BUG on page->_count == 0
----------------------------------------------------------

This patch effects that get_user_pages() may stop on a page that is
under a racing but it guarantees that the state of the page is
consistent. Therefore, the caller of get_user_pages() should always
check if the pinning all required pages is successful.

Change-Id: I7947db746e8aaed23ea7344ee0483073bbd2d51c
Signed-off-by: Sunyoung Kang <sy0816.kang@samsung.com>
Signed-off-by: Cho KyongHo <pullip.cho@samsung.com>
mm/gup.c

index b6f72ca9dcea1a5b0873c3d69d1902a46b524e2f..9877d3887884e650e741335cb79f93c05934b1f3 100644 (file)
--- a/mm/gup.c
+++ b/mm/gup.c
@@ -57,25 +57,32 @@ static bool __need_migrate_cma_page(struct page *page,
        return true;
 }
 
-static int __migrate_cma_pinpage(struct page *page, struct vm_area_struct *vma)
+static int __isolate_cma_pinpage(struct page *page)
 {
        struct zone *zone = page_zone(page);
-       struct list_head migratepages;
        struct lruvec *lruvec;
-       int tries = 0;
-       int ret = 0;
-
-       INIT_LIST_HEAD(&migratepages);
 
+       spin_lock_irq(zone_lru_lock(zone));
        if (__isolate_lru_page(page, 0) != 0) {
+               spin_unlock_irq(zone_lru_lock(zone));
                dump_page(page, "failed to isolate lru page");
-               return -EFAULT;
+               return -EBUSY;
        } else {
-               spin_lock_irq(zone_lru_lock(zone));
                lruvec = mem_cgroup_page_lruvec(page, zone->zone_pgdat);
                del_page_from_lru_list(page, lruvec, page_lru(page));
-               spin_unlock_irq(zone_lru_lock(zone));
        }
+       spin_unlock_irq(zone_lru_lock(zone));
+
+       return 0;
+}
+
+static int __migrate_cma_pinpage(struct page *page, struct vm_area_struct *vma)
+{
+       struct list_head migratepages;
+       int tries = 0;
+       int ret = 0;
+
+       INIT_LIST_HEAD(&migratepages);
 
        list_add(&page->lru, &migratepages);
        inc_zone_page_state(page, NR_ISOLATED_ANON + page_is_file_cache(page));
@@ -227,6 +234,13 @@ retry:
        }
 
        if (__need_migrate_cma_page(page, vma, address, flags)) {
+               if (__isolate_cma_pinpage(page)) {
+                       pr_warn("%s: Failed to migrate a cma page\n", __func__);
+                       pr_warn("because of racing with compaction.\n");
+                       WARN(1, "Please try again get_user_pages()\n");
+                       page = ERR_PTR(-EBUSY);
+                       goto out;
+               }
                pte_unmap_unlock(ptep, ptl);
                if (__migrate_cma_pinpage(page, vma)) {
                        ptep = pte_offset_map_lock(mm, pmd, address, &ptl);