[原理分析]linux内核中的链表原理实践[2]

摘要:

本文过程化的演进方式,将自己写的链表结构慢慢地演化到类似linux内核链表的实现。

正文:

在本系列1中,如果将data_node中的信息调换一下,也即value放在前面,将head_node信息放在后面,那么节点数据就不能正常输出。

typedef struct data_node{
	int value;
	head_node h;
}data_node;

要查找原因,主要还是看list_value函数的实现:

void list_value(data_node* d)
{
   data_node* dn = d;
   do{
     printf("%d ", dn->value);
     dn = (data_node*)dn->h.next;
   }while(dn!=d);
}

上述代码是将dn->h.next的指针强制转化为data_node的指针完成data_node首地址的确定,现在data_node内部的数据调换顺序后,head_node的首地址不再是data_node的首地址,所以出错。此处的解决方法也很直接:通过head_node的地址反向求得data_node的地址,基本思路:将head_node的地址,减去其在data_node中的地址偏移,也就是data_node的地址了。按照此思路将list_value修改后的代码如下:

void show_value(head_node* ptr)
{
	head_node* temp = ptr;
	unsigned long offset =(unsigned long)&((data_node*)0)->h;
	data_node* dn = (data_node*)((unsigned long)ptr  - offset);

	do{
		printf("%d ", dn->value);
		ptr = dn->h.next;
		unsigned long offset =(unsigned long)&((data_node*)0)->h;
		dn = (data_node*)((unsigned long)ptr  - offset);
	}while(&(dn->h) != temp);
}

求偏移时,采用下面的语句:(unsinged long) &((data_node*)0)->h;这里的求地址符号不能缺少,因为后面部分只是偏移到h,具体偏移的量还是也就是h所处的地址,因为地址是从0开始的;data_node的首地址就通过head_node的首地址,减去其在data_node中的偏移得到。上述的代码不清晰,我们将重复的部分写成宏的形式,那就有:

#define off()  (unsigned long)  (&((data_node*)0)->h)
#define  entry(ptr)  (data_node*) ((unsigned long)ptr  - off())

然后利用上述的宏再重写show_value函数:

void show_value(head_node* ptr){
	head_node* temp = ptr;
	data_node* dn = entry(ptr);
	do{
		printf("%d ", dn->value);
		ptr = dn->h.next;
		dn = entry(ptr);
	}while(&(dn->h) != temp);
}

看上去精简不少,我们再把对数据处理的函数写成单独的模块:

void showme(daat_node* dn)
{
    printf("%d ", dn->value);
}

回头看上述的代码,还是不能满足我们模块化的需要,上述代码中的循环被数据处理模块割裂,不能很好地模块化,我们希望看到的代码是下面的这个样子,将循环部分的代码尽量集中在括号外面,带来的好处:循环部分代码复用性提升,修改处理函数变得容易;

data_node* dn;
循环代码部分{
     showme(dn);
}

考察循环的常用三种写法:for, while, do while;do while已经被证明会被数据处理函数隔离,while的话,判断语句和操作语句的分离;最终只能考虑用for来实现我们上面的设想,将上述的show_value改成基于for循环的形式:

data_node* dn;
for(head_node* temp = ptr, dn = entry(ptr); &(dn->h)!=temp; ptr = dn->h.next, dn = entry(ptr))
{
	show_value(dn);
}

这样的修改方式在vs 2005上编译出现问题,只要将 head_node* temp的定义放在循环外面进行即可。于是代码变成如下的形式:

void show_value(head_node* ptr){
	data_node* dn = NULL;
	head_node* temp;

	for(temp = ptr, dn = entry(ptr); &(dn->h)!=temp; ptr = dn->h.next, dn = entry(ptr)){
		printf("%d ", dn->value);
	}
}

运行上述的代码后发现并没有输出,仔细检查下主要在于语句:&(dn->h)!=temp;该语句在循环第一次执行时就不满足条件,因为temp当前的值就是dn->h的当前的地址,也就是说temp和dn属于同一个数据节点。当然在初始化时,可以让dn指向下一个ptr的下一个节点,但这样的问题在于不能输出ptr当前节点的数据,造成数据漏输。这就是for循环带来的问题,在原来的do while版本中,由于是先输出数据,更新ptr后,再做的判断,所以不会存在上述的问题。那此处怎么修改呢?简单想到的一个解决方法就是跳过第一个节点,第一个节点中不存数据,只是将第一个节点作为标志节点,也就是头节点。增加头节点的代码修改:

	head_node* lh = (head_node*)malloc(sizeof(head_node));
	lh->next = lh;
	lh->prev = lh;

	node_add(lh, &(d1->h));
	node_add(lh, &(d2->h));
	node_add(lh, &(d3->h));

相应的show_value函数也要做对应修改:

void show_value(head_node* ptr){
	data_node* dn = NULL;
	head_node* temp;

	for(temp = ptr, dn = entry(ptr->next); &(dn->h)!=temp; ptr = dn->h.next, dn = entry(ptr)){
		showme(dn);
	}
}

调用端的代码也要做对应修改:show_value(lh);经过测试,上述三个节点中的数据都能输出,但是看上去show_vlaue中的代码还是不够清晰,我们再打磨下:

void show_value(head_node* head){
	data_node* dn = NULL;
	head_node* pos;

	for(pos = head->next, dn = entry(pos); pos!=head; pos = pos->next, dn = entry(pos)){
		showme(dn);
	}
}

上面的代码可以看到,for循环的操作和判断都移到大括号外面,括号里面只剩下操作函数,然后我们再将上述的for循环部分代码宏化:

