☰
slub set_track_update
2026/10/28 1:59:04 网站建设 项目流程

set_track_update()是 SLUB track 机制的“写入阶段”:把已经准备好的地址、调用栈句柄、CPU、PID、时间戳写入对象的struct track。它与上一问的set_track_prepare()配合,完成完整的追踪记录。

一、函数签名

static void set_track_update(struct kmem_cache *s, void *object, enum track_item alloc, unsigned long addr, depot_stack_handle_t handle)
参数含义
s缓存
object对象指针
allocTRACK_ALLOC或TRACK_FREE
addr调用地址(返回地址)
handle调用栈句柄,由set_track_prepare()生成

无返回值。

二、典型实现

static void set_track_update(struct kmem_cache *s, void *object, enum track_item alloc, unsigned long addr, depot_stack_handle_t handle) { struct track *p; if (!kmem_cache_debug_flags(s, SLAB_STORE_USER)) return; p = get_track(s, object, alloc); p->addr = addr; p->cpu = raw_smp_processor_id(); p->pid = current->pid; p->when = jiffies; #ifdef CONFIG_STACKDEPOT p->handle = handle; #endif }

逻辑:

  1. 调试标志检查:未开SLAB_STORE_USER直接返回,不写任何东西。

  2. 定位 track:get_track(s, object, alloc)返回指向track[alloc]的指针。

  3. 填写字段:

    • addr:调用地址。

    • cpu:当前 CPU 编号。

    • pid:当前进程 PID。

    • when:时间戳(jiffies)。

    • handle:调用栈句柄(仅CONFIG_STACKDEPOT时)。

三、与set_track_prepare()的分工

两个函数把写入拆成两阶段:

set_track(s, object, alloc, addr) │ ├─ 阶段 1:采集 │ handle = set_track_prepare(gfp_flags) │ ├─ stack_trace_save() // 采集调用栈 │ └─ stack_depot_save() // 存入 depot,返回句柄 │ └─ 阶段 2:写入 set_track_update(s, object, alloc, addr, handle) ├─ get_track() // 定位 track 槽位 └─ 填写 addr/cpu/pid/when/handle

为什么拆成两个函数

关键在于gfp_flags和调用上下文:

  • set_track_prepare()可能需要分配内存(stack depot 存新栈),因此需要gfp_flags,且必须在合适的上下文调用(比如不能在持有某些锁时睡眠)。

  • set_track_update()只是写内存,不分配,可以在任意上下文快速完成。

拆分后:

  • 采集可以在安全的位置做。

  • 写入可以在方便的位置做,甚至和采集不在同一处。

  • 如果采集失败(handle 为 0),写入仍能进行,只是没有完整调用栈,仍有addr保底。

这是典型的“准备工作”与“提交工作”分离。

四、get_track()回顾

static struct track *get_track(struct kmem_cache *s, void *object, enum track_item alloc) { struct track *p; p = object + get_info_end(s); return kasan_reset_tag(p + alloc); }
  • object + get_info_end(s):跳到对象元数据区起点(对象数据 + 可能的外部 freepointer 之后)。

  • p + alloc:按TRACK_ALLOC=0/TRACK_FREE=1索引到对应 track。

  • kasan_reset_tag():去 KASAN tag 得到真实地址。

所以track[TRACK_ALLOC]和track[TRACK_FREE]各存一份现场。

五、写入的字段含义

字段来源用途
addr参数传入单点定位调用者,%pS可解析符号
handle参数传入完整调用栈,stack_depot_fetch()还原
cpuraw_smp_processor_id()记录 CPU,分析并发/中断上下文
pidcurrent->pid记录进程,定位是哪个任务操作
whenjiffies时间戳,计算对象存活时长

raw_smp_processor_id()vssmp_processor_id()

  • smp_processor_id():在开启CONFIG_DEBUG_PREEMPT时检查是否在可抢占上下文误用。

  • raw_smp_processor_id():不做检查,直接读 per-CPU 变量。

  • 这里在调试路径,且可能处于原子上下文,用raw_版本避免检查开销和潜在告警。

六、使用场景

set_track_update()由set_track()调用,在对象分配和释放时记录现场:

static void set_track(struct kmem_cache *s, void *object, enum track_item alloc, unsigned long addr) { depot_stack_handle_t handle; if (!kmem_cache_debug_flags(s, SLAB_STORE_USER)) return; handle = set_track_prepare(GFP_ATOMIC); // 准备(采集栈) set_track_update(s, object, alloc, addr, handle); // 提交(写入) }
  • 分配:alloc_debug_processing()里记TRACK_ALLOC。

  • 释放:free_debug_processing()里记TRACK_FREE。

之后调试输出时:

struct track *t = get_track(s, object, TRACK_ALLOC); pr_err("Allocated by %pS, cpu %d, pid %d, jiffies %lu\n", (void *)t->addr, t->cpu, t->pid, t->when); // 有 STACKDEPOT 时还能打印完整栈 stack_depot_print(t->handle);

七、与前面知识的呼应

前面讲过这里体现
set_track_prepare()采集阶段,生成 handle
get_track()定位 track 槽位
struct track被写入的结构
enum track_itemalloc参数,决定写哪个槽
kmem_cache_debug_flags(s, SLAB_STORE_USER)门控条件
kasan_reset_tag()get_track()内使用
TRACK_ADDRS_COUNT采集栈深上限

八、设计要点总结

  1. 门控检查:未开SLAB_STORE_USER直接返回,零开销。

  2. 定位复用:用get_track()统一算地址,与读取路径一致。

  3. 字段完整:地址、句柄、CPU、PID、时间戳,覆盖定位所需全部信息。

  4. 条件编译:handle字段仅在CONFIG_STACKDEPOT时写入。

  5. 无分配:只写内存,可在原子上下文安全调用。

九、总结

set_track_update(s, object, alloc, addr, handle)是 track 机制的写入端:检查SLAB_STORE_USER后,用get_track()定位track[alloc],把调用地址、栈句柄、CPU、PID、时间戳写入struct track。它与set_track_prepare()分工——前者只管采集栈生成句柄,后者只管落盘写入,合起来构成 SLUB 完整的分配/释放追踪。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询