【威尼斯网址开户网站】Linux内核中的循环链表结构

随着Linux的发展,现在Linux越来越偏离以前的主题,越来越不符合它最初的含义,不过没有变得还是Linux内核。Linux内核的名字也是“Linux”。L本文讲解Linux内核循环链表结构
,希望对你学习Linux内核有所提高。

  注:文章中引用的代码来源于LXR,所分析的内核版本是v2.6.31。

文章中引用的代码来源于LXR,所分析的内核版本是v2.6.31。

一 。 Linux内核链表 

linux内核学习中– 史上最全 linux通用链表“list.h”详解

注:文章中引用的代码来源于LXR,所分析的内核版本是v2.6.31。

  linux内核通过定义list_head以及对于list_head上的一组操作实现对不同类型的循环链表的同类操作,这种做法避免了对于不同数据类型的循环链表定义重复的操作函数,使代码得到了充分的使用,是一种十分有效的编程方法。

linux内核通过定义list_head以及对于list_head上的一组操作实现对不同类型的循环链表的同类操作,这种做法避免了对于不同数据类型的循环链表定义重复的操作函数,使代码得到了充分的使用,是一种十分有效的编程方法。

   1 . 内核链表函数

Linux内核链表深度分析

威尼斯网址开户网站 1威尼斯网址开户网站 2

最近几天在学习linux内核,接触到“list.h”文件,学习了几天,在这里做一下总结。也在网上学习了很多前人的工作。好像大家的工作都比较零散,每个人都是仅仅解释了某几个函数。为了以后大家学习方便,,在这里我将所有的函数以及头文件通通解释下,算是比较全面的总结吧!。希望对大家今后的学习有用,也望大家对里面的错误和缺点指出。
下面,我开始了。 
第一段,我就不多解释了,大家应该能看懂,重点在后面了!!!

[html] view plain copy
#ifndef _LINUX_LIST_H  
#define _LINUX_LIST_H  

#define offsetof1(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)  

#define container_of(ptr, type, member) ( { \  
        const typeof( ((type *)0)->member ) *__mptr = (ptr); \  
        (type *)( (char *)__mptr - offsetof1(type,member) ); } )  

static inline void prefetch(const void *x) {;}  
static inline void prefetchw(const void *x) {;}  

//#define LIST_POISON1  ((void *) 0x00100100)  
//#define LIST_POISON2  ((void *) 0x00200200)  
#define LIST_POISON1  0  
#define LIST_POISON2  0  





//双向链表

[html] view plain copy
struct list_head {  
        struct list_head *next, *prev;       };  



//用同一对象初始化next 和prev. 如果对这部分不太理解,请查看我的博文
[html] view plain copy
#define LIST_HEAD_INIT(name) { &(name), &(name) }     

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




//初始化就是把指针指向自己
[html] view plain copy
#define INIT_LIST_HEAD(ptr) do { \            

        (ptr)->next = (ptr); (ptr)->prev = (ptr); \  
} while (0)  





/*
* Insert a new entry between two known consecutive entries.
*
* This is only for internal list manipulation where we know
* the prev/next entries already!
*/
//插入新条目,插在prev与next中间
[html] view plain copy
static inline void __list_add(struct list_head *new1,                  

                              struct list_head *prev,                  

                              struct list_head *next)  
{  
        next->prev = new1;  
        new1->next = next;  
        new1->prev = prev;  
        prev->next = new1;  
}  






/**
* list_add - add a new entry
* @new: new entry to be added
* @head: list head to add it after
*
* Insert a new entry after the specified head.
* This is good for implementing stacks.
*/
//头插法,调用_list_add()实现
[html] view plain copy
static inline void list_add(struct list_head *new1, struct list_head *head)    

{  
        __list_add(new1, head, head->next);  
}  





/**
* list_add_tail - add a new entry
* @new: new entry to be added
* @head: list head to add it before
*
* Insert a new entry before the specified head.
* This is useful for implementing queues.
*/
//尾部插法
[html] view plain copy
static inline void list_add_tail(struct list_head *new1, struct list_head *head)    

{  
        __list_add(new1, head->prev, head);  
}  





//删除结点。删除链表中prev与next之间的元素
[html] view plain copy
static inline void __list_del(struct list_head * prev, struct list_head * next)       

{  
        next->prev = prev;  
        prev->next = next;  
}  





  //删除一个结点entry,并将删除结点地址置为0
[html] view plain copy
static inline void list_del(struct list_head *entry)               

{                                                       
        __list_del(entry->prev, entry->next);  
        entry->next = LIST_POISON1;  //指针清除0  
        entry->prev = LIST_POISON2;  //指针清除0  
}  







  //从链表中删除元素entry,并将其初始化为新的链表。
[html] view plain copy
static inline void list_del_init(struct list_head *entry)      

{  
        __list_del(entry->prev, entry->next);  
        INIT_LIST_HEAD(entry);              
}  





//将该结点摘除并插入到链表头部
[html] view plain copy
static inline void list_move(struct list_head *list, struct list_head *head)   

{  
        __list_del(list->prev, list->next);  
        list_add(list, head);  
}  





//将该结点摘除并插入到链表尾部部
[html] view plain copy
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);  
}  





  //测试链表是否为空
[html] view plain copy
static inline int list_empty(const struct list_head *head)         

{  
        return head->next == head;  
}  





/*
基本的list_empty()仅以头指针的next是否指向自己来判断链表是否为空,Linux链表另行提供了一个list_empty_careful()宏,
它同时判断头指针的next和prev,仅当两者都指向自己时才返回真。
这主要是为了应付另一个cpu正在处理同一个链表而造成next、prev不一致的情况。
但代码注释也承认,这一安全保障能力有限:除非其他cpu的链表操作只有list_del_init(),否则仍然不能保证安全,也就是说,还是需要加锁保护。
*/
[html] view plain copy
static inline int list_empty_careful(const struct list_head *head)     
{  
        struct list_head *next = head->next;  
        return (next == head) && (next == head->prev);  
}  




//合并链表, 将链表list与head合并。
[html] view plain copy
static inline void __list_splice(struct list_head *list,               

                                 struct list_head *head)  
{  
        struct list_head *first = list->next;  
        struct list_head *last = list->prev;  
        struct list_head *at = head->next;  

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

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





/**
* list_splice - join two lists
* @list: the new list to add.
* @head: the place to add it in the first list.
*/
//list不为空的情况下,调用__list_splice()实现list与head的合并
[html] view plain copy
static inline void list_splice(struct list_head *list, struct list_head *head)  

{  
        if (!list_empty(list))  
                __list_splice(list, head);  
}  





/**
* list_splice_init - join two lists and reinitialise the emptied list.
* @list: the new list to add.
* @head: the place to add it in the first list.
*
* The list at @list is reinitialised
*/
  //将两链表合并,并将list初始化。
[html] view plain copy
static inline void list_splice_init(struct list_head *list,       

                                    struct list_head *head)  
{  
        if (!list_empty(list)) {  
                __list_splice(list, head);  
                INIT_LIST_HEAD(list);  
        }  
}  






/*Linux链表中仅保存了数据项结构中list_head成员变量的地址,那么我们如何通过这个list_head成员访问到作为它的所有者的节点数据呢?
Linux为此提供了一个list_entry(ptr,type,member)宏,其中ptr是指向该数据中list_head成员的指针,
也就是存储在链表中的地址值,type是数据项的类型,member则是数据项类型定义中list_head成员的变量名,*/ 
//根据list_head型指针ptr换算成其宿主结构的起始地址,
     // 该宿主结构是type型的,而ptr在其宿主结构中定义为member成员。    
[html] view plain copy
#define list_entry(ptr, type, member) container_of(ptr, type, member)           


/* 一下是container_of的定义
#define container_of(ptr, type, member) ({                  \
    const typeof( ((type *)0)->member ) *__mptr = (ptr);    \
    (type *)( (char *)__mptr - offsetof(type,member) );})
*/

/*它实际上是一个for循环,利用传入的pos作为循环变量,从表头head开始,逐项向后(next方向)移动pos,
直至又回到head(prefetch()可以不考虑,用于预取以提高遍历速度)。
*/
[html] view plain copy
#define list_for_each(pos, head) \  
        for (pos = (head)->next; prefetch(pos->next), pos != (head); \  
                pos = pos->next)  




//__list_for_each与list_for_each没什么不同,只是少了prefetch的内容,实现上更为简单易懂。
[html] view plain copy
#define __list_for_each(pos, head) \  
        for (pos = (head)->next; pos != (head); pos = pos->next)  



/*很典型的对于循环链表的迭代,但对于Linux内核中的链表,迭代的指针只能是一个struct list_head类型,
对于这个类型来说我们只能对它做一些移动或者添加的操作,并不能取出该list_head对应的节点。
这个迭代过程对于节点的删除操作其实是不安全的,假设我们在迭代中移除了pos节点,则进入下一次迭代时,
pos = pos->next这个就不知会指向哪里去了,Linux中也定义了对于删除安全的迭代宏:list_for_each_safe()
*/


//list_for_each_prev与list_for_each的遍历顺序相反,从链表尾逆向遍历到链表头。  
[html] view plain copy
#define list_for_each_prev(pos, head) \  
        for (pos = (head)->prev; prefetch(pos->prev), pos != (head); \  
                pos = pos->prev)  



/*list_for_each_safe 也是链表顺序遍历,只是更加安全。即使在遍历过程中,当前节点从链表中删除,也不会影响链表的遍历
参数上需要加一个暂存的链表节点指针n。
这个宏中使用了n这个struct list_head类型的临时变量,每次迭代之后pos的下一个节点储存在临时变量n中,则在迭代中删除pos节点后,
下一次迭代会重新给pos赋值为临时变量n,然后再做迭代。这样在迭代的过程中就可以安全地删除节点pos了。 
[html] view plain copy
#define list_for_each_safe(pos, n, head) \  
        for (pos = (head)->next, n = pos->next; pos != (head); \  
                pos = n, n = pos->next)     






/*循环遍历每一个pos中的member子项
*/
[html] view plain copy
#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_entry相对的反向遍历的list_for_each_entry_reverse
(个人觉得如果对应,命名为list_for_each_entry_prev可能要好些) 
*/
[html] view plain copy
#define list_for_each_entry_reverse(pos, head, member)                        \  
        for (pos = list_entry((head)->prev, typeof(*pos), member);        \  
             prefetch(pos->member.prev), &pos->member != (head);         \  
             pos = list_entry(pos->member.prev, typeof(*pos), member))  