#define list_each_entry(head, pos)   for(pos = head->next, dn = entry(pos) ; 						pos!=head; pos=pos->next, dn = entry(pos))

基于上述的宏,我们再修改show_value函数:

void show_value(head_node* head)
{
	data_node* dn = NULL;
	head_node* pos;

	list_each_entry(head, pos){
		showme(dn);
	}
}

结束语:

本文以linux内核代码中的链表实现为灯塔,采用自然演化的过程,慢慢根据自己的需求,将自己的代码修改成类linux实现的代码。

时间: 2024-10-02 21:37:20

[原理分析]linux内核中的链表原理实践[2]的相关文章

[原理分析]linux内核中的链表原理实践

摘要: 本文根据linux内核的链表定义,尝试自己来完成相关的接口设计,并且构建测试用例,来体会下链表接口的用法. 正文: 首先来看下linux内核提供的链表接口大致如下: struct head_node{ struct head_node* pre; struct head_node* next; }; 即节点中只有两个指针:一个指向前一个元素,一个指向后一个元素:假设我们现在要使用该接口,跟一个int值联合在一起形成数据节点,那么存在两种可能的方式: case1: typedef stru

[原理分析]linux内核中的链表原理实践[3]

摘要: 本文的第[一,二]系列主题虽然是链表操作,内容还是指针的操作,本文通过链表实例来阐述下指针操作.不仅仅涉及到数据节点指针,也还涉及到函数指针,最后还涉及基于指针的函数体优化. 正文: 本文主要阐述链表中的节点删除操作,并且在删除过程中使用回调函数.回调函数本身也很简单,就是判断当前节点中数据的奇性或者偶性,来判断是否删除该数据节点. #include "stdafx.h" #include <memory> typedef struct node{ int valu

Linux内核中namespace之PID namespace

前面看了LInux PCI设备初始化,看得有点晕,就转手整理下之前写的笔记,同时休息一下!!~(@^_^@)~ 这片文章是之前写的,其中参考了某些大牛们的博客!! PID框架的设计 一个框架的设计会考虑很多因素,相信分析过Linux内核的读者来说会发现,内核的大量数据结构被哈希表和链表链接起来,最最主要的目的就是在于查找.可想而知一个好的框架,应该要考虑到检索速度,还有考虑功能的划分.那么在PID框架中,需要考虑以下几个因素. 如何通过task_struct快速找到对应的pid 如何通过pid快

Linux内核中常见内存分配函数

1.原理说明 Linux内核中采用了一种同时适用于32位和64位系统的内存分页模型,对于32位系统来说,两级页表足够用了,而在x86_64系统中,用到了四级页表,如图2-1所示.四级页表分别为: l   页全局目录(Page Global Directory) l   页上级目录(Page Upper Directory) l   页中间目录(Page Middle Directory) l   页表(Page Table) 页全局目录包含若干页上级目录的地址,页上级目录又依次包含若干页中间目录

Linux内核中常见内存分配函数zz

https://blog.csdn.net/wzhwho/article/details/4996510 1.      原理说明 Linux内核中采用了一种同时适用于32位和64位系统的内存分页模型,对于32位系统来说,两级页表足够用了,而在x86_64系统中,用到了四级页表,如图2-1所示.四级页表分别为: l         页全局目录(Page Global Directory) l         页上级目录(Page Upper Directory) l         页中间目录(

Linux内核中的list用法和实现分析

这些天在思考知识体系的完整性,发现总是对消息队列的实现不满意,索性看看内核里面的链表实现形式,这篇文章就当做是学习的i笔记吧.. 内核代码中有很多的地方使用了list,而这个list的用法又跟我们平时在教科书中常见的用法有很大的不同,所以有必要详细了解下这里面的门道. 内核里面的list(如没有特殊说明,下文说的list都是指内核里面的list)可称之为侵入式链表.这种list最突出的特征就是其节点中不含有任何数据,相反,list节点是嵌入到特定的数据结构中的.大家自然就会问了,这样实现有什么好

linux内核中socket的创建过程源码分析(总结性质)

http://www.jianshu.com/p/5d82a685b5b6 在漫长地分析完socket的创建源码后,发现一片浆糊,所以特此总结,我的博客中同时有另外一篇详细的源码分析,内核版本为3.9,建议在阅读本文后若还有兴趣再去看另外一篇博文.绝对不要单独看另外一篇. 一:调用链: 二:数据结构 一一看一下每个数据结构的意义: 1) socket, sock, inet_sock, tcp_sock的关系创建完sk变量后,回到inet_create函数中: 这里是根据sk变量得到inet_s

Linux内核中的GPIO系统之(3):pin controller driver代码分析--devm_kzalloc使用【转】

转自:http://www.wowotech.net/linux_kenrel/pin-controller-driver.html 一.前言 对于一个嵌入式软件工程师,我们的软件模块经常和硬件打交道,pin control subsystem也不例外,被它驱动的硬件叫做pin controller(一般ARM soc的datasheet会把pin controller的内容放入GPIO controller的章节中),主要功能包括: (1)pin multiplexing.基于ARM core

linux 内核 中链表list

这个结构从list.h 移到了types.h, 可见内核对循环链表的重视 include/linux/types.h中定义 struct list_head {        struct list_head *next, *prev;}; include/linux/list.h 中的宏 初始化 一个叫name的链表节点 #define LIST_HEAD_INIT(name) { &(name), &(name) } #define LIST_HEAD(name) \        s