2017-04-07
脱离物理内存的管理,今天咱们来聊聊进程虚拟内存的管理。因为进程直接分配和使用的都是虚拟内存,而物理内存则是有系统“按需”分配给进程,在进程看来,只知道虚拟内存的存在!
前言:
关于虚拟内存和物理内存这些东东,本篇不作介绍,此等基础知识参考最普通的操作系统参考书籍即可。当然有问题也可留言,我们共同学习,共同进步!
进程虚拟地址空间:
每个进程拥有一个独立的虚拟地址空间,独立怎么体现?进程A有个地址1000,进程B也有个地址1000,为何两个地址不发生矛盾,还能正常工作,这就是独立性的体现。原来,两个相同的虚拟地址会经过自己所属进程的页表,转化成不同的物理地址。只要实际的物理地址不发生冲突,就没有关系。这一切怎么实现的呢?自然需要内核去维护。内核为每个进程维护一套页表,页表实现虚拟地址到物理地址的转换,其基地址存储在CR3寄存器中,是物理地址。x86架构下,地址线是32根,那么就可以寻址到2^32个地址,因为默认都是按字节寻址,所以虚拟地址空间就为2^32字节即4GB,64位额CPU使用48根地址线,虚拟地址空间更大。本文不做虚拟地址空间深入分析,旨在介绍虚拟地址空间是怎么使用的。
虚拟地址空间的划分:
每个进程都有自己的虚拟地址空间,但是也不能随意使用,还是需要规划好的,毕竟内存时比较稀缺的资源,咱们必须本着可持续发展的战略去使用。很明显,进程运行过程中需要的信息都要装入内存,那么进程运行期间都需要哪些信息呢?
- 程序运行代码的二进制代码。
- 程序使用的动态库的代码。
- 存储全局变量和动态产生数据的堆。
- 保存局部变量和实现函数过程调用的栈。
- 环境换量和命令行参数的段。
- 将文件内容映射到虚拟地址空间的的内存映射。
进程虚拟地址空间映射结构如下两种结构,在地址空间不富裕的情况下,前者比较合适。
进程的3GB空间安排大致如图所示,最底下是映射的可执行文件的映像,一般包含代码段和数据段。数据段中包含有全局变量和静态数据的空间。再往上就是堆空间,这里堆空间和MMAP区域是相对生长的,从而充分利用这些地址空间。MMAP区域上边是栈空间,而栈空间上面不使用了,所以栈一般位于进程地址空间的顶部。一般情况下栈的最高地址是用户空间可用的最高地址,当指定栈随机化时,会空闲一个随机大小,这样可在一定程度上,对栈实施保护。
每个进程结构有一个mm_struct结构管理该进程的地址空间,每个分配的内存块由一个vm_area_struct结构表示,所有的vm_area_struct结构构成一个红黑树,树根保存在mm_struct中的mm_rb字段。这点和windows的进程地址空间管理十分类似,windows下采用的是VAD树,也是个二叉树。在vm_area_struct结构中记录该内存块的起始和结束地址,同一个进程的所有vm_area_struct结构还会连接成一个链表,开始于mm_struct 中的mmap。总体来讲进程地址空间的分配主要涉及三部分:进程可执行文件的映射、堆栈的分配、MMAP区域的分配。
进程可执行文件的映射
可执行文件映射在图中的text段,这里text段包含代码、数据。代码自然是程序自身的代码,而数据就是一些常量,全局数据区,静态数据区等。具体映射方式需要根据ELF文件的格式,所以映射的细节我们不做详细介绍。当可执行文件的映像映射到进程的虚拟地址空间时,产生一组vm_area_struct结构,相当于还没执行之前,可执行文件已经被划分成了虚拟页面,由vm_area_struct管理。待程序执行时会根据vm_area_struct结构和物理内存建立映射并填充页表项,具体参见pagefault处理流程。
堆栈的分配
堆栈在程序运行时动态分配,二者的用途不同。堆主要用于分配较大一些的空间,且必须主动申请分配,使用完后需要手动释放。而栈有程序自动分配,有系统负责回收,且栈的大小一般受限,常分配比较小的内存块。
MMAP区域
MMAP 区域位于堆和栈之间,用于映射文件内容到进程虚拟地址空间。注意这里是虚拟地址空间而不是物理地址空间。 每个文件在内核对应一个inode节点,inode节点的 i_mapping字段是address_space类型,记录当前文件到进程地址空间的映射情况。address_space结构中关联了映射的内存区域vm_area_struct,私有和共享映射通过红黑树管理,而非线性映射通过链表管理。进程每打开一个文件,内核会使用一个file结构记录本次打开的情况,file结构通过f_mapping字段指向文件对应的address_space,这样进程和文件的联系就建立起来了。具体联系如下
我们提到的映射都是映射的是文件到进程虚拟地址空间,不牵涉物理地址。具体物理地址的映射由内存管理单元负责,当进程对文件发起读写操作时,最终会根据文件描述符定位到其address_space,根据address_space得到要读写的页,此时页具备虚拟地址,通过对虚拟地址进行读写发生pagefault,此时当前进程就被迫陷入到内核异常处理流程,pagefault处理流程就把本次需要的页调入内存,然后返回到用户空间继续刚才进程的运行,这里其实进程本身并感知不到缺页,它仅仅负责从文件读,具体缺页由CPU感知且处理。关于MMAP其实设计的东西很多,这里不再深入介绍,回头重开一篇文章单独讲述MMAP机制。
参考资料:LInux内核3.10.1源码、深入LInux内核架构