listhead小解析.docx
- 文档编号:25203146
- 上传时间:2023-06-06
- 格式:DOCX
- 页数:16
- 大小:21.72KB
listhead小解析.docx
《listhead小解析.docx》由会员分享,可在线阅读,更多相关《listhead小解析.docx(16页珍藏版)》请在冰豆网上搜索。
listhead小解析
list_head小解析
[Linux内核]postby陈俊生/2012-1-1317:
17Friday
List_head
下面的一些内容是我在学习linux内核中list.h文件中list_head时为了更好的理解而找的一些资料,我把它们整理一下发上来以供今后参考一下。
其中也有部分是我自己在学习中的体会。
下面的代码是我从list.h中复制过来的,是源码来的,没有改变,主要是为了大家能清楚的知道list.h的源码。
List_head这个结构体在list.h中的主要作用不是保存数据而是作为一个链表的一个节点来保存地址,为另一个结构体的数据能够获取作铺垫。
具体的情况看完了下面的一些资料就清晰明白了。
list.h头文件集中定义了双链表(structlist_head结构体)的相关操作。
比如这里的一个头文件中就有大量的structlist_head型的数据。
下面先介绍list_head的具体构成
Structlist_head{
Structlist_head*next;
Structlist_head*prev;
};
List_head中的两个成员分别为两个指针,这个两个指针分别是双向链表的两个不同指向的指针。
从这个结构体的形式可以看出它是不能存放数据的。
所以也只能用来作别人的链接。
下面介绍一下list.h各个函数的具体实现。
1.链表的初始化
其实可以从后往前看,这样更容易理解。
INIT_LIST_HEAD函数形成一个空链表。
这个list变量一般作为头指针(非头结点)。
staticinlinevoidINIT_LIST_HEAD(structlist_head*list)
{
list->next=list;
list->prev=list;
}
LIST_HEAD_INIT(name)将name的地址直接分别赋值给next和prev,那么它们事实上都指向自己,也形成一个空链表。
现在再回头看宏LIST_HEAD(name),它其实就是一个定义并初始化作用。
#defineLIST_HEAD_INIT(name){&(name),&(name)}
下面的宏生成一个头指针name,如何生成?
请看LIST_HEAD_INIT(name)。
#defineLIST_HEAD(name)\
structlist_headname=LIST_HEAD_INIT(name)
3.添加元素
这两个函数分别给链表头结点后,头结点前添加元素。
前者可实现栈的添加元素,后者可实现队列的添加元素。
staticinlinevoidlist_add(structlist_head*new,structlist_head*head);
staticinlinevoidlist_add_tail(structlist_head*new,structlist_head*head);
这两个函数如何实现的?
它们均调用的下面函数:
这种调用的情况在list.h文件中是很常见的。
staticinlinevoid__list_add(structlist_head*new,structlist_head*prev,structlist_head*next)
{
next->prev=new;
new->next=next;
new->prev=prev;
prev->next=new;
}
现在我们要关注的是,list_add和list_add_tail两函数在调用__list_add函数时,对应的各个参数分别是什么?
通过下面所列代码,我们可以发现这里的参数运用的很巧妙,类似JAVA中的封装。
staticinlinevoidlist_add(structlist_head*new,structlist_head*head)
{
__list_add(new,head,head->next);
}
staticinlinevoidlist_add_tail(structlist_head*new,structlist_head*head)
{
__list_add(new,head->prev,head);
}
注意,这里的形参prev和next是两个连续的结点。
这其实是数据结构中很普通的双链表元素添加问题,在此不再赘述。
下面的图可供参考,图中1~4分别对应__list_add函数的四条语句。
--[if!
vml]-->
--[endif]-->
3.删除元素
这里又是一个调用关系,__list_del函数具体的过程很简单,分别让entry节点的前后两个结点(prev和next)“越级”指向彼此。
请注意这个函数的后两句话,它属于不安全的删除。
其中LIST_POISON1;LIST_POISON2;是两个宏来的他们的定义不在list.h中,
具体的定义:
#defineLIST_POISON1 ((void*)0x00100100+POISON_POINTER_DELTA)
#defineLIST_POISON2 ((void*)0x00200200+POISON_POINTER_DELTA)
staticinlinevoidlist_del(structlist_head*entry)
{
__list_del(entry->prev,entry->next);
entry->next=LIST_POISON1;
entry->prev=LIST_POISON2;
}
想要安全的删除,那么可以调用下面函数。
还记得INIT_LIST_HEAD(entry)吗,它可以使entry节点的两个指针指向自己。
为什么为安全看了下面的代码就知道了。
staticinlinevoidlist_del_init(structlist_head*entry)
{
__list_del_entry(entry);
INIT_LIST_HEAD(entry);
}
4.替换元素
用new结点替换old结点同样很简单,几乎是在old->prev和old->next两结点之间插入一个new结点。
画图即可理解。
staticinlinevoidlist_replace(structlist_head*old,
structlist_head*new)
{
new->next=old->next;
new->next->prev=new;
new->prev=old->prev;
new->prev->next=new;
}
下面是它的另一种版本,也可以说是安全版本吧,具体的使用看个人的喜好。
staticinlinevoidlist_replace_init(structlist_head*old,//初始化并代替
structlist_head*new)
{
list_replace(old,new);
INIT_LIST_HEAD(old);
}
5.移动元素
理解了删除和增加结点,那么将一个节点移动到链表中另一个位置,其实就很清晰了。
list_move函数最终调用的是__list_add(list,head,head->next),实现将list移动到头结点之后;而list_move_tail函数最终调用__list_add_tail(list,head->prev,head),实现将list节点移动到链表末尾。
staticinlinevoidlist_move(structlist_head*list,structlist_head*head)
{
__list_del_entry(list);//删除
list_add(list,head);//添加
}
staticinlinevoidlist_move(structlist_head*list,structlist_head*head)
{
__list_del_entry(list);//删除
list_add(list,head);//添加
}
6.测试函数
接下来的几个测试函数,基本上是“代码如其名”。
list_is_last函数是测试list是否为链表head的最后一个节点。
staticinlineintlist_is_last(conststructlist_head*list,
conststructlist_head*head)
{
returnlist->next==head;
}
下面的函数是测试head链表是否为空链表。
注意这个list_empty_careful函数,他比list_empty函数“仔细”在那里呢?
前者只是认为只要一个结点的next指针指向头指针就算为空,但是后者还要去检查头节点的prev指针是否也指向头结点。
另外,这种仔细也是有条件的,只有当其他cpu的链表操作只有list_del_init()时,否则仍然不能保证安全。
staticinlineintlist_empty(conststructlist_head*head)
{
returnhead->next==head;
}
staticinlineintlist_empty_careful(conststructlist_head*head)
{
structlist_head*next=head->next;
return(next==head)&&(next==head->prev);
}
下面的函数是测试head链表是否只有一个结点:
这个链表既不能是空而且head前后的两个结点都得是同一个结点。
staticinlineintlist_is_singular(conststructlist_head*head)
{
return!
list_empty(head)&&(head->next==head->prev);
}
7.将链表左转180度
正如注释说明的那样,此函数会将这个链表以head为转动点,左转180度。
整个过程就是将head后的结点不断的移动到head结点的最左端。
如果是单个结点那么返回真,否则假。
staticinlinevoidlist_rotate_left(structlist_head*head)
{
structlist_head*first;
if(!
list_empty(head)){
first=head->next;
list_move_tail(first,head);
}
}
上述函数每次都调用list_move_tail(first,head);其实我们将其分解到“最小”,那么这个函数每次最终调用的都是:
__list_del(first->prev,first->next);和__list_add(list,head->prev,head);这样看起来其实就一目了然了。
8.将链表一分为二
这个函数是将head后至entry之间(包括entry)的所有结点都“切开”,让他们成为一个以list为头结点的新链表。
我们先从宏观上看,如果head本身是一个空链表则失败;如果head是一个单结点链表而且entry所指的那个结点又不再这个链表中,也失败;当entry恰好就是头结点,那么直接初始化list,为什么?
因为按照刚才所说的切割规则,从head后到entry前事实上就是空结点。
如果上述条件都不符合,那么就可以放心的“切割”了。
staticinlinevoidlist_cut_position(structlist_head*list,
structlist_head*head,structlist_head*entry)
{
if(list_empty(head))
return;
if(list_is_singular(head)&&
(head->next!
=entry&&head!
=entry))
return;
if(entry==head)
INIT_LIST_HEAD(list);
else
__list_cut_position(list,head,entry);
}
具体如何切割,这里的代码貌似很麻烦,可是我们画出图后,就“一切尽在不言中”了。
staticinlinevoid__list_cut_position(structlist_head*list,
structlist_head*head,structlist_head*entry)
{
structlist_head*new_first=entry->next;
list->next=head->next;
list->next->prev=list;
list->prev=entry;
entry->next=list;
head->next=new_first;
new_first->prev=head;
}
图示:
--[if!
vml]-->
--[endif]-->
9.合并链表
既然我们可以切割链表,那么当然也可以合并了。
先看最基本的合并函数,就是将list这个链表(不包括头结点)插入到prev和next两结点之间。
这个代码阅读起来不困难,基本上是“见码知意”。
staticinlinevoid__list_splice(conststructlist_head*list,
structlist_head*prev,
structlist_head*next)//合并的实现
{
structlist_head*first=list->next;
structlist_head*last=list->prev;
first->prev=prev;
prev->next=first;
last->next=next;
next->prev=last;
}
理解了最基本的合并函数,那么将它封装起来,就可以形成下面两个函数了,分别在head链表的首部和尾部合并。
这里的调用过程类似增加,删除功能。
staticinlinevoidlist_splice(conststructlist_head*list,
structlist_head*head)//合并在前面
{
if(!
list_empty(list))
__list_splice(list,head,head->next);
}
staticinlinevoidlist_splice_tail(structlist_head*list,
structlist_head*head)//合并的位置不一样,这个是再后面
{
if(!
list_empty(list))
__list_splice(list,head->prev,head);
}
合并两个链表后,list还指向原链表,因此应该初始化。
在上述两函数末尾添加初始化语句INIT_LIST_HEAD(list);后,就安全了。
10.遍历
下面我们要分析链表的遍历。
虽然涉及到遍历的宏比较多,但是根据我们前面分析的那样,掌握好最基本的宏,其他宏就是进行“封装”。
遍历中的基本宏是:
#define__list_for_each(pos,head)\
for(pos=(head)->next;pos!
=(head);pos=pos->next)
head是整个链表的头指针,而pos则不停的往后移动。
但是你有没有觉得,这里有些奇怪?
因为我们在上篇文章中说过,structlist_head结构经常和其他数据组成新的结构体,那么现在我们只是不停的遍历新结构体中的指针,如何得到其他成员?
因此我们需要搞懂list_entry这个宏:
#definelist_entry(ptr,type,member)\
container_of(ptr,type,member)
这个宏是很重要的一个宏,他的具体实现就是container_of
下面是containerof的具体实现
这个宏的作用是通过ptr指针获取type结构的地址,也就是指向type的指针。
其中ptr是指向member成员的指针。
这个list_entry宏貌似很简单的样子,就是再调用container_of宏,可是当你看了container_of宏的定义后……
#definecontainer_of(ptr,type,member)({\
consttypeof(((type*)0)->member)*__mptr=(ptr);\
(type*)((char*)__mptr-offsetof(type,member));})
是不是让人有点抓狂?
别急,我们一点点来分析。
首先这个宏包含两条语句。
第一条:
consttypeof(((type*)0)->member)*__mptr=(ptr);首先将0转化成type类型的指针变量(这个指针变量的地址为0×0),然后再引用member成员(对应就是((type*)0)->member))。
注意这里的typeof(x),是返回x的数据类型,那么typeof(((type*)0)->member)其实就是返回member成员的数据类型。
那么这条语句整体就是将__mptr强制转换成member成员的数据类型,再将ptr的赋给它(ptr本身就是指向member的指针)。
第二句中,我们先了解offsetof是什么?
它也是一个宏被定义在:
linux/include/stddef.h中。
原型为:
#defineoffsetof(type,member)((size_t)&((type*)0)->member)
这个貌似也很抓狂,不过耐心耐心:
((TYPE*)0)->MEMBER)这个其实就是提取type类型中的member成员,那么&((TYPE*)0)->MEMBER)得到member成员的地址,再强制转换成size_t类型(unsignedint)。
但是这个地址很特别,因为TYPE类型是从0×0开始定义的,那么我们现在得到的这个地址就是member成员在TYPE数据类型中的偏移量。
我们再来看第二条语句,(type*)((char*)__mptr–offsetof(type,member))求的就是type的地址,即指向type的指针。
不过这里要注意__mptr被强制转换成了(char*),为何要这么做?
因为如果member是非char型的变量,比如为int型,并且假设返回值为offset,那么这样直接减去偏移量,实际上__mptr会减去sizeof(int)*offset!
这一点和指针加一减一的原理相同。
有了这个指针,那么就可以随意引用其内的成员了。
关于此宏的更具体了解,不妨亲自动手测试这里的程序。
好了,现在不用抓狂了,因为了解了list_entry宏,接下来的事情就很简单了。
下面这个宏会得到链表中第一个结点的地址。
#definelist_first_entry(ptr,type,member)\
list_entry((ptr)->next,type,member)
真正遍历的宏登场了,整个便利过程看起来很简单,可能你对prefetch()陌生,它的作用是预取节点,以提高速度。
#definelist_for_each(pos,head)\
for(pos=(head)->next;prefetch(pos->next),pos!
=(head);\
pos=pos->next)
我们再来看一开始我们举例的那个便利宏。
注意它和上述便利宏的区别就是没有prefetch(),因为这个宏适合比较少结点的链表。
#define__list_for_each(pos,head)\
for(pos=(head)->next;pos!
=(head);pos=pos->next)
下面两个宏是上述两个便利宏的安全版,我们看它安全在那里?
它多了一个与pos同类型的n,每次将下一个结点的指针暂存起来,防止pos被释放时引起的链表断裂。
#definelist_for_each_safe(pos,n,head)\
for(pos=(head)->next,n=pos->next;pos!
=(head);\
pos=n,n=pos->next)
#definelist_for_each_prev_safe(pos,n,head)\
for(pos=(head)->prev,n=pos->prev;\
prefetch(pos->prev),pos!
=(head);\
pos=n,n=pos-
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- listhead 解析