Linux内存管理宏观篇(六)物理内存:分配小内存块

简介: Linux内存管理宏观篇(六)物理内存:分配小内存块

前言

前面的关于内存的部分分配是伙伴系统算法,这个是基于以页面为最小单位,一个页面还是蛮大的,像很多嵌入式设备对于内存的请求分配可能只需要几十个字节,这样如果给别人一个页,也就太浪费了。

进一步,就算你是个大方的好人,但是如果有很多这样的请求,那对于内存的浪费是不是很可怕。就必须要有一种分配更小内存的方法:slab机制。

1、slab机制的必要性和大概样子

当然可能你在这个内存的学习过程中,还遇到过slob、slub。这是由slab机制针对特殊场景演化出来的。

slub:大型服务器

slob:嵌入式系统

这样的原因是因为slab机制存在两个问题:

  • 使用的元数据开销比较大,这个元数据是对于数据的管理成本。
  • slab分配器的代码量和复杂度很高

(对于元数据,你可能需要有点认识,这个第一次看我是在学数据结构中见到的,这个元数据的含义我决定写一篇解读一下)

这里我们还是基于slab机制来讲解。

不知道你在面对这个小内存分配有没有想到什么招数?根据伙伴系统的联想,我们可以在这里构建一个以2的order字节大小的内存分配机制。这个是kmalloc的机制思想。

这里slab是以数据结构为对象进行内存池的构建,比如内核常见的struct mm_struct数据结构、进程控制块mm_struct。slab就建立一个mm_struct的对象缓存池,这样在需要的时候就直接拿出来,也不会存在多余的内存浪费,速度也快。你想想要是基于其他的,你给申请者一片内存,我还需要切割成小字节的,然后再使用,又浪费又慢。

(还有个问题,就是页面分配器去申请物理页面,会产生阻塞、睡眠的可能,对于这个速度不稳定)

所以slab机制,解决了内存浪费的问题,也保证了内存分配速度的问题。

2、slab分配的核心思想

从上面大概认识了slab就是个分配小内存的机制,同时这个分配不是基于内存大小的,而是基于数据机构对象的。知道这个是干啥的,但是对于这个具体分配的过程我们这里来看看。

最核心的就是在空闲时建立缓存对象池。(挺好奇这个空闲时创建的机制是怎么运转的),建立的这个对象缓存池包括本地也包括共享的对象缓存池。

2.1、本地缓存池

本地缓存池是在创建每个slab描述符时,就为每个cpu创建一个本地的缓存池。(这个描述符就是数据结构)本地可以减少多核cpu之间的锁竞争。

共享对象缓存池是所有共享的,本地没有时,就从共享缓存池中搬移一批对象到本地缓存池。(共享缓存池是多久建立的?)

其中涉及到三个概念

2.2、slab描述符

struct kmem_cache数据结构是slab分配器中的核心数据结构,我们把它称为slab描述符。

struct kmem_cache数据结构的定义如下。

[include/linux/slab_def.h]
0 /*
1 * kmem cache数据结构的核心成员
2 */
3 struct kmem_cache {
4  struct array_cache __percpu *cpu_cache;
7   unsigned int batchcount;
8   unsigned int limit;
9   unsigned int shared;
11  unsigned int size;
12  struct reciprocal_value reciprocal_buffer_size;
15 unsigned int flags;
16 unsigned int num;
20 unsigned int gfporder;
23 gfp_t allocflags;
25 size_t colour;
26 unsigned int colour_off;
27 struct kmem_cache *freelist_cache;
28 unsigned int freelist_size;
31 void (*ctor)(void *obj);
34 const char *name;
35 truct list_head list;
36 int refcount;
37 int object_size;
38 int align;
41 struct kmem_cache_node *node[MAX_NUMNODES];
42    }

每个slab描述符都由一个struct kmem_cache数据结构来抽象描述。

cpu_cache:一个Per-CPU的struct array_cache数据结构,每个CPU一个,表示本地CPU的对象缓冲池。

batchcount:表示当前CPU的本地对象缓冲池array_cache为空时,从共享的缓冲池或者slabs_partial/slabs_free列表中获取对象的数目。

limit:当本地对象缓冲池的空闲对象数目大于limit时,会主动释放batchcount个对象,便于内核回收和销毁slab。

shared:用于多核系统。

size:对象的长度,这个长度要加上align对齐字节。

flags:对象的分配掩码。

num:一个slab中最多可以有多少个对象。

gfporder:一个slab中占用2^gfporder个页面。

colour:一个slab中有几个不同的cache line。

colour_off:一个cache colour的长度,和L1缓存行大小相同。

