Kernel Exploring
  • 前言
  • 支持
  • 老司机带你探索内核编译系统
    • 编译出你的第一个内核
    • 内核编译中的小目标
    • 可能是kbuild中最直接的小目标 – help
    • 使用了一个kbuild函数的目标 – cscope
    • 内核中单个.o文件的编译过程
    • 根目录vmlinux的编译过程
    • 启动镜像bzImage的前世今生
    • setup.bin的诞生记
    • 真假vmlinux–由vmlinux.bin揭开的秘密
    • bzImage的全貌
    • kbuild系统浅析
  • 启动时的小秘密
    • INIT_CALLS的秘密
    • 内核参数
  • 内核加载全流程
    • bootloader如何加载bzImage
    • 内核压缩与解压
    • 内核加载的几个阶段
    • 保护模式内核代码赏析
  • 内存管理
    • 内核页表成长记
      • 未解压时的内核页表
      • 内核早期的页表
      • cleanup_highmap之后的页表
      • 映射完整物理地址
      • 启用init_level4_pgt
    • 自底而上话内存
      • e820从硬件获取内存分布
      • 原始内存分配器--memblock
      • 页分配器
        • 寻找页结构体的位置
        • 眼花的页结构体
        • Node-Zone-Page
        • 传说的伙伴系统
        • Compound Page
        • GFP的功效
        • 页分配器的用户们
      • slub分配器
        • slub的理念
        • 图解slub
      • 内存管理的不同粒度
      • 挑战和进化
        • 扩展性的设计和实现
        • 减少竞争 per_cpu_pageset
        • 海量内存
        • 延迟初始化
        • 内存热插拔
        • 连续内存分配器
    • 虚拟内存空间
      • 页表和缺页中断
      • 虚拟地址空间的管家--vma
      • 匿名反向映射的前世今生
      • 图解匿名反向映射
      • THP和mapcount之间的恩恩怨怨
      • 透明大页的玄机
      • NUMA策略
      • numa balance
      • 老版vma
    • 内存的回收再利用
      • 水线
      • Big Picture
      • 手动触发回收
      • Page Fram Reclaim Algorithm
      • swapfile原理使用和演进
    • 内存隔离
      • memcg初始化
      • 限制memcg大小
      • 对memcg记账
    • 通用
      • 常用全局变量
      • 常用转换
    • 测试
      • 功能测试
      • 性能测试
  • 中断和异常
    • 从IDT开始
    • 中断?异常?有什么区别
    • 系统调用的实现
    • 异常向量表的设置
    • 中断向量和中断函数
    • APIC
    • 时钟中断
    • 软中断
    • 中断、软中断、抢占和多处理器
  • 设备模型
    • 总线
    • 驱动
    • 设备
    • 绑定
  • nvdimm初探
    • 使用手册
    • 上帝视角
    • nvdimm_bus
    • nvdimm
    • nd_region
    • nd_namespace_X
    • nd_dax
      • dev_dax
  • KVM
    • 内存虚拟化
      • Qemu内存模型
      • KVM内存管理
  • cgroup
    • 使用cgroup控制进程cpu和内存
    • cgroup文件系统
    • cgroup层次结构
    • cgroup和进程的关联
    • cgroup数据统计
  • 同步机制
    • 内存屏障
    • RCU
  • Trace/Profie/Debug
    • ftrace的使用
    • 探秘ftrace
    • 内核热补丁的黑科技
    • eBPF初探
    • TraceEvent
    • Drgn
  • 内核中的数据结构
    • 双链表
    • 优先级队列
    • 哈希表
    • xarray
    • B树
    • Maple Tree
    • Interval Tree
  • Tools
  • Good To Read
    • 内核自带文档
    • 内存相关
    • 下载社区邮件
Powered by GitBook
On this page
  • 页表的层次
  • 页表层级常用helper
  • 页表的填写
  • 缺页中断
  • 架构无关代码
  • 匿名页填写
  • 页表的释放
  • 参考文档

Was this helpful?

  1. 内存管理
  2. 虚拟内存空间

页表和缺页中断

虚拟内存空间的物理根本可以说就是页表了,没有页表虚拟地址空间是无法幻化出让人眼花缭乱的变化。

页表的层次

当然,页表本身已经够让人眼花缭乱了。那就先让我们来看一下页表的样子先。

            47               39 38              30 29              21 20              12 11                  0
            +------------------+------------------+------------------+------------------+---------------------+
            |PML4              |Page Directory Ptr|Page Directory    |Page Table        |Offset               |
            +------------------+------------------+------------------+------------------+---------------------+
                   |                    |                      |                     |
                   |                    |                      |                     |
                   |                    |                      |                     |
                   |                    |                      |                     |
  pgd_index(addr)  |    pud_index(addr) |      pmd_index(addr) |     pte_index(addr) |      +----------+
                   |                    |                      |                     |      |          |
                   |                    |                      |                     |      |          |
                   |                    |                      |                     |      +----------+
                   |                    |                      |    pte_offset_map() +----> |pte       |
                   |                    |                      |                            +----------+
                   |                    |                      |     +----------+           |          |
                   |                    |                      |     |          |           |          |
                   |                    |                      |     |          |           |          |
                   |                    |                      | pmd +----------+           |          |
                   |                    |         pmd_offset() +---->| *pmd     |---------->+----------+
                   |                    |                            +----------+
                   |                    |                            |          |
                   |                    |                            |          |
                   |                    |                            |          |
                   |                    |     +----------+           |          |
                   |                    |     |          |           |          |
                   |                    | pud +----------+           |          |
                   |       pud_offset() +---->| *pud     |---------->+----------+
                   |                          +----------+
                   |     +----------+         |          |
                   |     |          |         |          |
                   | pgd +----------+         |          |
