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); } } }
核心问题分析
- 哈希表生命周期错误:
nodes_hash是module_init内的局部变量,初始化完成后栈内存被释放,后续refresh访问的是无效内存,这是空指针崩溃的主要根源。 - 遍历宏选择错误:
hash_for_each是全表遍历,效率低且易因链表结构异常触发空指针;你的需求是按tid查找,完全不需要遍历所有桶。
哈希表迭代宏区别
hash_for_each:遍历哈希表所有桶的所有节点,适合全表扫描,但遍历过程中不能删除节点(会导致迭代器失效),且若链表存在非法空引用会触发空指针。hash_for_each_safe:在hash_for_each基础上提前保存下一个节点指针,允许遍历过程中删除当前节点,但依然是全表扫描。hash_for_each_possible:仅遍历与目标key哈希值匹配的桶内节点,效率极高,是按key查找场景的最优选择,返回的节点均为有效引用(前提哈希表操作合法)。
解决步骤
修正哈希表生命周期:将
nodes_hash改为全局变量或动态分配:// 全局变量方式 static DEFINE_HASHTABLE(nodes_hash, 3); static int __init module_init(void) { hash_init(nodes_hash); return 0; }替换为正确的遍历宏:用
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; } }修复其他潜在问题:
- 用户空间数据访问:
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
相关产品推荐
相关产品推荐

