关键词:
接上篇 https://www.daodaodao123.com/?p=776
本篇解析缺页异常分支之一,写时复制缺页异常;
1.写时复制缺页异常触发条件
(1)pte页表项的PRESENT置位 (2)pte表项为不为空 (3)vma可写,pte只读,进行写操作
2.应用场景
(1)进程fork子进程的时候,为了避免复制物理页,父子进程以只读的方式共享所有的私有的匿名页和文件页,当父子进程有一方试图去写只读页时,触发写时复制缺页异常,内核会分配新的物理页,拷贝旧的物理页到新页,然后把新页映射虚拟页;
(2)进程创建私有匿名映射,读之后写(读映射到0页,写发生COW);
(3)进程创建私有文件映射,读之后写(读映射到page cache, 写发生COW);
3.fork时做的准备
dup_mm
->dum_mmap
->copy_page_range
...
->copy_pte_range
->copy_present_pte
->if(is_cow_mapping(vm_flags)&&pte_write(pte))
ptep_set_wrprotect(src_mm, addr, src_pet);
pte = pte_wrpotect(pte);
可见,对于私有的可写的页,fork时,将父子进程的页表改为只读.
注:子进程会创建一套独立新页表,只是PTE页表的内容,跟父进程页表完全一样。当先访问进程触发缺页异常时,修改的知识PTE页表,L0~2级页表是不变的。
4.两个重要函数
4.1 wp_page_copy()写时复制函数
static vm_fault_t wp_page_copy(struct vm_fault *vmf)
struct vm_area_struct *vma = vmf->vma;
struct mm_struct *mm = vma->vm_mm;
struct page *old_page = vmf->page;
struct page *new_page = NULL;
pte_t entry;
int page_copied = 0;
struct mmu_notifier_range range;
if (unlikely(anon_vma_prepare(vma))) ///检查VMA是否初始化了RMAP
goto oom;
if (is_zero_pfn(pte_pfn(vmf->orig_pte))) ///PTE如果是系统零页,分配一个内容全零的页面
new_page = alloc_zeroed_user_highpage_movable(vma,
vmf->address);
if (!new_page)
goto oom;
else ///分配一个新物理页面,并且把old_page内容复制到new_page中
new_page = alloc_page_vma(GFP_HIGHUSER_MOVABLE, vma,
vmf->address);
if (!new_page)
goto oom;
if (!cow_user_page(new_page, old_page, vmf))
/*
* COW failed, if the fault was solved by other,
* its fine. If not, userspace would re-fault on
* the same address and we will handle the fault
* from the second attempt.
*/
put_page(new_page);
if (old_page)
put_page(old_page);
return 0;
if (mem_cgroup_charge(new_page, mm, GFP_KERNEL))
goto oom_free_new;
cgroup_throttle_swaprate(new_page, GFP_KERNEL);
__SetPageUptodate(new_page); ///设置PG_uptodate, 表示内容有效
///注册一个mmu_notifier,并告知系统使dd_page无效
mmu_notifier_range_init(&range, MMU_NOTIFY_CLEAR, 0, vma, mm,
vmf->address & PAGE_MASK,
(vmf->address & PAGE_MASK) + PAGE_SIZE);
mmu_notifier_invalidate_range_start(&range);
/*
* Re-check the pte - we dropped the lock
*/ ///重新读取PTE,并判定是否修改
vmf->pte = pte_offset_map_lock(mm, vmf->pmd, vmf->address, &vmf->ptl);
if (likely(pte_same(*vmf->pte, vmf->orig_pte)))
if (old_page)
if (!PageAnon(old_page)) ///如果oldpage是文件映射
dec_mm_counter_fast(mm,
mm_counter_file(old_page)); ///减少一个文件映射页面技术
inc_mm_counter_fast(mm, MM_ANONPAGES); ///增加匿名页面计数
else
inc_mm_counter_fast(mm, MM_ANONPAGES);
flush_cache_page(vma, vmf->address, pte_pfn(vmf->orig_pte));
entry = mk_pte(new_page, vma->vm_page_prot);
entry = maybe_mkwrite(pte_mkdirty(entry), vma); ///利用new_page生成一个新PTE
/*
* Clear the pte entry and flush it first, before updating the
* pte with the new entry, to keep TLBs on different CPUs in
* sync. This code used to set the new PTE then flush TLBs, but
* that left a window where the new PTE could be loaded into
* some TLBs while the old PTE remains in others.
*/
ptep_clear_flush_notify(vma, vmf->address, vmf->pte); ///刷新这个页面的TLB
page_add_new_anon_rmap(new_page, vma, vmf->address, false); ///new_page添加到RMAP系统中
lru_cache_add_inactive_or_unevictable(new_page, vma); ///new_page添加到LRU链表中
/*
* We call the notify macro here because, when using secondary
* mmu page tables (such as kvm shadow page tables), we want the
* new page to be mapped directly into the secondary page table.
*/
set_pte_at_notify(mm, vmf->address, vmf->pte, entry); ///新pte设置到硬件PTE中
update_mmu_cache(vma, vmf->address, vmf->pte);
if (old_page) ///准备释放old_page,真正释放操作在page_cache_release()函数
/*
* Only after switching the pte to the new page may
* we remove the mapcount here. Otherwise another
* process may come and find the rmap count decremented
* before the pte is switched to the new page, and
* "reuse" the old page writing into it while our pte
* here still points into it and can be read by other
* threads.
*
* The critical issue is to order this
* page_remove_rmap with the ptp_clear_flush above.
* Those stores are ordered by (if nothing else,)
* the barrier present in the atomic_add_negative
* in page_remove_rmap.
*
* Then the TLB flush in ptep_clear_flush ensures that
* no process can access the old page before the
* decremented mapcount is visible. And the old page
* cannot be reused until after the decremented
* mapcount is visible. So transitively, TLBs to
* old page will be flushed before it can be reused.
*/
page_remove_rmap(old_page, false);
/* Free the old page.. */
new_page = old_page;
page_copied = 1;
else
update_mmu_tlb(vma, vmf->address, vmf->pte);
if (new_page)
put_page(new_page);
pte_unmap_unlock(vmf->pte, vmf->ptl);
/*
* No need to double call mmu_notifier->invalidate_range() callback as
* the above ptep_clear_flush_notify() did already call it.
*/
mmu_notifier_invalidate_range_only_end(&range);
if (old_page)
/*
* Dont let another task, with possibly unlocked vma,
* keep the mlocked page.
*/
if (page_copied && (vma->vm_flags & VM_LOCKED))
lock_page(old_page); /* LRU manipulation */
if (PageMlocked(old_page))
munlock_vma_page(old_page);
unlock_page(old_page);
put_page(old_page);
return page_copied ? VM_FAULT_WRITE : 0;
oom_free_new:
put_page(new_page);
oom:
if (old_page)
put_page(old_page);
return VM_FAULT_OOM;
4.2 wp_page_reuse()页面复用函数
当发生COW缺页异常时,发现这个匿名页只被映射到一个vma中,则不再发生写时复制,如果vma属性为可写,直接修改页表为可写。
static inline void wp_page_reuse(struct vm_fault *vmf)
__releases(vmf->ptl)
struct vm_area_struct *vma = vmf->vma;
struct page *page = vmf->page; ///获取缺页异常页面
pte_t entry;
/*
* Clear the pages cpupid information as the existing
* information potentially belongs to a now completely
* unrelated process.
*/
if (page)
page_cpupid_xchg_last(page, (1 << LAST_CPUPID_SHIFT) - 1);
flush_cache_page(vma, vmf->address, pte_pfn(vmf->orig_pte)); ///刷新缺页异常页面的高速缓存
entry = pte_mkyoung(vmf->orig_pte); ///设置PTE的AF位
entry = maybe_mkwrite(pte_mkdirty(entry), vma); ///设置可写,置脏位
if (ptep_set_access_flags(vma, vmf->address, vmf->pte, entry, 1)) ///设置新PTE到实际页表中
update_mmu_cache(vma, vmf->address, vmf->pte);
pte_unmap_unlock(vmf->pte, vmf->ptl);
count_vm_event(PGREUSE);
5.写时复制过程解析:
写时复制过程流程图
源码解析
static vm_fault_t do_wp_page(struct vm_fault *vmf)
__releases(vmf->ptl)
struct vm_area_struct *vma = vmf->vma;
if (userfaultfd_pte_wp(vma, *vmf->pte))
pte_unmap_unlock(vmf->pte, vmf->ptl);
return handle_userfault(vmf, VM_UFFD_WP);
/*
* Userfaultfd write-protect can defer flushes. Ensure the TLB
* is flushed in this case before copying.
*/
if (unlikely(userfaultfd_wp(vmf->vma) &&
mm_tlb_flush_pending(vmf->vma->vm_mm)))
flush_tlb_page(vmf->vma, vmf->address);
vmf->page = vm_normal_page(vma, vmf->address, vmf->orig_pte); ///查找缺页异常地址对应页面的page数据结构,返回为NULL,说明是一个特殊页面
if (!vmf->page) ///处理特殊页面
/*
* VM_MIXEDMAP !pfn_valid() case, or VM_SOFTDIRTY clear on a
* VM_PFNMAP VMA.
*
* We should not cow pages in a shared writeable mapping.
* Just mark the pages writable and/or call ops->pfn_mkwrite.
*/
if ((vma->vm_flags & (VM_WRITE|VM_SHARED)) ==
(VM_WRITE|VM_SHARED)) ///特殊页面,且vma是可写且共享
return wp_pfn_shared(vmf); ///复用
pte_unmap_unlock(vmf->pte, vmf->ptl);
return wp_page_copy(vmf); ///vma不是可写共享页面,写时拷贝
/*
* Take out anonymous pages first, anonymous shared vmas are
* not dirty accountable.
*/
if (PageAnon(vmf->page)) ///PageAnon判断是否为匿名页面
struct page *page = vmf->page;
/* PageKsm() doesnt necessarily raise the page refcount */
if (PageKsm(page) || page_count(page) != 1)
goto copy;
if (!trylock_page(page))
goto copy;
if (PageKsm(page) || page_mapcount(page) != 1 || page_count(page) != 1)
unlock_page(page);
goto copy;
/*
* Ok, weve got the only map reference, and the only
* page count reference, and the page is locked,
* its dark out, and were wearing sunglasses. Hit it.
*/
unlock_page(page);
wp_page_reuse(vmf); ///PageAnon判断是否为匿名页面,且不为KSM匿名页面, 复用
return VM_FAULT_WRITE;
else if (unlikely((vma->vm_flags & (VM_WRITE|VM_SHARED)) ==
(VM_WRITE|VM_SHARED)))
return wp_page_shared(vmf); ///处理可写的共享页面,复用
copy:
/*
* Ok, we need to copy. Oh, well..
*/
get_page(vmf->page);
pte_unmap_unlock(vmf->pte, vmf->ptl);
return wp_page_copy(vmf); ///处理写时复制的情况
linux源码解析11–缺页异常之swap缺页异常(代码片段)
接上篇https://www.daodaodao123.com/?p=776本篇解析缺页异常分支之一,swap缺页异常;1.swap分区的来由当系统内存不足时,首先回收pagecache页面,仍然不足时,继续回收匿名页面,但是匿名页面没有对应文件,因此建立一个swap文件,来... 查看详情
linux源码解析09–缺页异常之文件映射(代码片段)
...。(3)匿名映射为共享时,走shmem,等同文件映射处理。3.源码解析do_fault()函数staticvm_fault_tdo_fault(structvm_fault*vmf)... if(!vma->vm_ops->fault)///处理没有实现fault()回调函数的情况,出错处理... elseif(!(vmf->flags&FAULT_FLAG_WRITE)) ret=do_... 查看详情
linux内核源码分析之缺页异常
目录一、什么是缺页异常?二、处理器特定部分2.1生成页错误异常2.2处理页错误异常三、匿名页的缺页异常四、文件的缺页异常4.1处理文件页错误,具体处理读文件页错误的方法4.2文件写私有文件页错误的方法4.3文件写... 查看详情
linux0.11-写时复制-30(代码片段)
...章,通过看看Linux0.11这个最简单的操作系统,从源码层面把写时复制的原理搞清楚。很简单哦,你可别中途就放弃了。直接干!哦不行, 查看详情
linux0.11-写时复制-30(代码片段)
...章,通过看看Linux0.11这个最简单的操作系统,从源码层面把写时复制的原理搞清楚。很简单哦,你可别中途就放弃了。直接干!哦不行, 查看详情
性能优化之写时复制(copy-on-write:cow)
写入时复制(英语:Copy-on-write,简称COW)是一种计算机程序设计领域的优化策略。其核心思想是,如果有多个调用者(callers)同时请求相同资源(如内存或磁盘上的数据存储),他们会共... 查看详情
缺页中断和缺页异常
缺页异常 会出现缺页异常的情况:线性地址不在虚拟地址空间中线性地址在虚拟地址空间中,但没有访问权限接上一条,没有与物理地址建立映射关系fork等系统调用时并没有映射物理页,写数据->缺页异常->写时拷贝映... 查看详情
sylixos异常管理(代码片段)
...一种平台无关的异常管理方法。当系统产生了写时拷贝、缺页等MMU访问失效异常或其他种类异常时,SylixOS会在异常中构建异常处理线程执行陷阱,在异常退出后的任务上下文中进行异常处理,完成所有的页面操作,如果操作失... 查看详情
并发容器之写时拷贝的list和set
对于一个对象来说,我们为了保证它的并发性,通常会选择使用声明式加锁方式交由我们的Java虚拟机来完成自动的加锁和释放锁的操作,例如我们的synchronized。也会选择使用显式锁机制来主动的控制加锁和释放锁的操作,例如... 查看详情
我可以在 Linux 中进行写时复制 memcpy 吗?
】我可以在Linux中进行写时复制memcpy吗?【英文标题】:CanIdoacopy-on-writememcpyinLinux?【发布时间】:2010-12-0614:46:51【问题描述】:我有一些代码经常复制一大块内存,通常是在对其进行非常小的更改之后。我已经实现了一个跟踪更... 查看详情
linux内核源码分析之巨型页(代码片段)
目录巨型页概述ARM64处理器支持巨型页标准巨型页透明巨型页巨型页概述 当运行内存需求量较大的应用程序时,如果使用长度为4KB的页,将会产生较多的TLB未命中和缺页异常,严重影响应用程序的性能。如... 查看详情
linux内核内存管理内存映射原理②(内存映射概念|文件映射|匿名映射|内存映射原理|分配虚拟内存页|产生缺页异常|分配物理内存页|共享内存|进程内存)(代码片段)
...存映射概念二、内存映射原理1、分配虚拟内存页2、产生缺页异常3、分配物理内存页三、共享内存四、进程内存段的内存映射类型一、内存映射概念内存映射概念:"内存映射“就是在进程的”用户虚拟地址空间"中,创建一个... 查看详情
2021-12-03wpf上位机104-西门子s7协议之写数据方法流程解析(代码片段)
文章目录前言一、西门子S7协议之写数据方法流程解析二、使用步骤1.写数据总结前言随着人工智能的不断发展,物联网这门技术也越来越重要,很多人都开启了物联网学习,本文就介绍了物联网的S7报文协议。提示:以下是本篇... 查看详情
copyonwritearraylist源码解析(基于jdk8)
目录1基本属性和方法2读3写3.1add3.2remove3.3set/clear4迭代器5copyOnWriteArraySetCopyOnWriteArrayList是一种写时复制的ArrayList,在写操作时加锁,拷贝原数组成员,在拷贝的数组上进行修改,并重置数组。该类对于读写可以并发执行,如果写... 查看详情
netty源码之写数据(代码片段)
目录前言和快递比较一下几种场景发送数据的分类写数据flush数据前言write是把数据写到buf里flush是将数据发出去writeAndFlush写入数据到buf并立刻发送出去和快递比较一下 几种场景1、netty协数据,写不进去了,会停止写ÿ... 查看详情
netty源码之写数据(代码片段)
目录前言和快递比较一下几种场景发送数据的分类写数据flush数据前言write是把数据写到buf里flush是将数据发出去writeAndFlush写入数据到buf并立刻发送出去和快递比较一下 几种场景1、netty协数据,写不进去了,会停止写ÿ... 查看详情
利用“写时复制”将数据复制到 Multiprocessing.Pool() 工作进程
】利用“写时复制”将数据复制到Multiprocessing.Pool()工作进程【英文标题】:Leveraging"Copy-on-Write"toCopyDatatoMultiprocessing.Pool()WorkerProcesses【发布时间】:2016-10-3106:20:53【问题描述】:我有一点multiprocessingPython代码,看起来有... 查看详情
nacos源码解析
参考技术ANacos服务注册表结构:Map<namespace,Map<group::serviceName,Service>>举例说明:1.查看spring.factories文件中帮我们自动装配的类2.查看自动装配的类3.NacosAutoServiceRegistration里的调用链路--添加到队列里调用链路为Cluster类的成... 查看详情