Linux内核中实现了一套经典的哈希表操作,定义在/include/linux/list.h文件中,本文基于linux内核源码6.2.7,记录了其常用操作哈希表的API函数,便于在阅读linux内核源码时更好的理解程序执行的过程和细节。
在Linux内核中也提供了一个/include/linux/hashtable.h文件,并以
hash_xxx
类似文本命名函数,本质上这些函数是对/include/linux/list.h文件中的hlist操作做了封装。本文仅分析/include/linux/list.h文件中的函数。
出自Linux内核文件/include/linux/types.h
:
struct hlist_head {
struct hlist_node *first;
};
struct hlist_head
是Linux内核中实现散列表的基础数据结构之一,它提供了高效的链表管理机制,用于处理具有相同哈希值的元素。在struct hlist_head
结构中,first
是一个指向链表中第一个节点的指针,它指向struct hlist_node
类型的节点,表示链表中的首个元素,定义如下:
struct hlist_node {
struct hlist_node *next, **pprev;
};
通过使用struct hlist_head
,可以将具有相同哈希值的元素按照链表的形式连接起来,并且可以通过first
指针快速访问链表中的第一个元素。
在散列表的实现中,每个桶都使用struct hlist_head
来维护对应链表的头节点。这样可以方便进行链表的插入、删除和遍历操作。
注意:
struct hlist_head
只是链表头节点的数据结构,并不存储实际的数据。实际的数据存储在链表节点struct hlist_node
中,该节点通过嵌入在数据结构中的方式来使用。
#define HLIST_HEAD_INIT { .first = NULL }
#define HLIST_HEAD(name) struct hlist_head name = { .first = NULL }
#define INIT_HLIST_HEAD(ptr) ((ptr)->first = NULL)
static inline void INIT_HLIST_NODE(struct hlist_node *h)
{
h->next = NULL;
h->pprev = NULL;
}
hlist_unhashed()
是一个用于判断给定节点是否未被哈希的宏,即判断节点是否不属于任何哈希桶。在Linux内核中常用。
该宏的原型如下:
/**
* hlist_unhashed - Has node been removed from list and reinitialized?
* @h: Node to be checked
*
* Not that not all removal functions will leave a node in unhashed
* state. For example, hlist_nulls_del_init_rcu() does leave the
* node in unhashed state, but hlist_nulls_del() does not.
*/
static inline int hlist_unhashed(const struct hlist_node *h)
{
return !h->pprev;
}
宏接受一个参数:
h
:要判断的节点。hlist_unhashed()
宏通过检查节点的pprev
成员来判断节点是否未被哈希。如果节点的pprev
成员为NULL
,则表示该节点不属于任何哈希桶,即未被哈希;反之,如果pprev
成员不为NULL
,则表示该节点已被哈希。
以下是一个示例,展示如何使用hlist_unhashed()
宏判断给定节点是否未被哈希:
struct person {
const char *name;
int age;
struct hlist_node hash_node;
};
struct person p1, p2;
struct hlist_head hash_table[HASH_SIZE]; // 哈希表
// 判断节点是否未被哈希
int is_node_unhashed(struct person *node) {
if (hlist_unhashed(&node->hash_node)) {
printf("Node is unhashed.\n");
} else {
printf("Node is hashed.\n");
}
return 0;
}
在上述示例中,定义了一个结构体person
,其中包含了一个hlist_node
成员hash_node
。通过使用hlist_unhashed()
宏,可以判断给定节点的hash_node
是否未被哈希,并输出相应的结果。
使用
hlist_unhashed()
宏可以方便地判断给定节点是否未被哈希,用于对节点的状态进行判断和处理。
/**
* hlist_empty - Is the specified hlist_head structure an empty hlist?
* @h: Structure to check.
*/
static inline int hlist_empty(const struct hlist_head *h)
{
return !READ_ONCE(h->first);
}
hlist_del()
是一个用于在哈希表的链表中删除节点的函数,该函数用于从哈希表的链表中删除给定节点。
函数原型如下:
static inline void __hlist_del(struct hlist_node *n)
{
struct hlist_node *next = n->next;
struct hlist_node **pprev = n->pprev;
WRITE_ONCE(*pprev, next);
if (next)
WRITE_ONCE(next->pprev, pprev);
}
/**
* hlist_del - Delete the specified hlist_node from its list
* @n: Node to delete.
*
* Note that this function leaves the node in hashed state. Use
* hlist_del_init() or similar instead to unhash @n.
*/
static inline void hlist_del(struct hlist_node *n)
{
__hlist_del(n);
n->next = LIST_POISON1;
n->pprev = LIST_POISON2;
}
函数接受一个参数:
n
:要删除的节点。hlist_del()
函数会将给定节点从链表中断开,使其不再属于任何哈希桶。
以下是一个代码示例,展示如何使用hlist_del()
函数从哈希表的链表中删除节点:
struct person {
const char *name;
int age;
struct hlist_node hash_node;
};
struct person p1, p2, p3;
struct hlist_head hash_table[HASH_SIZE]; // 哈希表
// 从哈希表的链表中删除节点
void delete_node(struct person *node) {
hlist_del(&node->hash_node);
// 执行其他必要的清理操作
}
在上述示例中,定义了一个结构体 person
,其中包含了一个 hlist_node
成员 hash_node
。通过调用 hlist_del()
函数,我们可以从哈希表的链表中删除给定的节点 node
。
使用
hlist_del()
函数可以方便地从哈希表的链表中删除节点,适用于需要删除特定节点的场景,例如在哈希表中查找到不再需要的节点并将其删除。
hlist_del_init()
是一个在Linux内核中常用的函数,用于从哈希表的链表中删除节点,并将节点的链表指针初始化。
函数原型如下:
/**
* hlist_del_init - Delete the specified hlist_node from its list and initialize
* @n: Node to delete.
*
* Note that this function leaves the node in unhashed state.
*/
static inline void hlist_del_init(struct hlist_node *n)
{
if (!hlist_unhashed(n)) {
__hlist_del(n);
INIT_HLIST_NODE(n);
}
}
函数接受一个参数:
n
:要删除的节点。例如下列代码:
struct person {
const char *name;
int age;
struct hlist_node hash_node;
};
struct person p1, p2, p3;
struct hlist_head hash_table[HASH_SIZE]; // 哈希表
// 从哈希表的链表中删除节点并初始化
void delete_node(struct person *node) {
hlist_del_init(&node->hash_node);
// 执行其他必要的清理操作
}
在上述示例中,我们定义了一个结构体 person
,其中包含了一个 hlist_node
成员 hash_node
。通过调用 hlist_del_init()
函数,我们可以从哈希表的链表中删除给定的节点 node
,并将其链表指针初始化为NULL。
使用
hlist_del_init()
函数可以方便地从哈希表的链表中删除节点,并将节点的链表指针初始化,适用于需要删除特定节点并进行必要的清理操作的场景。
hlist_add_head()
是一个在Linux内核中常用的函数,用于将节点插入到哈希表的链表头部。
函数原型如下:
/**
* hlist_add_head - add a new entry at the beginning of the hlist
* @n: new entry to be added
* @h: hlist head to add it after
*
* Insert a new entry after the specified head.
* This is good for implementing stacks.
*/
static inline void hlist_add_head(struct hlist_node *n, struct hlist_head *h)
{
struct hlist_node *first = h->first;
WRITE_ONCE(n->next, first);
if (first)
WRITE_ONCE(first->pprev, &n->next);
WRITE_ONCE(h->first, n);
WRITE_ONCE(n->pprev, &h->first);
}
函数接受两个参数:
n
:要插入的节点。h
:链表的头节点。hlist_add_head()
函数会将给定节点 node
插入到链表的头部,使其成为新的头节点,并将原来的头节点作为其后继节点。
以下是一个示例,展示如何使用hlist_add_head()
函数将节点插入到哈希表的链表头部:
struct person {
const char *name;
int age;
struct hlist_node hash_node;
};
struct person p1, p2, p3;
struct hlist_head hash_table[HASH_SIZE]; // 哈希表
// 将节点插入到哈希表的链表头部
void insert_node(struct person *node, struct hlist_head *head) {
hlist_add_head(&node->hash_node, head);
}
使用
hlist_add_head()
函数可以方便地将节点插入到哈希表的链表头部,适用于需要在链表头部添加节点的场景,例如在哈希表中插入新的节点或进行相关操作。
/**
* hlist_add_before - 在指定的条目之前添加一个新条目
* @n: n要添加的新条目
* @next: hlist节点,该节点必须为非NULL。
*/
static inline void hlist_add_before(struct hlist_node *n,
struct hlist_node *next)
{
WRITE_ONCE(n->pprev, next->pprev);
WRITE_ONCE(n->next, next);
WRITE_ONCE(next->pprev, &n->next);
WRITE_ONCE(*(n->pprev), n);
}
/**
* hlist_add_behind -在指定的条目之后添加新条目
* @n: 要添加的新条目
* @prev: hlist节点,该节点必须为非NULL
*/
static inline void hlist_add_behind(struct hlist_node *n,
struct hlist_node *prev)
{
WRITE_ONCE(n->next, prev->next);
WRITE_ONCE(prev->next, n);
WRITE_ONCE(n->pprev, &prev->next);
if (n->next)
WRITE_ONCE(n->next->pprev, &n->next);
}
/**
* hlist_add_fake - create a fake hlist consisting of a single headless node
* @n: Node to make a fake list out of
*
* This makes @n appear to be its own predecessor on a headless hlist.
* The point of this is to allow things like hlist_del() to work correctly
* in cases where there is no list.
*/
static inline void hlist_add_fake(struct hlist_node *n)
{
n->pprev = &n->next;
}
/**
* hlist_fake: Is this node a fake hlist?
* @h: Node to check for being a self-referential fake hlist.
*/
static inline bool hlist_fake(struct hlist_node *h)
{
return h->pprev == &h->next;
}
hlist_is_singular_node()函数用于检查给定的节点是否是哈希桶中的唯一节点。如果一个节点是单节点,意味着它是该哈希桶中唯一的节点。这通常用于判断一个节点是否处于哈希表中的孤立位置,即没有与之发生哈希碰撞的其他节点。
函数原型如下:
/**
* hlist_is_singular_node - is node the only element of the specified hlist?
* @n: Node to check for singularity.
* @h: Header for potentially singular list.
*
* Check whether the node is the only node of the head without
* accessing head, thus avoiding unnecessary cache misses.
*/
static inline bool
hlist_is_singular_node(struct hlist_node *n, struct hlist_head *h)
{
return !n->next && n->pprev == &h->first;
}
函数接受两个参数:
n:要检查的节点的指针。
h:哈希桶的头节点的指针。
函数返回一个布尔值,如果给定的节点是哈希桶中的唯一节点,则返回true;否则返回false。
通常情况下,使用hlist_is_singular_node()函数可以判断节点是否存在哈希碰撞。如果一个节点是孤立的,意味着它没有与之发生哈希碰撞的其他节点,可以利用这个特性来进行更高效的哈希表操作。
hlist_move_list()
函数的作用是将一个完整的哈希桶(链表)从一个哈希表中删除,并将其插入到另一个哈希表中。这个操作可以用于动态地重新分配哈希桶,或者在哈希表之间转移数据。
函数实现如下:
/**
* hlist_move_list - Move an hlist
* @old: hlist_head for old list.
* @new: hlist_head for new list.
*
* Move a list from one list head to another. Fixup the pprev
* reference of the first entry if it exists.
*/
static inline void hlist_move_list(struct hlist_head *old,
struct hlist_head *new)
{
new->first = old->first;
if (new->first)
new->first->pprev = &new->first;
old->first = NULL;
}
函数接受两个参数:
old
:要移动的源哈希桶的头节点指针。new
:目标哈希桶的头节点指针。调用hlist_move_list()
函数后,源哈希桶中的所有节点都将从源哈希表中删除,并按照相同的顺序插入到目标哈希表中。注意,此操作是原子的,它保证在并发环境中的正确性。
使用
hlist_move_list()
函数可以方便地将哈希桶从一个哈希表转移到另一个哈希表,从而实现哈希表的动态调整或数据迁移。
hlist_entry()
是一个在Linux内核中常用的宏,用于通过节点的成员指针获取所属的结构体指针。
原型如下:
#define hlist_entry(ptr, type, member) container_of(ptr,type,member)
宏接受三个参数:
ptr
:节点的成员指针。type
:结构体的类型。member
:结构体中表示节点的成员的名称。hlist_entry()
宏内部使用了另一个常用的宏container_of()
,它的作用是根据结构体成员的指针、结构体类型和成员名称来获取整个结构体的指针。
通过使用hlist_entry()
宏,我们可以方便地从节点的成员指针获得所属的结构体指针,进而访问结构体中的其他成员。
例如,假设有以下结构体定义和哈希表节点定义:
struct person {
const char *name;
int age;
struct hlist_node hash_node;
};
struct person *p;
struct hlist_node *node;
我们可以使用hlist_entry()
宏来获取struct person
结构体指针:
p = hlist_entry(node, struct person, hash_node);
这样,我们就可以通过p
指针来访问struct person
结构体中的成员,如p->name
和p->age
。
使用
hlist_entry()
宏可以方便地在Linux内核中进行节点和所属结构体之间的转换,减少了代码的冗余和错误。
hlist_entry_safe()宏的作用与hlist_entry()类似,它也是通过节点的成员指针获取所属的结构体指针。然而,hlist_entry_safe()宏会在进行指针转换之前先进行额外的安全检查,以确保指针的有效性。
该宏定义如下:
#define hlist_entry_safe(ptr, type, member) \
({ typeof(ptr) ____ptr = (ptr); \
____ptr ? hlist_entry(____ptr, type, member) : NULL; \
})
hlist_entry_safe()宏在进行指针转换之前,会先检查传入的指针是否为NULL,以确保在使用无效指针进行转换时不会导致访问非法内存。如果传入的指针为NULL,宏将返回NULL,否则将调用hlist_entry()宏进行正常的指针转换。
hlist_for_each()
是一个在Linux内核中常用的宏,用于遍历哈希表中的链表。
该宏的原型如下:
#define hlist_for_each(pos, head) \
for (pos = (head)->first; pos ; pos = pos->next)
宏接受两个参数:
pos
:迭代器变量,表示当前遍历到的节点。head
:哈希桶的头节点。hlist_for_each()
宏会从哈希桶的头节点开始遍历链表,通过不断更新迭代器变量pos
,直到遍历完整个链表为止。在每一次循环迭代中,可以使用pos
来访问当前节点,并执行相应的操作。
以下是一个示例,展示如何使用hlist_for_each()
宏遍历哈希表中的链表:
struct person {
const char *name;
int age;
struct hlist_node hash_node;
};
struct hlist_head hash_table[HASH_SIZE]; // 哈希表
// 遍历哈希表中的链表
int iterate_hash_table() {
struct hlist_node *node;
struct person *p;
for (int i = 0; i < HASH_SIZE; i++) {
hlist_for_each(node, &hash_table[i]) {
p = hlist_entry(node, struct person, hash_node);
// 对每个节点执行操作
printf("Name: %s, Age: %d\n", p->name, p->age);
}
}
return 0;
}
在上述示例中,hlist_for_each()
宏用于遍历哈希表中的链表。通过结合hlist_entry()
宏,我们可以在每次迭代中获取节点所属的结构体指针,并对结构体中的成员执行操作。
使用
hlist_for_each()
宏可以方便地遍历哈希表中的链表,对每个节点进行处理,例如查找、修改或删除等操作。
hlist_for_each_entry()
是一个在Linux内核中常用的宏,用于在哈希表的链表中遍历结构体,并对每个结构体执行指定的操作。
该宏的原型如下:
#define hlist_for_each_safe(pos, n, head) \
for (pos = (head)->first; pos && ({ n = pos->next; 1; }); \
pos = n)
/**
* hlist_for_each_entry - iterate over list of given type
* @pos: the type * to use as a loop cursor.
* @head: the head for your list.
* @member: the name of the hlist_node within the struct.
*/
#define hlist_for_each_entry(pos, head, member) \
for (pos = hlist_entry_safe((head)->first, typeof(*(pos)), member);\
pos; \
pos = hlist_entry_safe((pos)->member.next, typeof(*(pos)), member))
宏接受三个参数:
pos
:迭代器变量,表示当前遍历到的结构体指针。head
:哈希桶的头节点。member
:结构体中表示节点的成员的名称。hlist_for_each_entry()
宏会从哈希桶的头节点开始遍历链表,通过不断更新迭代器变量pos
,直到遍历完整个链表为止。在每一次循环迭代中,可以使用pos
来访问当前结构体,并执行相应的操作。
以下是一个示例,展示如何使用hlist_for_each_entry()
宏遍历哈希表中的链表中的结构体:
struct person {
const char *name;
int age;
struct hlist_node hash_node;
};
struct hlist_head hash_table[HASH_SIZE]; // 哈希表
// 遍历哈希表中的链表中的结构体
int iterate_hash_table() {
struct person *p;
for (int i = 0; i < HASH_SIZE; i++) {
hlist_for_each_entry(p, &hash_table[i], hash_node) {
// 对每个结构体执行操作
printf("Name: %s, Age: %d\n", p->name, p->age);
}
}
return 0;
}
在上述示例中,hlist_for_each_entry()
宏用于遍历哈希表中的链表中的结构体。通过结合hlist_entry_safe()
宏,我们可以在每次迭代中获取结构体指针,并对结构体中的成员执行操作。
使用
hlist_for_each_entry()
宏可以方便地遍历哈希表中的链表,并对链表中的结构体执行操作,例如查找、修改或删除等操作。
hlist_for_each_entry_continue()
是一个在Linux内核中常用的宏,用于在哈希表的链表中继续遍历结构体。
该宏的原型如下:
/**
* hlist_for_each_entry_continue - iterate over a hlist continuing after current point
* @pos: the type * to use as a loop cursor.
* @member: the name of the hlist_node within the struct.
*/
#define hlist_for_each_entry_continue(pos, member) \
for (pos = hlist_entry_safe((pos)->member.next, typeof(*(pos)), member);\
pos; \
pos = hlist_entry_safe((pos)->member.next, typeof(*(pos)), member))
宏接受两个参数:
pos
:迭代器变量,表示当前遍历到的结构体指针。member
:结构体中表示节点的成员的名称。hlist_for_each_entry_continue()
宏会从指定的节点继续遍历链表,通过不断更新迭代器变量pos
,直到遍历完整个链表为止。在每一次循环迭代中,可以使用pos
来访问当前结构体,并执行相应的操作。
例如下列代码:
struct person {
const char *name;
int age;
struct hlist_node hash_node;
};
struct hlist_head hash_table[HASH_SIZE]; // 哈希表
// 在哈希表的链表中继续遍历结构体
int iterate_hash_table_continue(struct person *start) {
struct person *p;
hlist_for_each_entry_continue(p, &start->hash_node) {
// 对每个结构体执行操作
printf("Name: %s, Age: %d\n", p->name, p->age);
}
return 0;
}
在上述示例中,hlist_for_each_entry_continue()
宏用于在哈希表的链表中继续遍历结构体。我们传入了一个起始结构体指针start
,然后从该结构体节点的下一个节点开始遍历链表。通过结合hlist_entry_safe()
宏,我们可以在每次迭代中获取结构体指针,并对结构体中的成员执行操作。
使用
hlist_for_each_entry_continue()
宏可以方便地在哈希表的链表中继续遍历结构体,并对链表中的结构体执行操作,例如查找、修改或删除等操作。
hlist_for_each_entry_from()
宏的作用是从指定节点开始,在哈希表的链表中遍历结构体,并对每个结构体执行指定的操作。
宏的原型如下:
/**
* hlist_for_each_entry_from - iterate over a hlist continuing from current point
* @pos: the type * to use as a loop cursor.
* @member: the name of the hlist_node within the struct.
*/
#define hlist_for_each_entry_from(pos, member) \
for (; pos; \
pos = hlist_entry_safe((pos)->member.next, typeof(*(pos)), member))
宏接受两个参数:
pos
:迭代器变量,表示当前遍历到的结构体指针。member
:结构体中表示节点的成员的名称。hlist_for_each_entry_from()
宏会从指定的节点开始遍历链表,通过不断更新迭代器变量pos
,直到遍历完整个链表为止。在每一次循环迭代中,可以使用pos
来访问当前结构体,并执行相应的操作。
以下是一个示例,展示如何使用hlist_for_each_entry_from()
宏从指定节点开始在哈希表的链表中遍历结构体:
struct person {
const char *name;
int age;
struct hlist_node hash_node;
};
struct hlist_head hash_table[HASH_SIZE]; // 哈希表
// 从指定节点开始在哈希表的链表中遍历结构体
int iterate_hash_table_from(struct person *start) {
struct person *p;
hlist_for_each_entry_from(p, &start->hash_node) {
// 对每个结构体执行操作
printf("Name: %s, Age: %d\n", p->name, p->age);
}
return 0;
}
在上述示例中,hlist_for_each_entry_from()
宏用于从指定节点开始在哈希表的链表中遍历结构体。我们传入了一个起始结构体指针start
,然后从该结构体节点开始遍历链表。通过结合hlist_entry_safe()
宏,我们可以在每次迭代中获取结构体指针,并对结构体中的成员执行操作。
hlist_for_each_entry_safe()
宏的作用是在哈希表的链表中安全地遍历结构体,并对每个结构体执行指定的操作。安全遍历是指在遍历过程中允许删除当前节点而不会破坏遍历的完整性。
宏的原型如下:
/**
* hlist_for_each_entry_safe - iterate over list of given type safe against removal of list entry
* @pos: the type * to use as a loop cursor.
* @n: a &struct hlist_node to use as temporary storage
* @head: the head for your list.
* @member: the name of the hlist_node within the struct.
*/
#define hlist_for_each_entry_safe(pos, n, head, member) \
for (pos = hlist_entry_safe((head)->first, typeof(*pos), member);\
pos && ({ n = pos->member.next; 1; }); \
pos = hlist_entry_safe(n, typeof(*pos), member))
宏接受四个参数:
pos
:迭代器变量,表示当前遍历到的结构体指针。n
:临时变量,用于在删除当前节点时保存下一个节点的指针。head
:哈希桶的头节点。member
:结构体中表示节点的成员的名称。hlist_for_each_entry_safe()
宏会从哈希桶的头节点开始遍历链表,通过不断更新迭代器变量pos
和临时变量n
,直到遍历完整个链表为止。在每一次循环迭代中,可以使用pos
来访问当前结构体,并执行相应的操作。
同时,宏在每次迭代之前都会使用hlist_entry_safe()
宏来安全地获取下一个节点的指针,并将其保存在临时变量n
中。这样,即使在遍历过程中删除了当前节点,也能够安全地继续遍历下一个节点。
例如下列代码:
struct person {
const char *name;
int age;
struct hlist_node hash_node;
};
struct hlist_head hash_table[HASH_SIZE]; // 哈希表
// 安全地遍历哈希表的链表中的结构体
int iterate_hash_table_safe() {
struct person *p, *n;
for (int i = 0; i < HASH_SIZE; i++) {
hlist_for_each_entry_safe(p, n, &hash_table[i], hash_node) {
// 对每个结构体执行操作
printf("Name: %s, Age: %d\n", p->name, p->age);
// 删除当前节点
hlist_del(&p->hash_node);
//
可以安全地继续遍历下一个节点,不会受到删除操作的影响
}
}
return 0;
}
在上述示例中,hlist_for_each_entry_safe()
宏用于安全地遍历哈希表的链表中的结构体。通过结合hlist_entry_safe()
宏,在每次迭代之前保存下一个节点的指针,即使在遍历过程中删除了当前节点,也能够安全地继续遍历下一个节点。
使用hlist_for_each_entry_safe()
宏可以方便、安全地遍历哈希表的链表,并对链表中的结构体执行操作,例如查找、修改或删除等操作,而不会破坏遍历的完整性。