在内核初始化完成后,内存管理的责任由伙伴系统(高效、高速)承担。前端
系统内存中的每一个物理内存页(页帧),都对应于一个struct page实例。每一个内存域都关联了一个struct zone的实例,其中保存了用于管理伙伴数据的主要数组。node
1 struct zone { 2 ... 3 struct free_area free_area[MAX_ORDER]; //不一样长度的空闲区域 4 ... 5 } ;
sruct free_area是一个辅助结构,以下所示。程序员
1 struct free_area { 2 struct list_head free_list[MIGRATE_TYPES]; //用于链接空闲页的链表 3 unsigned long nr_free; //当前内存区中空闲页块的数目 4 };
阶(order)是伙伴系统中一个很是重要的术语。它描述了内存分配的数量单位内存区的管理单位,内存块的长度是2的order次方。图1是伙伴系统中相互链接的内存区,内存区中第1页内的链表元素,可用于将内存区维持在链表中。所以,也没必要引入新的数据结构来管理物理上连续的页,不然这些页不可能在同一内存区中,MAX_ORDER根据硬件不一样而设置不一样的值,表示一次分配能够请求的最大页数的以2为底的对数。算法
图1 伙伴系统相互链接的内存区后端
伙伴没必要是彼此链接的。若是一个内存区在分配其间分解为两半,内核会自动将未用的一半加入到对应的链表中。若是在将来的某个时刻,因为内存释放的缘故,两个内存区都处于空闲状态,可经过其地址判断其是否为伙伴。数组
基于伙伴系统的内存管理专一于某个结点的某个内存域,例如,DMA或高端内存域。但全部内存域和结点的伙伴系统都经过备用分配列表链接起来。如图2所示。缓存
图2 伙伴系统和内存域/结点之间的关系安全
Linux系统启动并长期运行后,物理内存会产生不少碎片。这对用户空间应用程序没有问题(其内存经过页表进行映射,物理内存分布与应用程序看到的内存无关),但对内核来讲,碎片是一个问题(大多数物理内存一致映射到地址空间内核部分)。网络
(1)依据可移动性组织页数据结构
文件系统的碎片主要经过碎片合并工具解决,不一样于物理内存,许多物理内存页不能移动到任意位置,阻碍了该方法的实施。内核处理避免碎片的方法是反碎片(版本2.6.24),试图从最初开始尽量防止碎片。
内核将已分配页划分为如下3种不一样类型:
页的可移动性,依赖该页属于3种类别的哪种。内核使用的反碎片技术,将具备相同可移动性的页进行分组。根据页的可移动性,将其分配到不一样的列表中,防止不可移动的页位于可移动内存区中间的状况出现。这样对于不可移动页中仍然难以找到较大的连续空闲时间,但对可回收的页就相对容易了。
内核定义了一些宏来表示迁移类型:
1 #define MIGRATE_UNMOVABLE 0 //类型 2 #define MIGRATE_RECLAIMABLE 1 //类型 3 #define MIGRATE_MOVABLE 2 //类型 4 #define MIGRATE_RESERVE 3 //向具备特定可移动性的列表请求分配内存失败,从MIGRATE_RESERVE分配内存(紧急分配) 5 #define MIGRATE_ISOLATE 4 //不能从这里分配,特殊的虚拟区域,用于跨越NUMA结点移动物理内存页 6 #define MIGRATE_TYPES 5
对伙伴系统的主要数据结构影响是将空闲列表分解为MIGRATE_TYPE个列表,代码以下:
1 struct free_area { 2 struct list_head free_list[MIGRATE_TYPES]; 3 unsigned long nr_free; //全部列表上空闲页的数目 4 };
内核提供了一个备用列表,规定了在指定列表中没法知足分配请求时,接下来使用的迁移类型的种类。(在内核想要分配不可移动页时,若是对应链表为空,则后退到可回收页链表,接下来到可移动页链表,最后到紧急分配链表。)
页可移动性分组特性老是编译到内核中,但只有在系统中有足够内存能够分配到多个迁移类型对应的链表时,才会起做用。两个全局变量pageblock_order和pageblock_nr_pages提供每一个迁移链表对应的适当数量的内存。第一个表示内核认为是“大”的一个分配阶,pageblock_nr_pages则表示该分配阶对应的页数。若是体系结构提供了巨型页机制,则pageblock_order一般定义为巨型页对应的分配阶(IA-32巨型页长度是4MB),若是体系结构不支持巨型页,则将其定义为第二高的分配阶(MAX_ORDER-1)。若是各迁移类型的链表中没有一块较大的连续内存,那么页面迁移不会提供任何好处,所以在可用内存太少时内核会经过设置全局变量page_group_by_mobility为0关闭该特性(一旦停用了页面迁移特性,全部页都是不可移动的)。
在内存子系统初始化期间,memmap_init_zone负责处理内存域的page实例。它将全部的页最初都标记为可移动的,此时若是须要分配不可移动的内存,则必须“盗取”(见4分配API)。实际上,启动期间分配可移动内存区的状况较少,分配器有很高的概率分配长度最大的内存区,并将其从可移动列表转换到不可移动列表。因为分配的内存区长度是最大的,所以不会向可移动内存中引入碎片。这种作法避免了启动期间内核分配的内存(常常在系统的整个运行时间都不释放)散布到物理内存各处,从而使其余类型的内存分配免受碎片的干扰,这也是页可移动性分组框架的最重要的目标之一。
(2)虚拟可移动内存域
依据可移动性组织页是防止物理内存碎片的一种可能方法,内核还提供了另外一种阻止该问题的手段:虚拟内存域ZONE_MOVABLE,其特性必须由管理员显示激活。
基本思想:可用的物理内存划分为两个内存域,一个用于可移动分配,一个用于不可移动分配。
kernelcore参数用来指定用于不可移动分配的内存数量(用于既不能回收也不能迁移的内存数量)。参数movablecore控制用于可移动内存分配的内存数量。若是同时指定两个参数,内核会按照必定的方法进行计算,取指定值与计算值中较大的一个。
ZONE_MOVABLE并不关联到任何硬件上有意义的内存范围,该内存域中的内存取自高端内存域或普通内存域,所以称虚拟内存域。
从物理内存域提取用于ZONE_MOVABLE的内存数量主要考虑如下两个因素:
最后是计算结果,用于为虚拟内存域ZONE_MOVABLE提取内存页的物理内存域,保存在全局变量movable_zone中;对每一个结点来讲,zone_movable_pfn[node_id]表示ZONE_MOVABLE在movable_zone内存域中所取得内存的起始地址。
(虚拟内存域具体的实如今4分配API中)
在启动期间,各体系结构相关的代码须要确立系统中各内存域的页帧的边界(max_zone_pfn数组);肯定各结点页帧的分配状况(全局变量early_node_map)。
(1)管理数据结构的建立
图3概述了管理数据结构创建的过程。
图3 管理数据结构构建过程示意图
图4 free_area_init_nodes的代码流程图
free_area_init_nodes代码流程图如图4所示,完成如下工做:
(2)对各个结点建立数据结构
在内存域边界已经肯定以后,free_area_init_nodes分别对各个内存域调用free_area_init_node建立数据结构。这涉及到几个辅助函数(见图4):
此时,空闲页的数目(nr_free)当前仍然规定为0,这显然没有反映真实状况。直至停用bootmem分配器、普通的伙伴分配器生效,才会设置正确的数值。
伙伴系统接口对于NUMA和UMA体系结构没有差异,可是它只能分配2的整数幂个页(分配必须指定阶),内核中的细粒度分配只能借助于slab分配器(或者slub、slob分配器)。
(1)分配掩码
分配器API中的mask参数,称为掩码,它包含了图5所示的内容。 (GFP表示get free page)
图5 GFP掩码布局
(2)内存分配宏
经过使用标志、内存域修饰符和各个分配函数,内核提供了一种很是灵活的内存分配体系,全部接口函数均可以追溯到一个基本函数alloc_pages_node,如图6所示。
图6 伙伴系统的各分配函数之间关系
相似地,内存释放函数也能够归约到一个主要的函数__free_pages,如图7所示(只是调用参数不一样)。
图7 伙伴系统各内存释放函数之间关系
free_pages和__free_pages之间的关系经过函数而不是宏创建,由于首先必须将虚拟地址转换为指向struct page的指针。
内核源代码将__alloc_pages称之为“伙伴系统的心脏”,由于它处理的是实质性的内存分配。
(1)选择页
内核定义了一些函数使用的标志,用于控制到达各水印指定的临界状态时的行为。
#define ALLOC_NO_WATERMARKS 0x01 /* 彻底不检查水印 */ #define ALLOC_WMARK_MIN 0x02 /* 使用pages_min水印 */ #define ALLOC_WMARK_LOW 0x04 /* 使用pages_low水印 */ #define ALLOC_WMARK_HIGH 0x08 /* 使用pages_high水印 */ #define ALLOC_HARDER 0x10 /* 试图更努力地分配,即放宽限制 */ #define ALLOC_HIGH 0x20 /* 设置了__GFP_HIGH */ #define ALLOC_CPUSET 0x40 /* 检查内存结点是否对应着指定的CPU集合 */
默认状况下(即没有因其余因素带来的压力而须要更多的内存),只有内存域包含页的数目至少为zone->pages_high时,才能分配页。这对应于ALLOC_WMARK_HIGH标志。若是要使用较低(zone->pages_low)或最低(zone->pages_min)设置,则必须相应地设置ALLOC_WMARK_MIN或ALLOC_WMARK_LOW。ALLOC_HARDER通知伙伴系统在急
需内存时放宽分配规则。在分配高端内存域的内存时,ALLOC_HIGH进一步放宽限制。最后,ALLOC_CPUSET告知内核,内存只能从当前进程容许运行的CPU相关联的内存结点分配,固然该选项只对NUMA系统有意义。
__alloc_pages是伙伴系统的主函数,函数比较复长,可用内存足够时必要工做很快完成,可用内存太少或逐渐用完时,函数就会变得比较复杂。
在最简单的情形中,分配空闲内存区只涉及调用一次get_page_from_freelist,而后返回所需数目的页(由标号got_pg处的代码处理)。
其余状况中,会进行屡次内存分配尝试:
(2)移除选择的页
若是内核找到适当的内存域,具备足够的空闲页可供分配,那么还有两件事情须要完成。首先它必须检查这些页是不是连续的;其次,必须按伙伴系统的方式从free_lists移除这些页,这可能须要分解并重排内存区。
内核将工做委托给辅助函数buffered_rmqueue完成,其代码流程图如图8所示。
图8 buffered_rmqueue代码流程图
首先,判断阶数,若为0,则表示只请求一页。此时,内核试图借助于per-CPU缓存加速请求的处理。若是缓存为空,内核可借机检查缓存填充水平。若是per-CPU缓存中没法找到适当的页,则向缓存添加一些符合当前要求迁移类型的页,而后从per-CPU列表移除一页,接下来进一步处理。
若不是0,则表示请求多页。内核调用__rmqueue(要求页连续)会从内存域的伙伴列表中选择适当的内存块。若有必要,该函数会自动分解大块内存,将未用的部分放回列表中。若分配失败,则会返回NULL指针。全部失败情形都跳转到标号failed处理,这能够确保内核到达当前点以后,page指向一系列有效的页。在返回指针以前,prep_new_page须要作一些准备工做,以便内核可以处理这些页(若是所选择的页出了问题,则该函数返回正值。在这种状况下,分配将从头从新开始)。
__free_pages是一个基础函数,用于实现内核API中全部涉及内存释放的函数。其代码流程图如图9所示。
图9 __free_pages代码流程图
__free_pages首先判断所需释放的内存是单页仍是较大的内存块?若是释放单页,则不还给伙伴系统,而是置于per-CPU缓存中,对极可能出如今CPU高速缓存的页,则放置到热页的列表中。出于该目的,内核提供了free_hot_page辅助函数,该函数只是做一下参数转换,接下来调用free_hot_cold_page。若是释放多个页,那么__free_pages将工做委托给__free_pages_ok,最后到__free_one_page。与其名称不一样,该函数不只处理单页的释放,也处理复合页释放。
物理上连续的映射对内核是最优的,但不可能老是成功使用。对此,内核分配了其虚拟地址空间的一部分,用于创建连续映射。如图10所示,在IA-32系统中,紧随直接映射的前892 MiB物理内存,在插入的8 MiB安全隙以后,是一个用于管理不连续内存的区域。这一段具备线性地址空间的全部性质。经过修改负责该区域的内核页表,能够将其中的页映射到物理内存的任何地方。每一个vmalloc分配的子区域都是自包含的,与其余vmalloc子区域经过一个内存页分隔。相似于直接映射和vmalloc区域之间的边界,不一样vmalloc子区域之间的分隔也是为防止不正确的内存访问操做。
图10 IA-32系统上内核的虚拟地址空间中的vmalloc区域
(1)用vmalloc分配内存
vmalloc是一个接口函数,内核使用它来分配虚拟内存中连续但在物理内存中不必定连续的内存。
void *vmalloc(unsigned long size);
该函数只须要一个参数,用于指定所需内存区的长度(字节)。
内核对模块的实现中,有不少使用vmalloc的地方,由于函数可能在任什么时候候加载,若是模块数比较多,那么没法保证有足够的连续内存可用(尤为是系统已经运行了比较长时间的状况下)。由于用于vmalloc的内存页老是必须映射在内核地址空间中,所以使用ZONE_HIGHMEM内存域的页要优于其余内存域。这使得内核能够节省更宝贵的较低端内存域,而又不会带来额外的坏处。
vmalloc的代码流程图如图11所示。
图11 vmalloc代码流程图
vmalloc的实现分为三个部分,首先,get_vm_area在vmalloc地址空间中找到一个适当的区域。接下来从物理内存分配各个页,最后将这些页连续地映射到vmalloc区域中,完成分配虚拟内存的工做。
(2)备选映射方法
(3)释放内存
有两个函数用于向内核释放内存,vfree用于释放vmalloc和vmalloc_32分配的区域,而vunmap用于释放由vmap或ioremap建立的映射。两个函数都会归结到__vunmap。其代码流程图如图12所示。
图12 __vunmap代码流程图
尽管vmalloc函数族可用于从高端内存域向内核映射页帧,但这并非这些函数的实际用途。内核提供了其余函数用于将ZONE_HIGHMEM页帧显式映射到内核空间。
(1)持久内核映射
若是须要将高端页帧长期映射(做为持久映射)到内核地址空间中,必须使用kmap函数。须要映射的页用指向page的指针指定,做为该函数的参数。若是没有启用高端支持,该函数只须要返回页的地址;若是启用了高端支持,则相似于vmalloc,内核首先必须创建高端页和所映射到的地址之间的关联,在虚拟地址空间中分配一个区域以映射页帧,最后,内核必须记录该虚拟区域的哪些部分在使用中,哪些仍然是空闲的。
内核在IA-32平台上vmalloc区域以后分配了一个区域,从PKMAP_BASE到FIXADDR_START,该区域用于持久映射,不一样体系结构使用的方案是相似的。
(pkmap_count是一容量为LAST_PKMAP的整数数组,其中每一个元素都对应于一个持久映射页。它其实是被映射页的一个使用计数器,0意味着相关的页没有使用,1有特殊语义,n表明内核中有n-1处使用该页(n≥2)。)
用kmap映射的页,若是再也不须要,必须用kunmap解除映射。
(2)临时内核映射
kmap函数不能用于中断处理程序,由于它可能进入睡眠状态(pkmap数组中没有空闲位置时)。内核提供了kmap_atomic,该函数执行是原子的,比普通的kmap快速,不能用于可能进入睡眠的代码,对于很快就须要一个临时页的简短代码是很是理想的。
kmap_atomic的定义在IA-3二、PPC、Sparc32上是特定于体系结构的,但这3种实现只有很是细微的差异,其原型是相同的。
void *kmap_atomic(struct page *page, enum km_type type) //page是一个指向高端内存页的管理结构的指针,type定义了所需的映射类型
(内核的固定映射机制,使之能够在内核地址空间中访问用于创建原子映射的内存。能够在FIX_KMAP_BEGIN和FIX_KMAP_END之间创建一个用于映射高端内存页的区域,该区域位于fixed_addresses数组中,准确的位置须要根据当前活动的CPU和所需映射类型计算。)
在使用kmap_atomic时不会阻塞。若是发生阻塞,那么另外一个进程可能创建一样类型的映射,覆盖现存的项。
kunmap_atomic函数从虚拟内存解除一个现存的原子映射,该函数根据映射类型和虚拟地址,从页表删除对应的项。
(3)没有高端内存的计算机上的映射函数
许多体系结构不须要支持高端内存(好比AMD64),为了避免须要老是区分高端内存和非高端内存体系结构,内核定义了几个在普通内存实现兼容函数的宏(在支持高端内存的计算机上,若是停用了高端内存,也会使用这些宏)。
1 #ifdef CONFIG_HIGHMEM 2 ... 3 #else 4 static inline void *kmap(struct page *page) 5 { 6 might_sleep(); 7 return page_address(page); 8 } 9 #define kunmap(page) do { (void) (page); } while (0) 10 #define kmap_atomic(page, idx) page_address(page) 11 #define kunmap_atomic(addr, idx) do { } while (0) 12 #endif
相似于C语言中的malloc,slab分配器提供小块内存,同时它也用做一个缓存,主要针对常常分配并释放的对象。slab分配器将释放内存块保存在一个内部列表中,并不立刻返回给伙伴系统,以便下一次高速的内存分配。这样内核没必要使用伙伴系统算法,处理时间会变短,同时该内存块仍然驻留在CPU告诉缓存的几率较高。
slab分配器有两大好处:
在大型系统上仅slab的数据结构就须要不少GB内存。对嵌入式系统来讲,slab分配器代码量和复杂性都过高,所以诞生了slob分配器和slub分配器。
slob分配器进行了特别优化,以便减小代码量。它围绕一个简单的内存块链表展开,在分配内存时,使用了一样简单的最早适配算法(速度非最高效,不适用大型系统);
slub分配器经过将页帧打包为组,并经过struct page中未使用的字段来管理这些组,试图最小化所需的内存开销。
全部分配器的前端接口都是相同的。每一个分配器都实现了一组特定的函数,用于内存分配和缓存。
图13阐释了物理页帧、伙伴系统、通用分配器与通常内核代码接口关联。
图13 伙伴系统、通用分配器与通常内核代码接口关联示意图
内核中通常的内存分配和释放函数与C标准库中等价函数的名称相似,用法也几乎相同。
与用户空间程序设计相比,内核还包括percpu_alloc和percpu_free函数,用于为各个系统CPU分配和释放所需内存区。
全部活动缓存的列表保存在/proc/slabinfo中(终端输入cat /proc/slabinfo便可查看),包含用于标识各个缓存的字符串名称,缓存中活动对象的数量,缓存中对象的总数(已用和未用),所管理对象的长度(按字节计算),一个slab中对象的数量,每一个slab中页的数量,活动slab的数量,在内核决定向缓存分配更多内存时,所分配对象的数量。
slab分配器由一个紧密地交织的数据和内存结构的网络组。如图14所示,slab缓存由保存管理性数据的缓存对象和保存被管理对象的各个slab。
图14 slab分配器各个部分
每一个缓存只负责一种对象类型,或提供通常性的缓冲区。各个缓存中slab的数目各有不一样,这与已经使用的页的数目、对象长度和被管理对象的数目有关。
系统中全部的缓存都保存在一个双链表中。这使得内核有机会依次遍历全部的缓存。
(1)缓存的精细结构
图15 slab缓存的精细结构
图15描述了缓存各组成部分,除了管理性数据,缓存结构包括两个特别重要的成员:
缓存结构指向一个数组,其中包含了与系统CPU数目相同的数组项。每一个元素都是一个指针,指向一个进一步的结构称之为数组缓存(array cache),其中包含了对应于特定系统CPU的管理数据(就整体来看,不是用于缓存)。管理性数据以后的内存区包含了一个指针数组,各个数组项指向slab中未使用的对象。
为最好地利用CPU高速缓存,在分配和释放对象时,采用后进先出原理(LIFO,last in first out)。内核假定刚释放的对象仍然处于CPU高速缓存中,会尽快再次分配它。仅当per-CPU缓存为空时,才会用slab中的空闲对象从新填充它们。这样,对象分配的体系就造成了一个三级的层次结构(分配成本和操做对CPU高速缓存和TLB的负面影响逐级升高):
(2)slab精细结构
用于每一个对象的长度进行了舍入,以知足某些对齐方式的要求,对于对齐方案,有两种:
slab建立时使用标志SLAB_HWCACHE_ALIGN,slab用户能够要求对象按硬件缓存行对齐;
若是不要求按硬件缓存行对齐,那么内核保证对象按BYTES_PER_WORD对齐,该值是表示void指针所需字节的数目。
在32位处理器上,void指针须要4个字节。所以,对有6个字节的对象,则须要8 = 2×4个字节,多余的字节称为填充字节。填充字节能够加速对slab中对象的访问,若是使用对齐的地址,那么在几乎全部的体系结构上,内存的访问都会更快。
slab的起始处是管理结构,保存了全部的管理数据(和用于链接缓存链表的链表元素)。其后面是一个数组,每一个(整数)数组项对应于slab中的一个对象(只有在对象没有分配时,相应的数组项才有意义)。此时,它指定了下一个空闲对象的索引。因为最低编号的空闲对象的编号还保存在slab起始处的管理结构中,内核无需使用链表或其余复杂的关联机制,便可找到当前可用的全部对象。数组的最后一项老是一个结束标记,值为BUFCTL_END。管理数组与slab对象的关系如图16所示。
图16 slab中空闲对象的管理
管理数据能够放置在slab自身,也能够放置到使用kmalloc分配的不一样内存区中。内核的选择取决于slab的长度和已用对象的数量。
最后,内核经过对象自身(page结构的一个链表元素lru.next和lru.prev)识别slab(以及对象驻留的缓存)。根据对象的物理内存地址,找到相关的页,从而在全局mem_map数组中找到对应的page实例。
slab系统带有大量调试选项,代码中遍及着预处理语句:
(1)数据结构
每一个缓存由kmem_cache结构的一个实例表示。结构内容以下:
1 struct kmem_cache { 2 /* 1) per-CPU数据,在每次分配/释放期间都会访问 */ 3 struct array_cache *array[NR_CPUS]; //指向数组的指针,每一个数组项都对应于系统中的一个CPU。每一个数组项都包含了另外一个指针,指向下文讨论的array_cache结构的实例 4 /* 2) 可调整的缓存参数。由cache_chain_mutex保护 */ 5 unsigned int batchcount; //per-CPU列表为空时,从缓存的slab中获取对象的数目;还表示在缓存增加时分配的对象数目 6 unsigned int limit; //指定了per-CPU列表中保存的对象的最大数目 7 unsigned int shared; 8 unsigned int buffer_size; //指定了缓存中管理的对象的长度 9 u32 reciprocal_buffer_size; 10 /* 3) 后端每次分配和释放内存时都会访问 */ 11 unsigned int flags; /* 常数标志 */ 12 unsigned int num; /* 每一个slab中对象的数量 */ 13 /* 4) 缓存的增加/缩减 */ 14 unsigned int gfporder; //指定了slab包含的页数目以2为底的对数 15 /* 强制的GFP标志,例如GFP_DMA */ 16 gfp_t gfpflags; 17 size_t colour; //颜色的最大数目 18 unsigned int colour_off; //着色偏移 19 struct kmem_cache *slabp_cache; //slab头部的管理数据存储在slab外部时,指向分配所需内存的通常性缓存; slab头部在slab上时,为NULL指针 20 unsigned int slab_size; 21 unsigned int dflags; // 标志集合,描述slab的“动态性质” 22 void (*ctor)(struct kmem_cache *, void *); //指向在对象建立时调用的构造函数 23 /* 5) 缓存建立/删除 */ 24 const char *name; //是一个字符串,包含该缓存的名称 25 struct list_head next; //用于将kmem_cache的全部实例保存在全局链表cache_chain上 26 /* 6) 统计量 */ 27 ... 28 struct kmem_list3 *nodelists[MAX_NUMNODES]; 29 };
(2)初始化
为初始化slab数据结构,内核须要若干小内存块(最适合由kmalloc分配),可是只有slab系统启用以后,才能使用kmalloc,于是内核借助了一些技巧。
kmem_cache_init函数用于初始化slab分配器。它在内核初始化阶段(start_kernel)、伙伴系统启用以后调用。第一步:kmem_cache_init建立系统中的第一个slab缓存,以便为kmem_cache的实例提供内存,内核使用的主要是在编译时建立的静态数据;第二步:kmem_cache_init接下来初始化通常性的缓存,用做kmalloc内存的来源(针对所需的各个缓存长度,分别调用kmem_cache_create);第三步:在kmem_cache_init的最后一步,把到如今为止一直使用的数据结构的全部静态实例化的成员,用kmalloc动态分配的版本替换。
(3)建立缓存
建立新的slab缓存必须调用kmem_cache_create,这是一个冗长的过程,其代码示意图如图17所示。
图17 kmem_cache_create的代码流程图
(4)分配对象
kmem_cache_alloc用于从特定的缓存获取对象,它须要用于获取对象的缓存,以及精确描述分配特征的标志变量两个参数,结果多是指向分配内存区的指针,也可能分配失败返回NULL。
图18 kmem_cache_alloc的代码流程图
(5)缓存的增加
图19描述了cache_grow代码流程图。
图19 cache_grow的代码流程图
(6)释放对象
当一个分配的对象再也不须要时,使用kmem_cache_free将其返回给slab分配器。图20为kmem_cache_free代码流程图。
图20 kmem_cache_free的代码流程图
当即调用__cache_free,根据per-CPU缓存的状态不一样,执行如下两种操做:
(7)销毁缓存
若是要销毁只包含未使用对象的一个缓存,则必须调用kmem_cache_destroy函数。该函数主要在删除模块时调用,此时须要将分配的内存都释放。主要步骤以下:
若是不涉及对象缓存,而是传统意义上的分配/释放内存,则必须调用kmalloc和kfree函数。kmalloc和kfree实现为slab分配器的前端,其语义尽量地模仿C标准库malloc和free。
内核提供了一些命令直接做用于处理器的高速缓存和TLB,用于维护缓存内容的一致性,确保不出现不正确和过期的缓存项。
不一样体系结构上,高速缓存和TLB的硬件实现不一样,所以内核须要创建TLB和高速缓存的视图,在其中考虑到各类不一样的硬件实现方法,兼顾各个体系结构的特定性质。
TLB的语义抽象是将虚拟地址转换为物理地址的一种机制;
内核将高速缓存视为经过虚拟地址快速访问数据的一种机制,该机制无需访问物理内存。数
据和指令高速缓存并不老是明确区分。
内核中各个特定于CPU的部分都必须提供下列函数(即便只是空操做),以便控制TLB和高速缓存:
内核对数据和指令高速缓存不做区分。若是须要区分,特定于处理器的代码可根据vm_area_struct->flags的VM_EXEC标志位是否设置,来肯定高速缓存包含的是指令仍是数据。
flush_cache_...和flush_tlb_...函数常常成对出现。
好比在使用fork复制进程地址空间时,操做的顺序是:刷出高速缓存、操做内存、刷出TLB,缘由有两个: