内存管理基础学习笔记 - 4.3 缺页中断处理 - handle_pte_fault

1. 前言

本专题我们开始学习内存管理部分,本文为缺页中断处理相关学习笔记。本文主要参考了《奔跑吧, Linux内核》、ULA、ULK的相关内容。
前面概述部分主要介绍了arm64缺页中断的底层逻辑,本文主要以数据异常的do_page_fault为例来说明缺页的详细处理过程。主要介绍do_page_fault->handle_pte_fault的执行过程,handle_pte_fault主要采用vm_fault数据结构来管理很多参数,它主要通过vma首先判断addr所对应的pte是否为空,如果为空则进一步判断是匿名映射还是文件映射,并创建pte加入到pte table;如果不为空,则进一步根据pte页表项,触发页面换入、写时复制等

2. handle_pte_fault

handle_pte_fault(&vmf)
    |--vmf->pte = pte_offset_map(vmf->pmd, vmf->address)
    |--vmf->orig_pte = *vmf->pte
    |--barrier()
    |------//>>>>>>第1种情形:PTE为空
    |--if (!vmf->pte)  
    |      //第1.1种情形:vma是匿名映射线性区
    |      if (vma_is_anonymous(vmf->vma)) 
    |          return do_anonymous_page(vmf)//匿名映射处理
    |      //第1.2种情形:vma是文件映射线性区
    |      else                            
    |          return do_fault(vmf)        //文件映射处理
    |------//>>>>>>//第2种情形:PTE不为空
    |--else           
    |      //pte页表项PRESENT未置位,说明PTE还没有映射物理页面(没有页面或页面被交换出去)
    |      if (!pte_present(vmf->orig_pte))
    |          return do_swap_page(vmf) //从交换分区读回页面
    |      if (pte_protnone(vmf->orig_pte) && vma_is_accessible(vmf->vma))
    |          return do_numa_page(vmf);
    |      //如果是写内存触发的缺页异常
    |      if (vmf->flags & FAULT_FLAG_WRITE)
    |          if (!pte_write(entry)) //pte具有只读属性
    |              return do_wp_page(vmf) //写时复制(如父子进程共享的内存,一方需要写入时)
    |          entry = pte_mkdirty(entry)//pte可写时,标记为脏,可写为何会触发page fault?
    |      //标记pte项刚刚被访问过,以免页面被换出
    |      entry = pte_mkyoung(entry) 
    |      //判断pte内容是否变化
    \      if (ptep_set_access_flags(vmf->vma, vmf->address, vmf->pte, entry,
               vmf->flags & FAULT_FLAG_WRITE))
               update_mmu_cache(vmf->vma, vmf->address, vmf->pte)//刷新对应的TLB和高速缓存

handle_pte_fault主要采用vm_fault数据结构来管理很多参数,它主要通过vma首先判断addr所对应的pte是否为空,主要区分两种情形进行处理:

  1. PTE为空:需要进一步区分是匿名映射还是文件映射发生的page fault,分别进行不同处理。
    (1)do_anonymous_page
    对于匿名映射则通过do_anonymous_page分别区分vma是只读和可写两种情况,分配物理页面,并设置pte到硬件页表;
    (2)do_fault
    对于文件映射则通过do_fault区分不同情况,总体是将文件内容读取到物理页面,并为此物理页面建立与缺页地址的映射关系
    do_read_fault:缺页由读内存导致,do_read_fault将文件内容读取到vmf->page页面,并为此物理页面建立与缺页地址的映射关系
    do_cow_fault:缺页由写内存导致,且是非共享映射。do_cow_fault分配vmf->page页面,并将文件内容读取到vmf->page页面,将vmf->page拷贝到vmf->cow_page,并为vmf->cow_page页面分配pte, 建立缺页地址与vmf->page页面的映射关系
    do_shared_fault:缺页由写内存导致,且是共享映射。do_shared_fault将文件内容读取到vmf->page页面,并为此物理页面建立与缺页地址的映射关系

  2. PTE不为空:需要进一步区分几种情况,如页面是否已经换出、是否是访问权限不匹配等分别作不同处理。
    (1)do_numa_page
    (2)do_swap_page
    由于匿名物理页面被回收了,所以进程再次访问一块虚拟地址时,就会产生缺页中断,最终进入到 do_swap_page,在这个函数中会重新分配新的页面,然后再从swap分区读回这块虚拟地址对应的数据。
    (3)do_wp_page
    写时复制一般发生在父子进程之间,fork时copy_mm时会将父进程和子进程的页表项都设置为只读,无论是父进程或子进程执行写入都会触发page fault,通过此函数执行写时复制,分配新的page,拷贝旧page到新page, 并修改相应的页表项为读写。参数vmf->vma保存了线性区原有的读写权限

2.1 do_anonymous_page(匿名映射缺页中断)

do_anonymous_page(vmf)
    |  //防止共享的VMA进入匿名页面的缺页中断?
    |--if (vma->vm_flags & VM_SHARED)
    |      return VM_FAULT_SIGBUS
    |  //分配一个pte,此时使用pte_offset_map不安全?
    |--pte_alloc(vma->vm_mm, vmf->pmd) 
    |------//>>>>>>vma是只读的情况
    |--if (!(vmf->flags & FAULT_FLAG_WRITE) && mm_forbids_zeropage(vma->vm_mm))
    |      //获取0页帧号,设置pte的special位
    |      entry = pte_mkspecial(pfn_pte(my_zero_pfn(vmf->address),vma->vm_page_prot))
    |      vmf->pte = pte_offset_map_lock(vma->vm_mm, vmf->pmd,vmf->address, &vmf->ptl)
    |      if (userfaultfd_missing(vma))
    |          return handle_userfault(vmf, VM_UFFD_MISSING)//传递page fault到用户空间
    |      goto setpte
    |-------//>>>>>>vma可写的情况
    |--page = alloc_zeroed_user_highpage_movable(vma, vmf->address) //分配可移动的匿名页面,底层通过alloc_page
    |      \--alloc_page_vma(GFP_HIGHUSER | movableflags,vma, vaddr)
    |--mem_cgroup_charge(page, vma->vm_mm, GFP_KERNEL)
    |--group_throttle_swaprate(page, GFP_KERNEL)
    |--__SetPageUptodate(page)
    |--entry = mk_pte(page, vma->vm_page_prot)//设置pte指向新分配的匿名页
    |--entry = pte_sw_mkyoung(entry)
    |--if (vma->vm_flags & VM_WRITE)
    |      entry = pte_mkwrite(pte_mkdirty(entry)) //设置页表项可写
    |--vmf->pte = pte_offset_map_lock(vma->vm_mm, vmf->pmd, vmf->address,&vmf->ptl)//获取address的pte页表项地址
    |--inc_mm_counter_fast(vma->vm_mm, MM_ANONPAGES)//递增进程的匿名页面数目anon_rss
    |--page_add_new_anon_rmap(page, vma, vmf->address, false)//匿名页面添加到RMAP系统
    |--lru_cache_add_inactive_or_unevictable(page, vma)
    |--setpte:
    |--set_pte_at(vma->vm_mm, vmf->address, vmf->pte, entry)//将pte页表项值entry设置到硬件page_table页表项
    |--update_mmu_cache(vma, vmf->address, vmf->pte)
    

do_anonymous_page分别区分vma是只读和可写两种情况,分配物理页面,并设置pte到硬件页表

  1. vma->vm_flags & VM_SHARED:防止共享的VMA进入匿名页面的缺页中断,发现是共享匿名映射,在缺页中断处理则退出?

  2. pte_alloc:分配一个pte

  3. vma是只读的情况:
    (0)my_zero_pfn:获取0化页面的物理页帧号;
    (1)pte_mkspecial:置pte的special位,表示这是一个特殊页面
    (2)pte_offset_map_lock:获取address的pte页表项地址
    (3)跳转到setpte
    (4)set_pte_at:将pte页表项值entry设置到硬件page_table页表项
    (5)update_mmu_cache:更新页表项的TLB cache
    vma可写的情况:
    (1)alloc_zeroed_user_highpage_movable:分配可移动的匿名页面,底层通过alloc_page
    (2)mk_pte:设置pte指向新分配的匿名页
    (3)pte_offset_map_lock:获取address的pte页表项地址
    (4)page_add_new_anon_rmap:匿名页面添加到RMAP系统
    (5)lru_cache_add_inactive_or_unevictable
    (6)set_pte_at:将pte页表项值entry设置到硬件page_table页表项
    (7)update_mmu_cache:更新页表项的TLB cache

