简单来说,讨论linux页表就是讨论linux进程的的页表:linux页表的创建与更新都包含于进程的创建与更新中。当前的linux内核采用的是写时复制方法,在创建一个linux进程时,完全复制父进程的页表,并且将父子进程的页表均置为写保护(即写地址的时候会产生缺页异常等)。那么父子进程谁向地址空间写数据时,产生缺页异常,分配新的页,并将两个页均置为可写,按照这种方式父子进程的地址空间渐渐变得不同。
    按照上面的分析, 只需要讨论第一个进程页表初始化,进程创建时页表的拷贝,以及缺页异常时页表的更新即可。
1.init_task进程页表的初始化
    init_task的地址空间是init_mm, init_mm在内核初始化的时候就赋值给了current->active_mm. init_mm的初始化页表是swapper_pg_dir,在mips架构中swapper_pg_dir初始化在函数pagetable_init中,初始化关系是
swapper_pg_dir -> invalide_pmd_table -> invalide_pte_table 或
swapper_pg_dir -> invalide_pte_table.
即在init_mm中,页表指向的全部是invalide_pte_table。

2.创建进程时页表的拷贝
    进程创建一般调用的是do_fork函数,按照如下调用关系:
    do_fork->copy_process->copy_mm->dup_mm->dup_mmap->copy_page_range
    找到copy_page_range函数,这个函数便是负责页表的拷贝,函数核心代码如下:
 874     do {
 875         next = pgd_addr_end(addr, end);
 876         if (pgd_none_or_clear_bad(src_pgd))
 877             continue;
 878         if (unlikely(copy_pud_range(dst_mm, src_mm, dst_pgd, src_pgd,
 879                         vma, addr, next))) {
 880             ret = -ENOMEM;
 881             break;
 882         }
 883     } while (dst_pgd++, src_pgd++, addr = next, addr != end);
     copy_pud_range便是拷贝pud表,copy_pud_range调用copy_pmd_range, copy_pmd_range调用copy_pte_range,以此完成对三级页表的复制。需要注意的是在copy_pte_range调用的copy_one_pte中有如下代码:
 694     if (is_cow_mapping(vm_flags)) {
 695         ptep_set_wrprotect(src_mm, addr, src_pte);
 696         pte = pte_wrprotect(pte);
 697     }
这里便是判断如果采用的是写时复制,便将父子页均置为写保护,即会产生如下所示的缺页异常。

3.缺页异常时页表的更新
  由页表的初始化可以看到,init_mm的页表全指向无效页表,然而普通的进程中不可能页表均指向无效项,因此肯定拥有一个不断扩充页表的机制,这个机制是通过缺页异常实现的。
  以mips为例,mips的缺页异常最终会调用do_page_fault,do_page_fault调用handle_mm_fault,handle_mm_fault是公共代码,一般所有的缺页异常均会调用handle_mm_fault的核心代码如下:
3217     pud = pud_alloc(mm, pgd, address);
3218     if (!pud)
3219         return VM_FAULT_OOM;
3220     pmd = pmd_alloc(mm, pud, address);
3221     if (!pmd)
3222         return VM_FAULT_OOM;
3223     pte = pte_alloc_map(mm, pmd, address);
3224     if (!pte)
3225         return VM_FAULT_OOM;
其中pud_alloc代码如下:
1056 static inline pud_t *pud_alloc(struct mm_struct *mm, pgd_t *pgd, unsigned long address)
1057 {   
1058     return (unlikely(pgd_none(*pgd)) && __pud_alloc(mm, pgd, address))?
1059         NULL: pud_offset(pgd, address);
1060 }   
其中pgd_none用于判断pgd是否为invalide,如果是可调用__pud_alloc,如果不是获得其地址继续查。
pmd_alloc函数和pte_alloc_map函数类似。

因此可以看出,在缺页异常中,会按照地址一次查三张页表,如果页表为invalide,比如invalide_pmd_table或invalide_pte_table,则会分配一个新的页表项取代invalide的页表项。这便是页表扩充的机制。

需要注意的是handle_mm_fault最终会调用handle_pte_fault,在handle_pte_fault函数中有如下代码:
3171     if (flags & FAULT_FLAG_WRITE) {
3172         if (!pte_write(entry))
3173             return do_wp_page(mm, vma, address,
3174                     pte, pmd, ptl, entry);
3175         entry = pte_mkdirty(entry);
3176     }
即在缺页异常中如果遇到写保护会调用do_wp_page,这里面会处理上面所说的写时复制中父子进程区分的问题。

如上三个部分便是linux页表的大体处理框架

三级页表结构示意图[zz]


图3.3 Linux的三级页表结构

Linux总是假定处理器有三级页表。每个页表通过所包含的下级页表的页面框号来访问。图3.3给出了虚拟地址是如何分割成多个域的,每个域提供了 某个指定页表的偏移。为了将虚拟地址转换成物理地址,处理器必须得到每个域的值。这个过程将持续三次直到对应于虚拟地址的物理页面框号被找到。最后再使用 虚拟地址中的最后一个域,得到了页面中数据的地址。

为了实现跨平台运行,Linux提供了一系列转换宏使得核心可以访问特定进程的页表。这样核心无需知道 页表入口的结构以及它们的排列方式。

