C语言 如何使用内核哈希表API?

cqoc49vn  于 2023-03-12  发布在  其他
关注(0)|答案(1)|浏览(230)

我试图理解和使用kernel hash tables,我已经阅读了thisthis链接,但我不理解他们中的任何一个。为什么我们的结构体必须包含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中的名称

ybzsozfc

ybzsozfc1#

为什么我们的结构体里面必须有struct h_list?如果我们要通过struct h_list访问我们的结构体,我们的结构体不应该在struct h_list里面,而不是相反?
这是因为哈希表是如何在Linux内核中实现的。哈希表只是一个固定大小为struct hlist_head的数组。每个数组代表一个桶,并且是一个链表的头。哈希表只包含一堆struct hlist_node的链表,没有其他内容。它并没有真正“存储”整个用户定义的结构体。它仅保存指向每个元素的struct hlist_node字段的指针。
当你添加一个元素到哈希表中时,一个bucket被选中,并且一个指向你的结构体的struct hlist_node字段的指针被插入到bucket列表中。(例如通过hash_for_each()),container_of()宏用于获取您的真实的结构,知道它的类型和用户定义的结构中struct hlist_node类型的结构成员的名称。
这可以在下面的源代码中看到。例如,对于hash_for_each(),我们有:

  1. 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)
  1. 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))
  1. hlist_entry_safe()剂量:
({ typeof(ptr) ____ptr = (ptr); \
    ____ptr ? hlist_entry(____ptr, type, member) : NULL; \
 })

1.最后,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!

相关问题