page_add_new_anon_rmap(page, vma, vmf->address, false)
    |--__SetPageSwapBacked(page)
    |--atomic_set(&page->_mapcount, 0)/* increment count (starts at -1) */
    |--__mod_lruvec_page_state(page, NR_ANON_MAPPED, nr)
    \--__page_set_anon_rmap(page, vma, address, 1)
           |--struct anon_vma *anon_vma = vma->anon_vma
           |--if (PageAnon(page))
           |      return;
           |--anon_vma = (void *) anon_vma + PAGE_MAPPING_ANON//最低位置1表示匿名映射
           |--page->mapping = (struct address_space *) anon_vma
           \--page->index = linear_page_index(vma, address)//保存了pte的索引

PageAnon: 通过page->mapping的bit0是否为1来判断是否为匿名页面, 为1则为匿名映射页,为0则为文件映射页,如果页面已经为匿名页说明已经将此页做过处理

关于page的mapping变量
page的mapping变量用于判断页是映射页还是匿名页。
(1)对于交换高速缓存页面,mapping指向交换分区的swapper_spaces的address_space对象
(2)对于匿名页面,mapping最低位是1,mapping保存vma->anon_vma的地址,anon_vma为匿名页所在线性区VMA链表的链表头
(3)对于文件映射页面,mapping最低位为0,mapping指向对应文件的address_space对象
(4) 对于KSM页面,mapping最低两位均为1
注:
a.mapping的bit0用于判断匿名页还是文件页
b.mapping的bit1用于判断是否为LRU页面
c.address_space对象地址在64bit系统RAM中按8字节对齐,所以最低位可以使用
d.PageAnon以页描述符为参数,返回mapping的最低位是否置1

下图可以看出page, 匿名线性区链表anon_vma, vma之间的关系

https://blog.csdn.net/u012489236/article/details/114734823
在这里插入图片描述

2.2 do_fault(文件映射缺页中断)

do_fault(struct vm_fault *vmf)
    |--if (!vma->vm_ops->fault)
    |      //获取锁,防止获取的是pte的临时状态?
    |      vmf->pte = pte_offset_map_lock(vmf->vma->vm_mm,vmf->pmd,
    |                  vmf->address,&vmf->ptl)
    |      //无效的pte
    |      if (unlikely(pte_none(*vmf->pte))) 
    |          ret = VM_FAULT_SIGBUS;
    |      //有效的pte
    |      else                              
    |          ret = VM_FAULT_NOPAGE;//本次缺页不需要返回一个新的页面
    |      pte_unmap_unlock(vmf->pte, vmf->ptl)
    |  //缺页由读内存导致
    |--else if (!(vmf->flags & FAULT_FLAG_WRITE)) 
    |      ret = do_read_fault(vmf)
    |  // 缺页由写内存导致,且是非共享映射
    |--else if (!(vma->vm_flags & VM_SHARED)) 
    |      ret = do_cow_fault(vmf)//写时复制
    |  //缺页由写内存导致,且是共享映射
    \--else                                  
           ret = do_shared_fault(vmf)
          

do_fault会区分不同情况,总体是将文件内容读取到物理页面,并为此物理页面建立与缺页地址的映射关系

  1. pte_offset_map_lock:获取锁,防止获取的是pte的临时状态?其它CPU可能对pte进行读-修改-写入操作

  2. do_read_fault:缺页由读内存导致,do_read_fault将文件内容读取到vmf->page页面,并为此物理页面建立与缺页地址的映射关系

  3. do_cow_fault:缺页由写内存导致,且是非共享映射。do_cow_fault分配vmf->page页面,并将文件内容读取到vmf->page页面,将vmf->page拷贝到vmf->cow_page,并为vmf->cow_page页面分配pte, 建立缺页地址与vmf->page页面的映射关系

  4. do_shared_fault:缺页由写内存导致,且是共享映射。do_shared_fault将文件内容读取到vmf->page页面,并为此物理页面建立与缺页地址的映射关系

2.2.1 do_read_fault

do_read_fault(vmf)
    |--if (vma->vm_ops->map_pages && fault_around_bytes >> PAGE_SHIFT > 1)
    |      ret = do_fault_around(vmf)
    |          |--vmf->vma->vm_ops->map_pages(vmf, start_pgoff, end_pgoff)
    |--__do_fault(vmf)
    |      |--ret = vma->vm_ops->fault(vmf)//将文件内容读取到vmf->page
    |      |--if (unlikely(!(ret & VM_FAULT_LOCKED))) 
    |             lock_page(vmf->page) //为页面加锁?
    |--ret |= finish_fault(vmf)
    |      |--page = vmf->page //获取缺页异常对应的物理页面
    |      |--alloc_set_pte(vmf, page) //分配pte, 建立虚拟地址与物理页面的映射关系
    \--unlock_page(vmf->page);

