linux内存管理简单总结

1、伙伴系统alloc_pages

1.伙伴系统是基于zoom来管理分配的。

2.分配的最小的单位就是一个page就是(4k),不同的系统还可以设置成(16k),最大能分配的内存块是max_order(11),就是4M物理页面。

3.内存在初始化的时候,会计算出zoom对应的3个水位值分别是watermark high,low,min。

4.zoom数据结构里有free_area数据结构,分别对应order从0~10的不同的内存块,free_area又细分不同的迁移类型:有Moveable(用户态malloc的页面就是可迁移类型),UnMoveable(内核分配的页面一般都是不可迁移类型),ReCliantable(文件映射,可以回写磁盘的页面),linux系统刚刚初始化完成之后,基本都是在order等于10,并且是Moveable的那个大内存链表中。

5.alloc_pages(gfp_mask,order)gfp_mask==>低4个bit可以看出从哪个zoom优先分配---HighMem-Zoom,还是Normal-zoom。其他bit可以GFP_KERNEL、GFP_USER=====>都有可能引起进程睡眠,GFP_ATOMIC=====>高优先级的,不会引起睡眠 。

6. zone_watermark_ok=====》检查水位是否满足要求。

7.buffered_rmqueue
    1)内存大多数情况下都是order等于0的时候,每个cpu有个单叶匡的高速缓存,直接从单叶匡的高速缓存中分配。
    2)order大于0的话,rmqueue去分配内存。根据gfp_mask分配掩码找到free_area中的freelist,从order开始寻找,依次++,直到找到空闲的大内存块,然后需要把多余的内存块进行expand切割,加入到小order的freelist里面。

8.alloc_free释放函数
        判断相邻的内存块是不是伙伴:1.大小相同2.地址连续3.从同一个大块中切割出来的。才能合并成一个大内存。

2、buffered_rmqueue

单页框的情况:
        1)直接从本地cpu的页框高速缓存中分配,每个cpu都有一个独立的缓存池。
        2)不需要申请zoom->lock这个锁。
        3)不需要再进行内存块的切割操作,因为一开始的已经分配好了。
        order==0的时候,从zoom区域的pageset高速缓存页里面分配,pcp = &this_cpu_ptr(zone->pageset)->pcp;pcp里面有空闲页面的话,直接分配。如果没有的话,也是调用__rmqueue从伙伴系统中分配。
        order大于0的时候,才会__rmqueue从伙伴系统中分配。

        spin_lock_irqsave(&zone->lock, flags);
        page = __rmqueue(zone, order, migratetype);
        spin_unlock(&zone->lock);

3、__rmqueue

1)从alloc_pages参数里的order对应的free_area开始寻找,对应的迁移类型为空的话,就往上找一个,order++。
2)直到找到不为空的free_area,对于的order。把对应的内存page从free_list里面删除。
3)取出这个内存快,还需要把剩下的切割(expand),把剩下的内存块加入到order的free_list链表中去。

4、slub机制:

1.伙伴系统能分配的最小的内存是4k,内存使用slab机制来分配小段内存。
2.slub,slob都是slab的一种,是在slab基础上的优化,现在内核基本都使用slub机制,slub相比slab没有color着色区,还有空闲、部分空闲、满链表。主要的结构体定义、还有分配的接口都是兼容的。
3.slub分配内存主要4个接口:
        kmem_cache_create/创建slab缓存,并不实际分配slab对象
        kmem_cache_alloc/才会去真正分配一个slab对象
        kmem_cache_free
        kmem_cache_destroy
4.slab缓存的描述符kmem_cache的重要成员:
    1)struct kmem_cache_cpu __percpu *cpu_slab;//指向本地CPU缓冲池
        本地cpu缓存池:1.freelist成员指向第一个可用内存obj首地址
                      2.page成员指向当前slab所使用的page页面
                      3.partial链表,本地cpu缓存的一些空闲slab
    2)kmem_cache_node 缓存节点:1.也有一个partial联系,所有cpu的共享的空闲slab链表。2.自旋锁3.当前kmem_cache_node的slab的数量。

5.kmem_cache_create
        分配一个kmem_cache缓存池对象,这个对象也是用slub分配的。====>就像函数要调用自己来创建自己,kmem_cache_init()函数,静态定义第一个kmem_cache(static struct kmem_cache boot_kmem_cache;)该kmem_cache就是为了kmem_cache_create()函数创建kmem_cache结构体的slab缓存池。
        1)初始化object size(实际size加上align对齐)。
        2)根据object 的size大小,来算一个slab需要多少个页面,就是order值。还有一个slab中能包含多少object。需要的页面的页面order和能包含多少object保存在oo成员里面,order_object。
        3)offset:slub分配在管理object的时候采用的方法是:既然每个object在没有分配之前不在乎每个object中存储的内容,那么完全可以在每个object中存储下一个object内存首地址,就形成了一个单链表。很巧妙的设计。那么这个地址数据存储在object什么位置呢?offset就是存储下个object地址数据相对于这个object首地址的偏移。
        4)分配per cpu的缓存对象,kmem_cache_node节点。
        5)主要就初始化一些kmem_cache缓存的一些成员,并不实际分配内存,也不分配slab对象。

6.kmem_cache_alloc
    1)刚创建的kmem_cache缓存,第一次分配slab对象的时候,本地CPU的缓存和共享的缓存node节点的partial链表都是空的。
    2)就需要通过伙伴系统alloc_pages来分配页面,freelist执行page的地址,page赋值当前正常使用的page,重要的一步就是:遍历page内存大小来划分object大小,就是在每个object内部的offset地址来存储下一个object的地址,这样就形成了一个单链表。
    3)分配完page之后,就freelist直接分配一个object,freelist指向单链表的以一个object地址,下次分配的时候,链表不为空的话,还直接分配。
    4)首先从cpu 本地缓存池分配,如果freelist不存在,就会转向per cpu partial分配,如果per cpu partial也没有可用对象,继续查看per node partial,如果很不幸也不没有可用对象的话,就只能从伙伴系统分配一个slab了,并挂入per cpu freelist。

7.kmem_cache_free
    1)slab释放,slab里面维护了一个单链表,新释放的object对象,也需要加入到这个单链表中去。===》1.让被释放的obj的set地址存储freelist值。2.freelist指向新的object。
    2)obj属于的page就是当前使用的page,那么就直接释放。需要判断释放是满slab,需要挂入到本地cpu的partial链表,page数据结构体里面也有个freelist专门用来slab机制,用page里面的freelist来指向新的object地址。
    3)需要额为判断的是:本地CPU的空闲的slab大于一定数目后,需要转移一些到共享的node里面,如果kmem_cache_node节点中空闲的slab数量大于一定后,需要往伙伴系统中释放。per cpu partial链表管理的所有slab的free object数量超过kmem_cache的cpu_partial成员的话,就需要将per cpu partial链表管理的所有slab移动到per node partial链表管理。

kmalloc:
1)是内核用来分配内存的接口,就是基于slab机制实现。
2)分配的size大于4k的话,还是kmalloc_large在伙伴系统中分配,小于4k的话,kmalloc_slab是slab分配。
3)系统中专门为kmalloc申请了一系列大小的kmem_cache缓冲区,分别对应8字节、16字节、32、字节的slab缓存池。
4)找到对应的kmem_cache后,就是通过slab来分配释放内存。

5、malloc

1)内存布局
        栈自顶向下扩展,推至第向上扩展,mmap区域也是至顶向下。

2)glibc库
        malloc和free都是glibc封装的用户态函数,来动态分配和释放内存。
        malloc底层是通过brk、mmap系统调用来申请内存,每次都通过系统调用来申请的内存会影像性能。为了保持高效的分配, allocator 一般都会预先分配一块大于用户请求的内存, 并通过某种算法管理这块内存. 来满足用户的内存分配要求, 用户 free 掉的内存也并不是立即就返回给操作系统。

3)为什么需要glibc而不直接brk,或者mmap分配内存?
        brk、mmap都是系统调用,都要从用户态切换到内核台,频繁的小内存申请、释放会增加系统的负担。glibc采用了内存池的设计,每次先从内存池中申请、没有的话再向内核申请。分配内存 < DEFAULT_MMAP_THRESHOLD(128k),走__brk,从内存池获取,失败的话走brk系统调用 
分配内存 > DEFAULT_MMAP_THRESHOLD,走__mmap,直接调用mmap系统调用。

4)内存池保存在bins这个长128的数组中,每个元素都是一双向个链表。其中bins[0]目前没有使用
bins[1]的链表称为unsorted_list,用于维护free释放的chunk。bins[2,63)的区间称为small_bins,用于维护<512字节的内存块,其中每个元素对应的链表中的chunk大小相同,均为index*8。
bins[64,127)称为large_bins,用于维护>512字节的内存块,每个元素对应的链表中的chunk大小不同,index越大,链表中chunk的内存大小相差越大,例如: 下标为64的chunk大小介于[512, 512+64),下标为95的chunk大小介于[2k+1,2k+512)。同一条链表上的chunk,按照从小到大的顺序排列。

5)do_brk
do_brk仅进行匿名映射,申请从addr开始len大小的虚拟地址空间。
do_brk首先判断虚拟地址空间是否足够,然后查找VMA插入点,并判断是否能够进行VMA合并。如果找不到VMA插入点,则新建一个VMA,并更新到mm->mmap中。

6)VM_LOCK标志位
会立即建立和物理页面的映射,按照PAGE_SIZE为步长
__mlock_vma_pages_range
    ====>__get_user_pages
 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值