Skip to content

Latest commit

 

History

History
676 lines (613 loc) · 25.7 KB

内存管理系列二十:内存压缩算法之数据同步.md

File metadata and controls

676 lines (613 loc) · 25.7 KB

前沿

往篇回顾

在上篇中主要学习了内存压缩的主要过程,从搜索migrate pages到搜索free pages;核心函数为compact_zone,主要流程如下:

  • 扫描是以zone为单位,以pageblock维度进行的(1K个page),连续扫描32个pageblock后会触发中场休息
  • 从zone的头指针向尾扫描可移动页,每次扫描一个符合要求的pageblock中的所有页框
  • 当扫描完zone中所有页框后,得到一个migratepages链表;在完成Migrate pages收集后,主要任务就在migrate_pages->unmap_and_move迁移函数中了
  • 在unmap_and_move()中首先会使用compaction_alloc()从zone中收集足够的free pages用于页面迁移,free pages是从zone的尾部开始向前扫描空闲页
  • 当找到符合条件的空闲页框数量>=可移动的页框数量时,则停止扫描空闲页,调用__unmap_and_move()同步页面数据,以及处理同步过程中的被迁移页面访问问题
  • 如果对一个pageblock扫描后,无法隔离出一个可移动页框,则该pageblock会被标记zone->PB_migrate_skip,该标志只有在以下两种情况下会被清除
    1. zone内完成一次完整扫描(migrate_pfn==free_pfn)
    2. zone的内存压缩推迟次数达到最大值(zone->compact_defer_shift == COMPACT_MAX_DEFER_SHIFT并且zone->compact_considered >= 1UL « zone->compact_defer_shift)

数据同步关键点

  • 在对被迁移页面同步数据、重新释放到伙伴系统过程中,如果有请求访问该页面如何处理?

本篇主要内容

分析在内存压缩过程中,如何保证对被压缩页面的访问,以及数据同步过程

##代码分析

__unmap_and_move

/*
 * 尝试迁移一个page:如果迁移失败,外层会最多重试10次
 * 1. 获取PG_locked锁,保证回收过程同步进行
 * 2. 对异常情况特殊处理;例如mapping==NULL的页面不需要unmap,如果page正在回写则返回失败等
 * 3. 使用try_to_unmap,利用RMAP对映射本页面的所有pte表做修改,写入特殊标志位TTU_MIGRATION
 * 4. 使用move_to_new_page,将页面内容复制到新页,并重新生成pte表并刷新到映射中去
 * force: 只有在连续3次尝试迁移本page后,第4次再尝试迁移时,为true
 */
static int __unmap_and_move(struct page *page, struct page *newpage,
				int force, enum migrate_mode mode)
{
	int rc = -EAGAIN;
	int page_was_mapped = 0;
	struct anon_vma *anon_vma = NULL;
    /* 获取这个page锁(PG_locked标志)是关键,是整个回收过程中同步的保证 
     * 当我们对所有映射了此页的进程进行unmap操作时,会给它们一个特殊的页表项
     * 当这些进程再次访问此页时,会由于访问了这个特殊的页表项进入到缺页异常,然后在缺页异常中等待此页的这个锁释放
     * 当此页的这个锁释放时,页面迁移已经完成了,这些进程的此页表项已经在释放锁前就映射到了新页上,这时候已经可以唤醒这些等待着此页的锁的进程
     * 这些进程下次访问此页表项时,就是访问到了新页
     */
	if (!trylock_page(page)) {
	    /* 异步此时一定需要拿到锁,否则就返回,因为下面还有一个lock_page(page)获取锁,这个有可能会导致阻塞等待 
		 * force:retry>2时此标志会置为true
		 */
		if (!force || mode == MIGRATE_ASYNC)
			goto out;

		if (current->flags & PF_MEMALLOC)
			goto out;
		 /* 同步和轻同步的情况下,都有可能会为了拿到这个锁而阻塞在这 */
		lock_page(page);
	}
	/* 此页正在回写到磁盘 */
	if (PageWriteback(page)) {
		/* 异步和轻同步模式都不会等待 */
		if (mode != MIGRATE_SYNC) {
			rc = -EBUSY;
			goto out_unlock;
		}
		if (!force)
			goto out_unlock;
		/* 同步模式下,等待此页回写完成 */
		wait_on_page_writeback(page);
	}
	/* 匿名页并且不使用于ksm的情况 */
	if (PageAnon(page) && !PageKsm(page)) {
	/* 获取匿名页所指向的anon_vma,如果是文件页,则返回NULL */
		anon_vma = page_get_anon_vma(page);
		if (anon_vma) {
			/*
			 * 匿名页不做任何处理
			 */
		} else if (PageSwapCache(page)) {
	/* page在swapcache中且anon_vma==NULL: 进行过unmap的匿名页,没有进程映射此页 */
		} else {
			/* 文件页*/
			goto out_unlock;
		}
	}

	if (unlikely(isolated_balloon_page(page))) {
		 /* balloon使用的页 */
		rc = balloon_page_migrate(newpage, page, mode);
		goto out_unlock;
	}

	/* page->mapping为空的情况,有两种情况
     * 1.此页是已经加入到swapcache,并且进行过unmap的匿名页,现在已经没有进程映射此页
     * 2.一些特殊的页,这些页page->mapping为空,但是page->private指向一个buffer_head链表(日志缓冲区使用的页?)
     */
	if (!page->mapping) {
		VM_BUG_ON_PAGE(PageAnon(page), page);
		/* page->private有buffer_head */
		if (page_has_private(page)) {
			/* 释放此页所有的buffer_head,之后此页将被回收 */
			try_to_free_buffers(page);
			goto out_unlock;
		}
		/* 由于本page的mapping已经为空,因此可以跳过unmap阶段 */
		goto skip_unmap;
	}

    /* Establish migration ptes or remove ptes */
    /* umap此页
	 * 1. 生成一个迁移使用的swp_entry_t,这个swp_entry_t指向的页就是此page 
     * 2. 将此swp_entry_t替换映射了此页的页表项
     * 3. 对此页的页描述符的_mapcount进行--操作,表明反向映射到的一个进程取消了映射
	 * TTU_MIGRATION:RMAP为页面迁移进行;TTU_IGNORE_MLOCK: 可以对mlock在内存中的page进行
     */
	if (page_mapped(page)) {
		try_to_unmap(page,
			TTU_MIGRATION|TTU_IGNORE_MLOCK|TTU_IGNORE_ACCESS);
		page_was_mapped = 1;
	}

skip_unmap:
	
	if (!page_mapped(page))
	/* 将page的内容复制到newpage中,会进行将newpage重新映射到page所属进程的pte中 */
		rc = move_to_new_page(newpage, page, page_was_mapped, mode);
    /* 
	 * 本page内存迁移失败:
	 * 当在move_to_new_page()中进行remove_migration_ptes()失败时,这里才会执行 
     * 这里是将所有映射了旧页的进程页表项再重新映射到旧页上
     */
	if (rc && page_was_mapped)
		remove_migration_ptes(page, page);

	/* Drop an anon_vma reference if we took one */
	if (anon_vma)
		put_anon_vma(anon_vma);

out_unlock:
    /* 释放此页的锁(PG_locked清除)
     * 在unmap后,所有访问此页的进程都会阻塞在这里,等待此锁释放
     * 这里释放后,所有访问此页的进程都会被唤醒
     */
	unlock_page(page);
out:
	return rc;
}

try_to_unmap

/**
 * try_to_unmap - try to remove all page table mappings to a page
 * @page: the page to get unmapped
 * @flags: action and flags
 *
 */
int try_to_unmap(struct page *page, enum ttu_flags flags)
{
	int ret;
	/* 反向映射控制结构 */
	struct rmap_walk_control rwc = {
		/* 对一个vma所属页表进行unmap操作 */
		.rmap_one = try_to_unmap_one,
		.arg = (void *)flags,
		/* 对一个vma进行unmap后会执行此函数 */
		.done = page_not_mapped,
		/* 用于对整个anon_vma的红黑树进行上锁,用读写信号量,锁是aon_vma的rwsem */
		.anon_lock = page_lock_anon_vma_read,
	};

	VM_BUG_ON_PAGE(!PageHuge(page) && PageTransHuge(page), page);

	/*
	 * During exec, a temporary VMA is setup and later moved.
	 * The VMA is moved under the anon_vma lock but not the
	 * page tables leading to a race where migration cannot
	 * find the migration ptes. Rather than increasing the
	 * locking requirements of exec(), migration skips
	 * temporary VMAs until after exec() completes.
	 */
	if ((flags & TTU_MIGRATION) && !PageKsm(page) && PageAnon(page))
		rwc.invalid_vma = invalid_migration_vma;
	/* 里面会对所有映射了此页的vma进行遍历,实际处理函数为rmap_walk_anon->try_to_unmap_one
	 * rmap_walk_anon: 对av上挂的avc红黑树进行遍历;通过avc找到对应vma
	 */
	ret = rmap_walk(page, &rwc);
	/* 没有vma要求此页锁在内存中,并且page->_mapcount为-1了,表示没有进程映射了此页 */
	if (ret != SWAP_MLOCK && !page_mapped(page))
		ret = SWAP_SUCCESS;
	return ret;
}

