内存管理
让我们先思考一个问题:当多个应用程序同时运行时,操作系统如何管理它们共同使用物理内存资源呢???
方法一:
程序A运行时,允许访问所有物理内存,在切换到程序B运行时,操作系统将程序A所有的内存数据保存到磁盘中,然后将程序B从磁盘加载到内存。
弊端:
操作系统读写磁盘速度很慢,切换程序时间开销太大。
方法二:
每个程序使用物理内存的一部分,数据一直驻留在内存。
弊端:
无法保证不同程序使用的物理内存之间的隔离性(程序A运行时将数据写入程序B的物理内存中)。
无法保证应用程序可用的地址时连续和统一的。
操作系统究竟怎么做的呢???
普遍做法是在程序与物理内存之间加入:虚拟内存(virtual memroy)。程序运行时只能使用虚拟地址,CPU负责将虚拟地址翻译成物理地址,操作系统负责将虚拟地址与物理地址的映射。
每个程序只能看到自己的虚拟地址空间,保证不同程序所用内存之间的隔离。
1、虚拟地址与物理内存
CPU通过总线访问物理地址,从内存中 读取/写入 数据。
程序运行时,CPU把虚拟地址转化为物理地址,然后访问物理内存(地址翻译)。
1.1、 使用虚拟地址访问物理内存
内存管理单元(Memroy Management Unit,MMU),将虚拟地址转化为物理地址。
为了加速地址翻译,CPU引入转址旁路缓存(Translation Lookaside Buffer,TLB)。
以Hello World为例:操作系统将程序从磁盘加载到内存中,CPU执行程序,由于程序在内存中。CPU发出的指令时虚拟地址,然后被MMU翻译成物理地址,然后内存把该物理地址对应的内容发送给CPU。
1.2、分段与分页
MMU将虚拟地址翻译成物理地址有两种机制:分段和分页
分段:
操作系统以一段连续的物理内存的形式管理 / 分配物理内存。
应用程序的虚拟地址空间由若干个不同大小的段组成,代码段、数据段。CPU访问虚拟地址空间中的某一段时,MMU通过查询段表,得到对应的物理内存。
虚拟地址由2部分组成:
段号:
表示该虚拟地址属于虚拟地址空间中的哪一段
段内地址:
相对该段起始地址的偏移量。
2、虚拟内存功能
虚拟内存使程序能偶拥有一个独立而连续的虚拟地址空间,通过页表与硬件配合能够在对程序透明的前提下自动进行虚拟地址到物理地址翻译。
2.1、共享内存
共享内存(shared memory)允许同一个物理页在不同的程序间共享。
程序A的虚拟页V1被映射到物理页P,程序B的虚拟也V2被映射到物理页P,物理页P使程序A和程序B的共享内存。程序A读取虚拟页V1和程序B读取虚拟页V2得到相同内容,可以相互看到对方修改的内容。
共享内存用途使让不同的程序之间互相通信、传递数据。还有一些基于共享内存思想,写时拷贝(copy-on-write)、内存去重(memory dediplication)
2.2、大页
3、物理内存分配与管理
物理内存分配设计有两个重要的评价维度。
物理内存分配要追求更高的资源利用率,减少资源浪费。
内存碎片: 无法被利用的内存,会导致内存资源利用率下降。
内存碎片分为外部碎片(external fragmentation)和 内部碎片(internal fragmentation)
如上,外部碎片通常在多次分配和回收之后产生。
多次分配之后物理内存上空闲的部分处于离散分布状态。此时有一个内存分配请求,请求内存大小大于任意单独空闲碎片,小于空闲碎片总和。
直观解决外部碎片的方式,将物理内存固定大小划分成若干块,每次用一块服务一个分配请求。虽然会解决外部碎片,但是会产生严重内部碎片。
3.1、伙伴系统
伙伴系统(buddy system)用于分配连续的物理内存页。
将物理内存划分成连续的块,以块为单位进行分配。不同块大小可以不同,每个块由一个或多个连续的物理页组成,物理页数量为 2n (0 <= n < 预设最大值),预设最大值决定能够分配的连续物理内存区域的最大值。
当一个请求需要分配m个物理页时,伙伴系统将寻找一个合适大小的块,该块包含 2n 个物理页,满足2n-1 < m < 2n 。处理分配请求时,大的块可以分裂成两半,知道得到一个大小合适的块。在一个块被释放后,分配器找到其伙伴块,伙伴块页空闲,将这两个伙伴块进行合并。分裂和合并都是级联的,很好能够缓解外部碎片问题。
空闲链表数组取实现伙伴系统。有一个有序数组,数组的每一项指向一条空闲链表,每条链表将其对应大小的空闲块连接起来(一条链表中的空闲块大小相同)。当接收到分配请求后,伙伴系统分配器首先算出应该分配多大空闲块,查找对应的空闲链表。如下分配请求分配的15KB的内存,合适大小时16KB,先检查第2条(22)空闲链表,链表不为空,可以直接从链表头部取出空闲块进行分配。
我们这个例子中,该链表为空,分配器会依次查找存储更大块的链表。由于第3条链表不为空,分配器从该链表头取出空闲块(32KB)进行分裂操作,从而获得两个大小为16KB的块,将其中一个用于服务请求(不需要在向下分裂),将另一个作为空闲块插入第2条链表中,若收到一个大小为8KB的分配请求,分配器从第1条链表中取出空闲块来服务请求。之后继续收到一个大小为4KB的分配请求,分配器会取出第2条链表中大小为16KB的空闲块进行连续分裂,从而服务请求。
3.6、SLAB
SLAB,SLUB,SLOB 都称为SLAB分配器。
系统伙伴最小的分配单位时一个物理页(4KB),但大都数情况下,内核需要分配的内存大小通常时 十几字节或几百字节,远远小于一个物理页。使用系统伙伴分配产生严重的碎片问题。
SLUB为了满足操作系统分配小对象的需求,其依赖于伙伴系统进行物理页分配。
SLUB就是把伙伴系统分配的大块内存进一步细分成小块内存进行管理。SLUB只分配固定大小的内存块2n 字节(3<=n<12)。