• 【操作系统】 用户态&内核态内存映射


    用户态

    在这里插入图片描述
    内存映射 : 物理内存和虚拟内存之间、 文件中的内容映射到虚拟内存空间。

    • 申请小块内存用 brk; 申请大块内存或文件映射用 mmap

    • mmap:映射文件, 由 fd 得到 struct file

      • 调用 …->do_mmap
        • 调用 get_unmapped_area 找到一个可以进行映射的 vm_area_struct
        • 调用 mmap_region 进行映射
      • get_unmapped_area
        • 匿名映射: 找到前一个 vm_area_struct
        • 文件映射: 调用 file 中 file_operations 文件的相关操作, 最终也会调用到 get_unmapped_area
      • mmap_region
        • 通过 vm_area_struct 判断, 能否基于现有的块扩展(调用 vma_merge)
        • 若不能, 调用 kmem_cache_alloc 在 slub 中得到一个 vm_area_struct 并进行设置
        • 若是文件映射: 则调用 file_operations 的 mmap 将 vm_area_struct 的内存操作设置为文件系统对应操作(读写内存就是读写文件系统)
        • 通过 vma_link 将 vm_area_struct 插入红黑树
        • 若是文件映射, 调用 __vma_link_file 建立文件到内存的反映射
    • 内存管理不直接分配内存, 在使用时才分配

    • 用户态缺页异常, 触发缺页中断, 调用 do_page_default

    • __do_page_fault 判断中断是否发生在内核

      • 若发生在内核, 调用 vmalloc_fault, 使用内核页表进行映射
      • 若不是, 找到对应 vm_area_struct 调用 handle_mm_fault
      • 得到多级页表地址 pgd 等
      • pgd 存在 task_struct.mm_struct.pgd 中

    四级页表
    在这里插入图片描述

    • 全局页目录项 pgd 在创建进程 task_struct 时创建并初始化, 会调用 pgd_ctor 拷贝内核页表到进程的页表

    • 进程被调度运行时, 通过 switch_mm_irqs_off->load_new_mm_cr3 切换内存上下文

    • cr3 是 cpu 寄存器, 存储进程 pgd 的物理地址(load_new_mm_cr3 加载时通过直接内存映射进行转换)

    • cpu 访问进程虚拟内存时, 从 cr3 得到 pgd 页表, 最后得到进程访问的物理地址

    • 进程地址转换发生在用户态, 缺页时才进入内核态(调用__handle_mm_fault)

    • __handle_mm_fault 调用 pud_alloc, pmd_alloc, handle_pte_fault 分配页表项

      • 若不存在 PTE (页表项,新映射的页)
        • 匿名页: 调用 do_anonymous_page 分配物理页 ①
        • 文件映射: 调用 do_fault ②
      • 若存在 pte, 调用 do_swap_page 换入内存 ③
      • ① 为匿名页分配内存
        • 调用 pte_alloc 分配 pte 页表项
        • 调用 …->__alloc_pages_nodemask 分配物理页
        • mk_pte 页表项指向物理页; set_pte_at 插入页表项
      • ② 为文件映射分配内存 __do_fault
        • 以 ext4 为例, 调用 ext4_file_fault->filemap_fault
        • 文件映射一般有物理页作为缓存 find_get_page 找缓存页
        • 若有缓存页, 调用函数预读数据到内存
        • 若无缓存页, 调用 page_cache_read 分配一个, 加入 lru 队列, 调用 readpage 读数据: 调用 kmap_atomic 将物理内存映射到内核临时映射空间, 由内核读取文件, 再调用 kunmap_atomic 解映射
      • ③ do_swap_page 如果长时间不用 就要换出磁盘 , 也就是swap
        • 先检查对应 swap 有没有缓存页
        • 没有, 读入 swap 文件(也是调用 readpage)
        • 调用 mk_pte(创建页表项); set_pet_at(将页表项插入页表); swap_free(清理 swap)
          在这里插入图片描述
    • 避免每次都需要经过页表(存再内存中)访问内存

      • (TLB :快表 ,专门用来做地址映射的硬件设备,提高映射速度)
      • TLB就是页表的cache,存储了部分页表项,相对于是一个副本
      • 一般操作系统先查TLB,如果查不到 , 才会到内存中查询页表.
        在这里插入图片描述
        用户态的内存映射机制包含以下几个部分。

    用户态内存映射函数 mmap,包括用它来做匿名映射和文件映射。

    用户态的页表结构,存储位置在 mm_struct 中。

    在用户态访问没有映射的内存会引发缺页异常,分配物理页表、补齐页表。如果是匿名映射则分配物理内存;如果是 swap,则将 swap 文件读入;如果是文件映射,则将文件读入。

    内核态

    涉及三块内容:

    - 内存映射函数 vmalloc, kmap_atomic
    - 内核态页表存放位置和工作流程
    - 内核态缺页异常处理
    
    • 1
    • 2
    • 3

    内核态页表

    在这里插入图片描述

    • 内核态页表, 系统初始化时就创建
      • swapper_pg_dir 指向内核顶级页目录 pgd
        • xxx_ident/kernel/fixmap_pgt 分别是直接映射/内核代码/固定映射的xxx级页表目录
      • 创建内核态页表
        - swapper_pg_dir 指向 init_top_pgt, 是 ELF 文件的全局变量, 因此再内存管理初始化之间就存在
        • init_top_pgt 先初始化了三项
          • 第一项指向 level3_ident_pgt (内核代码段的某个虚拟地址) 减去 __START_KERNEL_MAP (内核代码起始虚拟地址) 得到实际物理地址
          • 第二项也是指向 level3_ident_pgt
          • 第三项指向 level3_kernel_pgt 内核代码区
      • 初始化各页表项, 指向下一集目录
        • 页表覆盖范围较小, 内核代码 512MB, 直接映射区 1GB
        • 内核态也定义 mm_struct 指向 swapper_pg_dir
        • 初始化内核态页表, start_kernel setup_arch
          • load_cr3(swapper_pg_dir) 并刷新 TLB
          • 调用 init_mem_mappingkernel_physical_mapping_init, 用 __va 将物理地址映射到虚拟地址, 再创建映射页表项
          • CPU 在保护模式下访问虚拟地址都必须通过 cr3, 系统只能照做
          • load_cr3 之前, 通过 early_top_pgt 完成映射

    vmalloc 和 kmap_atomic

    • 内核的虚拟地址空间 vmalloc 区域用于内存映射
      - kmap_atomic 临时映射
      • 32 位, 调用 set_pte 通过内核页表临时映射
      • 64 位, 调用 page_address→lowmem_page_address 进行映射

    内核态缺页异常 (没有页表的时候)

    - kmap_atomic 直接创建页表进行映射
    - vmalloc 只分配内核虚拟地址, 访问时触发缺页中断, 调用 do_page_fault→vmalloc_fault 用于关联内核页表项
    
    • 1
    • 2
    • kmem_cache 和 kmalloc 用于保存内核数据结构, 不会被换出; 而内核 vmalloc 会被换出
      在这里插入图片描述
  • 相关阅读:
    Pycharm 搭建 Django 项目 (非常详细)
    从 AST 到 自定义 Babel 插件
    快速构建电脑软件系统 、超好用经典的网页推荐汇总
    PFC232-SOP8/14/16应广一级可带烧录程序编带
    用最少的代码打造一个Mini版的gRPC框架
    1、LeetCode之两数之和
    Mybatis
    如何处理Vue2项目里大规模的数据计算?
    JVM内存区域划分
    P1596 [USACO10OCT]Lake Counting S——dfs连通块
  • 原文地址:https://blog.csdn.net/weixin_49486457/article/details/125981798