do_read_fault将文件内容读取到vmf->page页面,并为此物理页面建立与缺页地址的映射关系

  1. do_fault_around:若定义了vma->vm_ops->map_pages,可以在缺页异常地址附近提前映射尽可能多的页面(不会实际创建page cache),提高效率。fault_around_bytes是全局变量,默认65536字节,16个页面。do_fault_around以当前缺页地址为中心,通过vmf->vma->vm_ops->map_pages提前为缺页地址附近的页面映射pte,减少缺页中断发生的次数。

  2. __do_fault:此处的vma->vm_ops->fault(vmf)会调用具体文件系统定义的fault回调(如f2fs为f2fs_filemap_fault),最终将调用filemap_fault,进而调用mapping->a_ops->readpage将文件内容读取到vmf->page页面

  3. finish_fault:获取缺页异常对应的物理页面,并为此物理页面建立与缺页地址的映射关系

2.2.2 do_cow_fault

do_cow_fault(vmf)
    |--struct vm_area_struct *vma = vmf->vma
    |--vmf->cow_page = alloc_page_vma(GFP_HIGHUSER_MOVABLE, vma, vmf->address)//分配vmf->cow_page页面
    |--ret = __do_fault(vmf)//将文件内容读取到vmf->page页面
    |--copy_user_highpage(vmf->cow_page, vmf->page, vmf->address, vma)//将vmf->page拷贝到vmf->cow_page
    |--__SetPageUptodate(vmf->cow_page)
    |--ret |= finish_fault(vmf)//获取缺页异常对应的物理页面,并为此物理页面建立与缺页地址的映射关系
    |      |--page = vmf->cow_page //获取缺页异常对应的物理页面
    |      |--alloc_set_pte(vmf, page) //分配pte, 建立缺页地址与物理页面的映射关系
    |             |--page_add_new_anon_rmap(page, vma, vmf->address, false)//?
    |--unlock_page(vmf->page) //解锁页面
    \--put_page(vmf->page)

do_cow_fault:分配vmf->page页面,并将文件内容读取到vmf->page页面,并为vmf->page页面分配pte, 建立缺页地址与vmf->page页面的映射关系

  1. alloc_page_vma:分配vmf->cow_page页面

  2. __do_fault:将文件内容读取到vmf->page页面

  3. copy_user_highpage:将vmf->page拷贝到vmf->cow_page

  4. finish_fault:获取缺页异常对应的物理页面,并为此物理页面建立与缺页地址的映射关系

2.2.3 do_shared_fault

do_shared_fault(vmf) 
    |--ret = __do_fault(vmf)//将文件内容读取到vmf->page页面
    |--if (vma->vm_ops->page_mkwrite)
    |      unlock_page(vmf->page)
    |      do_page_mkwrite(vmf) //通知用户空间,只读页面将变成可写
    |--ret |= finish_fault(vmf) //获取缺页异常对应的物理页面,并为此物理页面建立与缺页地址的映射关系
    |      |--page = vmf->page //获取缺页异常对应的物理页面
    |      |--alloc_set_pte(vmf, page) //分配pte, 建立虚拟地址与物理页面的映射关系
    |             |--page_add_file_rmap(page, false)
    \--ret |= fault_dirty_shared_page(vmf) 

do_shared_fault将文件内容读取到vmf->page页面,并为此物理页面建立与缺页地址的映射关系

  1. __do_fault:将文件内容读取到vmf->page页面

  2. do_page_mkwrite(vmf) :通知用户空间,只读页面将变成可写

  3. finish_fault:获取缺页异常对应的物理页面vmf->page,并为此物理页面建立与缺页地址的映射关系

下图可以看出page, 文件映射线性区rb_tree, vma之间的关系

from: https://blog.csdn.net/u012489236/article/details/114734823在这里插入图片描述

2.3 do_numa_page

2.4 do_swap_page(页面换入)

swapin 的入口是do_swap_page,由于匿名物理页面被回收了,所以进程再次访问一块虚拟地址时,就会产生缺页中断,最终进入到 do_swap_page,在这个函数中会重新分配新的页面,然后再从swap分区读回这块虚拟地址对应的数据。

from:https://blog.csdn.net/qkhhyga2016/article/details/88722458
Linux swap机制是基于内存管理之下建立的,由于swap会对磁盘进行读写,所以设计上与page cache 相类似地建立了一个 swap cache 的缓存,提高swap的读写效率。swap core 是swap的核心层,主要完成管理各个swap分区,决策内存数据需要交换到哪个磁盘上,以及发起读写请求的工作。
swap分区的swapper_spaces[][]是一个全局数组,当用户通过 swapon 命令增加一个swap 分区时,会分配N个 struct address_space 变量,并加到全局数组变量swapper_spaces[][]中,一个struct address_space 管理的swap 空间大小是64M,即一个swap分区分成N个大小为64M的struct address_space,比如192M的swap 分区,在创建时被拆出了3个struct address_space,而全局数组变量swapper_spaces[][]的第一维元素是代表第几个swap分区,第二维存储的是该swap分区所有的64M大小的struct address_space
在这里插入图片描述
1.当内存管理模块需要回收一个匿名页面的时候,首先通过swap core 选择合适的swap 分区,并修改pte,指向swap分区的存储块位置,同时把该页面加入到 swap cache 缓存起来,然后再通过swap core 发起回写请求进行回写,等待回写结束后,内存管理模块释放该页面。
2.当用户需要访问处于swap分区的数据时,首先先通过内存管理模块确定pte的swap entry,然后在swap cache 中快速地查找swap entry 对应的物理页面,如果这时物理页面仍末被回收,就能找到对应的页面,则直接修改pte,指向该page,重新建立映射,如果没找到对应的页面,表明物理页面已经被回收了,则在swap core 中通过 swap entry 查找对应的swap 分区和数据地址,最后申请一个page并发起读操作(swapin),等待读操作完成后,修改pte,指向该page,重新建立映射
3.swapin 的入口是do_swap_page,由于匿名物理页面被回收了,所以进程再次访问一块虚拟地址时,就会产生缺页中断,最终进入到 do_swap_page,在这个函数中会重新分配新的页面,然后再从swap分区读回这块虚拟地址对应的数据。

2.5 do_wp_page(写时复制)

do_wp_page(vmf)
    |--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)
    |--vmf->page = vm_normal_page(vma, vmf->address, vmf->orig_pte)
    |  //----处理特殊映射的写时复制------
    |  //page为NULL说明是一个特殊映射参考《内存管理基础-3.2 进程地址空间-brk系统调用》
    |--if (!vmf->page)
    |      if ((vma->vm_flags & (VM_WRITE|VM_SHARED)) ==            //共享可写页面
    |            (VM_WRITE|VM_SHARED))
    |          return wp_pfn_shared(vmf)//处理可写并且共享的特殊映射页面
    |      else                                                     //非共享可写页面
    |          pte_unmap_unlock(vmf->pte, vmf->ptl)
    |          return wp_page_copy(vmf)//处理写时复制  
    |  // -------处理普通映射的写时复制------
    |--if (PageAnon(vmf->page))                                     //匿名页面
    |      struct page *page = vmf->page
    |      unlock_page(page)
    |      wp_page_reuse(vmf)//处理可以复用的页面
    |      return VM_FAULT_WRITE
    |--else if (unlikely((vma->vm_flags & (VM_WRITE|VM_SHARED))==  //共享可写页面
    |           (VM_WRITE|VM_SHARED)))
    |      return wp_page_shared(vmf)//处理可写的并且共享的普通映射页面
    \--else                                                        //文件映射,非共享可写页面
           return wp_page_copy(vmf)//处理写时复制      

写时复制一般发生在父子进程之间,fork时copy_mm时会将父进程和子进程的页表项都设置为只读,无论是父进程或子进程执行写入都会触发page fault,通过此函数执行写时复制,分配新的page,拷贝旧page到新page, 并修改相应的页表项为读写。参数vmf->vma保存了线性区原有的读写权限

  1. wp_pfn_shared:处理可写并且共享的特殊映射页面(包括VM_MIXEDMAP或VM_PFNMAP页面),并调用wp_page_reuse函数来复用缺页异常页面

  2. wp_page_reuse:处理可以复用的页面

  3. wp_page_copy:处理写时复制,分配新的page,拷贝旧页面到新页面,并根据vm_page_prot生成新的pte,添加到硬件页表

  4. wp_page_shared:处理可写的并且共享的普通映射页面,并调用wp_page_reuse函数来复用缺页异常页面

2.5.1 wp_page_reuse

wp_page_reuse(vmf)
    |--struct vm_area_struct *vma = vmf->vma
    |--struct page *page = vmf->page
    |--pte_t entry
    |--flush_cache_page(vma, vmf->address, pte_pfn(vmf->orig_pte))//flush缺页异常页面的cache
    |--entry = pte_mkyoung(vmf->orig_pte)//设置pte的PTE_AF bit
    |--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)

