文章每周持续更新。原创并不容易。让更多人看到“三通”是我最大的肯定。可以在微信搜索公众号「后端技术学院」立即阅读(一般一两篇文章比博客更早更新)。今天继续学习linux内存管理,什么?你想多学时间管理,我不配,拿个西瓜去微博学吧。言归正传,上一篇别说你不懂Linux内存管理,10张图给你安排的一清二楚!Linux内存管理机制分析完,忘了的同学还可以回头看看,也强烈推荐先看那篇再看这一篇。限于篇幅,上一篇没有深入研究物理内存管理和虚拟内存分配,今天就来了解一下。通过前面的学习,我们知道程序不是那么容易骗的,让你的内存管理把玩虚拟地址空间,最后还是要给程序真正的物理内存,否则程序会罢工,所以物理内存这么重要的资源一定要好好管理和使用(物理内存就是你实际的记忆棒),那么内核是怎么管理物理内存的呢?物理内存管理利用Linux系统中的分段和分页机制,将物理内存划分为4K内存页(也称为PageFrames)。物理内存的分配和回收是基于内存页的。分页管理的好处是巨大的。如果系统申请了小块内存,可以预先分配一个页面给它,避免重复申请和释放小块内存造成频繁的系统开销。如果系统需要大块内存,可以用多页内存拼凑而成,不需要大块连续内存。可以看到不管内存大小都可以自由收放,完美的解决了分页机制!可是,理想很丰满,现实却很骨感。如果直接这样对内存进行分页,不额外管理的话还是会出现一些问题。我们来看看系统在多次分配和释放物理页面时会遇到哪些问题。物理页面管理中的问题物理内存页面的分配会造成外部碎片和内部碎片。所谓“内部”和“外部”是指“页面框架的内部和外部”。一个页框内的内存碎片是内部碎片,页框之间的多个Fragment是外部碎片。当外部碎片需要分配大块内存时,合并几个页面就足够了。系统在分配物理内存页时,会尝试分配连续的内存页。物理页的频繁分配和回收会导致大量的小块内存。在分配页面的中间,形成了外部碎片。比如内部分片的物理内存是按页分配的,这样当实际只需要少量内存时,至少会分配4K的页,内核有很多场景需要以字节为单位分配内存.这样,只需要几个字节,但必须分配一页内存。删除使用的字节后,其余部分形成内部碎片。页面管理算法的方法总是比困难更难,因为以上问题,聪明的程序员灵机一动,引入页面管理算法来解决以上碎片化问题。Buddy(伙伴)分配算法Linux内核引入了伙伴系统算法(Buddysystem),是什么意思?就是把大小相同的页框块用链表串起来。页框块就像手拉手的好伙伴,这也是这个算法名字的由来。具体来说,将所有空闲页框分组为11个块列表,每个块列表包含大小为1、2、4、8、16、32、64、128、256、512和1024个连续页框的页面。堵塞。最多可申请1024个连续页框,对应4MB连续内存。因为任何正整数都可以由2^n的和组成,所以总能找到合适大小的内存块并分配,减少了外部碎片的产生。分配示例:我需要申请4个页框,但是连续4个页框块的链表中没有空闲页框块,伙伴系统会从连续8个页框块的链表中获取一个,并拆分将其分成两个连续的4个页框块,取其中一个,将另一个放入连续4个页框块的空闲链表中。释放时会检查被释放页框前后的页框是否空闲,是否可以组成下一级长度的块。命令查看[lemon]]#cat/proc/buddyinfoNode0,zoneDMA10002110113Node0,zoneDMA323198410849404773403021848911806732330Node0,zone4Normal437404160354386610121223001slaballocator看到这里,你可能会想,有了伙伴系统,总能管理好物理内存吧?不,还不够,否则就没有sl??ab分配器了。那么什么是平板分配器呢?一般来说,内核对象的生命周期是这样的:分配内存-初始化-释放内存。内核中存在大量的小对象,如文件描述结构对象、任务描述结构对象等。如果按照伙伴系统按页分配和释放内存,频繁执行小对象的“分配内存-初始化-释放内存”会消耗大量性能。伙伴系统分配的内存还是以页框为单位的,对于内核的很多场景来说,分配的都是一小块内存,远小于一页内存的大小。slab分配器应用于内核对象的缓存,根据不同的对象将内存划分成不同大小的空间。buddy系统和slab不是替代关系,slab内存分配器是对buddy分配算法的补充。用大白话来说,原理就是对于每个内核中相同类型的对象,比如:task_struct、file_struct等需要复用的小内核数据对象,都会有一个slabcachepool来缓存大量常用的使用“初始化”对象。当你要申请这种类型的对象时,你从缓冲池的slab列表中分配一个;并且当你要释放的时候,再次保存在链表中,而不是直接返回给伙伴系统,从而避免了内部碎片,同时也大大提高了内存分配性能。主要优点slab内存管理基于小的内核对象,不需要每次都分配一页内存,充分利用内存空间,避免内部碎片。slab缓存内核中频繁创建和释放的小对象,复用部分相同的对象,减少内存分配次数。数据结构kmem_cache是??一个cache_chain链表节点,代表内核中同类型的“对象缓存”。每个kmem_cache通常是一个连续的内存块,包括三种类型的slab链表:slabs_full(fullAllocatedslablinkedlist)slabs_partial(partiallyallocatedslablinkedlist)slabs_empty(slablinkedlistwithoutallocatedobjects)kmem_cache中有一个重要的结构kmem_list3其中包含上述三个数据结构的声明。slab是slab分配器的最小单位。在实现中,一个slab由一个或多个连续的物理页面(通常只有一页)组成。单个slab可以在slab链表之间移动。例如,如果一个“half-fullslabs_partial链表”在分配对象后变满,则必须将其从slabs_partial中删除并插入到“fullslabs_full链表”中。内核slab对象的分配过程如下:如果slabs_partial链表还有未分配的空间,则分配该对象。如果分配后变满,将slab移动到slabs_full链表。如果slabs_partial链表没有未分配空间,则进入下一步。如果slabs_empty链表还有未分配空间,分配对象,移动slab进入slabs_partial链表。如果slabs_empty为空,则请求伙伴系统分页并创建一个新的空闲slab。按照步骤3分配对象并检查以上内容。先从康康系统中的slab说起吧!其实可以通过cat/proc/slabinfo命令查看系统中的slab信息。slabtop实时显示内核slab内存缓存信息。slab缓存的分类slab缓存分为“通用缓存”和“专用缓存”两大类。一般的cacheslaballocator使用kmem_cache来描述cache的结构,也需要slaballocator对其进行缓存。cache_cache保存的是“cacheofcachedescriptors”,是一个通用的缓存,存放在cache_chain链表的第一个元素中。另外,slab分配器提供的小块连续内存的分配也是由通用缓存实现的。通用高速缓存提供的对象具有从32到131072字节的几何分布大小。内核提供了kmalloc()和kfree()两个接口,分别申请和释放内存。专用缓存内核为专用缓存的申请和释放提供了一套完整的接口,根据传入的参数为指定对象分配slab缓存。私有缓存申请和释放kmem_cache_create()用于为指定对象创建缓存目的。它从cache_cache公共缓存中为新的私有缓存分配一个缓存描述符,并将这个描述符插入到缓存描述符组成的cache_chain链表中。kmem_cache_destory()用于从cache_chain列表中撤消和删除缓存。slab申请与释放内核中slab数据结构的定义如下:kmem_cache_alloc()在其参数指定的缓存中分配一个slab,对应的kmem_cache_free()在其参数指定的缓存中释放一个slab。虚拟内存分配上面的讨论都是关于物理内存的管理。Linux通过虚拟内存管理,欺骗用户程序假装每个程序都有4G的虚拟内存寻址空间(如果你不明白我在说什么,建议你回头看,别说你不知道)不懂linux内存管理,10张图给你整理清楚!)。那么我们来研究一下虚拟内存的分配,包括用户空间虚拟内存和内核空间虚拟内存。请注意,分配的虚拟内存尚未映射到物理内存。只有当请求的虚拟内存被访问时才会出现pagefault,然后通过上面介绍的partnersystem和slaballocator申请物理内存。用户空间内存分配mallocmalloc用于在用户空间申请虚拟内存。当申请小于128KB的小内存时,malloc使用sbrk或brk分配内存;申请内存大于128KB时,使用mmap函数申请内存;由于brk/sbrk/mmap是系统调用,因此存在问题。如果每次申请内存都会产生系统调用开销,CPU会频繁地在用户态和内核态之间切换,对性能影响很大。此外,堆从低地址向高地址增长。如果低地址的内存不释放,高地址的内存就无法回收,容易产生内存碎片。为了解决这个问题,malloc采用了内存池的实现方式,先申请一大块内存,然后把内存分成大小不一的内存块,然后用户申请内存的时候,直接选择一块相近的内存从内存池中阻塞并分配它。内核空间内存分配在讲内核空间内存分配之前,我们先来回顾一下内核地址空间。kmalloc和vmalloc分别用于分配不同映射区域的虚拟内存。kmallockmalloc()分配的虚拟地址范围在内核空间的“直接内存映射区”。以字节为单位的虚拟内存一般用于分配小块内存,释放内存对应kfree,可以分配连续的物理内存。函数原型在
