凑字数
用户态
内存映射 : 物理内存和虚拟内存之间、 文件中的内容映射到虚拟内存空间。
申请小块内存用 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_mmap
内存管理不直接分配内存, 在使用时才分配
用户态缺页异常, 触发缺页中断, 调用 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)
- 先检查对应
- 若不存在 PTE (页表项,新映射的页)
避免每次都需要经过页表(存再内存中)访问内存
- (TLB :快表 ,专门用来做地址映射的硬件设备,提高映射速度)
- TLB就是页表的cache,存储了部分页表项,相对于是一个副本
- 一般操作系统先查TLB,如果查不到 , 才会到内存中查询页表.
用户态的内存映射机制包含以下几个部分。
用户态内存映射函数 mmap,包括用它来做匿名映射和文件映射。
用户态的页表结构,存储位置在 mm_struct 中。
在用户态访问没有映射的内存会引发缺页异常,分配物理页表、补齐页表。如果是匿名映射则分配物理内存;如果是 swap,则将 swap 文件读入;如果是文件映射,则将文件读入。
内核态
涉及三块内容:
- 内存映射函数 vmalloc, kmap_atomic
- 内核态页表存放位置和工作流程
- 内核态缺页异常处理
内核态页表
- 内核态页表, 系统初始化时就创建
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_mapping
→kernel_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 用于关联内核页表项
- kmem_cache 和 kmalloc 用于保存内核数据结构, 不会被换出; 而内核 vmalloc 会被换出