linux内核中的Hlist与List_head结构

本文介绍了Linux内核中链表机制的实现原理与使用方法,包括list_head结构定义、链表初始化、链表操作宏定义等内容,并给出了具体示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

List_Head

操作系统内核经常需要维护数据结构。内核有标准的循环链表、双向 链表的实现。在 <Linux/list.h> 文件中定义了一个 list_head 类型简单结构:

 

struct list_head {

   struct list_head *next, *prev;

};

 

通用链表的常用用途是将某一个数据结构本身串成链表,或将某些链 表与一个数据结构联系起来,这两种情况实质上都是由结构 list_head 组成链表,只是 list_head 背负 的负载不一样。下面分别举例说明这两种用途。

以下示例说明了如何将某一个数据结构本身串成链表,并对链表进行 操作,同时还说明 list_head 结构的实现与使用。

示例:将某一个数据结构本身串成链表。

1 )加入 list_head 结构成员。

假设有一个 example_struct 结构需连接成链表,因而在其结构里面加上 list_head 成员,就组成了结构链表,如下:

 

struct example_struct {

   struct list_head list;

   int priority; 

   ……// 其他成员

};

 

example_struct 结构中的 list 成员,用来将 example_struct 结构串成链表。可理解为 list_head“ 背负 的负载是 example_struct 结构。

2 )创建 list_head 结构。

使用前必须申请链表头并用 INIT_LIST_HEAD 宏来 初始化链表头。可使用两种方法。

 

方法 1

struct list_head example_list;

INIT_LIST_HEAD(&example_list);

 

方法 2

LIST_HEAD(example_list);

 

其中,这两个宏在 include/Linux/list.h 中定义如下:

 

#define LIST_HEAD(name) /

       struct list_head name = LIST_HEAD_INIT(name)

 

#define INIT_LIST_HEAD(ptr) do { /

       (ptr)->next = (ptr); (ptr)->prev = (ptr); /

} while (0)

 

宏定义 INIT_LIST_HEAD 初始化了链表头,即向前、向后的指针都指向链表头。这样,就已 初始化了一个 example_list 的链表头,以后就可以向链表中增加链表元素了。

3 )链表与用户结构连接。

list_entry 宏将 exmplelist 链表与 exmple_struct 结构类型连接起来。

有两项链表的链表头

List_entry 宏的效果

list_head 的定制结构

next

prev

list_head 结构

空链表

<linux/list. h> 中的链表

下面这个代码行就是从 examplelist 链表中得到节点对应的 example_struct 结构指针,其中 ptr examplelist 链表中的指针,如 ptr = examplelist->next

 

struct example_struct *node =

   list_entry(ptr, struct example_struct, list);

 

在上面代码行中的宏定义 list_entry 将一个 list_head 结构指针映射回一个指向结构 example_struct 的指针,即得到 list_head 的宿主结构。下面分析这个宏定义(在 include/Linux/list.h 中):

 

 #define list_entry(ptr, type, member) /

       container_of(ptr, type, member)

 

list_entry 的功能是得到链表中节点的结构,它的参数含义为:

Ø Ø   ptr 是链表中的一个 struct list_head 结构元素指针。

Ø Ø   type 是用户定义的结构类型,其中,包含 struct list_head 结构成员。

Ø Ø   member 用户定义结构中的 struct list_head 结构成员名字。

include/Linux/kernel.h 中有 container_of 的定义,参数含义与 list_entry 中一致, container_of 得到 list 的容器结构,即含有 list 成员的结构 type container_of 的定义如下:

 

#define container_of(ptr, type, member) ({                  /

     // 将链表中的元素 ptr 转换成结构 type 中成员 member 的类型

        const typeof( ((type *)0)->member ) *__mptr = (ptr);     /

     //__mptr 减去 member 成员偏移地址正好是 type 结构地址

        (type *)( (char *)__mptr - offsetof(type,member) );})

 

include/Linux/stddef.h 中有宏 offsetof 的定义,列出如下:

 

#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)

 

offsetof 宏对于上述示例的展开及分析是: &((struct example_struct *)0)->list 表示当结构 example_struct 正好在地址 0 上时其成员 list 的地址,即成员位移。

4 )遍历链表

下面使用 list_entry 宏遍历链表得到链表指针,再从链表指针映射回对应结构 example_struct 的指针。然后,对其成员 priority 进行操作。函数 example_add_entry 的功能是给链表加入新的结构成员。

 

void example_add_entry(struct example_struct *new)

