微信手机网站,门户网站开发难点,辽宁建设工程信息网怎么报名,vps怎么添加网站我们根据上面的流程图#xff0c;依次看下slub是如何分配的 首先从kmem_cache_cpu中分配#xff0c;如果没有则从kmem_cache_cpu的partial链表分配#xff0c;如果还没有则从kmem_cache_node中分配#xff0c;如果kmem_cache_node中也没有#xff0c;则需要向伙伴系统申请…
我们根据上面的流程图依次看下slub是如何分配的 首先从kmem_cache_cpu中分配如果没有则从kmem_cache_cpu的partial链表分配如果还没有则从kmem_cache_node中分配如果kmem_cache_node中也没有则需要向伙伴系统申请内存。 第一步先看看kmem_cache_cpu是如何实现
/** When changing the layout, make sure freelist and tid are still compatible* with this_cpu_cmpxchg_double() alignment requirements.*/
struct kmem_cache_cpu {union {struct {void **freelist; /* Pointer to next available object */unsigned long tid; /* Globally unique transaction id */};freelist_aba_t freelist_tid; /* 将 freelist 和 tid 封装为原子操作单元 */};struct slab *slab; /* 当前用于分配对象的Slab页 */
#ifdef CONFIG_SLUB_CPU_PARTIALstruct slab *partial; /* 部分分配的冻结Slab链表仅启用CPU Partial时存在 */
#endiflocal_lock_t lock; /* 保护上述字段的本地CPU锁 */
#ifdef CONFIG_SLUB_STATSunsigned stat[NR_SLUB_STAT_ITEMS]; /* Slab分配统计信息 */
#endif
};接下来我们可以详细拆解一下这段代码 联合体Union中的 freelist 和 tid
union {struct {void **freelist; // 指向当前Slab中下一个可用对象的指针unsigned long tid; // 全局唯一的事务IDTransaction ID};freelist_aba_t freelist_tid; // 将两者封装为一个原子操作单元
};目的
无锁快速路径在SLUB分配器中对象的分配和释放通常通过无锁操作如this_cpu_cmpxchg_double()实现以规避传统锁的性能开销。
ABA问题防御tid事务ID用于防止ABA问题。每次修改freelist时tid会递增确保即使freelist的值在并发操作中“看似未变”如A→B→A其tid也已变化使得原子操作能检测到状态不一致。
联合体的作用
freelist_tid类型通常为u64或类似将freelist和tid在内存中紧密打包确保它们占据连续且对齐的内存空间满足双字原子操作如cmpxchg_double的硬件对齐要求。
例如在64位系统中freelist8字节和tid8字节组合为一个16字节的单元对齐到16字节边界从而允许通过单条指令原子地比较和交换这两个字段。## 对齐要求
this_cpu_cmpxchg_double()需要操作的两个字段必须满足 a. 在内存中连续。 b. 对齐到双字例如16字节对齐。 联合体强制freelist和tid共享同一内存区域确保它们的布局符合上述条件。
slab指针
struct slab *slab; // 当前活跃的Slab页用于快速分配对象作用指向当前CPU正在使用的Slab页其中包含可分配的对象。 性能优化通过本地化访问减少NUMA或缓存一致性开销。
partial 指针条件编译
#ifdef CONFIG_SLUB_CPU_PARTIAL
struct slab *partial; // 部分空闲的冻结Slab链表
#endif功能
当启用CONFIG_SLUB_CPU_PARTIAL时每个CPU会缓存部分空闲的Slab称为“冻结”状态避免频繁向全局链表归还/申请Slab。 在内存压力或特定条件下如flush_slab这些Partial Slab会被转移到全局链表NUMA节点的partial链表。
local_lock_t lock
local_lock_t lock; // 本地CPU锁保护kmem_cache_cpu结构中的字段作用
在慢速路径如Slab切换、统计更新中防止同一CPU上的不同上下文如进程与中断竞争访问kmem_cache_cpu结构。 注意快速路径无锁分配/释放不依赖此锁仅在慢速路径中使用。
stat 统计数组条件编译
#ifdef CONFIG_SLUB_STATS
unsigned stat[NR_SLUB_STAT_ITEMS]; // 统计计数器如分配次数、缓存命中率等
#endif功能在内核启用CONFIG_SLUB_STATS时记录Slab分配器的运行时性能指标用于调优和监控。
现在我们来看看kmem_cache_cpu在慢速分配时候是如何工作___slab_alloc
/** Slow path. The lockless freelist is empty or we need to perform* debugging duties.** Processing is still very fast if new objects have been freed to the* regular freelist. In that case we simply take over the regular freelist* as the lockless freelist and zap the regular freelist.** If that is not working then we fall back to the partial lists. We take the* first element of the freelist as the object to allocate now and move the* rest of the freelist to the lockless freelist.** And if we were unable to get a new slab from the partial slab lists then* we need to allocate a new slab. This is the slowest path since it involves* a call to the page allocator and the setup of a new slab.** Version of __slab_alloc to use when we know that preemption is* already disabled (which is the case for bulk allocation).*/
static void *___slab_alloc(struct kmem_cache *s, gfp_t gfpflags, int node,unsigned long addr, struct kmem_cache_cpu *c, unsigned int orig_size)
{void *freelist;struct slab *slab;unsigned long flags;struct partial_context pc;stat(s, ALLOC_SLOWPATH);reread_slab:slab READ_ONCE(c-slab);if (!slab) {/** if the node is not online or has no normal memory, just* ignore the node constraint*/if (unlikely(node ! NUMA_NO_NODE !node_isset(node, slab_nodes)))node NUMA_NO_NODE;goto new_slab;}
redo:if (unlikely(!node_match(slab, node))) {/** same as above but node_match() being false already* implies node ! NUMA_NO_NODE*/if (!node_isset(node, slab_nodes)) {node NUMA_NO_NODE;} else {stat(s, ALLOC_NODE_MISMATCH);goto deactivate_slab;}}/** By rights, we should be searching for a slab page that was* PFMEMALLOC but right now, we are losing the pfmemalloc* information when the page leaves the per-cpu allocator*/if (unlikely(!pfmemalloc_match(slab, gfpflags)))goto deactivate_slab;/* must check again c-slab in case we got preempted and it changed */local_lock_irqsave(s-cpu_slab-lock, flags);if (unlikely(slab ! c-slab)) {local_unlock_irqrestore(s-cpu_slab-lock, flags);goto reread_slab;}freelist c-freelist;if (freelist)goto load_freelist;freelist get_freelist(s, slab);if (!freelist) {c-slab NULL;c-tid next_tid(c-tid);local_unlock_irqrestore(s-cpu_slab-lock, flags);stat(s, DEACTIVATE_BYPASS);goto new_slab;}stat(s, ALLOC_REFILL);load_freelist:lockdep_assert_held(this_cpu_ptr(s-cpu_slab-lock));/** freelist is pointing to the list of objects to be used.* slab is pointing to the slab from which the objects are obtained.* That slab must be frozen for per cpu allocations to work.*/VM_BUG_ON(!c-slab-frozen);c-freelist get_freepointer(s, freelist);c-tid next_tid(c-tid);local_unlock_irqrestore(s-cpu_slab-lock, flags);return freelist;deactivate_slab:local_lock_irqsave(s-cpu_slab-lock, flags);if (slab ! c-slab) {local_unlock_irqrestore(s-cpu_slab-lock, flags);goto reread_slab;}freelist c-freelist;c-slab NULL;c-freelist NULL;c-tid next_tid(c-tid);local_unlock_irqrestore(s-cpu_slab-lock, flags);deactivate_slab(s, slab, freelist);new_slab:if (slub_percpu_partial(c)) {local_lock_irqsave(s-cpu_slab-lock, flags);if (unlikely(c-slab)) {local_unlock_irqrestore(s-cpu_slab-lock, flags);goto reread_slab;}if (unlikely(!slub_percpu_partial(c))) {local_unlock_irqrestore(s-cpu_slab-lock, flags);/* we were preempted and partial list got empty */goto new_objects;}slab c-slab slub_percpu_partial(c);slub_set_percpu_partial(c, slab);local_unlock_irqrestore(s-cpu_slab-lock, flags);stat(s, CPU_PARTIAL_ALLOC);goto redo;}new_objects:pc.flags gfpflags;pc.slab slab;pc.orig_size orig_size;freelist get_partial(s, node, pc);if (freelist)goto check_new_slab;slub_put_cpu_ptr(s-cpu_slab);slab new_slab(s, gfpflags, node);c slub_get_cpu_ptr(s-cpu_slab);if (unlikely(!slab)) {slab_out_of_memory(s, gfpflags, node);return NULL;}stat(s, ALLOC_SLAB);if (kmem_cache_debug(s)) {freelist alloc_single_from_new_slab(s, slab, orig_size);if (unlikely(!freelist))goto new_objects;if (s-flags SLAB_STORE_USER)set_track(s, freelist, TRACK_ALLOC, addr);return freelist;}/** No other reference to the slab yet so we can* muck around with it freely without cmpxchg*/freelist slab-freelist;slab-freelist NULL;slab-inuse slab-objects;slab-frozen 1;inc_slabs_node(s, slab_nid(slab), slab-objects);check_new_slab:if (kmem_cache_debug(s)) {/** For debug caches here we had to go through* alloc_single_from_partial() so just store the tracking info* and return the object*/if (s-flags SLAB_STORE_USER)set_track(s, freelist, TRACK_ALLOC, addr);return freelist;}if (unlikely(!pfmemalloc_match(slab, gfpflags))) {/** For !pfmemalloc_match() case we dont load freelist so that* we dont make further mismatched allocations easier.*/deactivate_slab(s, slab, get_freepointer(s, freelist));return freelist;}retry_load_slab:local_lock_irqsave(s-cpu_slab-lock, flags);if (unlikely(c-slab)) {void *flush_freelist c-freelist;struct slab *flush_slab c-slab;c-slab NULL;c-freelist NULL;c-tid next_tid(c-tid);local_unlock_irqrestore(s-cpu_slab-lock, flags);deactivate_slab(s, flush_slab, flush_freelist);stat(s, CPUSLAB_FLUSH);goto retry_load_slab;}c-slab slab;goto load_freelist;
}___slab_alloc 是 SLUB 分配器的慢速路径函数当快速路径无锁分配失败时负责处理复杂场景
___slab_alloc 是 Linux 内核 SLUB 分配器中处理慢速路径的核心函数主要用于以下场景和逻辑 核心作用
处理快速路径失败 当 CPU 本地缓存kmem_cache_cpu-freelist无可用对象时通过慢速路径获取新对象。NUMA 优化 确保内存分配符合请求的 NUMA 节点减少跨节点访问延迟。调试与隔离 支持调试功能如内存跟踪、毒化和隔离 PFMEMALLOC 内存用于内存回收的专用页。并发同步 通过锁和事务 IDtid确保多核环境下的数据一致性避免 ABA 问题。 核心逻辑流程 初始化检查 读取当前 CPU 的活跃 Slabc-slab若为空则跳转至新 Slab 分配new_slab。检查 NUMA 节点是否有效若无效则忽略节点约束。 NUMA 与 PFMEMALLOC 匹配 若 Slab 的 NUMA 节点与请求不匹配停用当前 Slabdeactivate_slab。检查 Slab 的 PFMEMALLOC 标志是否与分配标志gfpflags匹配不匹配则停用。 加锁与状态重验 获取本地锁local_lock_irqsave防止同一 CPU 上的进程与中断竞争。二次验证 Slab 是否被其他上下文修改若已修改则重新读取reread_slab。 获取空闲链表Freelist 若本地 freelist 存在可用对象直接分配。若本地 freelist 为空尝试从 Slab 页获取新 freelistget_freelist。若获取失败标记 Slab 失效c-slab NULL触发新 Slab 分配。 分配新对象 从 CPU 的 Partial 链表获取若启用 CONFIG_SLUB_CPU_PARTIAL优先重用部分空闲 Slab。从节点的 Partial 链表获取通过 get_partial 批量获取部分空闲对象。分配全新 Slab调用伙伴系统new_slab分配新页初始化并冻结 Slab。 更新状态 递增事务 IDc-tid确保后续快速路径能检测到状态变化。若启用调试记录内存分配跟踪信息set_track。 异常处理 若内存不足slab_out_of_memory触发 OOM 处理。若并发冲突如锁内发现 Slab 被修改回滚并重试。 关键设计 锁与无锁混合 快速路径无锁通过原子操作this_cpu_cmpxchg_double实现高效分配。慢速路径加锁使用本地锁保护 kmem_cache_cpu 结构避免并发修改。 事务 IDtid防 ABA 每次修改 freelist 后递增 tid确保并发操作能检测到状态变化。 Partial 链表优化 缓存部分空闲 Slab减少全局锁争用和内存碎片化。 NUMA 感知 优先从请求的 NUMA 节点分配降低跨节点访问开销。 性能影响
快速恢复通过重用 Partial 链表减少全新 Slab 分配频率。最小化锁范围仅对关键操作加锁缩短锁持有时间。统计与调试通过 stat() 记录性能事件支持调优和问题排查。 总结
___slab_alloc 是 SLUB 分配器在复杂场景下如本地缓存耗尽、NUMA 约束、调试需求实现内存分配的核心逻辑。其通过精细的状态管理、锁优化和资源重用平衡了性能与可靠性确保多核系统中内存分配的高效性和正确性。
参考链接https://zhuanlan.zhihu.com/p/382056680#/