pgd_offset(mm,addr)+---->| *pgd     |-------->+----------+
                         +----------+
                         |          |
                         |          |
                         |          |
           mm->pgd --->  +----------+

其中最上面一行中出现的名词,如PML4,是在intel手册上的。而下方的xx_index/xx_offset是在内核代码中对应使用的名字。

当然上面这个图例已经有点过时了,这个是四层页表的情况,现在已经有五层也表了。

  +-----+
  | PGD |
  +-----+
     |
     |   +-----+
     +-->| P4D |
         +-----+
            |
            |   +-----+
            +-->| PUD |
                +-----+
                   |
                   |   +-----+
                   +-->| PMD |
                       +-----+
                          |
                          |   +-----+
                          +-->| PTE |
                              +-----+

页表层级常用helper

首先我们从上面图中看到内核中对页表每一层都起了自己的名字:

  • pgd

  • p4d

  • pud

  • pmd

  • pte

在操作对应层级时,也有对应的helper帮助我们获取对应的信息:

  • xxx_index(): 获取对应层级偏移量,用来计算下级页表地址

  • xxx_offset(): 在xxx_index()的基础上,取出下一级的页表entry内容

  • xxx_alloc(): 如果已经有页表,返回结果同xxx_offset();否则分配xxx对应层级的页表

  • xxx_none(): xxx对应这个entry是否为空,空说明需要分配下级页表了

  • xxx_present(): xxx对应这个entry是否存在,其实是看下一层页表是否存在

  • xxx_populate(): 安装页表,把下一层新分配的页表地址填到xxx表示的这一层

  • xxx_install(): 和xxx_populate()差不多,多了一个判断,最后调用xxx_populate()

页表的填写

那这张表怎么填写呢?当然途径不止一条,不过最重要的就是缺页中断了。

总的来讲就是按照虚拟地址来遍历整个页表,根据不同PTE的状态做不同的处理。

缺页中断

首先是架构相关的中断处理程序代码:

exc_page_fault
  handle_page_fault
    do_kern_addr_fault
    do_user_addr_fault
      vma = lock_vma_under_rcu()              <--- 锁住对应vma
      handle_mm_fault(FAULT_FLAG_VMA_LOCK)    <--- 架构无关代码
      vma_end_read()

      ... or

      vma = lock_mm_and_find_vma()            <--- 锁住mmap_lock
      handle_mm_fault()                       <--- 架构无关代码
      mmap_read_unlock()

架构无关代码

然后就是架构无关的缺页处理代码:

handle_mm_fault(vma, address, flags, regs)
    hugetlb_fault()                <--- hugetlb处理
    __handle_mm_fault
        // pud/pmd level
        // pte level
        handle_pte_fault           <--- 包括PTE这层页表,和做后的page
            // empty pte
            do_pte_missing
                do_anonymous_page
                do_fault
            // other1
            do_swap_page
            do_numa_page
            // other2
            do_wp_page
            pte_mkdirty
            pte_mkyoung

匿名页填写

页表按照映射对象来分主要是两种:

  • 匿名页表

  • 文件页表

这里我们先看匿名页表 -- do_anonymous_page。

do_anonymous_page()
  pte_alloc()                        <--- 这里会分配pte这一层页表,如果没有的话
  // 如果不是写,用zero-page
  entry = pte_mkspecial(pfn_pte(my_zero_pfn(), ..));

  // 准备匿名映射
  ret = vmf_anon_prepare()
    ret = __vmf_anon_prepare(vmf)
      __anon_vma_prepare(vma)        <--- 分配或者查找相邻可用vma->anon_vma
    return ret
  // 准备真正需要映射的内存
  folio = alloc_anon_folio(vmf);
    // THP or not
    folio_prealloc(, vma, vmf->address, true)
      vma_alloc_folio(GFP_HIGHUSER_MOVABLE, )    <-- 指定了可用的zone

  // 最后设置到pte页表中
  set_ptes()

页表的释放

有借必有还,有写必有擦。

看过了页表构造的过程(虽然糙了点),那也该看看页表释放的过程。当然我不确定是不是有很多地方可以做释放的动作,不过下面这个函数是我找到的接口之一。

unmap_region
    unmap_vmas()
    free_pgtables()

其中unmap_vmas释放了真正对应的内存,而free_pgtables才释放页表。

写到这里,感觉写完了,估计是很多细节自己还不知道。没事,留着以后有新发现再来挖掘。

参考文档

Previous虚拟内存空间Next虚拟地址空间的管家--vma

Last updated 2 months ago

Was this helpful?

比如在中可以看到五层是这样的。

page table
内核文档 -- page tables