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

Linux内核链表(二)

阅读更多

4)遍历

遍历是链表最经常的操作之一,为了方便核心应用遍历链表,Linux链表将遍历操作抽象成几个宏。

在介绍遍历宏之前,我们先看看如何从链表中访问到我们真正需要的数据项:

a)由链表结点到数据项变量

由上面的分析可知,内核链表中仅保存了数据项结构中list_head成员变量的地址,那么我们如何通过这个list_head成员访问到作为它的所有者的节点数据呢?在list.h文件中有一个list_entry(ptr,type,member)宏,其中ptr是指向该数据中list_head成员的指针,也就是存储在链表中的地址值,type是数据项的类型,member则是数据项类型定义中list_head成员的变量名。

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

 

该宏的作用是通过ptr指针获得类型为type的属主结点的地址,从而访问type结构的数据成员。该宏最终调用container_of(ptr,type,member)宏来实现,相比之下container_of(ptr,type,member)就比较复杂一点了。

//from include/linux/kernel.h
#define container_of(ptr, type, member) ({          \
            const typeof( ((type *)0)->member ) *__mptr = (ptr);    \
            (type *)( (char *)__mptr - offsetof(type,member) );})

//from include/linux/stddef.h
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)

size_t最终定义为unsigned int(i386)。

这里使用的是一个利用编译器技术的小技巧,即先求得结构成员在与结构中的偏移量,然后根据成员变量的地址反过来得出属主结构变量的地址。

 

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

首先将0转化成type类型的指针变量(这个指针变量的地址为0×0),然后再引用member成员(对应就是((type *)0)->member ))。

注意这里的typeof(x)(住:typeof()是gcc的扩展,和sizeof()类似),是返回x的数据类型,那么 typeof( ((type *)0)->member )其实就是返回member成员的数据类型。那么这条语句整体就是将__mptr强制转换成member成员的数据类型,再将ptr的赋给它。

 

第二:在offsetof()中,这个member成员的地址实际上就是type数据结构中member成员相对于结构变量的偏移量。

((TYPE *)0)->MEMBER)这个其实就是提取type类型中的member成员,那么&((TYPE *)0)->MEMBER)得到member成员的地址,再强制转换成size_t类型(unsigned int)。但是这个地址很特别,因为TYPE类型是从0×0开始定义的,那么我们现在得到的这个地址就是member成员在TYPE数据类型中的偏移量

 

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

结果就是属主结点的起始地址。

不过这里要注意__mptr被强制转换成了(char *),为何要这么做?因为如果member是非char型的变量,比如为int型,并且假设返回值为offset,那么这样直接减去偏移量,实际上__mptr会减去sizeof(int)*offset!这一点和指针加一减一的原理相同

 

b)遍历宏

上面分析清楚了,链表的遍历就很简单了

#define __list_for_each(pos, head) \
	for (pos = (head)->next; pos != (head); pos = pos->next)

#define list_for_each(pos, head) \
	for (pos = (head)->next; prefetch(pos->next), pos != (head); \
        	pos = pos->next)

 

它实际上是一个for循环,利用传入的pos作为循环变量,从表头head开始,逐项向后(next方向)移动pos,直至又回到head(prefetch()可以不考虑,用于预取以提高遍历速度)

大多数情况下,遍历链表的时候都需要获得链表节点数据项,也就是说list_for_each()和list_entry()总是同时使用。对此Linux给出了一个list_for_each_entry()宏:

#define list_for_each_entry(pos, head, member)				\
	for (pos = list_entry((head)->next, typeof(*pos), member);	\
	     prefetch(pos->member.next), &pos->member != (head); 	\
	     pos = list_entry(pos->member.next, typeof(*pos), member))

 与list_for_each()不同,这里的pos是数据项结构指针类型,而不是(struct list_head *)。利用这个宏可以使链表的遍历更简单。

 

某些应用需要反向遍历链表,Linux提供了list_for_each_prev()和list_for_each_entry_reverse()来完成这一操作,使用方法和上面介绍的list_for_each()、list_for_each_entry()完全相同。

如果遍历不是从链表头开始,而是从已知的某个节点pos开始,则可以使用list_for_each_entry_continue(pos,head,member)。有时还会出现这种需求,即经过一系列计算后,如果pos有值,则从pos开始遍历,如果没有,则从链表头开始,为此,Linux专门提供了一个list_prepare_entry(pos,head,member)宏,将它的返回值作为list_for_each_entry_continue()的pos参数,就可以满足这一要求。

诸如此类的宏很多,具体可以查看list.h文件

 

c)安全性考虑

 

前面介绍了用于链表遍历的几个宏,它们都是通过移动pos指针来达到遍历的目的。但如果遍历的操作中包含删除pos指针所指向的节点,pos指针的移动就会被中断,因为list_del(pos)将把pos的next、prev置成LIST_POSITION2和LIST_POSITION1的特殊值。

当然,调用者完全可以自己缓存next指针使遍历操作能够连贯起来,但为了编程的一致性,Linux链表仍然提供了两个对应于基本遍历操作的"_safe"接口:list_for_each_safe(pos, n, head)、list_for_each_entry_safe(pos, n, head, member),它们要求调用者另外提供一个与pos同类型的指针n,在for循环中暂存pos下一个节点的地址,避免因pos节点被释放而造成的断链。

#define list_for_each_safe(pos, n, head) \
	for (pos = (head)->next, n = pos->next; pos != (head); \
		pos = n, n = pos->next)

 


基本上list.h文件大部分内容就是这些,其他的关于hlist等内容暂不分析。

当然内核链表并不仅限于在内核使用,可以通过改写list.h文件使之可以应用在普通用户程序中。

 

参考资料:

IBM developerWorks:http://www.ibm.com/developerworks/cn/linux/kernel/l-chain/index.html  深入分析linux内核链表

 

 

 

分享到:
评论

相关推荐

    linux内核链表源代码

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

    linux内核链表介绍与了解

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

    linux内核链表提取与使用

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

    Linux内核链表测试

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

    linux内核链表实现

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

    抽取linux内核链表模块

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

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

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

    linux内核链表与测试代码

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

    基于Linux的内核链表源代码

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

    Linux内核双向链表简单分析

    ### Linux内核双向链表简单分析 #### 链表数据结构简介 链表作为一种基本且重要的数据结构,在操作系统及各种软件系统中扮演着至关重要的角色。尤其在Linux内核中,链表更是广泛应用于内存管理、进程调度、文件...

    linux内核链表 让你熟悉内核

    剖析linux 内核 linux内核链表

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

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

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

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

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

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

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

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

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

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

    linux内核链表测试用例

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

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

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

    Linux内核链表分析.pdf

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

Global site tag (gtag.js) - Google Analytics