try_to_unmap_one

/*
 * 此函数吧所有可能进行反向映射unmap操作的情况都写进去了,例如:页面回收、页面迁移
 * 1. 首先通过page_check_address()判断此vma是否真实映射了此页
 * 2. 通过make_migration_entry()生成用于页面迁移的swap类型页表项;
 * 3. 通过set_pte_at()写入到进程对应的页表项中
 */
static int try_to_unmap_one(struct page *page, struct vm_area_struct *vma,
		     unsigned long address, void *arg)
{
	struct mm_struct *mm = vma->vm_mm;
	pte_t *pte;
	pte_t pteval;
	spinlock_t *ptl;
	int ret = SWAP_AGAIN;
	enum ttu_flags flags = (enum ttu_flags)arg;
    /* 检查此vma有没有映射此page,有则返回此page在此进程地址空间的页表项:
     * 1. address是page在此vma所属进程地址空间的线性地址,获取方法: address = vma->vm_pgoff + page->pgoff << PAGE_SHIFT;
     * 2. 通过线性地址address获取页表项,然后通过页表项映射的页框号和page的页框号比较,则知道页表项是否映射了此page
     * 3. 会对页表上锁
     */
	pte = page_check_address(page, mm, address, &ptl, 0);
	/* pte为空,则说明page没有映射到此mm所属的进程地址空间,则跳到out */
	if (!pte)
		goto out;

	/* 如果flags没有要求忽略mlock的vma */
	if (!(flags & TTU_IGNORE_MLOCK)) {
		/* 如果此vma要求里面的页都锁在内存中,则跳到out_mlock */
		if (vma->vm_flags & VM_LOCKED)
			goto out_mlock;
		/* flags标记了对vma进行mlock释放模式,则跳到out_unmap,因为这个函数中只对vma进行unmap操作 */
		if (flags & TTU_MUNLOCK)
			goto out_unmap;
	}
	/* 忽略页表项中的Accessed */
	if (!(flags & TTU_IGNORE_ACCESS)) {
		/* 清除页表项的Accessed标志 */
		if (ptep_clear_flush_young_notify(vma, address, pte)) {
			/* 清除失败,发生在清除后检查是否为0 */
			ret = SWAP_FAIL;
			goto out_unmap;
		}
  	}

	/* Nuke the page table entry. */
	flush_cache_page(vma, address, page_to_pfn(page));
	/* 获取页表项内容,保存到pteval中,然后清空页表项 */
	pteval = ptep_clear_flush(vma, address, pte);

	/* 如果页表项标记了此页为脏页 */
	if (pte_dirty(pteval))
		/* 设置页描述符的PG_dirty标记 */
		set_page_dirty(page);

	/* 更新进程所拥有的最大页框数 */
	update_hiwater_rss(mm);
	/* 此页是被标记为"坏页"的页,这种页用于内核纠正一些错误,是否用于边界检查? */
	if (PageHWPoison(page) && !(flags & TTU_IGNORE_HWPOISON)) {
		if (!PageHuge(page)) {
			if (PageAnon(page))
			/* 是匿名页,则mm的MM_ANONPAGES-- */
				dec_mm_counter(mm, MM_ANONPAGES);
			else
			/* 此页是文件页,则mm的MM_FILEPAGES-- */
				dec_mm_counter(mm, MM_FILEPAGES);
		}
		/* 设置页表项新的内容为 swp_entry_to_pte(make_hwpoison_entry(page)) */
		set_pte_at(mm, address, pte,
			   swp_entry_to_pte(make_hwpoison_entry(page)));
	} else if (pte_unused(pteval)) {
		/* 一些架构上会有这种情况,X86不会调用到这个判断中 */
		if (PageAnon(page))
			dec_mm_counter(mm, MM_ANONPAGES);
		else
			dec_mm_counter(mm, MM_FILEPAGES);
	} else if (PageAnon(page)) {
	/* 获取page->private中保存的内容,调用到try_to_unmap()前会把此页加入到swapcache,然后分配一个以swap页槽偏移量为内容的swp_entry_t */
		swp_entry_t entry = { .val = page_private(page) };
		pte_t swp_pte;
        /* 对于内存回收,基本都是这种情况,因为page在调用到这里之前已经被移动到了swapcache */
		if (PageSwapCache(page)) {
            /* 检查entry是否有效
             * 并且增加entry对应页槽在swap_info_struct的swap_map的数值,此数值标记此页槽的页有多少个进程引用
             */
			if (swap_duplicate(entry) < 0) {
				/* 检查失败,把原来的页表项内容写回去 */
				set_pte_at(mm, address, pte, pteval);
				ret = SWAP_FAIL;
				goto out_unmap;
			}
			/* entry有效,并且swap_map中目标页槽的数值也++了
             * 这个if的情况是此vma所属进程的mm没有加入到所有进程的mmlist中(init_mm.mmlist) 
			 */
			if (list_empty(&mm->mmlist)) {
				spin_lock(&mmlist_lock);
				if (list_empty(&mm->mmlist))
					list_add(&mm->mmlist, &init_mm.mmlist);
				spin_unlock(&mmlist_lock);
			}
			/* 减少此mm的匿名页统计 */
			dec_mm_counter(mm, MM_ANONPAGES);
			 /* 增加此mm的页表中标记了页在swap的页表项的数量 */
			inc_mm_counter(mm, MM_SWAPENTS);
		} else if (IS_ENABLED(CONFIG_MIGRATION)) {
/**************************************************处理页面迁移********************************************/
			/* 执行到这里,就是对匿名页进行页面迁移工作(内存压缩时使用) */
			BUG_ON(!(flags & TTU_MIGRATION));
			/* 为此匿名页创建一个页迁移使用的swp_entry_t,此swp_entry_t指向此匿名页 */
			entry = make_migration_entry(page, pte_write(pteval));
		}
		/*
         * 这个entry有两种情况,保存在page->private中的以在swap中页槽偏移量为数据的swp_entry_t
         * 另一种是一个迁移使用的swp_entry_t
         */
        /* 将entry转为一个页表项 */
		swp_pte = swp_entry_to_pte(entry);
		/* 页表项有一位用于_PAGE_SOFT_DIRTY,用于kmemcheck */
		if (pte_soft_dirty(pteval))
			swp_pte = pte_swp_mksoft_dirty(swp_pte);
		/* 将配置好的新的页表项swp_pte写入页表项中 */
		set_pte_at(mm, address, pte, swp_pte);
	} else if (IS_ENABLED(CONFIG_MIGRATION) &&
		   (flags & TTU_MIGRATION)) {
        /* 文件页迁移: 为映射了此文件页的进程创建一个swp_entry_t,这个swp_entry_t指向此文件页 */
		swp_entry_t entry;
		/* 建立一个迁移使用的swp_entry_t,用于文件页迁移 */
		entry = make_migration_entry(page, pte_write(pteval));
		/* 将此页表的pte页表项写入entry转为的页表项内容 */
		set_pte_at(mm, address, pte, swp_entry_to_pte(entry));
	} else
		/* 文件页,仅对此mm的文件页计数--,文件页不需要设置页表项,只需要对页表项进行清空 */
		dec_mm_counter(mm, MM_FILEPAGES);
    /* 对此页的页描述符的_mapcount进行--操作,当_mapcount为-1时,表示此页已经没有页表项映射了 */
	page_remove_rmap(page);
	/* 每个进程对此页进行了unmap操作,此页的page->_count--,并判断是否为0,如果为0则释放此页,一般这里不会为0 */
	page_cache_release(page);

out_unmap:
	pte_unmap_unlock(pte, ptl);
	if (ret != SWAP_FAIL && !(flags & TTU_MUNLOCK))
		mmu_notifier_invalidate_page(mm, address);
out:
	return ret;

out_mlock:
	pte_unmap_unlock(pte, ptl);


	/*
	 * We need mmap_sem locking, Otherwise VM_LOCKED check makes
	 * unstable result and race. Plus, We can't wait here because
	 * we now hold anon_vma->rwsem or mapping->i_mmap_rwsem.
	 * if trylock failed, the page remain in evictable lru and later
	 * vmscan could retry to move the page to unevictable lru if the
	 * page is actually mlocked.
	 */
	if (down_read_trylock(&vma->vm_mm->mmap_sem)) {
		if (vma->vm_flags & VM_LOCKED) {
			mlock_vma_page(page);
			ret = SWAP_MLOCK;
		}
		up_read(&vma->vm_mm->mmap_sem);
	}
	return ret;
}

