内存分配机制
linux中将内存区域分为了3块
- DMA
- NORMAL:OS占用的内存空间
- HIGH:非OS占用的内存空间
虚拟内存
作用如下:
- 提供了进程间的数据共享
- 实现了物理内存按需分页
- 降低了使用物理内存的门槛
虚拟内存的管理使用了红黑树,key为起始地址,value为vma
MMU
memory management unit,也叫分页内存管理单元。它负责虚拟地址到物理地址的转换(虚拟内存管理),内存保护和中央处理器高速缓存的控制。
NUMA
Non-uniform memory access,非均匀访存模型。它是一种为多处理器的电脑设计的内存架构,内存访问时间取决于内存相对于处理器的位置。在NUMA下,处理器访问它自己的本地内存的速度比非本地内存(内存处于另一个处理器,或者是处理器之间共享的内存)要快一些。
zone
在NUMA中,每一个处理器的本地内存被认为是一个节点(node),而每一个node又被划分为多个内存管理区域,这些区域就是zone。zone也有3种
- zone_dma
- zone_normal
- zone_high
三级缓存机制
- 一级缓存可以分为一级数据缓存(Data Cache,D-Cache)和一级指令缓存(InstructionCache,I-Cache)。
- 每一个CPU内部都会有一个二级缓存
- 三级缓存由所有CPU共享
现在的缓存一般都采用SRAM存储器实现
大内存的分配
大内存的分配一般是使用伙伴算法,以页为基本单位,每次分配1个或多个页,伙伴算法可以看下图
linux将所有的空闲页框分组为11块链表,每一块链表分别包含大小为1,2,4,8,16,32,64,128,256,512和1024的连续页框,其中每一个页框的大小是4KB。
举个例子来说明伙伴算法。比如现在需要分配4个页框大小的空间,就直接从2号块拿出一个4块连续的页框空间进行分配
slab
伙伴算法只能针对大空间进行分配,它可以避免外部碎片的产生,但无法避免内部碎片的产生。slab算法针对小对象的分配,可以避免产生内部碎片。
slab算法的整体结构如图所示(图片截自https://www.bilibili.com/video/BV1wk4y1y7gL?from=search&seid=10125783671322033796)
每一块高速缓存都由多个slab组成,每一个slab中存储了多个对象(每个对象的大小都是相同的)。详细的可以看下面这张图(图片截自https://www.bilibili.com/video/BV1wk4y1y7gL?from=search&seid=10125783671322033796)
slab分为slab_full(存满的slab),slab_partial(只用了一部分的slab),slab_free(完全没用的slab)
例如要分配128B大小的对象,选择object_size=128B的高速缓存,然后再从中选择slab_partial或者slab_free进行对象的分配
内存回收机制
内存的回收使用LRU(最近最少使用)算法。linux在具体实现时设置了active_list和inactive_list两个队列。
- active_list存储最近使用的页
- inactive_list存储最近未使用的页
两个队列都是队头添加元素,队尾删除元素。
页标记
active_list队列中的页PG_active都会被标记为1,而inactive_list队列中的页的PG_active会被标记为0。
具体流程
-
当inactive_list中的页被访问时,如果它的PG_referenced=1则将该页从inactive_list中移除,然后添加到active_list中,如果PG_referenced=0,则只将PG_referenced置为1
-
需要进行页的淘汰时,会淘汰inactive_list队尾PG_referenced=0的页。因为PG_referenced=1的页表示该页在inactive_list中被访问了一次。
-
同样,如果active_list队列满了,要从队尾淘汰PG_referenced=0的页,将其放入inactive_list中。但如果active_list队尾的页PG_referenced=1,则将该页重新加入到active_list队头,同时PG_referenced设为0
存在的问题
定义当页面刚被回收,又马上被访问到,这样产生的缺页中断叫refault。
- 当inactive_list比较长时可以保证每个页面在被回收前有充分的时间再次被访问到,这样可以减少refault的数量,但是内存是有限的,inactive_list太长了,actifve_list就比较短了。那么要怎么平衡二者的长度?
我们可以在页面被回收时记录一个timestamp1,当该页面refault被换入时再记录一个timestamp2。接着我们可以比较该页面再次被换出时经历的时间与timestamp2和timestamp1的差值的大小,如果前者较大说明页面可以在inactive_list中再次被访问到,此时的inactive_list长度适中,否则就要增加inactive_list的长度了。实现时我们使用计数值来代替timestamp。
- 多个cpu访问对active_list和inactive_list执行add和delete操作,这两个队列是需要进行加锁操作的,但频繁的加锁对于性能的影响是比较大的。所以每次的add和delete操作都是先将被操作的节点加入到lur cache中,当lru cache的大小达到某个阈值(15个页面)时,再获取锁将这15个页面一起加入或者从队列中删除