内存映射原理
内存映射的原理
创建内存映射时,在进程的用户虚拟地址空间中分配一个虚拟内存区域。内核采用延迟分配物理内存的策略,在进程第一次访问虚拟页的时候,产生缺页异常。如果是文件映射,那么分配物理页,把文件指定区间的数据读到物理页,然后再页表中把虚拟页映射到物理页,如果匿名映射,就分配物理页,然后在页表中把虚拟页映射到物理页
内存映射即在进程的虚拟地址空间中创建一个映射,分为两种:
- 文件映射:文件支持的内存映射,把文件的一个区间映射到进程的虚拟地址空间,数据源是存储设备上的文件
- 匿名映射:没有文件支持的内存映射,把物理内存映射到里程的虚拟地址空间,没有数据源。
物理地址空间
物理地址是处理器在系统总线上看到的地址,使用RISC的处理器通常只实现一个物理地址空间,外围设备和物理内存使用统一的物理地址空间,有些处理器架构把分配给外围设备地址区域称为设备内存。
处理器通过外围设备控制器的寄存器访问外围设备,寄存器分为控制寄存器,状态寄存器和数据寄存器三大类。外围设备的寄存器通常被连续编址,处理器对外围设备寄存编址方式分为两种:I/O映射方式(I/O-mapped),内存映射方式(memory-mapped)。
应用程序只能通过虚拟地址访问外设寄存器,内核提供API函数来把外设寄存器的物理地址映射到虚拟地址空间。
ARM64架构(物理地址宽度最大支持48位)分为两种内存类型:
正常内存(normal memory) :包括物理内存和只读存储器(ROM);
设备内存(Device Memory):指的是分配给外围设备寄存器的物理地址区域。
设备内存共享属性总是外部共享,缓存属性总是不可缓存(必须绕过处理器的缓存)
两个进程可以使用共享的文件映射实现共享内存, 匿名影视通常是私有映射,共享的匿名映射只可能出现在父子进程之间 在进程虚拟地址空间中,代码段和数据段时私有的文件映射,未初始化数据段,堆栈是私有的匿名映射
修改过的脏页面不会立即更新到文件中,可以调用msync来强制同步写入文件。
数据结构
虚拟内存区域分配给进程的一个虚拟地址范围,内核使用结构体vm_area_struct 描述虚拟内存区域,主要核心成员如下:
struct vm_area_struct {
unsigned long vm_start;//虚拟内存空间的首地址
unsigned long vm_end;//虚拟内存空间的末地址的第一个字节的地址
strucyt vm_area_struct *vm_next,*vm_prev;//分别VMA链表的前后成员链接操作
struct rb_node vm_rb;//如果采用链表组织化,会影响到它搜索速度问题,解决此问题采用红黑树
unsigned long rb_subtree_gap;
struct mm_struct *vm_mm; //指向内存描述符,即虚拟内存区域所属的用户虚拟地址空间。
pgprot_t vm_page_prot; //保护位,即访问权限。
/*标志
#define VM_READ 0x00000001
#define VM_WRITE 0x00000002
#define VM_EXEC 0x00000004
#define VM_SHARED 0x00000008
*/
unsigned long vm_flags;//标志
//为了支持查询一个文件区间被映射到哪些虚拟内存区域,把一个文件映射到所有虚拟内存区域加入该文件地址空间结构
//address_space 的成员i_mmap 指向的区域树
struct {
struct rb_node rb;
unsigned long rb_subtree_last;
}shared;
//把虚拟内存区域关联的所有anon_vma实例串联起来,一个虚拟内存区域会关联到父进程的anon_vma 实例和自己的anon_vma 实例
struct list_head anon_wma_chain;
//主要是指向一个anon_vma实例,结构anon_vma用来阻止匿名页被映射到所有的虚拟地址空间。
struct anno_vma *anon_vma;
//虚拟内存操作集合
/*
struct vm_operations_struct {
void (*open) (struct vm_area_struct *area);//创建虚拟内存区域时调用open 方法
void (*close) (struct vm_area_struct *area);//在删除虚拟内存区域的时候调用close 方法
int (*mremap)(struct vm_area_struct *area);//使用系统调用mremap 移动虚拟内存区域调用mremap方法
int (*fault)(struct vm_fault *vmf);//访问文件映射的虚拟页,如果没有映射到物理页,生成缺页异常,异常处理程序调用fault 就去把文件的数据读到文件页缓存当中
int (*huge_fault)(struct vm_fault *vmf,enum page_entry_size pe_size);//与fault 类似,区别是huge_fault方法针对使用透明巨型页的文件映射
//读文件映射的虚拟页时,如果没有映射到物理页,生成缺页异常,异常处理程序除了读入正在访问的文件页,还会预读后续的文件页,还会预读后续的文件页
// 调用map_pages 方法在文件的页缓存中分配物理页
void (*map_pages)(struct vm_fault *vmf, pgoff_t start_pgoff,pgoff_t end_pgoff);
// 第一次写私有的文件映射时,生成页错误异常,异常处理程序执行写时复制,调用page_mkwrite 方法以通知文件系统页即将变成可写,以方便文件系统检查时候允许写,或者等待页进入合适的状态。
int (*page_mkwrite)(struct vm_fault *vmf);
}
/*
const struct vm_operations_struct *vm_ops;
unsigned long vm_pgoff; //文件偏移 ,单位是页
struct file *vm_file ;//文件,如果是私有的匿名映射,该成员是空指针。
void *vm_private_data ; //指向内存区的私有数据
}
虚拟内存区域(vm_area_struct)
struct vm_operations_struct vm_ops struct file vm_file;
(虚拟内存操作集合) vm_operations_struct
int (page_mkwrite)(struct vm_fault vmf);
int (pfn_mkwrite)(struct vm_fault vmf);
const char (name)(struct vm_area_struct *vma);
文件的打开实例
struct inode *f_inode
文件索引节点inode
系统调用
内存管理子系统提供如下常用的系统调用函数
- mmap() ---- 创建 内存 映射
#include <sys/mman.h>
void *mmap(void *addr,size_t length,int prot,int flags,int fd, off_t offset);
系统调用mmap(): 进程创建匿名的内存映射,把内存的物理页映射到进程的虚拟地址空间。进程把文件映射到进程的虚拟地址空间,可以像访问内存一样访问文件,不需要调用系统read()/write()访问文件,从而避免用户模式和内核模式之间的切换,提高读写文件速度,两个进程针对同一个文件创建共享的内存映射,实现共享内存。
- munmap() ---- 删除内存映射
#include <sys/mman.h>
int munmap(void *addr,size_t len);
- mprotect() ---- 设置虚拟内存区域的访问权限
#include <sys/mman.h>
int mprotect(void *addr,size_t len , int prot);
三,系统调用
应用程序通常使用C标准库提供的函数malloc()申请内存,glic库的内存分配器ptmalloc 使用brk或者mmap向内核以页为单位申请虚拟内存,然后把页划分为小内存块分配给应用程序,默认的阈值是128kb,如果应用程序申请的内存长度小阈值,ptmalloc分配器使用brk向内核申请虚拟内存,否则ptmalloc 分配器使用mmap向内核申请虚拟内存。
应用程序可以直接使用mmap向内核申请虚拟内存。
mmap 内存映射原理的三个阶段:
- 进程启动映射过程,并且在虚拟地址空间中为映射创建虚拟映射区域。
- 调用内核空间的系统调用函数mmap(不同于用户空间函数),实现文件物理地址 和 进程虚拟的一一映射关系。
- 进程发起对这片映射空间的访问,引发缺页异常,实现文件内容到物理内存(主存)的拷贝。