这种策略相当成功,无论在具有三级页表结构的Alpha AXP还是两级页表的Intel X86处理器中,Linux总是使 用相同的页表操纵代码。

linux页表创建与更新相关推荐

  1. arm的2级页表在Linux内核创建过程解析

    系统DDR的基地址为0x0,内存为1GB,所以TTB的基地址为0x4000.下面要创建虚拟地址0xfe700000到物理地址0xffff0000之间的映射,映射大小为64KB,即16页.由于物理地址不 ...

  2. Linux下动态库的创建与更新

    Linux下动态库(libname.x.y.z)的创建与更新 由于主程序和它依赖的共享库是由不同的开发者开发的.共享库的开发者会不停地更新共享库的版本,以修正bug,增加功能或改进性能.版本多了之后, ...

  3. arm linux 进程页表,arm-linux内存页表创建

    linux的内存(正式)页表是在内核代码执行到start_kernel函数后执行paging _init函数建立的,这里要注意一个事情就是说,这里paging_init函数可以正常创建内存页表的条件有 ...

  4. Linux页表 - - 启动过程临时页表创建过程

    目录 1.为什么需要虚拟内存地址 2.页表 3.内核启动初始页表创建 获取内核在内存中起始页地址 __create_page_tables 1.为什么需要虚拟内存地址 个人理解通过创建页表实现物理地址 ...

  5. ARMv8 MMU及Linux页表映射 LoyenWang

    背景 Read the fucking source code! --By 鲁迅 A picture is worth a thousand words. --By 高尔基 说明: Kernel版本: ...

  6. 【内存】Linux 页表、大页与透明大页|大页内存

    目录 页表与MMU CPU访问的是什么地址(虚拟地址,物理地址)? MMU如何工作 MMU对内存的保护 多级页表 一. 内存映射与页表 1. 内存映射 2. 页表 4. 页表的简单工作原理 大页 什么 ...

  7. Git的纯命令操作,Install,Clone , Commit,Push,Pull,版本回退,撤销更新,分支的创建/切换/更新/提交/合并,代码冲突...

    Git的纯命令操作,Install,Clone , Commit,Push,Pull,版本回退,撤销更新,分支的创建/切换/更新/提交/合并,代码冲突 这篇是接着上篇分布式版本库--Windows下G ...

  8. echo添加换行 linux_在 Linux 上创建文件的 10 个方法

    我下面将会介绍多个在 Linux 上创建文件的方法.我建议你选择几个简单高效的来辅助你的工作. -- Vinoth Kumar 我们都知道,在 Linux 上,包括设备在内的一切都是文件.Linux ...

  9. ARMv8 MMU及Linux页表映射:TLB

    <ARM SMMU原理与IOMMU技术("VT-d" DMA.I/O虚拟化.内存虚拟化)> <Linux内存管理:分页机制> <Linux内存管理:内 ...

  10. Aspose.Cells使用教程:使用 .NET 在 Linux 上创建或编辑 Excel 文件

    如今自动化解决方案的出现使得电子表格自动化在各个行业中都很流行.电子表格文档是从 Web 或桌面解决方案中以编程方式创建和操作的.因此,本文介绍了如何使用 .NET 在 Linux 平台上创建 Exc ...

最新文章

  1. JavaScript变量和对象参数传值问题
  2. ASP.NET Core 导入导出Excel xlsx 文件
  3. Corda服务的异步流调用
  4. 解决office2007每次打开提示向程序发送命令时出现错误
  5. 你真的懂ArrayList吗?说说foreach与iterator时remove的区别
  6. C#操作XML的完整例子——XmlDocument篇(转载,仅做学习之用)
  7. C#之放入透明背景字体(label标签的透明设置)
  8. linux按行分割文件,按行切割大文件(linux split 命令简版)
  9. 【2022修复版】社群扫码进群活码引流完整运营源码/对接免签约支付接口/推广正常绑定下级/带视频搭建教程
  10. webservice接口等待时间_调用webservice超时问题的解决
  11. 数值计算·第八集:二阶锥规划(CVXPY版)
  12. centos 7 时间与网络同步
  13. pythonjam怎么使用_Jam 使用说明
  14. 七夕礼物送什么给男朋友好?七夕礼物清单
  15. 复杂推理的进展与挑战——从LSAT讲起
  16. 006_STM32程序移植之_SYN6288语音模块
  17. Android筑基——自定义属性详解
  18. 创建ROS工作空间,ROS功能包(Package)
  19. String.replace 效率最高的一种java字符串替换方式
  20. 计算机病毒课程毕业论文,计算机病毒及防治毕业论文设计,课程ppt,答辩翻译

热门文章

  1. python3.7升级pip_完美解决python3.7 pip升级 拒绝访问问题
  2. Java NIO框架Netty教程(四) – ServerBootStrap启动流程源码分析
  3. Alibaba秋招前端测试题
  4. idea打包SpringBoot项目打包成jar包和war
  5. Enterprise Library 4.0 - May 2008 发布了
  6. SQLite 3.7.13的加密解密(三)—— 创建加密解密函数
  7. 嵌入式系统内存泄漏检测
  8. Java是传值还是传引用
  9. ntldr is missing什么意思应该如何解决
  10. 多态在 Java 和 C++ 编程语言中的实现比较