freelist_size:每个对象要占用1字节来存放freelist。

name:slab描述符的名称。

object_size: 对象的实际大小。

align:对齐的长度。

node:slab节点,在NUMA系统中每个节点有一个struct kmem_cache_node数据结构。

在ARM Vexpress平台中,只有一个节点。

struct array_cache数据结构定义如下。

struct array_cache {
unsigned int avail;
unsigned int limit;
unsigned int batchcount;
unsigned int touched;
void *entry[];
};

slab描述符给每个CPU都提供一个对象缓存池(array_cache)。

batchcount/limit:和struct kmem_cache数据结构中的语义一样。

avail:对象缓存池中可用的对象数目。

touched:从缓冲池移除一个对象时,将touched置1;而收缩缓存时,将touched置0。

entry:保存对象的实体。

2.3、slab

一个slab的组成如图7.17所示,它由1个或者多个(2的order次方)连续的物理页面组成。注意:这是连续的物理页面。

(注意这里是gforder)

一般根据缓存对象object大小、align大小等参数来统一计算出来究竟由多少个页面组成一个slab是最经济、最合适的。最后会计算出来,一个slab里面最多可以有多少个cache colour,这里cache colour指的cache着色区。这是在slab机制里特有的,但是slub里已经去掉了。着色区后面紧跟着freelist,用来管理后面的object的。freelist后面就是一个个object对象了。

2.4、slab机制

slab机制分两步完成。第一步是使用kmem_cache_create()函数创建一个slab描述符,使用struct kmem_cache数据结构来描述。struct kmem_cache数据结构里有几个主要的成员,一个是指向本地缓存池的指针,另一个是指向slab节点的node指针。每个内存节点有一个slab节点,通常ARM只有一个内存节点,这里就假设系统只有一个slab节点。其他是描述这个slab描述符的信息,比如这个slab的对象的大小、名字以及align等信息。

这个slab节点里有3个链表,分别是slab空闲链表、slab满链表和slab partial链表。这些链表的成员是slab,不是对象。另外,该节点里有一个指针指向一个共享缓存池,它和本地缓存池是相对的。

第二步是从这个slab描述符中去分配空闲对象。一个CPU要从这个slab描述符中分配对象,它首先去访问当前CPU对应的这个slab描述符里的本地缓存池。如果本地缓冲池里有空闲对象,就直接获取,没有其他 CPU 过来竞争。如果本地缓冲池里没有空闲对象,那么需要去共享缓冲池里查询是否有空闲对象。如果有,就从共享缓存池里搬移几个空闲对象到自己的缓存池中。

可是刚创建slab描述符时,本地缓冲池和共享对象缓冲池里都是空的,没有空闲对象,那slab是怎么建立的呢?

建立 slab 所使用的物理页面需要向页面分配器申请,这个过程可能会睡眠。如图 7.18所示,建好一个slab之后,会把这个slab添加到 slab节点的slab空闲链表里,所以slab中的3个链表的成员是slab,而不是对象。这里是通过slab的第一个页面的lru成员挂入链表中的。另外,空闲的对象会搬移到共享缓冲池和本地缓冲池,供分配器使用。

2.4、slab回收

slab回收就是slab运行的机制。当然,slab不能只分配,不用的slab还是会被回收的。如果一个slab描述符中有很多空闲对象,那么系统是否要回收一些空闲的缓存对象,从而释放内存归还系统呢?这是必须要考虑的问题,否则系统有大量的 slab 描述符,每个 slab 描述符还有大量不用的、空闲的slab对象。

slab系统有两种方式来回收内存。

使用kmem_cache_free释放一个对象。当发现本地和共享对象缓冲池中的空闲对象数目 ac->avail 大于等于缓冲池的极限值 ac->limit 时,系统会主动释放bacthcount个对象。当系统所有空闲对象数目大于系统空闲对象数目极限值,并且这个 slab没有活跃对象时,系统就会销毁这个slab,从而回收内存。

slab系统还注册了一个定时器,定时扫描所有的slab描述符,回收一部分空闲对象,达到条件的slab也会被销毁,实现函数为cache_reap()。

3、slab分配接口

上面知道了slab的分配机制,概念还是比较抽象。再结合api函数,以及其参数来看看。

创建slab描述符

struct kmem_cache *kmem_cache_create(const char *name, size_t size, size_t align,unsigned long flags, void (*ctor)(void *))

释放slab描述符

void kmem_cache_destroy(struct kmem_cache *s)

分配缓存对象

void *kmem_cache_alloc(struct kmem_cache *, gfp_t flags);

释放缓存对象

void kmem_cache_free(struct kmem_cache *, void *);

kmem_cache_create()函数中有如下参数。

name:slab描述符的名称。
size:缓存对象的大小。
align:缓存对象需要对齐的字节数。
flags:分配掩码。
ctor:对象的构造函数。

这个时候还记得描述符和对像是什么?

例如,在Intel显卡驱动中就大量使用了kmem_cache_create()创建自己的slab描述符。

[drivers/gpu/drm/i915/i915_gem.c]

voidi915_gem_load(struct drm_device *dev)
{
  dev_priv->slab =kmem_cache_create("i915_gem_object",sizeof(struct drm_i915_gem_object), 0,SLAB_HWCACHE_ALIGN,NULL);
}
void *i915_gem_object_alloc(struct drm_device *dev)
{
  #分配缓存对象
  return kmem_cache_zalloc(dev_priv->slab, GFP_KERNEL);
}

4、kmalloc机制

5、小结

待解决

问题

元数据是什么?

参考资料

《奔跑吧 Linux内核》

目录
相关文章
|
1月前
|
缓存 Java Linux
如何解决 Linux 系统中内存使用量耗尽的问题?
如何解决 Linux 系统中内存使用量耗尽的问题?
139 48
|
24天前
|
存储 监控 算法
Java内存管理深度剖析:从垃圾收集到内存泄漏的全面指南####
本文深入探讨了Java虚拟机(JVM)中的内存管理机制,特别是垃圾收集(GC)的工作原理及其调优策略。不同于传统的摘要概述,本文将通过实际案例分析,揭示内存泄漏的根源与预防措施,为开发者提供实战中的优化建议,旨在帮助读者构建高效、稳定的Java应用。 ####
37 8
|
21天前
|
算法 Linux
深入探索Linux内核的内存管理机制
本文旨在为读者提供对Linux操作系统内核中内存管理机制的深入理解。通过探讨Linux内核如何高效地分配、回收和优化内存资源,我们揭示了这一复杂系统背后的原理及其对系统性能的影响。不同于常规的摘要,本文将直接进入主题,不包含背景信息或研究目的等标准部分,而是专注于技术细节和实际操作。
|
1月前
|
缓存 Ubuntu Linux
Linux环境下测试服务器的DDR5内存性能
通过使用 `memtester`和 `sysbench`等工具,可以有效地测试Linux环境下服务器的DDR5内存性能。这些工具不仅可以评估内存的读写速度,还可以检测内存中的潜在问题,帮助确保系统的稳定性和性能。通过合理配置和使用这些工具,系统管理员可以深入了解服务器内存的性能状况,为系统优化提供数据支持。
38 4
|
1月前
|
Linux
如何在 Linux 系统中查看进程占用的内存?
如何在 Linux 系统中查看进程占用的内存?
|
1月前
|
缓存 Linux
如何检查 Linux 内存使用量是否耗尽?
何检查 Linux 内存使用量是否耗尽?
|
29天前
|
存储 算法 安全
深入理解Linux内核的内存管理机制
本文旨在深入探讨Linux操作系统内核的内存管理机制,包括其设计理念、实现方式以及优化策略。通过详细分析Linux内核如何处理物理内存和虚拟内存,揭示了其在高效利用系统资源方面的卓越性能。文章还讨论了内存管理中的关键概念如分页、交换空间和内存映射等,并解释了这些机制如何协同工作以提供稳定可靠的内存服务。此外,本文也探讨了最新的Linux版本中引入的一些内存管理改进,以及它们对系统性能的影响。
|
1月前
|
缓存 Prometheus 监控
Elasticsearch集群JVM调优设置合适的堆内存大小
Elasticsearch集群JVM调优设置合适的堆内存大小
291 1
|
22天前
|
存储 监控 算法
深入探索Java虚拟机(JVM)的内存管理机制
本文旨在为读者提供对Java虚拟机(JVM)内存管理机制的深入理解。通过详细解析JVM的内存结构、垃圾回收算法以及性能优化策略,本文不仅揭示了Java程序高效运行背后的原理,还为开发者提供了优化应用程序性能的实用技巧。不同于常规摘要仅概述文章大意,本文摘要将简要介绍JVM内存管理的关键点,为读者提供一个清晰的学习路线图。
|
1月前
|
Java
JVM内存参数
-Xmx[]:堆空间最大内存 -Xms[]:堆空间最小内存,一般设置成跟堆空间最大内存一样的 -Xmn[]:新生代的最大内存 -xx[use 垃圾回收器名称]:指定垃圾回收器 -xss:设置单个线程栈大小 一般设堆空间为最大可用物理地址的百分之80