`
cm14k
  • 浏览: 31414 次
  • 性别: Icon_minigender_1
  • 来自: 西安
社区版块
存档分类
最新评论

Linux内核链表

阅读更多

 一、链表数据结构简介

 

链表是一种常用的组织数据的数据结构,它通过指针将一系列数据节点连接成一条数据链,是线性表的一种重要实现方式。相对于数组,链表具有更好的动态性,建立链表时无需预先知道数据总量,可以随机分配空间,可以高效地在链表中的任意位置实时插入或删除数据。链表的开销主要是访问的顺序性和组织链的空间损失。

 

通常链表包括两个域:数据域和指针域,数据域用于存储数据,指针域用于建立与下一个节点的联系。链表一般可以分为单链表、双链表、循环链表等多种类型。

 

1、单链表

 

 

 

单链表是最简单的链表结构,它的结点包括两个域:数据域用来存储结点的数据信息;指针域用来存储数据元素的直接后继地址,即用来建立与下一个结点的联系。一般对单链表的遍历只能从头至尾依次顺序进行。

 

2、双链表

 

 

通过设计前驱和后继两个指针域,双链表可以从两个方向遍历,这是它区别于单链表的地方。如果打乱前驱、后继的依赖关系,就可以构成"二叉树";如果再让首节点的前驱指向链表尾节点、尾节点的后继指向首节点(如图中虚线部分),就构成了循环链表;如果设计更多的指针域,就可以构成各种复杂的树状数据结构。

 

3、循环链表

循环链表的特点是尾节点的后继指向首节点。双向循环链表的特点是从任意一个节点出发,沿两个方向的任何一个,都能找到链表中的任意一个数据。如果去掉前驱指针,就是单循环链表

 

在Linux内核中使用了大量的链表结构来组织数据,包括设备列表以及各种功能模块中的数据组织。这些链表大多采用在include/linux/list.h实现的一个相当精彩的链表数据结构。 

 

二、Linux内核链表分析

 

内核链表结构体的定义以及操作都定义在linux/list.h文件中

 

1、链表结构体的定义

内核链表数据结构定义很简单:

 

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

 

  

list_head结构包含两个指向list_head结构的指针prev和next,由此可见,内核的链表具备双链表功能,实际上,通常它都组织成双循环链表。

和一般的双链表结构模型不同,这里的list_head没有数据域。在Linux内核链表中,不是在链表结构中包含数据,而是在数据结构中包含链表节点。因此该结构一般与其他数据类型构成新的数据结构使用。这就使内核链表具有通用性,避免了为每个数据项类型定义自己的链表的麻烦。

通常可以这样使用:

struct my_list
{
	ElemType data;
	struct list_head list;
};

 

  

2、声明和初始化 

 

Linux只定义了链表节点,并没有专门定义链表头,那么一个链表结构是如何建立起来的呢?让我们来看看LIST_HEAD()这个宏:

#define LIST_HEAD_INIT(name) { &(name), &(name) }

#define LIST_HEAD(name) \
	struct list_head name = LIST_HEAD_INIT(name)

 

当我们用LIST_HEAD(head)声明一个名为head的链表头时,它的next、prev指针都初始化为指向自己,这样,我们就有了一个空链表,因为Linux用头指针的next是否指向自己来判断链表是否为空:

static inline int list_empty(const struct list_head *head)
{
	return head->next == head;
}

 

 LIST_HEAD(name)宏的作用就使是定义与初始化。

除了用LIST_HEAD()宏在声明的时候初始化一个链表以外,Linux还提供了一个INIT_LIST_HEAD(struct list_head *list)函数用于运行时初始化链表:

static inline void INIT_LIST_HEAD(struct list_head *list)
{
	list->next = list;
	list->prev = list;
}

 

 

例如:定义并初始化一个链表,该链表将连接struct my_list类型结点 LIST_HEAD(head) 创建一个头指针

 

3、插入、删除、合并

a)插入

对链表的插入操作有两种:在表头插入和在表尾插入。Linux为此提供了两个接口:

static inline void list_add(struct list_head *new, struct list_head *head);
static inline void list_add_tail(struct list_head *new, struct list_head *head);

 

因为内核链表是循环表,且表头的next、prev分别指向链表中的第一个和最末一个节点,所以,list_add和list_add_tail的区别并不大,实际上上述两个函数均调用下面的这个函数:

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;
}

  

该函数的作用是将new结点插入到prev和next之间。链表插入函数list_add和list_add_tail均调用这个函数来实现结点的插入操作,只不过参数不同而已。这也是内核链表实现的巧妙之处。

static inline void list_add(struct list_head *new, struct list_head *head)
{
	__list_add(new, head, head->next);
}


static inline void list_add_tail(struct list_head *new, struct list_head *head)
{
	__list_add(new, head->prev, head);
}

 

 

 

 

 可见,在表头插入是插入在head之后,而在表尾插入是插入在head->prev之后。

例如:

struct my_list newInfo;

list_add_tail(&newInfo.list, &head);

 

b)删除

链表结点的删除也很简单

static inline void list_del(struct list_head *entry)
{
	__list_del(entry->prev, entry->next);
	entry->next = LIST_POISON1;
	entry->prev = LIST_POISON2;
}

static inline void list_del_init(struct list_head *entry)
{
	__list_del(entry->prev, entry->next);
	INIT_LIST_HEAD(entry);
}

static inline void __list_del(struct list_head * prev, struct list_head * next)
{
	next->prev = prev;
	prev->next = next;
}

 

 

 

 

 当要删除newInfo项时,可以这么做:

list_del(&newInfo.list,&head);

 

 

 

 

被剔除下来的newInfo.list,prev、next指针分别被设为LIST_POSITION2和LIST_POSITION1两个特殊值,这样设置是为了保证不在链表中的节点项不可访问--对LIST_POSITION1和LIST_POSITION2的访问都将引起页故障。与之相对应,list_del_init()函数将节点从链表中解下来之后,调用LIST_INIT_HEAD()将节点置为空链状态。
 

c)移动元素

Linux提供了将原本属于一个链表的节点移动到另一个链表的操作,并根据插入到新链表的位置分为两类

static inline void list_move(struct list_head *list, struct list_head *head)
{
	__list_del(list->prev, list->next);
	list_add(list, head);
}

static inline void list_move_tail(struct list_head *list,
				  struct list_head *head)
{
	__list_del(list->prev, list->next);
	list_add_tail(list, head);
}

 

 

 

 

例如: list_move(&newInfo.list,&head)会把newInfo从所在链表上删除,再插入表头。

 

d)替换

static inline void list_replace(struct list_head *old,
				struct list_head *new)
{
	new->next = old->next;
	new->next->prev = new;
	new->prev = old->prev;
	new->prev->next = new;
}

static inline void list_replace_init(struct list_head *old,
					struct list_head *new)
{
	list_replace(old, new);
	INIT_LIST_HEAD(old);
}

 

 

 即用新结点替换旧结点,很容易理解。

 

e)合并

除了针对节点的插入、删除操作,内核链表还提供了整个链表的插入功能

先来看看合并的基本函数:

 

 

static inline void __list_splice(const struct list_head *list,
				 struct list_head *prev,
				 struct list_head *next)
{
	struct list_head *first = list->next;
	struct list_head *last = list->prev;

	first->prev = prev;
	prev->next = first;

	last->next = next;
	next->prev = last;
}

 

 

 

 

这段代码很容易理解,就是将list链表插入到prev和next之间(不包括头结点)。

 

其他合并函数都是调用这个基本函数,例如:

 

static inline void list_splice(const struct list_head *list,
				struct list_head *head)
{
	if (!list_empty(list))
		__list_splice(list, head, head->next);
}

  

 

假设当前有两个链表,表头分别是list1和list2(都是struct list_head变量),当调用list_splice(&list1,&list2)时,只要list1非空,list1链表的内容将被挂接在list2链表上,位于list2和list2.next(原list2表的第一个节点)之间。新list2链表将以原list1表的第一个节点为首节点,而尾节点不变。如图(虚箭头为next指针):


 

 

 当list1被挂接到list2之后,作为原表头指针的list1的next、prev仍然指向原来的节点,为了避免引起混乱,Linux提供了一个list_splice_init()函数。

 

 

 

  • 大小: 2.6 KB
  • 大小: 2.5 KB
  • 大小: 2.6 KB
分享到:
评论

相关推荐

    linux内核链表介绍与了解

    Linux内核链表是操作系统核心中一种重要的数据结构,它被广泛用于组织和管理系统中的各种数据,如设备列表、功能模块数据等。相比于数组,链表提供了更灵活的动态特性,无需预先知道数据总量即可创建,且能在任意...

    linux内核链表源代码

    linux内核链表源代码,是list.h是链表的实现,有兴趣的朋友可以下载分析。

    抽取linux内核链表模块

    标题中的"抽取linux内核链表模块"涉及到的是从Linux内核源码中提取出链表(list)的实现,以便在用户空间的Linux应用程序中复用这些功能。这一过程有助于理解链表的内部工作原理,并能在自己的代码中有效地利用内核...

    linux内核链表实现

    linux内核链表的实现,包括内核链表的定义,以及内核链表相关的操作

    Linux内核链表移植和测试代码

    本知识点将深入探讨Linux内核链表的移植和测试,以及`list.c`和`list.h`这两个文件的作用。 Linux内核中的链表API主要集中在`<linux/list.h>`头文件中,但在提供的`list.h`文件中,我们可以推测它是对原生内核链表...

    Linux内核链表测试

    在"Linux内核链表测试"这个主题中,我们将深入探讨如何在用户态下移植Linux内核链表到VC6.0编译环境中进行测试。首先,我们需要了解Linux内核链表的基本结构和操作,然后讨论用户态移植的过程,最后,通过分析给定的...

    linux内核链表提取与使用

    将linux内核源码中list.h拿出来, 增删与遍历部分写了详细注释, 关于链表合并, 没用过所以没写. 源码版本是2.6.32, 不过链表的源码改动应该不是很大. 我的邮箱2253238252@qq.com, 代码有什么不对的欢迎发邮件给我

    linux内核链表与测试代码

    Linux内核链表是操作系统内核中非常基础且重要的数据结构之一,用于高效地组织和管理内存中的数据。本文将深入探讨Linux内核链表的特点、实现方式以及它与普通链表的区别,并通过测试代码来加深理解。 首先,我们要...

    【最强悍的链表】Linux 内核链表源码

    本篇文章将深入探讨Linux内核链表的源码,解析其设计原理和主要功能。 链表的基本概念是每个元素(节点)包含数据以及指向下一个节点的指针。与数组不同,链表不需要连续的内存空间,这使得它们在动态分配和插入/...

    深入分析Linux内核链表(超详细)

    Linux内核链表是Linux内核中用于数据结构管理的基本工具之一,它被广泛地应用于内核的各个角落,从进程管理到网络通信等。链表作为一种数据结构,在计算机科学中非常常见,它能够有效地解决元素动态存储和管理的问题...

    基于Linux的内核链表源代码

    一、linux内核链表 1、普通链表的数据区域的局限性 之前定义数据区域时直接int data,我们认为我们的链表中需要存储的是一个int类型的数。但是实际上现实编程中链接中的节点不可能这么简单,而是多种多样的。 一般...

    Linux内核链表及其在虚拟文件系统中的应用.pdf

    "Linux内核链表及其在虚拟文件系统中的应用" Linux内核链表是一种抽象的双向循环链表结构,用于提高代码的重用性。这种链表可以将不同结构体类型的数据链接起来,并可以使 用相同的链表操作,从而能够有效地提高...

    linux内核链表 让你熟悉内核

    剖析linux 内核 linux内核链表

    linux 内核链表在用户态的应用

    Linux内核链表是Linux操作系统内核中一种高效的数据结构,用于组织和管理内存中的数据元素。它们在内核中广泛使用,以实现诸如进程管理、I/O调度、网络协议栈等多种功能。虽然它们主要在内核空间使用,但在特定情况...

    用linux内核链表修改韦东山的MP3程序

    在这个项目中,“用Linux内核链表修改韦东山的MP3程序”,我们可以理解为将原有的MP3播放程序的链表实现替换为Linux内核级别的链表,以提高程序的性能和适应性,特别是对于基于友善之臂ARM9平台的嵌入式系统。...

    linux内核链表测试用例

    这个“linux内核链表测试用例”是一个演示如何在实际编程中应用这些链表的实例。下面我们将深入探讨Linux内核链表的基本概念、结构、操作以及测试用例的重要性。 **Linux内核链表基本概念** Linux内核链表(list_...

    linux内核链表(一套精彩的链表数据结构)

    Linux内核链表是操作系统核心中一种至关重要的数据结构,用于高效地组织和管理内存中的数据。在Linux内核源代码中,链表的实现主要集中在`include/linux/list.h`这个头文件中,提供了一套强大且灵活的链表操作接口。...

    Linux内核链表分析.pdf

    Linux内核链表是操作系统开发中的重要数据结构,它在Linux内核中广泛用于组织和管理内存中的数据。本文主要探讨了Linux内核链表的设计思想、组织方式以及遍历方法,尤其强调了其内存效率和访问速度的优化。 首先,...

    Linux内核链表(移植完成)

    在本项目中,我们看到一个名为"Linux内核链表(移植完成)"的实现,这表明已经有人将Linux内核中的链表数据结构移植到了用户空间,使其可以应用于应用程序软件。 Linux内核链表的设计具有高度的灵活性和效率。它的...

Global site tag (gtag.js) - Google Analytics