如何使用内核哈希表 API?

Posted

技术标签:

【中文标题】如何使用内核哈希表 API?【英文标题】:How to use the kernel hashtable API? 【发布时间】:2020-07-07 07:03:47 【问题描述】:

我正在尝试理解和使用kernel hash tables,并且我已经阅读了this 和this 链接,但我一个都不理解。我的第一个问题是:为什么我们的结构必须在其中包含struct h_list?如果我们要通过struct h_list 访问我们的结构,我们的结构不应该在struct h_list 内,而不是相反?阅读教程后,我尝试编写以下代码:

DECLARE_HASHTABLE(nodes_hash, 3)

hash_init(nodes_hash);

struct h_node
    int data;
    char name[MAX_NAME_SIZE]; /*The key is the name of lua state*/
    struct hlist_node node;
;

struct h_node a = 
    .data = 3,
    .name = "foo",
    .node = 0   
;

struct h_node b = 
    .data = 7,
    .name = "bar",
    .node = 0   
;    

hash_add(nodes_hash,&a.node, "foo");
hash_add(nodes_hash,&b.node, "bar");

但这甚至不能编译。我做错了什么?我需要密钥与struct h_node 中存在的名称相同。所以我希望我的哈希表是这样的:

PS:在我的哈希表中它永远不会发生冲突(我会处理它永远不会发生)所以键可以是struct h_node中的名称

【问题讨论】:

知道了,但我还是需要了解如何在哈希表中插入元素以及它是如何工作的。 仅供参考,当您说“我已经阅读了这个和这个链接”时,您提供的两个链接指向同一个 URL。无论如何,您还应该提供编译代码时出现的编译错误。 键需要是整数类型,但您使用的是字符串文字。将字符串文字的地址转换为 unsigned long 不是一个好主意,因为相同的字符串文字不一定具有相同的地址(特别是如果它们在不同的 .c 文件中),因此搜索键不会匹配插入钥匙。您可以使用xxhash 将字符串内容散列为unsigned long 值并将该值用作键。相同的字节序列将 xxhash 到相同的值(只要它们使用相同的种子参数进行散列),因此它是稳定的。它需要CONFIG_XXHASH (如果您不能依赖 CONFIG_XXHASH 的配置,您可以使用自己的代码使用 FNV(Fowler/Noll/Vo)算法对字符串内容进行哈希处理。) 【参考方案1】:

为什么我们的结构必须在里面有struct h_list?如果我们要通过struct h_list 访问我们的结构,我们的结构不应该在struct h_list 内,而不是相反?

那是因为哈希表是如何在 Linux 内核中实现的。哈希表只是一个固定大小的数组struct hlist_head。每一个都代表一个桶,并且是链表的头部。哈希表只包含一堆struct hlist_node 的链表,没有别的。它并没有真正“存储”整个用户定义的结构,它只是保存一个指向每个元素的 struct hlist_node 字段的指针。

当您向哈希表添加元素时,会选择一个存储桶,并将指向您的结构的struct hlist_node 字段的指针插入到存储桶列表中。当您稍后检索一个元素时(例如通过hash_for_each()),container_of() 宏用于获取您的真实结构,知道它的类型和您用户定义的结构中struct hlist_node 类型的结构成员的名称。

这可以在源代码之后看到。例如,对于hash_for_each(),我们有:

    hash_for_each(name, bkt, obj, member) 会:

     for ((bkt) = 0, obj = NULL; obj == NULL && (bkt) < HASH_SIZE(name);\
                     (bkt)++)\
             hlist_for_each_entry(obj, &name[bkt], member)
    

    hlist_for_each_entry() 会:

     for (pos = hlist_entry_safe((head)->first, typeof(*(pos)), member);\
          pos;                           \
          pos = hlist_entry_safe((pos)->member.next, typeof(*(pos)), member))
    

    hlist_entry_safe() 会:

     ( typeof(ptr) ____ptr = (ptr); \
        ____ptr ? hlist_entry(____ptr, type, member) : NULL; \
     )
    

    最后hlist_entry() 使用container_of() 得到真正的结构:

     #define hlist_entry(ptr, type, member) container_of(ptr,type,member)
    

我需要密钥与struct h_node 中存在的名称相同。

这在本地是不可能的。 Linux 内核哈希表 API 仅处理整数键。如果你看一下linux/hashtable.h 中的实现,你可以看到使用的哈希函数是hash_32()hash_64(),它们都采用无符号整数值(分别为u32u64)。

Linux 内核哈希表 API 非常有限,它确实不会实现您在其他编程语言中习惯使用的相同类型的哈希表。它不能使用字符串作为键,并且它有固定的大小。

如果您想使用字符串,则必须对这些字符串进行哈希处理以生成无符号整数。为此,您可以使用xxhash() 或编写自己的函数。 xxhash() 函数相对较新,似乎还没有在内核代码中使用,所以我认为您的内核很可能是在没有它的情况下配置的,而您没有它可用。

