linux kernel 中的链表(二)

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 结构

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 真是个牛逼东西)。

基于STM32F407,使用DFS算法实现最短迷宫路径检索,分为三种模式:1.DEBUG模式,2. 训练模式,3. 主程序模式 ,DEBUG模式主要分析bug,测量必要数据,训练模式用于DFS算法训练最短路径,并将最短路径以链表形式存储Flash, 主程序模式从Flash….zip项目工程资源经过严格测试可直接运行成功且功能正常的情况才上传,可轻松复刻,拿到资料包后可轻松复现出一样的项目,本人系统开发经验充足(全领域),有任何使用问题欢迎随时与我联系,我会及时为您解惑,提供帮助。 【资源内容】:包含完整源码+工程文件+说明(如有)等。答辩评审平均分达到96分,放心下载使用!可轻松复现,设计报告也可借鉴此项目,该资源内项目代码都经过测试运行成功,功能ok的情况下才上传的。 【提供帮助】:有任何使用问题欢迎随时与我联系,我会及时解答解惑,提供帮助 【附带帮助】:若还需要相关开发工具、学习资料等,我会提供帮助,提供资料,鼓励学习进步 【项目价值】:可用在相关项目设计,皆可应用在项目、毕业设计、课程设计、期末/期/大作业、工程实训、大创等学科竞赛比赛、初期项目立项、学习/练手等方面,可借鉴此优质项目实现复刻,设计报告也可借鉴此项目,也可基于此项目来扩展开发出更多功能 下载后请首先打开README文件(如有),项目工程可直接复现复刻,如果基础还行,也可在此程序基础上进行修改,以实现其它功能。供开源学习/技术交流/学习参考,勿用于商业用途。质量优质,放心下载使用。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值