找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

953

积分

0

好友

123

主题
发表于 16 小时前 | 查看: 1| 回复: 0

在开发与 percpu map CPU flags 相关的内核补丁集时,我意外地踩到了 eBPF LRU hash map 的两个“坑”。更耐人寻味的是,当尝试修复这些问题时,社区的反馈颇为一致:最好别动它们。这背后究竟是怎样的权衡?今天就来聊聊这两个具体的问题。

问题一:数据丢失之谜

早有江湖传闻:LRU hash map 在某些情况下会“丢”数据。没想到,在为 percpu map 编写自测用例时,我就直接撞上了。具体场景是:当 LRU hash map 容量已满时,即使调用 update_elem() 试图更新一个已存在的条目,也可能导致数据丢失。

那么,update_elem() 内部到底发生了什么?我们可以追踪一下内核源码的调用栈:

htab_lru_map_update_elem()    // kernel/bpf/hashtab.c
|-->prealloc_lru_pop()        // kernel/bpf/bpf_lru_list.c
    |-->bpf_common_lru_pop_free() {
            node = __local_list_pop_free(loc_l);
            if (!node) {
                bpf_lru_list_pop_free_to_local(lru, loc_l);
                node = __local_list_pop_free(loc_l);
            }
    }
        |-->bpf_lru_list_pop_free_to_local()
            |-->__bpf_lru_list_shrink()
                |-->__bpf_lru_list_shrink_inactive()
                |   |-->lru->del_from_htab()
                |-->lru->del_from_htab()

关键路径在于,当本地空闲链表为空时,会触发 bpf_lru_list_pop_free_to_local(),进而调用 __bpf_lru_list_shrink() 从非活跃链表中收缩、淘汰节点以腾出空间。问题就出在这里:收缩过程可能会淘汰掉我们正要更新的那个条目! 这就是数据丢失的根本原因。虽然我曾尝试提出一些修复思路,但 LRU 模块的作者似乎并不青睐。社区的态度也倾向于:既然设计如此,且修复可能引入更复杂的逻辑,不如就保留这个已知的“特性”。

问题二:恼人的死锁警告

如果说数据丢失是功能性瑕疵,那么第二个问题则更偏向于系统稳定性。在社区的 CI 环境、syzbot 的模糊测试以及我本地运行 LRU 专项测试用例时,都复现了一个导致 死锁警告 的场景。以下是内核日志中报错的片段:

[  418.260323] bpf_testmod: oh no, recursing into test_1, recursion_misses 1
[  424.982201]
[  424.982207] ================================
[  424.982216] WARNING: inconsistent lock state
[  424.982219] 6.18.0-rc1-gbb1b9387787c-dirty #1 Tainted: G        W  OE
[  424.982221] --------------------------------
[  424.982223] inconsistent {INITIAL USE} -> {IN-NMI} usage.
[  424.982225] new_name/11207 [HC1[1]:SC0[0]:HE0:SE1] takes:
[  424.982229] ffffe8ffffd9c000 (&loc_l->lock){....}-{2:2}, at: bpf_lru_pop_free+0x2c6/0x1a50
...
[  424.982314]  *** DEADLOCK ***

请注意,这只是一个 WARNING: inconsistent lock state,并非一定会发生的硬死锁。内核锁调试机制检测到在中断上下文(NMI)中,试图去获取一个在进程上下文中已被持有的锁(&loc_l->lock),从而发出了潜在死锁风险的警告。

当我将这个问题的潜在修复方案提交到社区后,维护者的回复非常直接:“If it's too hard, then leave it as-is.” 换言之:解决它的成本太高了,风险大于收益,不如维持现状。这也是内核开发中常见的一种务实哲学——并非所有警告都必须消除,尤其是在那些极其罕见或修复难度极大的边缘场景里。

总结与启示

经历了这两个问题的提交与讨论,我对 eBPF 乃至更广阔的 Linux 内核社区的风格有了更深的理解。社区在平衡功能、性能、代码复杂性和稳定性时,往往非常谨慎。对于一个像 LRU hash map 这样底层且核心的数据结构,除非有严重到影响广泛应用的缺陷,否则“能不动就不动”的策略有时是更优解。这也提醒我们,在开发中,如果对数据一致性有严格要求,或许应该慎重考虑是否使用 LRU 类型的 map,或者为其设计更健壮的上层容错逻辑。毕竟,了解工具的边界与特性,是每一位开发者走向精通的必经之路。如果你也在使用 eBPF 时遇到过有趣的“坑”,欢迎在云栈社区分享与讨论。




上一篇:掌握Markdown、Python与英语:在AI时代提升效能的三大关键技能
下一篇:深入解读KRS:基于ROS 2与Xilinx FPGA的机器人开发框架设计与实战
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-1-27 19:32 , Processed in 0.355155 second(s), 40 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表