You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Linux内核哈希表迭代出现NULL指针解引用问题求助

内核哈希表空指针解引用问题及解决建议

问题背景

编写内核模块时,使用Linux内核哈希表存储线程信息,通过tid访问:每次获取信息时检查tid是否存在,存在则重置计数器,不存在则添加新结构。使用hash_for_each宏做存在性检查时,出现BUG: kernel NULL pointer dereference, address: 0000000000000000日志并导致线程崩溃,想了解hash_for_each_safe、hash_for_each_possible的区别及解决办法。

问题代码

struct thread_check {
    unsigned short  cnt;
    unsigned short  maxcnt;
    int tid;
    char functionname[32];
    char alm;
    struct hlist_node node;
};

static int __init module_init(void) {
    DECLARE_HASHTABLE(nodes_hash, 3);
    hash_init(nodes_hash);
}

function refresh(char procfs_buffer[1024]) {
    token = procfs_buffer;  // in the form s|1234|functionName
    strsep(&token, "|");    // skip first token - the s character
    res = kstrtol(strsep(&token, "|"), 10, &tid);
    found = false;
    hash_for_each(nodes_hash, bkt, cur, node) {
        printk(KERN_INFO "sono a %d\n", __LINE__);
        if (cur != NULL && cur->tid == tid) {
            printk(KERN_INFO "sono a %d\n", __LINE__);
            cur->cnt = 0;
            printk(KERN_INFO "sono a %d\n", __LINE__);
            found = true;
            break;
        }
        printk(KERN_INFO "sono a %d\n", __LINE__);
    }
    if (!found) {
        t = (struct thread_check *)kmalloc(sizeof(struct thread_check), GFP_KERNEL);
        if (t != NULL) {
            t->tid = tid;
            t->cnt = 0;
            t->maxcnt = 0;
            t->node.next = 0;
            t->node.pprev = 0;
            memset(t->functionname, 0, 32);
            strncpy(t->functionname, token, strlen(token));
            hash_add(nodes_hash, &t->node, t->tid);
        }
    }
}

核心问题分析

  1. 哈希表生命周期错误:nodes_hash是module_init内的局部变量,初始化完成后栈内存被释放,后续refresh访问的是无效内存,这是空指针崩溃的主要根源。
  2. 遍历宏选择错误:hash_for_each是全表遍历,效率低且易因链表结构异常触发空指针;你的需求是按tid查找,完全不需要遍历所有桶。

哈希表迭代宏区别

  • hash_for_each:遍历哈希表所有桶的所有节点,适合全表扫描,但遍历过程中不能删除节点(会导致迭代器失效),且若链表存在非法空引用会触发空指针。
  • hash_for_each_safe:在hash_for_each基础上提前保存下一个节点指针,允许遍历过程中删除当前节点,但依然是全表扫描。
  • hash_for_each_possible:仅遍历与目标key哈希值匹配的桶内节点,效率极高,是按key查找场景的最优选择,返回的节点均为有效引用(前提哈希表操作合法)。

解决步骤

  1. 修正哈希表生命周期:将nodes_hash改为全局变量或动态分配:

    // 全局变量方式
    static DEFINE_HASHTABLE(nodes_hash, 3);
    
    static int __init module_init(void) {
        hash_init(nodes_hash);
        return 0;
    }
    
  2. 替换为正确的遍历宏:用hash_for_each_possible替代hash_for_each,直接定位目标tid所在桶:

    found = false;
    hash_for_each_possible(nodes_hash, cur, node, tid) {
        if (cur->tid == tid) { // 无需检查cur非空,宏保证返回有效节点
            cur->cnt = 0;
            found = true;
            break;
        }
    }
    
  3. 修复其他潜在问题:

    • 用户空间数据访问:refresh的procfs_buffer需用char __user *类型,通过copy_from_user拷贝到内核缓冲区后再处理。
    • 字符串拷贝安全:替换strncpy(t->functionname, token, strlen(token))为:
      strncpy(t->functionname, token, sizeof(t->functionname)-1);
      t->functionname[sizeof(t->functionname)-1] = '\0';
      
    • 节点初始化:无需手动设置hlist_node的next和pprev,hash_add会自动处理链表链接。
    • 线程安全:若存在并发访问,添加自旋锁保护哈希表操作:
      static DEFINE_SPINLOCK(hash_lock);
      
      // 遍历/修改哈希表前加锁
      spin_lock(&hash_lock);
      // ... 哈希表操作 ...
      spin_unlock(&hash_lock);
      

内容的提问来源于stack exchange,提问作者bui3

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.19 23:36:10