Linux内核数据结构之双向循环链表 list_head

本文详细介绍了Linux内核中用于实现双向循环链表的list_head结构体,包括其初始化、插入等操作。list_entry宏作为关键,用于连接list_head与实际数据结构。此外,文章还展示了如何在实际数据结构中使用list_head,以及如何进行插入和删除操作。通过这种方式,数据结构和抽象链表得以分离,便于管理和操作。
摘要由CSDN通过智能技术生成

目录

概述

内核中 list_head 的实现

1.list_head 结构体

2.list_head 实现的操作

3.list_entry 宏

list_head 的使用

结语


概述

Linux 内核中双向循环链表采用将抽象结构和数据分离的特殊方式实现,即将抽象链表 list_head 嵌入到储存具体数据的数据结构中,通过 list_entry 宏(下文有讲解)将链表结构与数据连接起来。


内核中 list_head 的实现

list.h 文件实现了list_head 的所有功能,可在 .../include/linux 中找到(博主用的2.6版本内核)

1.list_head 结构体

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

list_head 的结构非常简单, 只储存其前后节点,这符合其抽象的特征。

2.list_head 实现的操作

list.h 内部实现的对链表的操作都是对 list_head 链表自身的,包括初始化、插入、删除、移动等操作,但并不实现对其所嵌入数据结构的操作,篇幅原因这里仅简单例举初始化和插入操作.

初始化

//使用举例 list_head *head = LIST_HEAD_INIT(head)
#define LIST_HEAD_INIT(name) { &(name), &(name) }

//使用举例 list_head *head;
//         LIST_Head(head);
#define LIST_HEAD(name) \
	struct list_head name = LIST_HEAD_INIT(name)

//使用举例 list_head *head;
//         INIT_LIST_HEAD(head);
static inline void INIT_LIST_HEAD(struct list_head *list)
{
	list->next = list;
	list->prev = list;
}

需要注意的是因为是双向循环链表因此初始化后的头节点的 prev 和 next 均指向 head 自己

插入

//在两个已知节点间插入
static inline void __list_add(struct list_head *new,
			      struct list_head *prev,
			      struct list_head *next)
{
	next->prev = new;
	new->next = next;
	new->prev = prev;
	prev->next = new;
}

//在 head 节点后添加新节点
static inline void list_add(struct list_head *new, struct list_head *head)
{
	__list_add(new, head, head->next);
}

//在 head 节点前添加新节点 即插入到尾部
static inline void list_add_tail(struct list_head *new, struct list_head *head)
{
	__list_add(new, head->prev, head);
}

list.h 中实现了三种插入操作,其中后两种操作便于实现队列。

list.h 中其他函数也并不复杂,请读者自行研究,下面我们来介绍一个重要的宏 list_entry 

3.list_entry 宏

list_head 结构和储存数据的结构联系起来的关键就是 list_entry 宏,这个宏一般是提供给与其绑定的数据结构使用的,首先我们看其定义:

#define list_entry(ptr, type, member) \
	container_of(ptr, type, member)

可以看到 list_entry 宏重新封装了另一个宏 container_of ,这个宏在内核中经常用到,定义如下:

#define container_of(ptr, type, member) ({	    \
	const typeof( ((type *)0)->member ) *__mptr = (ptr);    \
	(type *)( (char *)__mptr - offsetof(type,member) );})

这个宏比较复杂,让我们来层层拆开来分析,首先我们看三个参数,ptr实例化结构体 member 指针type结构体的类型名称member 为被嵌入在结构体中的 某成员的名称

下面让宏的内容更整齐一些

{

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

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

}

第一行巧妙的将0强制转换为 type 类型指针并指向其 member 成员然后再利用 typeof 获取此member 的类型即 typeof( ((type *)0)->member )  ,然后将 ptr 赋给此类型的临时指针 __mptr ,前置 const 防止 ptr 指向的内容被修改。【1.宏的参数中并未提供 member 的类型 2.宏 typeof 用于获取类型名称】

第二行 offsetof 用于获取 member 成员在数据结构中内存偏移,用 __mptr 指针减去偏移量即可获取 member 所嵌入数据结构的地址,即储存数据的结构的地址,之后将这个地址强转为 type 类型指针即可,宏返回的便是 type 类型的指针。【offsetof 由编译器提供用于计算元素相对于结构体头部的偏移】

下面的图将有助于理解:

 

用 list_node 的地址减去 data_size 即可取得 实例化的data_struct 的地址,注意图中所标识的地址是相对于结构体内部的。【注意这里的 data_size 为 指针 data 的da'xiao

使用如下:

struct data_struct {
    void *data;
    list_head list_node;
};

struct data_struct *temp = init();
list_entry(&temp->list_node, struct data_struct, list_node);

list_head 的使用

上面 data_struct 的例子便是使用 list_head 时数据的组织结构,list.h 中封装了对链表的很多操作使用时直接使用这些即可形成链表,而除 data 数据需要另行处理。

//此处援引上文中用到的 data_struct

struct data_struct *head = init();

//插入
struct data_struct *new_1 = init();
list_add(&new->list_node, &head->list_node);

//删除
struct data_struct *new_2 = init();
list_del(&new1->list_node);//仅仅删除抽象链表中的元素,并未清除任何内存

上面删除操作便很好的体现了数据和抽象链表的分离,当我们需要真正删除某一元素时需要自己定义删除函数来实现操作。

void list_del_m(struct data_struct *node)
{
    list_del(&node->list_node);
    free(node->data);
    node->data = NULL;
    free(node);
    node = NULL;
}

//援引上面代码
list_del_m(new_1);

结语

限于篇幅原因本文先写到这里,对于 list_head 的更多使用可以参考内核中 task 结构体及其相关功能,可以在 .../include/linux/sched.h 找到 task_struct 定义。

最后,非常感谢您能读到这里,有不妥之处敬请指正。

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值