wp_page_reuse复用缺页异常页面,通过修改原页面的pte由PTE_RDONLY变为PTE_WRITE,并重新设置到页表

  1. pte_mkyoung:设置pte的PTE_AF bit

  2. pte_mkdirty: 如果pte具有PTE_WRITE属性,清除PTE_RDONLY

  3. maybe_mkwrite:如果vma具有VM_WRITE属性,则对pte设置PTE_WRITE属性,清除PTE_RDONLY

  4. ptep_set_access_flags:将新的pte设置到页表

2.5.2 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;
    |--if (unlikely(anon_vma_prepare(vma)))
    |      goto oom;
    |--if (is_zero_pfn(pte_pfn(vmf->orig_pte))) //pte映射到0化页面
    |      new_page = alloc_zeroed_user_highpage_movable(vma,vmf->address)//分配一个全0的page
    |--else                                     //pte映射到非0化页面
    |      new_page = alloc_page_vma(GFP_HIGHUSER_MOVABLE, vma,vmf->address) //分配一个新的页面
    |	   cow_user_page(new_page, old_page, vmf) //拷贝内容给新分配的页面
    |--__SetPageUptodate(new_page)//表示新页面内容有效
    |--mmu_notifier_range_init(&range, MMU_NOTIFY_CLEAR, 0, vma, mm,...)//初始化mm_notiifer告知系统oldpage将无效?
    |--mmu_notifier_invalidate_range_start(&range)
    |  //由于分配页会导致进程阻塞,因此检测pte是否发生改变
    |--vmf->pte = pte_offset_map_lock(mm, vmf->pmd, vmf->address, &vmf->ptl)
    |--if (likely(pte_same(*vmf->pte, vmf->orig_pte))) //pte没有发生改变
    |      根据old_page页面是匿名还是文件映射,增加或减少相应的计数
    |      entry = mk_pte(new_page, vma->vm_page_prot) //根据新页和vm_page_prot生成新的pte
    |      entry = pte_sw_mkyoung(entry) 
    |      //copy_mm时会将父子进程的页表项设置为只读,此处是恢复页表项原有的读写权限?
    |      entry = maybe_mkwrite(pte_mkdirty(entry), vma);//设置PTE_WRITE,清PTE_RDONLY
    |      ptep_clear_flush_notify(vma, vmf->address, vmf->pte)//读取并清空pte,之后flush tlb
    |      page_add_new_anon_rmap(new_page, vma, vmf->address, false)//添加到RMAP系统
    |      lru_cache_add_inactive_or_unevictable(new_page, vma) //添加到活跃LRU链表
    |      set_pte_at_notify(mm, vmf->address, vmf->pte, entry)//新的 pte添加到硬件页表
    |      update_mmu_cache(vma, vmf->address, vmf->pte) //刷新TLB
    |      page_remove_rmap(old_page, false) //将旧页从rmap系统删除
    |      new_page = old_page
    |--else //pte发生了改变
    |      update_mmu_tlb(vma, vmf->address, vmf->pte)//刷新tlb
    |--put_page(old_page) //将旧页释放,表示当前进程不在拥有old_page

wp_page_copy分配新的page,拷贝旧页面到新页面,并根据vm_page_prot生成新的pte,添加到硬件页表

  1. alloc_zeroed_user_highpage_movable/alloc_page_vma: 根据pte是否指向0化页面来决定是否分配全0的page;

  2. cow_user_page:拷贝旧页面内容给新分配的页面

  3. __SetPageUptodate: 表示新页面内容有效

  4. mmu_notifier_range_init: 初始化mm_notiifer告知系统old_page将无效?

  5. pte_offset_map_lock: 由于前面分配页会导致进程阻塞,因此检测pte是否发生改变

  6. 根据old_page页面是匿名还是文件映射,增加或减少相应的计数

  7. mk_pte:根据新页和vm_page_prot生成新的pte

  8. maybe_mkwrite:以fork为例,copy_mm时会将父子进程的页表项设置为只读,此处是恢复页表项原有的读写权限?

  9. page_add_new_anon_rmap:新页添到RMAP系统

  10. lru_cache_add_inactive_or_unevictable:新页添加到活跃LRU链表

  11. page_remove_rmap:将旧页从rmap系统删除

  12. put_page:将旧页释放,表示当前进程不在拥有old_page

参考文档

  1. linux内存管理笔记(三十八)----反向映射
  2. 奔跑吧,Linux内核
  • 4
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值