{

   struct list_head *ptr;

   struct example_struct *entry;

   // 遍历链表

   for (ptr = exmple_list.next; ptr != &exmple_list; ptr = ptr->next) {

       // 映射回对应结构 example_struct 的指针

       entry = list_entry(ptr, struct todo_struct, list);

       if (entry->priority < new->priority) {

           list_add_tail(&new->list, ptr);

           return;

       }

   }

   list_add_tail(&new->list, &exmple_struct)

}

 

示例:将某些链表与一个数据结构联系起来。

函数 new_inode 为给定的超级块分配一个新的节点,并将新的节点加到链表 inode_in_use sb->s_inodes 中,从而在两个链表中链接了新的节点。一个是以 inode_in_use 为链表头的全局的节点链表;一个是超级块结构为链表头的节点链 表。

 

fs/inode.c

extern struct list_head inode_in_use;

struct inode *new_inode(struct super_block *sb)

{

       static unsigned long last_ino;

       struct inode * inode;

 

       spin_lock_prefetch(&inode_lock);

      

       inode = alloc_inode(sb);

       if (inode) {

              spin_lock(&inode_lock);

              inodes_stat.nr_inodes++;

      // inode 加到 inode_in_use 链表中

              list_add(&inode->i_list, &inode_in_use);

              list_add(&inode->i_sb_list, &sb->s_inodes);   // inode 加到超级块的节点链表中

              inode->i_ino = ++last_ino;

              inode->i_state = 0;

              spin_unlock(&inode_lock);

       }

       return inode;

……

 

……

 

……

 

s_inodes

 

 

i_sb_list

 

 

i_sb_list

 

 

list_head

 

 

list_head

 

 

list_head

 

 

*next

 

 

*prev

 

 

*prev

 

 

*prev

 

 

*next

 

 

*next

 

 

Super_block

 

inode

 

inode

 

}

include/Linux/list.h 中还定义了下面操作链表的函数。

Ø Ø list_add(struct list_head *new, struct list_head *head) ;这个函数在链表头后面 添加新的元素。如果是在链表的头部添加元素,就可以用来建立栈。还需要注意的是, head 并不一定非得是链表的第一项,如果传递了一个恰巧位于链表中间某 处的 list_head 结构,新入口会立即排在它的后面。因为 Linux 链表是循环的,链表头通常与其他入口没有本质区别。

Ø Ø list_add_tail(struct list_head *new, struct list_head *head) ; 在给定链表头的前面增加一个新的元素,即在链表的末尾添加。可使用 list_add_tail 建立 先入先出 队列。

Ø Ø list_del(struct list_head *entry) ;从链表中将给定的入口删除。

Ø Ø list_empty(struct list_head *head) ;如果给定的链表是空的,就返回一个非零值。

Ø Ø list_splice(struct list_head *list, struct list_head *head) ; 这个函数通过在 head 的 后面插入 list 来 合并两个链表。

 

Hlist

include/Linux/list.h 中有 list 链表与 hlist 哈希链表结构的定义,下面都列出它们的定义,可以对比一下:

 

 

struct list_head {struct list_head *next, *prev; };

struct hlist_head { struct hlist_node *first; };

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

 

 

双头( next prev )的双链表对于 Hash 表来说 过于浪费 ,因而另行设计了一套 Hash 表专用的 hlist 数据结构 —— 单指针表头双循环链表, hlist 的表头仅有一个指向首节点的指针,而没有指向尾节点的指针,这 样在可能是海量的 Hash 表 中存储的表头就能减少一半的空间消耗。

pprev 因为 hlist 不是一个完整的循环链表而不得不使用。在 list 中,表头和节点是同一个数据结构,直接用 prev 没问题;在 hlist 中,表头没有 prev ,也没有 next ,只有一个 first 。为了能统一地修改表头的 first 指针,即表头的 first 指针必须修改指向新插入的节点, hlist 就设计了 pprev hlist 节点的 pprev 不再是指向前一个节点的指针,而是指向前一个节点(可能是表 头)中的 next (对 于表头则是 first ) 指针( struct list_head **pprev ),从而在表头插入的操作可以通过一致的 “*(node->pprev)” 访问和修改前节点的 next (或 first )指针。

下面是 hlist 中常用的几个宏:

 

 

#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 INIT_HLIST_NODE(ptr) ((ptr)->next = NULL, (ptr)->pprev = NULL)

 

下面只列出 hlist_add_before 操作函数,其他 hlist 链表操作函数操作方法类似。这个函数中的参数 next 不能为空。它在 next 前面加入了 n 节点。函数的实现与 list 中对应函数类似。

 

 

static inline void hlist_add_before(struct hlist_node *n, struct hlist_node *next)

{

       n->pprev = next->pprev;

       n->next = next;

       next->pprev = &n->next;

       *(n->pprev) = n;

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值