1. 虚拟内存概述
虚拟内存是计算机系统内存管理的一种技术,它使得应用程序认为它拥有连续可用的内存(一个连续完整的地址空间),这使得系统管理多进程的内存请求更加方便且高效。Linux
操作系统为每一个进程提供一个固定大小、私有的虚拟地址空间,虚拟地址空间包含了当前进程需要的所有数据信息,然而虚拟内存指向的数据并非所有的都加载到主存中,一部分数据仅仅是记录了该数据在磁盘的起始位置以及大小,需要的时候由内核负责读入主存,同样的,内核也会在该内存不需要访问的时候将其回写磁盘,空出来共其他内存请求。正是这个原理,有限的主存可以保证多个进程同时运行或者高于主存的内存申请。
2. 虚拟内存与物理内存转换
如上是虚拟地址(VA)
到物理地址(PA)
再通过数据总线获取到数据的架构图,其中MMU(Memory Management Unit,内存管理单元)
负责地址的转换,其中还会涉及到更深层次的细节,如TLB命中
、PTE查询
、缺页异常处理等,本文不展开讨论。其基本过程就是执行单元通过地址总线向主存请求数据,对于具有MMU
模块的系统架构来说,MMU
拦截当前的请求,并完成VA
到PA
的转换,然后向主存请求数据,CPU
通过数据总线获取数据,这一过程无论是对于上层应用还是CPU
执行单元都是无感的过程。
3. 如何描述虚拟地址空间
为了更有效率的管理虚拟地址空间,系统根据数据的性质以及权限等考量,将虚拟地址空间通过分段的方式划分不同的段,如下是一个32位机器的虚拟地址分段展示图:
上面的虚拟地址空间仅仅抽象的描述,那么内核如何完成如上的描述呢?我们都知道内核通过task_struct
描述一个进程,其中就使用一个成员变量描述该进程对应的虚拟地址空间:struct mm_struct *mm
。进程虽说都独占4G的虚拟地址空间,实际上内核空间是每个进程共享的,所以这里主要探讨的是剩下来的3G的用户空间。每个进程都有6个分段:
- 代码段
(Text)
:用来存放程序二进制执行代码的内存区域,同时还包括一些只读的常数变量,如字符串,该区域通常为只读权限 - 数据段
(Data)
:保存已初始化的全局变量或者是静态变量 BSS(Block Started by Symbol)
:保存未初始化的全局变量或者是静态变量- 堆
(Heap)
:用于保存运行时被手动分配的变量,该内存段大小并不固定,调用malloc/free
会增大/缩小该区域 - 内存映射区
(Memory Mapping Segment)
:该区域主要是一些动态库的映射,或者通过mmap
映射的一段内存区域 - 栈
(Stack)
:由编译自动分配的变量,如函数中申明的局部变量
内核中通过vm_area_struct
描述进程地址分段,mm_struct
使用struct vm_area_struct *mmap
通过链表的方式记录全部的VMA
: