Linux Slub Fast Allocate path
slub 快速分配路径
static __fastpath_inline void *slab_alloc_node(struct kmem_cache *s,
gfp_t gfpflags, int node, const struct slab_alloc_context *ac)
{}
在快速分配路径中,会使用 slab_pre_alloc_hook进行预检,通过在 might_alloc 中 设置fs_reclaim_acquire和fs_reclaim_release向lockdep测试锁的获取和释放,进行检查是否会死锁,在未开启lockdep调试时,默认是空函数。
同时在might_alloc中,当带有PF_MEMALLOC标志时,页分配器会跳过回收及相关阻塞路径,所以跳过最后的睡眠检查
- 后续分析PF_MEMALLOC
最后进行睡眠检查,查看是否时在不允许睡眠的上下文中,当带有阻塞标志时,会进行睡眠检查。
在slab_pre_alloc_hook的最后,返回传入的kmem_cache,失败则为NULL,失败的问题是由should_failslab的触发的,should_failslab是由配置控制,是否失败进行测试使用。
之后会使用kfence_alloc,kfence也是由控制字段进行控制,kfence是一种基于采样技术的内存安全错误检测器,当进行内存分配时,使用kfence_alloc进行抽样分配,检测内存问题。
之后,通过apply_strict_numa_policy获取使用的NUMA节点,这个同样也是通过配置进行控制,同时进行要求了必须开启了strict_numa和node未指定的条件,当当前进程的内存分配策略存在时,
mpol->mode // 策略类型
mpol->nodes // 策略指定的节点集合
numa_mem_id() // 当前 CPU 对应的本地内存节点
if (策略是 MPOL_BIND && 本地内存节点在允许集合中) 不修改 node; else node = mempolicy_slab_node();
在memplicy_slab_node中
in_task() 判断当前是否处于任务上下文。若正在处理中断等非任务上下文,就直接返回本地节点,不使用当前任务的内存策略。
会根据当前的分配策略mpol->mode进行numa的节点分配,包括MPOL_PREFERRED:选择单个首选节点,MPOL_INTERLEAVE:轮流选择节点,MPOL_WEIGHTED_INTERLEAVE:按照权重分配选择次数,MPOL_BIND 和 MPOL_PREFERRED_MANY:按本地回退顺序,在策略集合里找节点,MPOL_LOCAL:直接选本地节点
在这里面BIND和PREFERRED共用一个处理
首先在处理中,使用了zone
zone时管理内存区域的结构体,而page是管理页的结构体,每个numa下都有不同的多个类型的zone。 为什么需要zone?所有的物理地址并不是对所有设备,内核用途都可用的,比如某些老设备只能在低地址的物理内存进行DMA。
每一个实际的 zone 都由一个 struct zone 描述
一个 zone 管理一段范围内的物理页:
struct zone
├── struct page
├── struct page
├── struct page
├── ...
└── struct page
zone 通常记录它管理的 PFN 范围。PFN 是 Page Frame Number,即物理页帧号。
在numa的系统中,每个内存节点由pg_data_t(也叫 struct pglist_data)描述,
它包含这个节点的各类 zone,概念上类似:
struct pglist_data {
struct zone node_zones[MAX_NR_ZONES];
...
};
层级关系为:
pg_data_t(NUMA 节点)
└── node_zones[ZONE_DMA]
└── node_zones[ZONE_DMA32]
└── node_zones[ZONE_NORMAL]
└── node_zones[ZONE_MOVABLE]
zone_idx 表示 zone 的类型编号。
由于有不同的配置,在不同的配置下各个类型的数值可能不同
假设有:
Node 3 的 ZONE_NORMAL
那么:
NUMA node id = 3
zone_idx = ZONE_NORMAL
二者含义完全不同:
- node id 表示内存属于哪个 NUMA 节点;
zone_idx表示这是什么类型的内存区域。
zone_idx(zone) 通常根据 zone 在所属节点的 node_zones[] 数组中的位置,算出它的类型编号。
而zonglist会将numa上各种类型的zoneref放置在一起,如果首选 zone 没有可用页,内核可能尝试其他 zone,甚至其他 NUMA 节点。
struct zonelist {
struct zoneref _zonerefs[MAX_ZONES_PER_ZONELIST + 1];
};
而zoneref是存储的某一类型的zone的指针,及它的类型,
/*
* This struct contains information about a zone in a zonelist. It is stored
* here to avoid dereferences into large structures and lookups of tables
*/
struct zoneref {
struct zone *zone; /* Pointer to actual zone */
int zone_idx; /* zone_idx(zoneref->zone) */
};
注释写的也很明确:
/*
* 这个结构体保存 zonelist 中某个 zone 的信息。
* 将这些信息直接保存在这里,是为了避免对大型结构体进行
* 多次指针解引用,以及避免额外的查表操作。
*/
zoneref是zonelist中的一个轻量级的条目,结构体中一个指向真实的zone,另一个表明当前zone的类型。
最终mempolicy_slab_node()返回的是,zone所属的numa编号。
struct node_barn {
spinlock_t lock;
struct list_head sheaves_full;
struct list_head sheaves_empty;
unsigned int nr_full;
unsigned int nr_empty;
};
struct slab_sheaf {
union {
struct rcu_head rcu_head;
struct list_head barn_list;
/* only used to defer call_rcu() in unknown context */
struct llist_node llnode;
/* only used for prefilled sheafs */
struct {
unsigned int capacity;
bool pfmemalloc;
};
};
struct kmem_cache *cache;
unsigned int size;
int node; /* only used for rcu_sheaf */
void *objects[];
};
struct slub_percpu_sheaves {
local_trylock_t lock;
struct slab_sheaf *main; /* never NULL when unlocked */
struct slab_sheaf *spare; /* empty or full, may be NULL */
struct slab_sheaf *rcu_free; /* for batching kfree_rcu() */
};
struct kmem_cache {
struct slub_percpu_sheaves __percpu *cpu_sheaves;
/* Used for retrieving partial slabs, etc. */
slab_flags_t flags;
unsigned long min_partial;
unsigned int size; /* Object size including metadata */
unsigned int object_size; /* Object size without metadata */
struct reciprocal_value reciprocal_size;
unsigned int offset; /* Free pointer offset */
unsigned int sheaf_capacity;
struct kmem_cache_order_objects oo;
/* Allocation and freeing of slabs */
struct kmem_cache_order_objects min;
gfp_t allocflags; /* gfp flags to use on each alloc */
int refcount; /* Refcount for slab cache destroy */
void (*ctor)(void *object); /* Object constructor */
unsigned int inuse; /* Offset to metadata */
unsigned int align; /* Alignment */
unsigned int red_left_pad; /* Left redzone padding size */
const char *name; /* Name (only for display!) */
struct list_head list; /* List of slab caches */
#ifdef CONFIG_SYSFS
struct kobject kobj; /* For sysfs */
#endif
#ifdef CONFIG_SLAB_FREELIST_HARDENED
unsigned long random;
#endif
struct kmem_cache_per_node_ptrs per_node[MAX_NUMNODES];
};
之后通过alloc_from_pcs进行分配对象。
在alloc_from_pcs中,会判断是都是执行必要的内存分配策略,检查当前的node是否是当前numa的节点。
之后通过获取当前cache中的struct slub_percpu_sheaves 指针,进行快速路径的分配。
快速路径分配涉及到node_barn,slab_sheaf,slub_percpu_sheaves这三个结构体。
在关系上,
sheaf(捆), barn(谷仓),他们的关系就是麦粒和谷仓的关系。
node_barn下会使用链表(默认侵入式),挂载empty和full的slab_sheaf,slub_percpu_sheaves(即pcs)中的main,empty等指向,slab_sheaf。
快速路径力求不经过完整的分配算法实现快速的分配。
当pcs中的main不为空时,可以直接通过main中的object进行分配。
当main为空时,则会进行main的替换操作,即进入到__pcs_replace_empty_main
在这个处理流程中,会先检查空闲指针spare指向的slab_sheaf是否是空余,是直接交换main和spare完成main的替换
否则,就是当前的pcs中,已经没有对象进行分配,也就是无sheaf可用,此时就会从barn即谷仓中,获取新的可用的sheaf。
在处理当中,barn_replace_empty_sheaf 会在barn中有可用slab_sheaf时,将pcs中的empty sheaf换入到barn中,将barn中可用的full empty换入到pcs中,barn和pcs中的sheaf的总数保持不变,返回可用的full sheaf
如果返回的full可用,则直接退出__pcs_replace_empty_main,否则认为barn中也没有可用对象分配。
在允许阻塞的情况下,会从本地或barn中获取empty容器,,并进行分配sheaf和填充可分配对象,并重新获取pcs指针。
之后进行容器的调整动作。在main为空,在spare不存在的时候,将spare设置为main,并将main设置为分配的full(代码中有,full = empty;),否则就是spare存在,将main放置到barn中,最终设置main为full。
其次当main不为空,spare不存在时,将spare设置为full。
当spare存在但为空时,将spare放置到barn中,并设置full。
这里可能有疑问,但是分配的原因就是因为main和spare没有可分配的对象猜进行的,为什么到这里还要再去判断他们到底有没有。
因为在代码中,使用的了
local_unlock(&s->cpu_sheaves->lock);
pcs = NULL;
在函数内部进行了解锁动作,如果此时发生了抢断,可能到最后分配的执行cpu不同,这也就是为什么下面重新设置了pcs。
最后,如果object并没有被分配,那么就会进入慢速路径分配___slab_alloc
struct kmem_cache_per_node_ptrs {
struct node_barn *barn;
struct kmem_cache_node *node;
};
/* Structure holding extra parameters for slab allocations */
struct slab_alloc_context {
unsigned long caller_addr;
size_t orig_size;
unsigned int alloc_flags;
struct list_lru *lru;
};
/*
* The slab lists for all objects.
*/
struct kmem_cache_node {
spinlock_t list_lock;
unsigned long nr_partial;
struct list_head partial;
#ifdef CONFIG_SLUB_DEBUG
atomic_long_t nr_slabs;
atomic_long_t total_objects;
struct list_head full;
#endif
};
/* Reuses the bits in struct page */
struct slab {
memdesc_flags_t flags;
struct kmem_cache *slab_cache;
union {
struct {
struct list_head slab_list;
/* Double-word boundary */
struct freelist_counters;
};
struct rcu_head rcu_head;
};
unsigned int __page_type;
atomic_t __page_refcount;
#ifdef CONFIG_SLAB_OBJ_EXT
unsigned long obj_exts;
#endif
};