物理背景
为什么会有缓存cache
在最初开发ARM架构时,处理器的时钟速度和内存的访问速度大致相同。今天的处理器内核要复杂得多,其时钟速度可以快上几个数量级。但是,外部总线和内存设备的频率并没有扩大到同样的程度。有可能实现小块的片上SRAM,它可以以与内核相同的速度运行,但是与标准的DRAM块相比,这种RAM非常昂贵,因为后者的容量可以达到数千倍。在许多基于ARM处理器的系统中,访问外部存储器需要几十甚至几百个内核周期。
缓存在哪里
缓存是位于核心和主内存之间的一个小型快速内存块。
它存储了主存储器中资料的副本。对高速缓冲存储器的访问比对主存储器的访问快得多。每当内核读取或写入一个特定的地址时,它首先在高速缓存中寻找。如果它在高速缓存中找到该地址,它就会使用高速缓存中的数据,而不是对主内存进行访问。这大大增加了系统的性能,因为它减少了缓慢的外部存储器访问时间的影响。
cpu集群和缓存l1 l2 l3的关系
实现ARMv8-A架构的处理器通常有两级或更多的高速缓存。这通常意味着处理器的每个内核都有小的L1指令缓存和数据缓存。Cortex-A53和Cortex-A57处理器通常采用两级或多级缓存,即一个小的L1指令和数据缓存和一个较大的、统一的L2缓存,该缓存在集群的多个内核之间共享。此外,还可以有一个外部L3高速缓存作为外部硬件块,在集群之间共享。
cache为啥能加速
向高速缓存提供数据的初始访问并不比正常速度快。对缓存值的任何后续访问才会更快,而性能的提高正是来自于此。核心硬件会检查缓存中所有的指令获取和数据读取或写入,尽管你必须将内存的某些部分,例如包含外围设备的部分,标记为不可缓存的。因为高速缓存只容纳了主内存的一个子集,所以你需要一种方法来快速确定你要找的地址是否在高速缓存中。
slab kem_cache cache 等名词扫盲
struct slab 结构体
为每种对象类型创建一个内存缓存,每个内存缓存由多个大块组成,一个大块是一个或者多个连续的物理页,每个大块包含多个对象。slab采用面向对象的思想,基于对象类型管理内存,每种对象被划分成一个类。比如进程描述符(task_struct)是一个类,每个进程描述符的实现是一个对象。
struct kmem_cache
从上面的图能看出
kmem_ cache是用于管理slab的高速缓存结构体。在一个kmem_ cache中,slab对象大小是相同的,也就是说,每个对象占用的空间大小都是固定的。 kmem cache 用来分配和管理或销毁 slab
struct kmem_cache_node
上面的结构体用来维护相同的slab 这个结构体用来存储所有的slab 给L1缓存使用
每个物理ram节点 都用一个kmem_cache_node进行描述
kmem_ cache_ node结构体中存储了三个列表: full partial 和empty列表。
这三个列表分别表了该节点上的满的slab、部分满的slab和空的slab。
Slab分配器
SLAB分配器的实现基于三个列表: full list、partial list和empty list。
当应用程序请求分配内存时,SLAB分配器首先在partial list中查找是否存在适合大小的内存块。
如果找到,则该内存块被分配并从partial list中移除。如果partial list中没有适合的内存块,
则SLAB分配器会从empty list中获取一块内存,并将其划分为多个大小相同的块。
其中一块被分配给请求方,并剩余的块被放入partial list中。
当释放内存时,被释放的内存块将被放回partial list中。
如果partial list已满,则会将其中的一-些块移动到full list中,以供下次分配使用。
在full list中的块只能被释放到empty list中,以便它们可以被重新划分为多个小块以供下次使用。
SLAB分配器的优点在于它可以提高内存分配和释放的速度,
因为它维护了partial list和emptylist,可以避免在每次分配和释放内存时进行重复的内存管理操作。
此外,SLAB分配器还可以避免内存碎片的产生,因为它只分配特定大小的内存块,不需要在内存池中寻找适合大小的碎片。
kmem_ cache
kmem cache是用于管理slab的高速缓存结构体。
slab是一 种内存分配机制,用于管理相同大小的对象。
在一个kmem_ cache中,slab对象大小是相同的,也就是说,每个对象占用的空间大小都是固定的。
kmem_ cache结构体包含了slab的相关参数,如每个slab中包含的对象数量、slab的页数、
每个对象的大小等。kmem_ cache还负责分配和管理slab内存,以及初始化和销毁slab。
每个kmem_ cache实例都维护着一组slab, 它们具有相同的大小和缓存属性。
因此,kmem. cache和slab是相互关联的,kmem_ cache用于管理和操作slab,而slab是由
kmem_cache创建和维护的。
在内核中,kmem_ cache和slab是 紧密结合的,它们一起实现了内核的内存管理功能。
在内核中,kmem_ cache并不是与zone一一对应的, 而是可以被多个zone共享。
当一个zone需要管理一种大小相同的对象时,它可以通过访问已经存在的kmem_ cache, 或者创建一个新的
kmem_ cache来管理这些对象。这样可以避免在不同的zone中重复创建相同的kmem_ cache, 节省内存开销。
kmem cache node
kmem_ cache_ node是一个用于节点的结构体,它的作用是维护不同节点的缓存空间的信息。
一个节点表示的是在NUMA系统中的一个节点,通常对应着一组处理器和一 些内存。
由于每个节点都可能有不同的内存资源,因此为每个节点单独维护缓存空间的信息可以提高内存分配的效率。
kmem_ cache_ node结构体中存储了三个列表: full、 partial 和empty列表。
这三个列表分别表了该节点上的满的slab、部分满的slab和空的slab。
每个节点都有一个kmem cache_ node结构体,
其中kmem_ cache以及每个slab中都包含一个指向所属节点的kmem_ cache_node的指针。
这样,通过遍历kmem_ cache_ node中的三个链表,可以高效地找到一个可以使用的slab进行内存分配。
该结构保存了Slab的三个链表,并基于结构对Slab对象进行管理。
kmlloc
kmalloc实际上调用的是slab_ alloc申请内存,所以kmalloc实际上分配小内存。下面是kmallic实
际上调用分配内存的 do kmalloc函数完成的工作:
1.检查要分配的内存大小是否超过了kmalloc所管理的内存池的最大值,如果超过了则返回
NULL。
2.调用kmalloc_slab函数获取对应大小的kmem_ cache结构体,如果获取失败则返回错误。
3.调用slab_ alloc函数从获取到的kmem cache对象中分配一块大小为size的内存, 如果分配失败
则返回错误。
4.调用kasan kmalloc函数给刚刚分配的内存打上KASAN标记,用于检测内存访问越界等问题。
5.调用trace kmalloc函数记录分配内存的相关信息,用于跟踪和分析内存分配情况。
6.返回分配的内存指针。
总结
伙伴系统的实现是为了减少物理内存碎片化,每个zone通过自己的伙伴系统减少内存碎片。slab
分配器使得内存重复利用,减少物理内存的分配和释放。即通过slab分配器减少了物理内存的碎片
化。在Linux中除了slab分配还有slub、slob分配器。 他们都是通过缓存对象,来减少实际内存的
分配和释放。在linux中通过伙伴系统管理大内存,slab管理小内存。Linux通过这套体系减少了物
理内存的碎片化。