   如果遍历不是从链表头开始,而是从已知的某个pos结点开始,则可以使用list_for_each_entry_continue(pos,head,member)。
但为了确保pos的初始值有效,Linux专门提供了一个list_prepare_entry(pos,head,member)宏,如果pos有值,则其不变;如果没有,
则从链表头强制扩展一个虚pos指针。将它的返回值作为list_for_each_entry_continue()的pos参数,就可以满足这一要求。
[html] view plain copy
#define list_prepare_entry(pos, head, member) \  
        ((pos) ? : list_entry(head, typeof(*pos), member))  





如果遍历不是从链表头开始,而是从已知的某个节点pos开始,则可以使用list_for_each_entry_continue(pos,head,member)
[html] view plain copy
#define list_for_each_entry_continue(pos, head, member)                 \  
        for (pos = list_entry(pos->member.next, typeof(*pos), member);        \  
             prefetch(pos->member.next), &pos->member != (head);        \  
             pos = list_entry(pos->member.next, typeof(*pos), member))  



/*它们要求调用者另外提供一个与pos同类型的指针n,在for循环中暂存pos下一个节点的地址,避免因pos节点被释放而造成的断链*/
[html] view plain copy
#define list_for_each_entry_safe(pos, n, head, member)                        \  
        for (pos = list_entry((head)->next, typeof(*pos), member),        \  
                n = list_entry(pos->member.next, typeof(*pos), member);        \  
             &pos->member != (head);                                         \  
             pos = n, n = list_entry(n->member.next, typeof(*n), member))  







/*Linux链表设计者(认为双头(next、prev)的双链表对于HASH表来说"过于浪费",
因而另行设计了一套用于HASH表应用的hlist数据结构--单指针表头双循环链表,
hlist的表头仅有一个指向首节点的指针,而没有指向尾节点的指针,这样在可能是海量的HASH表中存储的表头就能减少一半的空间消耗。
*/   
//HASH LIST
//表头结点(不同于结点的数据结构)
[html] view plain copy
struct hlist_head {                            
        struct hlist_node *first;  
};  


[html] view plain copy



/*pprev首先也是一个指针,不过它是指向指针的指针(这点请仔细体会),在hlist中pprev指向的是当前hlist_node变量中的前一个变量的next的地址,
如果是第一个元素的话,这个值指向的是first的地址,如果是最后一个节点的话,指向的是NULL。如果再深入理解一下,
其实*pprev和next这两个变量分别表示指向自己的指针和指向其他节点的指针。
*/
 //结点
[html] view plain copy
struct hlist_node {                                   

