在上篇中主要学习了内存压缩的主要过程,从搜索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,该标志只有在以下两种情况下会被清除
- zone内完成一次完整扫描(migrate_pfn==free_pfn)
- zone的内存压缩推迟次数达到最大值(zone->compact_defer_shift == COMPACT_MAX_DEFER_SHIFT并且zone->compact_considered >= 1UL « zone->compact_defer_shift)
- 在对被迁移页面同步数据、重新释放到伙伴系统过程中,如果有请求访问该页面如何处理?
分析在内存压缩过程中,如何保证对被压缩页面的访问,以及数据同步过程
##代码分析
/*
* 尝试迁移一个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 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;
}
/*
* 此函数吧所有可能进行反向映射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;
}
/*
* 在经过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;
}
/* 将旧页的参数和数据复制到新页中 */
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;
}
/*
* 如果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;
}
/*
* 不管迁移有没有成功,最后一步总会到这里
* 失败:在__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);
}
/*
* 对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;
}