move_to_new_page

/*
 * 在经过try_to_unmap()对所有映射此页的vma页表项进行特殊处理,并unmap后,调用本函数将old page复制的new page
 * 
 */
static int move_to_new_page(struct page *newpage, struct page *page,
				int page_was_mapped, enum migrate_mode mode)
{
	struct address_space *mapping;
	int rc;

	/* 对新页上锁,这里应该100%上锁成功,因为此页是新的,没有任何进程和模块使用 */
	if (!trylock_page(newpage))
		BUG();

    /* 将旧页的index、mapping和PG_swapbacked标志复制到新页 
     * 对于复制index和mapping有很重要的意义
     * 通过index和mapping,就可以对新页进行反向映射了
		当新页配置好后,对新页进行反向映射,找到的就是映射了旧页的进程,然后将它们的对应页表项映射到新页
     */
	newpage->index = page->index;
	newpage->mapping = page->mapping;
	if (PageSwapBacked(page))
		SetPageSwapBacked(newpage);
	/* 获取旧页的mapping */
	mapping = page_mapping(page);
	if (!mapping)
	/* 如果mapping为空,则执行默认的migrate_page() 
     * 注意到这里时,映射了此页的进程已经对此页进行了unmap操作,而进程对应的页表项被设置为了指向page(而不是newpage)的swp_entry_t
     */
		rc = migrate_page(mapping, newpage, page, mode);
	else if (mapping->a_ops->migratepage)
        /* 文件页、swapcache中的匿名页,都会到这里 
         * 匿名页:调用的是swap_aops->migrate_page()函数,而这个函数,实际上就是上面的migrate_page()函数
         * 根据文件系统的不同,这里可能会对脏文件页造成回写,只有同步模式才能进行回写
         */
		rc = mapping->a_ops->migratepage(mapping,
						newpage, page, mode);
	else
	    /* 当文件页所在的文件系统没有支持migratepage()函数时,会调用这个默认的函数,里面会对脏文件页进行回写,只有同步模式才能进行 */
		rc = fallback_migrate_page(mapping, newpage, page, mode);

	if (rc != MIGRATEPAGE_SUCCESS) {
		newpage->mapping = NULL;
	} else {
		mem_cgroup_migrate(page, newpage, false);
		/* 这个page_was_mapped默认就是1
         * 这里做的工作就是将之前映射了旧页的页表项,统统改为映射到新页,会使用到反向映射
         */
		if (page_was_mapped)
			remove_migration_ptes(page, newpage);
		page->mapping = NULL;
	}
	/* 释放newpage的PG_locked标志 */
	unlock_page(newpage);

	return rc;
}

