如何使用内核哈希表API?
How to use the kernel hashtable API?
我试图理解和使用 kernel hash tables and I've already read this and this link,但我不理解其中的 none。我的第一个问题是:为什么我们的结构必须在其中包含 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
中存在的名称相同。所以我希望我的散列 table 是这样的:
PS:在我的哈希 table 中它永远不会发生冲突(我会处理它永远不会发生)所以键可以是 struct h_node
Why our struct has to have the struct h_list
inside it? If we're gonna access our struct through the struct h_list
our struct shouldn't be within struct h_list
and not the opposite?
那是因为哈希表在 Linux 内核中是如何实现的。哈希表只是一个固定大小 struct hlist_head
的数组。每一个都代表一个桶,并且是链表的头部。 hashtable只包含一堆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)
-
for (pos = hlist_entry_safe((head)->first, typeof(*(pos)), member);\
pos; \
pos = hlist_entry_safe((pos)->member.next, typeof(*(pos)), member))
-
({ 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)
I need that the key be the same name present in the struct h_node
.
这在本地是不可能的。 Linux 内核哈希表 API 仅处理整数键。如果您查看 linux/hashtable.h
, you can see the hash functions used are hash_32()
and hash_64()
中的实现,它们都采用无符号整数值(分别为 u32
和 u64
)。
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!
我试图理解和使用 kernel hash tables and I've already read this and this link,但我不理解其中的 none。我的第一个问题是:为什么我们的结构必须在其中包含 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
中存在的名称相同。所以我希望我的散列 table 是这样的:
PS:在我的哈希 table 中它永远不会发生冲突(我会处理它永远不会发生)所以键可以是 struct h_node
Why our struct has to have the
struct h_list
inside it? If we're gonna access our struct through thestruct h_list
our struct shouldn't be withinstruct h_list
and not the opposite?
那是因为哈希表在 Linux 内核中是如何实现的。哈希表只是一个固定大小 struct hlist_head
的数组。每一个都代表一个桶,并且是链表的头部。 hashtable只包含一堆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)
-
for (pos = hlist_entry_safe((head)->first, typeof(*(pos)), member);\ pos; \ pos = hlist_entry_safe((pos)->member.next, typeof(*(pos)), member))
-
({ 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)
I need that the key be the same name present in the
struct h_node
.
这在本地是不可能的。 Linux 内核哈希表 API 仅处理整数键。如果您查看 linux/hashtable.h
, you can see the hash functions used are hash_32()
and hash_64()
中的实现,它们都采用无符号整数值(分别为 u32
和 u64
)。
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!