Linux Memory -- 三大allocator原理

Linux Memory – 三大allocator原理

boot memory allocator

  1. boot memory allocator的作用
    因为内核里面有很多memory struct,不可能在静态编译阶段就静态初始化所有的这些memory struct。另外,在系统启动过程中,系统启动后的物理内存分配器本身也需要初始化,如buddy allocator,那么buddy allocator如何获取内存来初始化自己呢 ?为了达到这个目标,Linux设计了一种简单的memory allocator:boot memory allocator
  2. boot memory allocator的原理
    在Linux内核中使用struct bootmem_data来描述一个boot memory allocator,其Node结构下的一个成员,也就是说每一个node都有一个boot memory allocator.
    boot memory allocator使用struct bootmem_data结构中的node_bootmem_map这个bitmap来呈现memory的状态,一个bit代表一个物理页框,也就是用struct page,如果一个bit为1,表示该page已经被分配了,如果bit是0,则表示该page未被分配,为了能够满足比一个page还小的内存块的分配,boot memory allocator会使用last_pos来记住上次分配所使用的PFN以及上次分配所使用的page内的偏移:last_offset,下次分配的时候结合last_pos和last_offset将细小的内存块分配尽量集中在相同的page中。

最新一次的分配能否和上一次分配合并。如果满足以下条件就能合并,
1.上次分配所后的last_pos,与此次分配找到的page是连续的
2.上次分配之后的page中海油空闲的空间last_offset != 0 对齐大小小于PAGE_SIZE
3.无论当前的分配是否有与上次分配合并,last_pos和last_offse都将更新用来显示当前分配所使用的是哪一个page以及该page使用了多少空间。如果该page全部被使用,则last_offset = 0。

boot memory

  1. boot memory allocator的缺点
    尽管boot memory allocator不会造成严重的内存碎片,但是每次分配过程需要线性扫描搜索内存来满足当前的分配。因为是检查bitmaps,所以代价比较昂贵,尤其是first fit算法倾向将小块内存放置在物理内存开头,但是这些内存区域在分配大块内存时,也需要扫描,所以该过程十分浪费。所以boot memory allocator在系统启动后就被弃用的原因。
  2. boot memory allocator的接口
    (1) void * alloc_bootmem(unsigned long size)
    从ZONE_NORMAL中分配size个字节的内存。通过此函数分配的地址将会与L1 硬件缓存对齐从而最大效率利用硬件缓存
    (2)void * alloc_bootmem_pages(unsigned long size)
    从ZONE_NORMAL中分配满足size的页对齐的pages,如:不足一页返回整页,不足两页返回两个整页。
    (3)void * alloc_bootmem_low_pages(unsigned long size)
    从ZONE_NORMAL中分配满足size的页对齐的pages,如:不足一页返回整页,不足两页返回两个整页。
  3. boot memory allocator退休,将物理memory填充到buddy allocator中,移交给buddy allocator进行管理。
    retire

buddy allocator

  1. buddy allocator原理
    物理内存被分成11个order:0 ~ 10,每个order中连续page的个数是2order,如果一个order中可用的memory size小于期望分配的size,那么更大order的内存块会被对半切分,切分之后的两个小块互为buddies。其中一个子块用于分配,另一个空闲的。这些块在必要时会连续减半,直到达到所需大小的memory 块为止,当一个block被释放之后,会检查它的buddies是否也是空闲的,如果是,那么这对buddies将会被合并。
    order
struct free_area {
	struct list_head	free_list;
	unsigned long		*map;
};

每个order用结构体free_area 表示,其中free_list表示当前order中空闲块的第一个page,map表示buddies的状态。

2.释放实例:
buddies

  1. buddy allocator的优缺点
    优点:由于将物理内存按照PFN将不同的page放入到不同order中,根据需要分配内存的大小,计算当前这次分配应该在哪个order中去找空闲的内存块,如果当前order中没有空闲,则到更高阶的order中去查找,因此分配的效率比boot memory的线性扫描bitmap要快很多。
    缺点:
    (1)释放page的时候调用方必须记住之前该page分配的order,然后释放从该page开始的2order 个page,这对于调用者来说有点不方便(2)因为buddy allocator每次分配必须是2order 个page同时分配,这样当实际需要内存大小小于2order 时,就会造成内存浪费,所以Linux为了解决buddy allocator造成的内部碎片问题,而引入slab allocator.

slab allocator

1.slab allocator的作用
<1>能够分配更小块的内存,可以帮助消除buddy allocator原本会造成的内部碎片问题
<2>缓存常用的object,因此内核不会在分配, 初始化和销毁object上浪费时间。
<3>通过将object地址与L1或者L2硬件cache对齐后可以更好的利用硬件缓存
2.slab allocator的原理
slab allocator由cache --> slab --> object三个结构相结合地方式来描述,其中cache由 struct kmem_cache_s来描述,struct kmem_cache_s.中的成员struct kmem_list3 lists数组包含三个slab list:full,part,free, 这三个list是有struct slab结构组成的链表。其中struct slab中的s_mem是第一个object的起始地址,因此就可以通过cache --> slab --> object找到空闲的object。其中slab中的page 的prev指向cache,next指向slab。
而slab还分为on-slab 和off-slab,是指当object的size特别大的时候,slab中新分配的page就全部放object,而struct slab结构单独从通用slab中进行分配,如果object的size比较小,则可以将struct slab放置在新分配的page开头,然后再放置object。

  1. object查找问题
    首先查找cache中part_list和free_list找到有空位的slab,然后在slab中通过struct free字段和和在struct slab中隐式的数组kmem_bufctl_t【】来查找下一个空闲的object,free表示当前空闲的object,而数组kmem_bufctl_t[free]表示下一个空闲的object,找到object的index后,通过slab中object的首地址就能找到该index 对应的object的首地址
  2. per-cpu cache,cache中被释放的object并不会立即还给slab,而是放置在per-cpu cache中,当per-cpu cache中有一定数量的object后,再将这些object放置到全局cache中,当全局cache中的object满了,再还原到slab中。这样保证每个CPU能使用相同object。
  3. slab cache的回收问题
    cache使用者现在可以通过set_shrinker()注册一个"shrink cache"的callback用于智能判断和收缩slab。这个简单的函数通过操作一个struct shrinker,其中有一个指针指向callback。幷包含一个seek字段用户表示重新创建一个object的困难程度,然后再将shinker放入到shrinker_list中。
    在page回收的过程中,shrink_slab()函数被调用,来遍历shrinker_list然后调用每个callback两次。首先传入参数0这表示该callback应该要返回它期望自己能释放的page的个数。使用一个启发的方式来决定是否值得调用该callback来回收pages。如果值得,该callback将被调用第二次,传入要释放的page的个数。
    每一个进程描述符struct task中有一个reclaim_state字段。当slab allocator释放pages时,这个字段被更新为这次释放的page个数。在调用shrink_slab()之前,这个字段被设置为0然后当shrink_cache()被调用后再次读取该字段的值来决定要释放多少个page。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章