1、Linux学习分析listh 之 函数部分Linux学习-分析list.h 之 函数部分 分类: Linux C 2010-08-31 09:57 1073人阅读 评论(3) 收藏 举报 在Linux中,最常见也是最经典的数据结构就是其中的双向链表,而对双向链表的各种操作都存储在list.h头文件中,最近仔细看了一下这个头文件,把我对它的理解记录下来,算是一个学习笔记吧。我看的是2.6.35.4版本的内核源代码,list.h在include/linux下存放,相对于一些其他的内核原文件来说,list.h算是比较小的了,而且只有700多行,主要是因为这个文件算是一个纯C文件,就是它是单纯的用C语
2、言来对双向链表进行操作,看起来也比较容易。 在list.h中,定义了如下一个数据结构:1. structlist_head2. structlist_head*next,*prev;3. ;这是一个双向链表,但是不存储数据,只是做一个相互连接的作用,定义了一个指向后一元素的指针和指向前一元素的指针。定义这样一个没有存储数据的数据结构,我觉得好处是可以广泛嵌套使用,把它当做一座桥,只是连接,然后再配合上其他变量来存储数据,一起形成一个结构体,这样后面对其操作就可以通用了,不必换一种数据就换一堆操作了。 对于双向链表的初始化,list.h中给出了两个方法,一个是宏定义的方法,另一个是函数的方法:1
3、. #defineLIST_HEAD_INIT(name)&(name),&(name) 2. #defineLIST_HEAD(name)/ 3. structlist_headname=LIST_HEAD_INIT(name)4. staticinlinevoidINIT_LIST_HEAD(structlist_head*list)5. 6. list-next=list;7. list-prev=list;8. 前两个宏定义要结合起来看,第二个宏定义是一个结构体的赋初值,再看第一个宏定义,就是说把name这个list_head类型的结构体的第一个元素赋值为name的地址,第二个元素也赋
4、值为name的地址,再结合上面的结构体的定义,就是说把name-next和name-prev都赋值为name自己,也就是指向自身,这样就是初始化好了一个双向链表的结点。而第二种用函数的方法就是直接把我上面的分析转换成代码的形式了,意思和功能是一样的。 对于链表的操作不过是“增删改查”几种,双向链表也不例外。 一、在进行增加操作时,list.h中的代码如下:1. staticinlinevoid_list_add(structlist_head*new,2. structlist_head*prev,3. structlist_head*next)4. 5. next-prev=new;6. n
5、ew-next=next;7. new-prev=prev;8. prev-next=new;9. 10. staticinlinevoidlist_add(structlist_head*new,structlist_head*head)11. 12. _list_add(new,head,head-next);13. 14. staticinlinevoidlist_add_tail(structlist_head*new,structlist_head*head)15. 16. _list_add(new,head-prev,head);17. 总共定义了三个函数,大致一看,后两个是调用
6、了第一个函数。对于第一个函数,首先分析它的参数是什么,第一个参数new,是一个类型为list_head的指针,指向要增加的结点,而prev和next也都是list_head类型的指针,顾名思义,就是指向增加的位置的前面和后面的结点,在函数内部,首先是把要增加位置的后面的结点的prev元素赋值为new,再把new的next元素指向后面结点,意思是先把后面的结点与新增结点相连;然后是把new的prev元素指向前面结点,再把前面结点的next赋值为new,意思是把前面的结点与新增结点相连。对于第二个函数,分析其调用_list_add()是的实参,第二个为head,第三个为head-next,也就是说
7、要把新增结点加到head之后,head-next之前,也就是增加一个结点到头结点后,类似于单链表的头插法。对于第三个函数,它调用_list_add()时,第二个参数为head-prev,第三个为head,就是要把新增结点加到head-prev之后,head之前,对于双向链表来说,head-prev正是链表的最后一个结点,所以这个函数的功能就是在链表的最后增加新结点,类似于单链表的尾插法。 二、对于删除操作,list.h的代码如下:1. staticinlinevoid_list_del(structlist_head*prev,structlist_head*next)2. 3. next-p
8、rev=prev;4. prev-next=next;5. 6. staticinlinevoidlist_del(structlist_head*entry)7. 8. _list_del(entry-prev,entry-next);9. entry-next=LIST_POISON1;10. entry-prev=LIST_POISON2;11. 12. staticinlinevoidlist_del_init(structlist_head*entry)13. 14. _list_del(entry-prev,entry-next);15. INIT_LIST_HEAD(entry)
9、;16. 同样也是定义了三个函数,而后两个函数也是调用了第一个函数。先看第一个函数,简单地说一下,就是把要删除的这个结点后面的结点的prev指向要删除结点的前面的结点,在把要删除的这个结点的前面的结点的next指向要删除的后面的那个结点,说的比较复杂,但是理解起来应该不难,简而言之,就是把要删除的这个的前后相连,就把它自己分离出来了。但是光把前后结点相连还没有彻底完成删除,还要对待删除结点进行一些操作,后面的两个函数的不同点就是处理这个待删除节点的操作时不一样的。先解释第三个函数的操作,INIT_LIST_HEAD(entry),就是前面说到过的那个初始化函数,作用是把entry的prev和n
10、ext都指向它自身,就不会让它们乱指,也是一种安全的删除方式。再看看第二个函数它是分别将next和prev赋值为LIST_POISON1和LIST_POISON2,这两个是宏定义的常量,在include/linux/poison.h中定义的,源代码如下:1. /*2. *Thesearenon-NULLpointersthatwillresultinpagefaults3. *undernormalcircumstances,usedtoverifythatnobodyuses4. *non-initializedlistentries.5. */6. #defineLIST_POISON1(
11、void*)0x00100100+POISON_POINTER_DELTA) 7. #defineLIST_POISON2(void*)0x00200200+POISON_POINTER_DELTA)大概意思就是一个常人基本不能用的一个地址,就相当于把prev和next屏蔽掉,不能通过它在切入到链表中,但是它属于不安全的方式。 三、对于修改操作,在list.h中是用替换的方式来的,源代码如下:1. staticinlinevoidlist_replace(structlist_head*old,2. structlist_head*new)3. 4. new-next=old-next;5.
12、new-next-prev=new;6. new-prev=old-prev;7. new-prev-next=new;8. 9. staticinlinevoidlist_replace_init(structlist_head*old,10. structlist_head*new)11. 12. list_replace(old,new);13. INIT_LIST_HEAD(old);14. 修改比较简单,就是把要修改的那个结点的prev和next赋值给新的结点,然后把原来前后的结点对应的next和prev再指向新结点,这就是第一个函数的作用,就是用来修改指向的,而对于修改过的那个结点
13、要做类似于删除的操作,在第二个函数中体现,与上文提到的删除一样,使用INIT_LIST_HEAD()函数,把原来的结点的prev和next都指向自己,安全删除。 四、对于移动操作,在list.h中的定义如下:1. staticinlinevoidlist_move(structlist_head*list,structlist_head*head)2. 3. _list_del(list-prev,list-next);4. list_add(list,head);5. 6. staticinlinevoidlist_move_tail(structlist_head*list,7. structlist_head*head)8. 9. _list_del(list-prev,list-next);10. list_add_tail(list,head);11. 定义了两个函数,但是函数体中的东西都是很眼熟的,我就不多说了,宏观上说一下,第一个函数是把一个结点移动到头结点之后,使用的方法就是把待移动结点从原来位置分离出来(使用_list_del函数),然后在把它增加到头结点后(使用list_add函数);第二个函数是把一个结点移动到最后,使用的方法也是先把待移动结点从原
copyright@ 2008-2022 冰豆网网站版权所有
经营许可证编号:鄂ICP备2022015515号-1