您的位置:首页 > 运维架构 > Linux

linux kernel 中的链表(二)

2016-04-06 21:09 561 查看

linux kernel 中的链表(二)

hlist 的定义

前文提到的
linux/list.h
,这个文件中实际包含了一个双向链表和一个哈希头,哈希链表的定义如下:

struct hlist_head {
struct hlist_node *first;
};

struct hlist_node {
struct hlist_node *next, **pprev;
};


hlist_head 是哈希头结点,里面只有一个指向链表结构的指针,表示哈希头下的第一个元素。

hlist_node 中有两个指针,next 指针比较好理解,就是指向下一个链表元素,但是 pprev 则比较难理解一点。

pprev 在使用中,是指向上一个结点的 next 指针(即指向指针的指针变量)。这样做的好处主要是为了保证结构体的通用性。如下所示:



hlist_head1 是一个哈希表数组,为了解决冲突,在数组中都会有一个 first 的指针,从它开始挂载各个冲突到同一个哈希值的元素。那么为了定义这些挂载的哈希元素,首先需要 next 指针做串联,然后需要一个 prev 指向他的前驱节点。那么问题来了,第一个挂载的元素,他的前驱节点是 hlist_head 和其他的节点的头不一致。为了解决这个问题,所有的 hlist_node 的前驱节点修改为
hlist_node **pprev
,这样第一个节点也能赋值 pprev = &head->first 。

hlist 的基础操作

类似于普通的链表,hlist 也提供诸如新增,删除等操作,具体如下:

/*
* 判断类型函数
*/
static inline int hlist_unhashed(const struct hlist_node *h) // 判断一个元素是否未在哈希表里
{
return !h->pprev;
}

static inline int hlist_empty(const struct hlist_head *h)  // 判断一个哈希桶下是否为空
{
return !h->first;
}

/*
* 基础操作
*/
static inline void hlist_del_init(struct hlist_node *n); // 从hlist中删除一个节点,并初始化它
static inline void hlist_del(struct hlist_node *n);
static inline void hlist_add_head(struct hlist_node *n, struct hlist_head *h);
static inline void hlist_add_before(struct hlist_node *n, struct hlist_node *next);
static inline void hlist_add_after(struct hlist_node *n, struct hlist_node *next);
static inline void hlist_move_list(struct hlist_head *old, struct hlist_head *new);
// 以上是一些顾名思义的操作了,包括删除节点,从头新增,在某个节点前新增,在某个节点后新增,移动一个节点


以其中删除节点为例子进行分析:

/*
* 这里的*pprev = next;体现出了 struct 中的设计的优越性
* 一句话屏蔽了对于前驱节点的判断,因为 head 和 node 的下一个节点的指针类型都是 hlist_node
*/
static inline void __hlist_del(struct hlist_node *n)
{
struct hlist_node *next = n->next;
struct hlist_node **pprev = n->pprev;
*pprev = next;   //前驱节点的下一个节点修改为后继节点
if (next)
next->pprev = pprev; //后继节点的上一个节点修改为前驱节点
}

static inline void hlist_del(struct hlist_node *n)
{
__hlist_del(n);
n->next = LIST_POISON1;
n->pprev = LIST_POISON2;
}


这里有两个宏定义:

poison.h:#define LIST_POISON1  ((void *) 0x00100100 + POISON_POINTER_DELTA)
poison.h:#define LIST_POISON2  ((void *) 0x00200200 + POISON_POINTER_DELTA)


只是一个访问之后会引起页错误的特殊地址,以下为.h里的原文:

/*
* These are non-NULL pointers that will result in page faults
* under normal circumstances, used to verify that nobody uses
* non-initialized list entries.
*/


常用宏

hlist 中的宏和 list 基本是一样的,也是包括初始化和枚举:

初始化:

#define HLIST_HEAD_INIT { .first = NULL }
#define HLIST_HEAD(name) struct hlist_head name = {  .first = NULL }
#define INIT_HLIST_HEAD(ptr) ((ptr)->first = NULL)


枚举宏:

#define hlist_entry(ptr, type, member) container_of(ptr,type,member)
/**
* hlist_for_each_entry_safe - iterate over list of given type safe against removal of list entry
* @tpos: the type * to use as a loop cursor.
* @pos:  the &struct hlist_node to use as a loop cursor.
* @n:    another &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(tpos, pos, n, head, member)      \
for (pos = (head)->first;          \
pos && ({ n = pos->next; 1; }) &&         \
({ tpos = hlist_entry(pos, typeof(*tpos), member); 1;}); \
pos = n)


这里的 tpos 是指自己的外包 struct 指针,pos 是用来循环的当前 hlist_node 指针,n为下一个链表元素(其实是个临时变量),head 就是 hlist_head 类型的指针了,member 为自己的结构体中对于链表的命名。

下一篇来写一些demo和使用心得,还有把里面比较细节的宏和函数做一些学习和总结吧(container_of 真是个牛逼东西)。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  linux kernel 链表