当前位置: 首页 > 科技观察

Linux内存管理的全局框架

时间:2023-03-22 01:48:26 科技观察

解释了复杂繁琐的机制原理。最流行的方法是以模型架构的形式呈现给读者。首先要整体理解大方向和大结构,然后根据大方向和大结构进行分支。深入,正如毛主席所说“战略上蔑视敌人,战术上注意敌人”。接下来,我也会这样画出每一个大模型,并做一个简单的说明。1、地址划分。1.CPU地址。CPU地址是指CPU地址总线的可寻址范围。32bit-CPU寻址范围是4G。这个地址是虚拟的。事实上,外部物理内存不会使用这么大的内存。CPU虚拟地址的4G空间通常分为两部分,一部分是内核虚拟地址,通常在3G-4G之间,另一部分是用户虚拟地址,通常在0G-3G之间。显然,用户进程可以使用的虚拟地址的地址范围远远大于内核可以使用的虚拟地址空间。然而,物理内存只限于几M、几G。内核虚拟地址如何使用物理内存,用户空间如何使用物理内存,是Linux内存管理的关键。.2.物理内存物理内存是指对外存储数据的设备,具有可以被CPU寻址的地址总线,由CPU的Cache和TLB/MMU管理和寻址。需要明确一个概念:任何代码都运行在CPU上,而不是物理内存上。物理内存是用于在用户进程空间或内核的关键数据结构中存储可执行代码的设备。这些代码或结构最终会被CPU通过MMU寻址,Cache***指令数据获取。NUMA的全称是非统一内存访问。通常是多核接入的概念。每个CPU核心都会有一个节点对应一部分物理内存。对这些节点的管理增加了这些数据结构:perCPU变量、list表系列节点遍历、zone划分、zonelist管理等。为了简化问题,我们只分析UMA一个节点的情况。当然,它也包含了NUMA的一些数据结构特点,后面会简单介绍。下图是图2-1所示NUMA的简化图抽象。图2-1NUMA多核物理内存分区示意图3.内核虚拟地址空间划分。如果读者只了解一些粗浅的信息,肯定会认为内核的虚拟地址空间只有逻辑地址。其实这只是内存内核虚拟地址划分的一个特例,并不是一个完整的描述。现在我画了一个完整的图并改变它。更改内核虚拟地址空间的名称,如图2-2图2-2内核虚拟地址空间的划分及其到物理内存的映射我们改一下名称,直接映射的地址可以称为内核物理直接映射地址或逻辑地址。Linux原则上只能使用1G虚拟空间中的896M,剩下的128M留作他用,所以直接映射以外的物理内存称为高端内存。128M之间的空间分为多个gap安全间隙,虚拟地址,固定映射和持久映射。请注意,这里的虚拟地址通常与前面提到的内核虚拟地址有些混淆。后者指的是CPU核心虚拟地址,是一个更广义的概念。由于直接映射的部分有个名字叫逻辑地址,所以这里的虚拟地址空间往往指的就是这部分。虚拟地址有以下用途。通过内核使用vm_struct结构管理高端内存,可以使用kmap方式获取高端物理内存的空间;也可以不映射物理高端内存,直接使用这个地址作为外部物理设备的ioremap地址,从而可以直接操作设备。当然这也暴露了外部设备的地址空间,容易造成干扰,所以通常不能直接访问ioremap映射的地址而是使用readb/writeb读写,必须优化barrier设置和释放它与iounmap。因为映射设备通常具有“边际效应”。如果没有高端内存,(当然32bit的嵌入式系统通常不会使用高端内存,至少我看到的那么多关于ARM,powerPC,MIPS32的嵌入式应用都没有使用高端内存),那么可能不会使用固定映射和持久映射。固定映射可以指定长期持有的物理内存的某些地址页的占用情况。这种映射关系可以在初始阶段进行配置,而持久化映射在启用时与高端内存物理页建立映射关系,其他阶段不存在。将被解除。我要强调的是,我这里不关心高端内存,内核直接映射逻辑地址可以覆盖所有物理内存。4、用户虚拟地址空间的划分用户虚拟地址空间的图结构并不复杂。复杂的是它在虚拟内存空间的应用,如何映射文件,如何组织区间映射,关联的进程是谁,对应的内存结构是什么,实例是什么等等,是用户虚拟映射最难的部分。下面的图只是为了对用户虚拟内存空间有一个大的了解而画的,如图2-3所示。图2-3用户空间虚拟内存布局既然用户空间是虚拟的,那么它是如何访问物理内存的呢?当然是PGD、PUD、PMD、PTE、OFFSET及其TLB快表查询。上层目录入口PUD和中间目录入口一般不考虑,考虑二级目录就可以了。图2-4摘自网络:图2-4用户进程空间访问物理内存的方法2.伙伴系统伙伴系统是一种分阶段管理外部物理内存的方法。最多有11个阶段,每个阶段有一个或多个页面合并的集合,使用指针串联起来,同时在同级的一个或多个页面集中形成自己的伙伴。需要强调的是,每个级别的合作伙伴数量等于页数。使用下图2-5更容易理解。 图2-5伙伴系统在内存中的一般模型当内核申请一块按页分配但不按顺序分配的内存时,通常会利用伙伴系统的原理,按顺序分配请求空间的最高阶,多余的页面根据伙伴系统的算法合并到其他层次的链表中,形成新的层次伙伴。释放内存空间时,被释放的空间会尝试寻找可以使用它的stage作为连接的伙伴,如果超过则进行拆分,如果多余则寻找其他可以使用的stage它作为合作伙伴。很啰嗦,但还是很容易理解的。后面会给出源码,并结合实例详细分析。3、防碎片技术:防碎片机制其实是在伙伴系统之前。主要将各个zone的物理内存分为可回收不可移动不可移动、可移动可移动、不可移动不可移动。这些标志按照一定的列表串联起来,在管理上,当外部条件申请物理内存,造成很多碎片时,它可以根据这些数据结构的标志,对物理内存进行重组和分类,从而减少碎片页面或孤独页。反碎片化技术在嵌入式系统中很少用到,大部分被伙伴系统占用,这里不做具体分析,简单讨论一下。4、slab分配机制。众所周知,操作系统采用伙伴系统来管理内存,不仅会造成大量的内存碎片,还会降低处理效率。SLAB是一种内存管理机制,处理效率高,可以有效避免内存碎片。它的核心思想是预分配。它按照SIZE对内存进行分类管理。当申请一块大小为SIZE的内存时,分配器会从SIZE集合中分配一个内存块(BLOCK)。当释放一块大小为SIZE的内存时,分配器会将该块放回原来的集合中,而不是释放给操作系统。当再次申请相同大小的内存时,可以重新使用之前回收的内存块(BLOCK),从而避免内存碎片的产生。【注:由于SLAB处理过程的细节比较多,这里只是理论上的解释1.整体结构图1SLAB内存结构2.处理流程如图1所示:SLAB管理机制将内存大致分为SLABheader、SLOT数组、PAGES数组、可分配空间、浪费空间等模块分开管理。各模块功能及作用:SLAB头:包含SLAB管理的汇总信息,如最小分配单元(min_size)、最小分配单元对应位移(min_shift)、页数组地址(pages)、空闲页链表(free)、可分配空间起始地址(start)、内存块结束地址(end)等信息(如代码1所示),在内存管理过程中,内存分配、回收、定位等操作都是取决于这些数据。SLOT数组:SLOT数组的每个成员负责一个固定大小的内存块(BLOCK)的分配和回收。nginx中SLOT[0]~SLOT[7]分别负责[1~8]、[9~16]、[17~32]、[33~64]、[65~128]、[129~256]],[257~512],[513~1024]字节的内存分配,但是为了方便内存块(BLOCK)的分配和回收,每个内存块(BLOCK)的大小都是上每个间隔的限制(8、16、32、64、128、256、512、1024)。例如:如果应用进程请求申请5个字节的空间,因为5在[1~8]范围内,SLOT[0]负责内存分配,但是[1~8]范围的上限]是8,所以即使申请了5个字节,还是有8个字节分配给了应用进程。以此类推:如果申请12个字节,12在[9~16]区间,上限为16个,所以SLOT[1]给申请进程分配了16个字节;如果申请50个字节,50在区间[33~64],上限为64,所以SLOT[2]给申请进程分配了64个字节;如果申请84字节,84在[65~128]范围内,上限为128,所以SLOT[3]分配128字节;...;如果申请722字节,722在[513~1024]之间,上限为1024,所以SLOT[7]分配了1024字节。PAGES数组:PAGES数组的每个成员负责查询、分配和回收可分配空间中的每一页,其处理流程可以参考3.2节的描述。可分配空间:SLAB在逻辑上将可分配空间划分为M个内存页,每个页大小为4K。内存的每一页都与PAGES数组的成员一一对应,PAGES数组的每一个成员负责每一个内存页的分配和回收。浪费空间:当按照每页4K的大小划分空间时,满足4K的空间将被PAGES数组作为可分配空间进行管理,剩余小于4K的内存将被丢弃,即浪费了!