 struct hlist_node *next, **pprev;  
};  





//给struct hlist_head变量来进行初始化的。
[html] view plain copy
#define HLIST_HEAD_INIT { .first = NULL }  



//定义一个一个struct hlist_head节点,并且给其初始化为NULL
[html] view plain copy
#define HLIST_HEAD(name) struct hlist_head name = {  .first = NULL }  




//给struct hlist_head节点进行初始化的。
[html] view plain copy
#define HLIST_HEAD(name) struct hlist_head name = {  .first = NULL }  




//这个宏函数是对struct hlist_node节点的一个变量进行初始化的操作。这个宏用在删除节点之后对节点的操作当中。
[html] view plain copy
#define INIT_HLIST_NODE(ptr) ((ptr)->next = NULL, (ptr)->pprev = NULL)  



/*h:struct hlist_node节点指针。
判断h->prev是不是为空,如果pprev的指向是空的话,表示这个节点没有添加到这个链表当中来,如果是空,返回true,否则返回false*/
[html] view plain copy
static inline int hlist_unhashed(const struct hlist_node *h)  
{  
        return !h->pprev;  
}  




/*h:struct hlist_head节点指针(hlist链表的头节点)。
判断hlist链表是不是空链表,如果是,返回true,否则返回false。
*/
[html] view plain copy
static inline int hlist_empty(const struct hlist_head *h)  
{  
        return !h->first;  
}  




/*真正意义上实现删除操作的函数。
n:要删除的节点。
对于删除操作的话,要注意n是不是末尾节点,如果是末尾节点的话,next就是NULL,所以就没有指向的pprev,就更不能进行相应的修改了,否则进行修改。
*/
[html] view plain copy
static inline void __hlist_del(struct hlist_node *n)  
{  
        struct hlist_node *next = n->next;  
        struct hlist_node **pprev = n->pprev;  
        *pprev = next;  
        if (next)  
                next->pprev = pprev;  
}  




/*n:要删除的节点。
在这个函数中首先删除了n节点,之后将n节点的两个指针指向了LIST_POSION,表示不可使用的地方
*/
[html] view plain copy
static inline void hlist_del(struct hlist_node *n)  
{  
        __hlist_del(n);  
        n->next = LIST_POISON1;  
        n->pprev = LIST_POISON2;  
}  




/*n:要删除的节点。
首先判断要删除的pprev是不是空,如果是,则不能删除,否则进行删除操作,删除完成之后,将n节点的next和pprev都指向NULL(初始化节点)。
*/
[html] view plain copy
static inline void hlist_del_init(struct hlist_node *n)  
{  
        if (n->pprev)  {  
                __hlist_del(n);  
                INIT_HLIST_NODE(n);  
        }  
}  




/* 
n:要添加的新的节点。
h:hlist链表的表头节点。
这个函数是给h的下一个和first节点中添加一个新的hlist_node节点,类似与头插。
*/
[html] view plain copy
static inline void hlist_add_head(struct hlist_node *n, struct hlist_head *h)  
{  
        struct hlist_node *first = h->first;  
        n->next = first;  
        if (first)  
                first->pprev = &n->next;  
        h->first = n;  
        n->pprev = &h->first;  
}  





/* next must be != NULL */
/*
n:要添加的新的节点。
next:在next节点之前添加n。
在next节点的前面添加一个新的节点n,在使用这个函数中要特别注意,next不能为NULL。
*/
[html] view plain copy
static inline void hlist_add_before(struct hlist_node *n,  
                                        struct hlist_node *next)  
{  
        n->pprev = next->pprev;  
        n->next = next;  
        next->pprev = &n->next;  
        *(n->pprev) = n;  
}  




/*n:表示在n节点之后添加next。
next:要添加的新的节点。
在n节点的后面添加一个新的节点next,这里也要求n不能为NULL
*/
[html] view plain copy
static inline void hlist_add_after(struct hlist_node *n,  
                                        struct hlist_node *next)  
{  
        next->next = n->next;  
        n->next = next;  
        next->pprev = &n->next;  