在任何情况下,请注意,如果散列函数将 不同 字符串转换为 相同 键,或者如果 hash_add() 最终在散列表数组中选择相同的索引插入元素,那么这两个元素将被放置在同一个哈希表桶中。因此,在检索任何元素时(例如使用hash_for_each_possible()),您需要考虑到这一点并正确检查其name

工作示例

这是一个完整的工作示例,用于演示内核哈希表的基本用法,在内核 v4.9 上进行了测试,但也应该在最新的 v5.7 上工作。请注意,在此示例中,为了简单起见,我将变量分配到模块 _init 函数的堆栈上。这意味着我不能从代码中的任何其他地方执行hash_for_each_possible(),除非从该函数内部。如果您想要一个全局哈希表能够保存稍后由不同函数访问的元素,则需要使用 kmalloc() 动态分配它们。

// SPDX-License-Identifier: GPL-3.0
#include <linux/hashtable.h> // hashtable API
#include <linux/module.h>    // module_init,exit, MODULE_*
#include <linux/string.h>    // strcpy, strcmp
#include <linux/types.h>     // u32 etc.

#define MAX 32

struct h_node 
    int data;
    char name[MAX];
    struct hlist_node node;
;

DECLARE_HASHTABLE(tbl, 3);

// Just to demonstrate the behavior when two keys are equal.
static u32 myhash(const char *s) 
    u32 key = 0;
    char c;

    while ((c = *s++))
        key += c;

    return key;


static int __init myhashtable_init(void)

    struct h_node a, b, *cur;
    u32 key_a, key_b;
    unsigned bkt;

    pr_info("myhashtable: module loaded\n");

    a.data = 3;
    strcpy(a.name, "foo");

    b.data = 7;
    strcpy(b.name, "oof");

    /* Calculate key for each element.
     * Since the above hash function only sums the characters, we will
     * end up having two identical keys here.
     */
    key_a = myhash(a.name);
    key_b = myhash(b.name);

    pr_info("myhashtable: key_a = %u, key_b = %u\n", key_a, key_b);

    // Initialize the hashtable.
    hash_init(tbl);

    // Insert the elements.
    hash_add(tbl, &a.node, key_a);
    hash_add(tbl, &b.node, key_b);

    // List all elements in the table.
    hash_for_each(tbl, bkt, cur, node) 
        pr_info("myhashtable: element: data = %d, name = %s\n",
            cur->data, cur->name);
    

    // Get the element with name = "foo".
    hash_for_each_possible(tbl, cur, node, key_a) 
        pr_info("myhashtable: match for key %u: data = %d, name = %s\n",
            key_a, cur->data, cur->name);

        // Check the name.
        if (!strcmp(cur->name, "foo")) 
            pr_info("myhashtable: element named \"foo\" found!\n");
            break;
        
    

    // Remove elements.
    hash_del(&a.node);
    hash_del(&b.node);

    return 0;


static void __exit myhashtable_exit(void)

    // Do nothing (needed to be able to unload the module).
    pr_info("myhashtable: module unloaded\n");



module_init(myhashtable_init);
module_exit(myhashtable_exit);
MODULE_VERSION("0.1");
MODULE_DESCRIPTION("Silly kernel hashtable API example module.");
MODULE_AUTHOR("Marco Bonelli");
MODULE_LICENSE("GPL");

dmesg 在我的机器上输出:

[ 3174.567029] myhashtable: key_a = 324, key_b = 324
[ 3174.567030] myhashtable: element: data = 7, name = oof
[ 3174.567031] myhashtable: element: data = 3, name = foo
[ 3174.567032] myhashtable: match for key 324: data = 7, name = oof
[ 3174.567033] myhashtable: match for key 324: data = 3, name = foo
[ 3174.567033] myhashtable: element named "foo" found!

【讨论】:

"如果你想要一个全局哈希表能够保存稍后被不同函数访问的元素,你需要使用kmalloc()动态分配它们。"这包括将被插入的节点或只是哈希表本身? @Matheus 它包括节点。 我试图在全局上下文中调用hash_init,但没有成功,当我在一个函数内部调用它时,这是有原因的吗?有办法在全局范围内调用吗? @Matheus 为什么你认为你可以在函数之外运行代码?在 C 中没有办法做到这一点。hash_init() 不只是一些花哨的宏,它执行真正的函数调用。

以上是关于如何使用内核哈希表 API?的主要内容,如果未能解决你的问题,请参考以下文章

Python 散列表查询_进入<哈希函数;为结界的世界

如何通过哈希比较限制 API 密钥的使用

操作系统 之 哈希表 Linux 内核 应用浅析

如何使用 glib 处理哈希表中的冲突

如何从 Github API 获取最新版本的提交哈希

什么是哈希表以及如何在 C 中创建它? [关闭]