migrate_page

/* 将旧页的参数和数据复制到新页中 */
int migrate_page(struct address_space *mapping,
		struct page *newpage, struct page *page,
		enum migrate_mode mode)
{
	int rc;
	/* 页都没加入到swapcache,更不可能会正在进行回写 */
	BUG_ON(PageWriteback(page));	/* Writeback must be complete */
    /* 如果旧页有加入到address_space的基树中,那么就用新页替换这个旧页的slot,新页替换旧页加入address_space的基树中
     * 并且会同步旧匿名页的PG_swapcache标志和private指针内容到新页
     * 对旧页会page->_count--(从基树中移除)
     * 对新页会page->_count++(加入到基树中)
     */
	rc = migrate_page_move_mapping(mapping, newpage, page, NULL, mode, 0);

	if (rc != MIGRATEPAGE_SUCCESS)
		return rc;
    /* 将page页的内容复制的newpage
     * 再对一些标志进行复制
     */
	migrate_page_copy(newpage, page);
	return MIGRATEPAGE_SUCCESS;
}

migrate_page_move_mapping

/*
 * 如果old page有加入address_space的基数树中,则用new page替换
 *
 * The number of remaining references must be:
 * 1 for anonymous pages without a mapping
 * 2 for pages with a mapping
 * 3 for pages with a mapping and PagePrivate/PagePrivate2 set.
 */
