深圳手机网站建设牛商网,住房与城乡建设部网站,建立公司官网,网站建设绩效考评文章说明#xff1a; Linux内核版本#xff1a;5.0 架构#xff1a;ARM64 参考资料及图片来源#xff1a;《奔跑吧Linux内核》 Linux 5.0内核源码注释仓库地址#xff1a; zhangzihengya/LinuxSourceCode_v5.0_study (github.com)
1. 可迁移页面
页面迁移机制支持两…文章说明 Linux内核版本5.0 架构ARM64 参考资料及图片来源《奔跑吧Linux内核》 Linux 5.0内核源码注释仓库地址 zhangzihengya/LinuxSourceCode_v5.0_study (github.com)
1. 可迁移页面
页面迁移机制支持两大类内存页面
传统LRU页面如匿名页面和文件映射页面非LRU页面如zsmalloc或者virtio-balloon页面以virtio-balloon页面为例它也有页面迁移的需求之前的做法是在virtio-balloon驱动中进行迁移操作和相应的逻辑。如果其他的驱动也想做类似的页面迁移那么它们就不能复用与virtio-balloon驱动相关的代码必须重新写一套代码这样会造成很多代码的重复和冗余。为了解决这个问题内存管理的页面迁移机制提供相应的接口来支持这些非LRU页面的迁移。
2. 页面迁移流程
页面迁移的本质是将页面的内容迁移到新的页面。这个过程中会分配新页面将旧页面的内容复制到新页面断开旧页面的映射关系并把映射关系映射到新页面最后释放旧页面。页面迁移的整个流程图如下所示 为了使读者有更真切的理解下文将根据流程图围绕源代码进行讲解这个过程。
页面迁移page migration在Linux内核的主函数是migrate_pages()函数
// 页面迁移的主函数
// from: 将要迁移页面的链表
// get_new_page: 申请新内存的页面的函数指针
// put_new_page: 迁移失败时释放目标页面的函数指针
// private: 传递给 get_new_page 的参数
// mode迁移模式
// reason: 迁移的原因
int migrate_pages(struct list_head *from, new_page_t get_new_page,free_page_t put_new_page, unsigned long private,enum migrate_mode mode, int reason)
{...rc unmap_and_move(get_new_page, put_new_page,private, page, pass 2, mode,reason);....
}migrate_pages()-unmap_and_move()
static ICE_noinline int unmap_and_move(new_page_t get_new_page,free_page_t put_new_page,unsigned long private, struct page *page,int force, enum migrate_mode mode,enum migrate_reason reason)
{...// 分配一个新的页面newpage get_new_page(page, private);if (!newpage)return -ENOMEM;if (page_count(page) 1) {...// 刚分配的页面需要调用 put_new_page() 回调函数if (put_new_page)put_new_page(newpage, private);...}// 尝试迁移页面到新分配的页面中rc __unmap_and_move(page, newpage, force, mode);if (rc MIGRATEPAGE_SUCCESS)set_page_owner_migrate_reason(newpage, reason);out:// 若返回值不等于 -EAGAIN说明可能迁移没成功if (rc ! -EAGAIN) {...}// 若返回值等于 MIGRATEPAGE_SUCCESS说明迁移成功释放页面if (rc MIGRATEPAGE_SUCCESS) {...// 处理迁移没成功的情况把页面重新添加到可移动的页面里。释放刚才新分配的页面} else {....}return rc;
}migrate_pages()-unmap_and_move()-__unmap_and_move()
// page被迁移的页面
// newpage: 迁移页面的目的地
// force: 表示是否强制迁移。在 migrate_pages() 中当尝试次数大于 2 时会设置为 10 表示强制迁移
// mode: 迁移模式
static int __unmap_and_move(struct page *page, struct page *newpage,int force, enum migrate_mode mode)
{...// __PageMovable() 函数用于判断这个页面是否属于非 LRU 页面它是通过 page 数据结构中的 mapping 成员// 是否设置了 PAGE_MAPPING_MOVABLE 标志位来判断的bool is_lru !__PageMovable(page);// trylock_page() 尝试给页面加锁返回 true 则表示当前进程已经成功获取锁if (!trylock_page(page)) {// 如果尝试获取也锁不成功// 若满足 !force || mode MIGRATE_ASYNC则直接忽略这个页面因为这种情况下没有必要睡眠等待页面释放锁if (!force || mode MIGRATE_ASYNC)goto out;// 如过当前进程设置了 PF_MEMALLOC 标志位表示当前进程可能处于直接内存压缩的内核路径上通过睡眠等待页锁是// 是不安全的所以直接忽略该页面if (current-flags PF_MEMALLOC)goto out;// 其他情况下只能等待页锁的释放lock_page(page);}// 处理正在回写的页面即设置了 PG_writeback 标志位的页面if (PageWriteback(page)) {// 只有当页面迁移的模式为 MIGRATE_ASYNC 或者 MIGRAIE_SYNC_LIGHT 且设置强制迁移force1// 时才会等待这个页面回写完成否则直接忽略该页面该页面不会被迁移switch (mode) {case MIGRATE_SYNC:case MIGRATE_SYNC_NO_COPY:break;default:rc -EBUSY;goto out_unlock;}if (!force)goto out_unlock;// 等待页面回写完成wait_on_page_writeback(page);}// 处理匿名页面的 anon_vma 可能被释放的特殊情况因为接下来 try_to_unmap() 函数运行完成时// page-_mapcount 会变成 0。在页面迁移的过程中我们无法知道 anon_vma 数据结构是否被释放了if (PageAnon(page) !PageKsm(page))// page_get_anon_vma() 增加 anon_vma-refcount 引用计数防止其被其他进程释放anon_vma page_get_anon_vma(page);// 尝试给 newpage 申请锁if (unlikely(!trylock_page(newpage)))goto out_unlock;// 若这个页面属于非 LRU 页面if (unlikely(!is_lru)) {// move_to_new_page() 函数中会通过驱动程序注册 migratepage() 函数来进行页面迁移rc move_to_new_page(newpage, page, mode);goto out_unlock_both;}// 接下来的代码用于处理传统的 LRU 页面if (!page-mapping) {// 处理一个特殊情况。当一个交换缓存页面从交换分区被读取之后它会被添加到 LRU 链表里我们把它当作// 一个交换缓存页面。但是它还没有设置 RMAP因此 page-mapping 为空。若调用 try_to_unmap() 可能// 会触发内核岩机因此这里做特殊处理并跳转到 out_unlock_both 标签处。VM_BUG_ON_PAGE(PageAnon(page), page);if (page_has_private(page)) {try_to_free_buffers(page);goto out_unlock_both;}// page_mapped() 判断该页面的 _mapcount 是否大于或等于 0若大于或等于 0说明有用户 PTE 映射该页面} else if (page_mapped(page)) {VM_BUG_ON_PAGE(PageAnon(page) !PageKsm(page) !anon_vma,page);// 对于有用户态进程地址空间映射的页面调用 try_to_unmap() 解除页面所有映射的用户 PTEtry_to_unmap(page,TTU_MIGRATION|TTU_IGNORE_MLOCK|TTU_IGNORE_ACCESS);page_was_mapped 1;}// 对于已经解除完所有用户 PTE 映射的页面调用 move_to_new_page() 把它们迁移到新分配的页面if (!page_mapped(page))rc move_to_new_page(newpage, page, mode);if (page_was_mapped)// 迁移页表// 对于迁移页面失败的情况调用 remove_migration_ptes() 删除迁移的 PTEremove_migration_ptes(page,rc MIGRATEPAGE_SUCCESS ? newpage : page, false);out_unlock_both:unlock_page(newpage);
out_unlock:/* Drop an anon_vma reference if we took one */if (anon_vma)put_anon_vma(anon_vma);unlock_page(page);
// 处理退出情况
out:if (rc MIGRATEPAGE_SUCCESS) {// 对于非 LRU 页面调用 put_page() 把 newpage 的 _refcount 减 1if (unlikely(!is_lru))put_page(newpage);// 对于传统 LRU 页面把 newpage 添加到 LRU 链表中elseputback_lru_page(newpage);}return rc;
}migrate_pages()-unmap_and_move()-__unmap_and_move()-move_to_new_page()
// 用于迁移旧页面到新页面中
static int move_to_new_page(struct page *newpage, struct page *page,enum migrate_mode mode)
{...// 判断页面是否属于传统的的 LRU 页面。通过 page 数据结构中的 mapping 成员// 是否设置了 PAGE_MAPPING_MOVABLE 标志位来判断的bool is_lru !__PageMovable(page);...// 返回页面的 mappingmapping page_mapping(page);if (likely(is_lru)) {// 若页面属于传统的 LRU 链表的页面按以下几种情况处理// 若 mapping 为空说明该页面是匿名页面但是没有分配交换缓存那么调用 migrate_page() 函数来迁移页面if (!mapping)rc migrate_page(mapping, newpage, page, mode);// 该页面实现了 migratepage()那么直接调用 mapping-a_ops-migratepage() 来迁移页面else if (mapping-a_ops-migratepage)rc mapping-a_ops-migratepage(mapping, newpage,page, mode);// 其他情况下elserc fallback_migrate_page(mapping, newpage,page, mode);// 对于页面属于非 LRU 页面的情况直接调用驱动程序为这个页面注册的 migratepage() 来迁移页面} else {...rc mapping-a_ops-migratepage(mapping, newpage,page, mode);WARN_ON_ONCE(rc MIGRATEPAGE_SUCCESS !PageIsolated(page));}// 处理迁移成功的情况if (rc MIGRATEPAGE_SUCCESS) {...}
out:return rc;
}migrate_pages()-unmap_and_move()-__unmap_and_move()-remove_migration_ptes()-remove_migration_pte()
static bool remove_migration_pte(struct page *page, struct vm_area_struct *vma,unsigned long addr, void *old)
{...// page_vma_mapped_walk() 遍历页表通过虚拟地址找到对应的 PTEwhile (page_vma_mapped_walk(pvmw)) {...// 根据新页面和 vma 属性来生成一个 PTEpte pte_mkold(mk_pte(new, READ_ONCE(vma-vm_page_prot)));...if (PageHuge(new)) {...} else{// 把新生成的 PTE 的内容写回到原来映射的页面中完成 PTE 的迁移这样用户进程地址空间就可以// 通过原来的 PTE 访问新页面set_pte_at(vma-vm_mm, pvmw.address, pvmw.pte, pte);// 把新页面添加到 RMAP 系统中if (PageAnon(new))page_add_anon_rmap(new, vma, pvmw.address, false);elsepage_add_file_rmap(new, false);}...// 更新相应的高速缓存update_mmu_cache(vma, pvmw.address, pvmw.pte);}return true;
}migrate_pages()-unmap_and_move()-__unmap_and_move()-move_to_new_page()-migratepage()
struct address_space_operations {...int (*migratepage) (struct address_space *,struct page *, struct page *, enum migrate_mode);...
};migratepage()方法会迁移旧页面的内容到新页面中并且设置page对应的成员和属性。驱动实现的migratepage()方法在完成页面迁移之后需要显式地调用__ClearPageMovable()函数清除PAGE_MAPPING_MOVABLE标志位。如果迁移页面不成功返回-EAGAIN那么根据页面迁移机制会重试一次。若返回其他错误值那么根据页面迁移机制就会放弃这个页面。