        if(next->next)  
                next->next->pprev  = &next->next;  
}  






/*说明:有关hlist中的宏定义与list中的宏定义大同小异,所以在此只是简单分析,具体分析见上面代码*/

/*ptr:表示struct hlist_node类型的一个地址。
type:结构体名
member:type结构体中的hlist_node成员变量的名称
这个宏在list链表中分析过了,表示得到ptr所指地址的这个结构体的首地址
*/
[html] view plain copy
#define hlist_entry(ptr, type, member) container_of(ptr,type,member)  




/*pos:struct hlist_node类型的一个指针;
head:struct hlist_head类型的一个指针,表示hlist链表的头结点。
这个实际上就是一个for循环,从头到尾遍历链表。
*/
[html] view plain copy
#define hlist_for_each(pos, head) \  
        for (pos = (head)->first; pos && ({ prefetch(pos->next); 1; }); \  
             pos = pos->next)  



/*这个实际上就是一个for循环,从头到尾遍历链表。这个和前面的不同的是多了一个n,这么做是为了遍历过程中防止断链的发生。
pos:struct hlist_node类型的一个指针;
n:struct hlist_node类型的一个指针;
head:struct hlist_head类型的一个指针,表示hlist链表的头结点。
*/   
[html] view plain copy
#define hlist_for_each_safe(pos, n, head) \  
        for (pos = (head)->first; pos && ({ n = pos->next; 1; }); \  
             pos = n)  




/*tops:用来存放遍历到的数据结构的地址,类型是type *;
pos:struct hlist_node类型的一个指针;
head:hlist链表的头结点;
member:struct hlist_node在type结构体中的变量的名称。
在循环中,我们就可以使用tops来指向type类型结构体的任何一个变量了。
*/
[html] view plain copy
#define hlist_for_each_entry(tpos, pos, head, member)                         \  
        for (pos = (head)->first;                                         \  
             pos && ({ prefetch(pos->next); 1;}) &&                         \  
                ({ tpos = hlist_entry(pos, typeof(*tpos), member); 1;}); \  
             pos = pos->next)  




/*tops:用来存放遍历到的数据结构的地址,类型是type *;
pos:struct hlist_node类型的一个指针;
member:struct hlist_node在type结构体中的变量的名称。
这个宏是从pos这个地址的下一个元素处开始继续往下遍历。
*/    
[html] view plain copy
#define hlist_for_each_entry_continue(tpos, pos, member)                 \  
        for (pos = (pos)->next;                                                 \  
             pos && ({ prefetch(pos->next); 1;}) &&                         \  
                ({ tpos = hlist_entry(pos, typeof(*tpos), member); 1;}); \  
             pos = pos->next)  




/*tops:用来存放遍历到的数据结构的地址,类型是type *;
pos:struct hlist_node类型的一个指针;
member:struct hlist_node在type结构体中的变量的名称。
这个宏是从pos这个地址处开始继续往下遍历。
*/    
[html] view plain copy
#define hlist_for_each_entry_from(tpos, pos, member)                         \  
        for (; pos && ({ prefetch(pos->next); 1;}) &&                         \  
                ({ tpos = hlist_entry(pos, typeof(*tpos), member); 1;}); \  
             pos = pos->next)  




/*tops:用来存放遍历到的数据结构的地址,类型是type *;
pos:struct hlist_node类型的一个指针;
n:struct hlist_node类型的一个指针;
head:hlist链表的头结点;
member:struct hlist_node在type结构体中的变量的名称。
在循环中,我们就可以使用tops来指向type类型结构体的任何一个变量了。这个宏函数也是为了防止在遍历的时候删除节点而引入的。
*/
[html] view plain copy
#define hlist_for_each_entry_safe(tpos, pos, n, head, member)                  \  
        for (pos = (head)->first;                                         \  
             pos && ({ n = pos->next; 1; }) &&                                  \  
                ({ tpos = hlist_entry(pos, typeof(*tpos), member); 1;}); \  
             pos = n)  

#endif  

View Code

 

 

威尼斯网址开户网站 3威尼斯网址开户网站 4

链表简介:
链表是一种常用的数据结构,它通过指针将一系列数据节点连接成一条数据链。相对于数组,链表具有更好的动态性,建立链表时无需预先知道数据总量,可以随机分配空间,可以高效地在链表中的任意位置实时插入或者删除数据。链表的开销主要是访问的顺序性和组织链的空间损失。
内核链表的好主要体现为两点,1是可扩展性,2是封装。可扩展性肯定是必须的,内核一直都是在发展中的,所以代码都不能写成死代码,要方便修改和追加。将链表常见的操作都进行封装,使用者只关注接口,不需关注实现。分析内核中的链表我们
可以做些什么呢?我觉得可以将其复用到用户态编程中,以后在用户态下编程就不需要写一些关于链表的代码了,直接将内核中list.h中的代码拷贝过来用。也可以整理出my_list.h,在以后的用户态编程中直接将其包含到C文件中。

1. 链表对比
传统链表和内核链表
传统链表:一般指的是单向链表
struct List
{
struct list *next;//链表结点指针域
};
内核链表:双向循环链表 设计初衷是设计出一个通用统一的双向链表!
struct list_head
{
 struct list_head    *head, *prev;
};
list_head结构包含两个指向list_head结构体的指针
prev和next,由此可见,内核的链表具备双链表功能,实际上,通常它都组织成双向循环链表
2. 内核链表使用
1. INIT_LIST_HEAD:创建链表
2. list_add:在链表头插入节点
3. list_add_tail:在链表尾插入节点
4. list_del:删除节点
5. list_entry:取出节点
6. list_for_each:遍历链表
(如果我们不知道这些函数的参数以及函数内部实现,学习查阅这些函数的参数或者实现代码最好的方法还是直接查看内核源码,结和前面的用sourceInsight工具直接搜索这些函数的名字)


下面举个例子:比如查阅INIT_LIST_HEAD函数,

这个是先将内核源码导入sourceInsight工程里面!源码可以在官网上下载,然后在Linux下解压(文件名Linux分大小写,windows不分大小写),然后通过Samba和映射网络驱动器功能(前面的sourceInsight博文有讲到),点击R图标左边的那个图标(像一个打开的一本书)

这样可以很快的查看到代码实现部分:在内核Mkregtale.c文件中
[html] view plain copy
/*  
 * This is a simple doubly linked list implementation that matches the  
 * way the Linux kernel doubly linked list implementation works.  
 */  

struct list_head {  
    struct list_head *next; /* next in chain */  
    struct list_head *prev; /* previous in chain */  
};  
这个不含数据域的链表,可以嵌入到任何数据结构中,例如可按如下方式定义含有数据域的链表:
[html] view plain copy
struct score  
{  
    int num;  
    int English;  
    int math;  
    struct list_head list;//链表链接域  
};  

struct list_head score_head;//所建立链表的链表头  

INIT_LIST_HEAD(&score_head);//初始化链表头 完成一个双向循环链表的创建
上面的红色部分初始化一个已经存在的list_head对象,score_head为一个结构体的指针,这样可以初始化堆栈以及全局区定义的score_head对象。调用INIT_LIST_HEAD()宏初始化链表节点,将next和prev指针都指向其自身,我们就构造了一个空的双循环链表。
初始化一个空链表:其实就是链表头,用来指向第一个结点!定义结点并且初始化!然后双向循环链表就诞生了
static 加在函数前,表示这个函数是静态函数,其实际上是对作用域的限制,指该函数作用域仅局限于本文件。所以说,static 具有信息隐蔽的作用。而函数前加 inline 关键字的函数,叫内联函数,表 示编译程序在调用这个函数时,立即将该函数展开。
[html] view plain copy
/* Initialise a list head to an empty list */  
static inline void INIT_LIST_HEAD(struct list_head *list)  
{  
        list->next = list;  
    list->prev = list;  
}  


list_add:在链表头插入节点
[html] view plain copy
/**  
 * list_add - add a new entry  
 * @new: new entry to be added  
 * @head: list head to add it after  
 *  
 * Insert a new entry after the specified head.  
 * This is good for implementing stacks.  
 */  
static inline void list_add(struct list_head *new, struct list_head *head)  
{  
  __list_add(new, head, head->next);  
}  

[html] view plain copy
/*  
 * Insert a new entry between two known consecutive entries.  
 *  
 * This is only for internal list manipulation where we know  
 * the prev/next entries already!  
 */  
#ifndef CONFIG_DEBUG_LIST  
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;  
}  
#else  
extern void __list_add(struct list_head *new,  
                  struct list_head *prev,  
                  struct list_head *next);  