int migrate_page_move_mapping(struct address_space *mapping,
		struct page *newpage, struct page *page,
		struct buffer_head *head, enum migrate_mode mode,
		int extra_count)
{
	int expected_count = 1 + extra_count;
	void **pslot;

	if (!mapping) {
		/* Anonymous page without mapping */
		/* 
		 * 对于未加入到swapcache中的旧匿名页,只要page->_count为1,就说明可以直接进行迁移
		 * page->_count为1说明只有隔离函数对此进行了++,其他地方没有引用此页,返回MIGRATEPAGE_SUCCESS
		 */
		if (page_count(page) != expected_count)
			return -EAGAIN;
		return MIGRATEPAGE_SUCCESS;
	}

	/* 对mapping中的基树上锁 */
	spin_lock_irq(&mapping->tree_lock);
	/* 获取此旧页所在基树中的slot */
	pslot = radix_tree_lookup_slot(&mapping->page_tree,
 					page_index(page));
    /* 对于加入了address_space的基树中的旧页
     * 判断page->_count是否为2 + page_has_private(page)
     */
	expected_count += 1 + page_has_private(page);
	if (page_count(page) != expected_count ||
		radix_tree_deref_slot_protected(pslot, &mapping->tree_lock) != page) {
		/* 如果不是,可能此旧页被某个进程映射了 */
		spin_unlock_irq(&mapping->tree_lock);
		return -EAGAIN;
	}
    /* 这里再次判断,这里就只判断page->_count是否为2 + page_has_private(page)了 */
	if (!page_freeze_refs(page, expected_count)) {
	/* 如果不是,可能此旧页被某个进程映射了 */
		spin_unlock_irq(&mapping->tree_lock);
		return -EAGAIN;
	}

	if (mode == MIGRATE_ASYNC && head &&
			!buffer_migrate_lock_buffers(head, mode)) {
		page_unfreeze_refs(page, expected_count);
		spin_unlock_irq(&mapping->tree_lock);
		return -EAGAIN;
	}
    /* page是处于page->mapping指向的address_space的基树中的,并且没有进程映射此页 
     * 下面要做的,就是用新页(newpage)数据替换旧页(page)数据所在的slot
     */
	/* 新的页的newpage->_count++,因为后面要把新页替换旧页所在的slot */
	get_page(newpage);	/* add cache reference */
	if (PageSwapCache(page)) {
	/* 设置新页也在swapcache中,后面会替换旧页,新页就会加入到swapcache中 */
		SetPageSwapCache(newpage);
		/* 将旧页的private指向的地址复制到新页的private 
         * 对于加入了swapcache中的页,这项保存的都是以swap分区页槽为索引的swp_entry_t
         * 这里注意与在内存压缩时unmap时写入进程页表项的swp_entry_t的区别,在内存压缩时,写入进程页表项的swp_entry_t是以旧页(page)为索引
         */
		set_page_private(newpage, page_private(page));
	}
	/* 用新页数据替换旧页的slot */
	radix_tree_replace_slot(pslot, newpage);

    /* 设置旧页的page->_count为expected_count - 1 
     * 这个-1是因为此旧页已经算是从address_space的基树中拿出来了
     */
	page_unfreeze_refs(page, expected_count - 1);

/* 统计,注意,加入到swapcache中的匿名页,也算作NR_FILE_PAGES的数量 */
	__dec_zone_page_state(page, NR_FILE_PAGES);
	__inc_zone_page_state(newpage, NR_FILE_PAGES);
	if (!PageSwapCache(page) && PageSwapBacked(page)) {
		__dec_zone_page_state(page, NR_SHMEM);
		__inc_zone_page_state(newpage, NR_SHMEM);
	}
	spin_unlock_irq(&mapping->tree_lock);

	return MIGRATEPAGE_SUCCESS;
}

remove_migration_ptes

/*
 * 不管迁移有没有成功,最后一步总会到这里
 * 失败:在__unmap_and_move函数中使用本函数对unmap后的原page重新建立映射
 * 成功: 在move_to_new_page中使用本函数对新page建立和vma的映射关系
 */
