进程虚拟地址空间是linux的一个重要抽象,系统为每一个运行的Linux进程提供了4GB的虚拟地址空间,进程之间不会相互干扰,仅能访问自己的虚拟地址空间地址。Linux进程的虚拟地址空间分为User和kernel两个部分,0~3G是为进程的User地址空间,3G~4G为kernel地址空间。此外进程虚拟地址空间也使得许多高级的linux技术得以实现:比如内存映射,延迟分配,共享内存等。因此进程虚拟内存技术和kernel部分的地址空间管理相比,要复杂的多。
希望通过如下小节,彻底搞清进程虚拟地址空间。
1 进程虚拟地址空间 简介
2. 进程虚拟地址空间 涉及的数据结构
3. 进程虚拟地址空间 辅助操作函数
- find_vma
- find_vma_intersection
- get_unmapped_area
- insert_vm_struct
4. 进程虚拟地址空间 操作函数
- 分配虚拟地址空间 do_mmap
- 释放虚拟地址空间 do_munmap
5. page fault处理函数
- 异常发生在已创建地址空间内
- 异常发生在已创建地址空间外
- 物理页框的按需分配
- Copy on write
- 访问kernel vmalloc区引发的异常
6. 堆的管理
基本概念
进程虚拟地址空间管理之所以复杂,我觉得和涉及到的技术,术语,概念比较多息息相关。所以先解释一些进程虚拟地址管理的术语和技术。
虚拟地址
又称为线性地址,对于IA32和其他的32bit体系结构来说,虚拟地址范围是0~4G,我们把虚拟地址划分为两个空间:User mode地址空间和Kernel模式地址空间。进程只能访问虚拟地址,通过进程页表由MMU进行地址转换(进程页表保存着虚拟地址到物理地址的映射关系),当CPU访问虚拟地址时,MMU会自动把虚拟地址翻译为对应的物理地址,并把请求发送给翻译后的物理地址。
物理地址
物理内存单元的地址,包括RAM, ROM, 模块寄存器,IO单元的访问地址。除了dma操作外,操作系统都是通过虚拟地址访问这些内存,设备和IO的。物理地址散布在0~4G的地址范围内,但是kernel会吧他们映射到3G~4G的kernel虚拟地址空间内
页框
也是物理页框,为了便于管理,把物理内存切分为4KB大小的页框。
用户地址空间
虚拟地址空间的一部分,大小依赖于User kernel空间的划分比例,一般情况下是3G,用户进程只能访问用户地址空间的数据,如果想要访问内核空间,那么必须通过系统调用进入内核空间。由于进程用户地址空间的页表不同(也有列外,从一个进程fork出来的子进程实际上共享整个地址空间),因此访问一个进程的用户地址空间不会影响到其他的进程。
kernel地址空间
虚拟(线性)地址空间的一部分,一般是1G大小,所有进程的kernel地址空间页表都是相同的,也就是说他们的kernel空间的内容是完全相同的。在kernel空间,对系统有绝对的控制权,可以访问任意的系统资源。一般来说(之所以用一般,是因为这也是体系结构相关,配置相关的),内核空间的划分如下。
Anonymous匿名映射
匿名映射是指映射区没有关联到磁盘上的文件,通常用做进程间共享内存。
推迟分配页面 demand paging
推迟分配是动态内存分配技术,在建立虚拟地址空间时,并不会建立虚拟地址到物理地址的映射,直到应用访问了这个虚拟地址空间内的地址。处理器触发page fault时,内核才会尝试分配物理页框,并建立这种映射关系。
推迟分配是绝对必要的:
1. 虽然大多数进程在3G的地址空间内仅占用了很小的空间,来建立虚拟地址映射,但是所有进程相加,这个数目仍然远超过系统的物理内存。
2. 一个进程映射了一个巨大的文件进行编辑,但是实际上编辑过程仅仅操作文件末尾的几页,因此根本没有必要为整个文件都建立页面映射。
如果留心,就会发现推迟遍布操作系统的每个角落,比如文件的迟写,文件buffer的存在,甚至处理器的cache写操作。
堆
堆是进程用于动态分配变量和数据的内存区域,堆的分配和管理对程序员来说是不可见的。程序员使用的是C库提供的标准接口malloc,而malloc则调用brk系统调用来扩大或者缩小堆。
栈
栈起始于STACK_TOP,如果设置了PF_RANDOMIZE,那么起始点要比STaCK_TOP减少一个小的随机量。每个体系结构都定义了自己的随机量。
Copy On Write
copy on write 是操作系统以及其他软件系统常见的一种技术,即大家共享一份初始copy,当writer1要更改时,就copy一份数据到给writer,writer在这个新建的copy上进行修改。
当linux fork进程时,子进程最初的页表是从父进程复制的,页帧的内容并没有被复制(太多了,也太低效),子进程和父进程以及兄弟们共享这些物理页框。当然这些共享的页框不能够被修改的,如果想修改页框,那么就会发生一个异常。页面异常处理函数会复制一个新页框并标记为可写,而原始的页框保持write-protected。
反向映射
虚拟内存映射的目的就是给定虚拟地址空间,通过页表找到虚拟地址空间对应的物理页框。所谓反向映射就是这个查找的反向过程,给定一个物理页框,找到所有映射了这个页面的虚拟地址。由于虚拟地址到物理地址的映射是多对一的,所以必须要增加一些附加的数据结构和方法,来实现这种反向映射。
之所以要引入反向映射,是因为在换出页时,需要更新所有关联了该物理页面的虚拟地址对应的页表。