#endif  


 list_add_tail:在链表尾插入节点
[html] view plain copy
/**  
 * list_add_tail - add a new entry  
 * @new: new entry to be added  
 * @head: list head to add it before  
 *  
 * Insert a new entry before the specified head.  
 * This is useful for implementing queues.  
 */  
static inline void list_add_tail(struct list_head *new, struct list_head *head)  
{  
    __list_add(new, head->prev, head);  
}  

用法示例:
struct score
{
int num;
int English;
int math;
struct list_head list;//链表链接域
};

struct list_head score_head;//所建立链表的链表头
//定义三个节点 然后插入到链表中
struct score stu1, stu2, stu3;
list_add_tail(&(stu1.list), &score_head);//使用尾插法
Linux 的每个双循环链表都有一个链表头,链表头也是一个节点,只不过它不嵌入到宿主数据结构中,即不能利用链表头定位到对应的宿主结构,但可以由之获得虚拟的宿主结构指针。

 list_del:删除节点
[html] view plain copy
/* Take an element out of its current list, with or without  
 * reinitialising the links.of the entry*/  
static inline void list_del(struct list_head *entry)  
{  
    struct list_head *list_next = entry->next;  
    struct list_head *list_prev = entry->prev;  

    list_next->prev = list_prev;  
    list_prev->next = list_next;  

}  


list_entry:取出节点
[html] view plain copy
/**  
 * list_entry - get the struct for this entry  
 * @ptr:the &struct list_head pointer.  
 * @type:the type of the struct this is embedded in.  
 * @member:the name of the list_struct within the struct.  
 */  
#define list_entry(ptr, type, member) \  
    container_of(ptr, type, member)  

[html] view plain copy
/**  
 * container_of - cast a member of a structure out to the containing structure  
 * @ptr:    the pointer to the member.  
 * @type:   the type of the container struct this is embedded in.  
 * @member: the name of the member within the struct.  
 *  
 */  