static void remove_migration_ptes(struct page *old, struct page *new)
{
	struct rmap_walk_control rwc = {
	/* 每获取一个vma就会调用一次此函数 */
		.rmap_one = remove_migration_pte,
	/* rmap_one的最后一个参数为旧的页框 */
		.arg = old,
	};
	/* 反向映射遍历vma函数 */
	rmap_walk(new, &rwc);
}

remove_migration_pte

/*
 * 对page映射的各个vma进行处理
 * 1. 首先根据线性地址addr生成对应的pte entry
 * 2. 检测pte页表项
 * 3. 把映射的pte页表项内容设置到新页面pte中,重新建立映射关系(set_pte_at)
 * 4. 把新页面newpage添加到RMAP反向映射系统中
 * 5. 更新cache
 */
static int remove_migration_pte(struct page *new, struct vm_area_struct *vma,
				 unsigned long addr, void *old)
{
	struct mm_struct *mm = vma->vm_mm;
	swp_entry_t entry;
 	pmd_t *pmd;
	pte_t *ptep, pte;
 	spinlock_t *ptl;

	if (unlikely(PageHuge(new))) {
		ptep = huge_pte_offset(mm, addr);
		if (!ptep)
			goto out;
		ptl = huge_pte_lockptr(hstate_vma(vma), mm, ptep);
	} else {
		/* 新的页是普通4k页
         * 这个addr是new和old在此进程地址空间中对应的线性地址,new和old会有同一个线性地址,因为new是old复制过来的
         * 获取线性地址addr对应的页中间目录项 
		 */
		pmd = mm_find_pmd(mm, addr);
		if (!pmd)
			goto out;
		/* 根据页中间目录项和addr,获取对应的页表项指针 */
		ptep = pte_offset_map(pmd, addr);

		ptl = pte_lockptr(mm, pmd);
	}

 	spin_lock(ptl);
	pte = *ptep;
	if (!is_swap_pte(pte))
	 /* 非swap类型的页表项内容(页迁移页表项属于swap类型的页表项),则准备跳出
	  * pte为空||pte在位就不是swap pte?
	  */
		goto unlock;
	/* 根据页表项内存转为swp_entry_t类型 */
	entry = pte_to_swp_entry(pte);

	if (!is_migration_entry(entry) ||
	    migration_entry_to_page(entry) != old)
	/* 1. 这个entry不是页迁移类型的entry
	 * 2. 此entry指向的页不是旧页*/
		goto unlock;
	/* 新页的page->_count++ */
	get_page(new);
	/* 根据新页new创建一个新的页表项内容 */
	pte = pte_mkold(mk_pte(new, vma->vm_page_prot));
	/* 这个好像跟numa有关,先不用理,无伤大雅 */
	if (pte_swp_soft_dirty(*ptep))
		pte = pte_mksoft_dirty(pte);

	/* Recheck VMA as permissions can change since migration started  */
	/* 如果获取的entry标记了映射页可写 */
	if (is_write_migration_entry(entry))
		pte = maybe_mkwrite(pte, vma);

#ifdef CONFIG_HUGETLB_PAGE
	if (PageHuge(new)) {
		pte = pte_mkhuge(pte);
		pte = arch_make_huge_pte(pte, vma, new, 0);
	}
#endif
	flush_dcache_page(new);
	/* 将设置好的新页的页表项内容写到对应页表项中,到这里,此页表项原来映射的是旧页,现在变成映射了新页了 */
	set_pte_at(mm, addr, ptep, pte);

	if (PageHuge(new)) {
		if (PageAnon(new))
			hugepage_add_anon_rmap(new, vma, addr);
		else
			page_dup_rmap(new);
	} else if (PageAnon(new))
	/* 针对匿名页 
	 * 主要对page->_count++,因为多了一个进程映射此页
	 */
		page_add_anon_rmap(new, vma, addr);
	else
	/* 针对文件页,同样,也是对page->_count++,因为多了一个进程映射此页 */
		page_add_file_rmap(new);

	/* No need to invalidate - it was non-present before */
	/* 刷新tlb */
	update_mmu_cache(vma, addr, ptep);
unlock:
	/* 释放锁 */
	pte_unmap_unlock(ptep, ptl);
out:
	return SWAP_AGAIN;
}

参考资料

tolimit-内存压缩(数据同步流程)