#define container_of(ptr, type, member) ({          \  
    const typeof(((type *)0)->member)*__mptr = (ptr);    \  
             (type *)((char *)__mptr - offsetof(type, member)); })  



list_for_each:遍历链表
[html] view plain copy
#define list_for_each(pos, head) \  
    for (pos = (head)->next; prefetch(pos->next), pos != (head); \  
    pos = pos->next)  

可以看出,使用了辅助指针pos,pos是从第一节点开始的,并没有访问头节点,直到pos到达头节点指针head的时候结束。
而且 这种遍历仅仅是找到一个个结点的当前位置,那如何通过pos获得起始结点的地址,从而可以引用结点的域?
list.h 中定义了 list_entry 宏:
           #define   list_entry( ptr, type, member )  \
              ( (type *) ( (char *) (ptr)  - (unsigned long) ( &( (type *)0 )  ->  member ) ) )
          分析:(unsigned long) ( &( (type *)0 )  ->  member ) 把 0 地址转化为 type 结构的指针,然后获取该
          结构中 member 域的指针,也就是获得了 member 在type 结构中的偏移量。其中  (char *) (ptr) 求
         出的是 ptr 的绝对地址,二者相减,于是得到 type 类型结构体的起始地址,即起始结点的地址。使用方法非常的巧妙!
比如下列用法:
struct score stu1, stu2, stu3;
struct list_head *pos;//定义一个结点指针
struct score *tmp;//定义一个score结构体变量
[html] view plain copy
//遍历整个链表,每次遍历将数据打印出来  
    list_for_each(pos, &score_head)//这里的pos会自动被赋新值  
    {  
        tmp = list_entry(pos, struct score, list);  
        printk(KERN_WARNING"num: %d, English: %d, math: %d\n", tmp->num, tmp->English, tmp->math);  
    }  



list_for_each_safe: 链表的释放
[html] view plain copy
/**  
 * list_for_each_safe - iterate over a list safe against removal of list entry  
 * @pos:the &struct list_head to use as a loop cursor.  
 * @n:another &struct list_head to use as temporary storage  
 * @head:the head for your list.  
 */  
#define list_for_each_safe(pos, n, head) \  
    for (pos = (head)->next, n = pos->next; pos != (head); \  
        pos = n, n = pos->next)  


3. 内核链表实现分析

4. 移植内核链表(这里先贴出一个使用内核链表的内核模块小例程)
mylist.c文件
[html] view plain copy
#include<linux/module.h>  
#include<linux/init.h>  
#include<linux/list.h>//包含内核链表头文件  

struct score  
{  
    int num;  
    int English;  
    int math;  
    struct list_head list;//链表链接域  
};  

struct list_head score_head;//所建立链表的链表头  

//定义三个节点 然后插入到链表中  
struct score stu1, stu2, stu3;  
struct list_head *pos;//定义一个结点指针  
struct score *tmp;//定义一个score结构体变量  

int mylist_init()  
{  
    INIT_LIST_HEAD(&score_head);//初始化链表头 完成一个双向循环链表的创建  

    stu1.num = 1;  
    stu1.English = 59;  
    stu1.math = 99;  

    //然后将三个节点插入到链表中  
    list_add_tail(&(stu1.list), &score_head);//使用尾插法  

    stu2.num = 2;  
    stu2.English = 69;  
    stu2.math = 98;  
    list_add_tail(&(stu2.list), &score_head);  

    stu3.num = 3;  
    stu3.English = 89;  
    stu3.math = 97;  
    list_add_tail(&(stu3.list), &score_head);  

    //遍历整个链表,每次遍历将数据打印出来  
    list_for_each(pos, &score_head)//这里的pos会自动被赋新值  
    {  
        tmp = list_entry(pos, struct score, list);  
        printk(KERN_WARNING"num: %d, English: %d, math: %d\n", tmp->num, tmp->English, tmp->math);  
    }  

    return 0;  
}  

void mylist_exit()  
{  
    //退出时删除结点  
    list_del(&(stu1.list));  
    list_del(&(stu2.list));  
    printk(KERN_WARNING"mylist exit!\n");  
}  

module_init(mylist_init);  
module_exit(mylist_exit);  


Makefile文件
[html] view plain copy
obj-m := mylist.o  

KDIR := /home/kernel/linux-ok6410  

all:  
    make -C $(KDIR) M=$(PWD) modules CROSS_COMPILE=arm-linux- ARCH=arm  

clean:  
    rm -f *.o *.ko *.order *.symvers  


在终端上加载运行内核模块:

这里rmmod 时会有个错误!不过没大事!百度有很多解决方案!

View Code

 

 

linux内核通过定义list_head以及对于list_head上的一组操作实现对不同类型的循环链表的同类操作,这种做法避免了对于不同数据类型的循环链表定义重复的操作函数,使代码得到了充分的使用,是一种十分有效的编程方法。

  list_head的定义:

list_head的定义:

     1.INIT_LIST_HEAD:创建链表

list_head的定义:

以下为引用的内容:
struct list_head {
struct list_head *next, *prev;
};

19struct list_head {

     2.list_add:在链表头插入节点

19struct list_head {  20struct list_head *next, *prev;  21}; 

  接着我们来看任意一种数据结构的循环链表(如图1),链表的每个节点中加入了一个list_head类型的变量,节点的其他变量任意。(注意:每个指针所指向的位置不是节点数据的起始位置,而是list_head类型变量的开始地址。)

20 struct list_head *next, *prev;

     3.list_add_tail:在链表尾插入节点

接着我们来看任意一种数据结构的循环链表如图1),链表的每个节点中加入了一个list_head类型的变量,节点的其他变量任意。注意:每个指针所指向的位置不是节点数据的起始位置,而是list_head类型变量的开始地址。)

威尼斯网址开户网站 5
图一

21};

     4.list_del:删除节点

威尼斯网址开户网站 6

  通过这样一种实现方式建立的链表,节点都是通过list_head类型的变量相连接的,那么我们如何由list_head类型得指针得到中间某个节点类型的指针呢?我们来看这样一个操作:list_entry(p,t,m),其中t是链表的节点类型,m是节点内list_head类型的变量名,p是指向该变量的指针,该操作用于从list_head指针得到指向链表节点的指针。

接着我们来看任意一种数据结构的循环链表(如图1),链表的每个节点中加入了一个list_head类型的变量,节点的其他变量任意。(注意:每个指针所指向的位置不是节点数据的起始位置,而是list_head类型变量的开始地址。)

     5.list_entry:取出节点

通过这样一种实现方式建立的链表,节点都是通过list_head类型的变量相连接的,那么我们如何由list_head类型得指针得到中间某个节点类型的指针呢?我们来看这样一个操作:list_entry(p,t,m),其中t是链表的节点类型,m是节点内list_head类型的变量名,p是指向该变量的指针,该操作用于从list_head指针得到指向链表节点的指针。

以下为引用的内容:
#define list_entry(ptr, type, member) \ 
container_of(ptr, type, member)

#define container_of(ptr, type, member) ({ \ 
const typeof( ((type *)0)->member ) *__mptr = (ptr); \ /*_mptr与ptr类型值都相同,是ptr的一个拷贝*/ 
(type *)( (char *)__mptr – offsetof(type,member) );}) /*地址减去偏移量(以字节为单位)即可*/

#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER) /*计算出变量在结构中的偏移量(以字节为单位)*/

威尼斯网址开户网站 7

     6.list_for_each:遍历链表

334#define list_entry(ptr, type, member) \   335container_of(ptr, type, member)  650#define container_of(ptr, type, member) ({  \   651const typeof( ((type *)0)->member ) *__mptr = (ptr);\/*_mptr与ptr类型值都相同,是ptr的一个拷贝*/   652(type *)( (char *)__mptr - offsetof(type,member) );})/*地址减去偏移量以字节为单位)即可*/  24#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)   /*计算出变量在结构中的偏移量以字节为单位)*/ 

linux内核通过定义list_head以及对于list_head上的一组操作实现对不同类型的循环…

图1

  2.程序代码

这就是Linux内核循环链表结构。

通过这样一种实现方式建立的链表,节点都是通过list_head类型的变量相连接的,那么我们如何由list_head类型得指针得到中间某个节点类型的指针呢?我们来看这样一个操作:list_entry(p,t,m),其中t是链表的节点类型,m是节点内list_head类型的变量名,p是指向该变量的指针,该操作用于从list_head指针得到指向链表节点的指针。

    威尼斯网址开户网站 8

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

    威尼斯网址开户网站 9

335 container_of(ptr, type, member)

650#define container_of(ptr, type, member) ({ \

651 const typeof( ((type *)0)->member ) *__mptr = (ptr); \
/*_mptr与ptr类型值都相同,是ptr的一个拷贝*/

652 (type *)( (char *)__mptr – offsetof(type,member) );})
/*地址减去偏移量(以字节为单位)即可*/

24#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
/*计算出变量在结构中的偏移量(以字节为单位)*/
 

linux内核通过定义list_head以及对于list_head上的一组操作实现对不同类型的循环链表…

相关文章