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

时间:2022-08-25 16:30:30

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

下面,我开始了。 

第一段,我就不多解释了,大家应该能看懂,重点在后面了!!!

 

#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


 

 

//双向链表

 

struct list_head {
struct list_head *next, *prev; };


 

//用同一对象初始化next 和prev. 如果对这部分不太理解,请查看我的博文

#define LIST_HEAD_INIT(name) { &(name), &(name) }   

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

 

 

//初始化就是把指针指向自己

#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中间

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()实现

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.
*/

//尾部插法

static inline void list_add_tail(struct list_head *new1, struct list_head *head)  

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

 

 

 

//删除结点。删除链表中prev与next之间的元素

static inline void __list_del(struct list_head * prev, struct list_head * next)     

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

 

 

 

  //删除一个结点entry,并将删除结点地址置为0

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,并将其初始化为新的链表

static inline void list_del_init(struct list_head *entry)    

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

 

 

 

//将该结点摘除并插入到链表头部

static inline void list_move(struct list_head *list, struct list_head *head) 

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

 

 

 

//将该结点摘除并插入到链表尾部部

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);
}

 

 

 

  //测试链表是否为空

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(),否则仍然不能保证安全,也就是说,还是需要加锁保护。
*/

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合并。

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的合并

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初始化。

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成员。
   

#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()可以不考虑,用于预取以提高遍历速度)。
*/

#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的内容,实现上更为简单易懂。

#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的遍历顺序相反,从链表尾逆向遍历到链表头。  

#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了。 

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



 

 

/*循环遍历每一个pos中的member子项
*/

#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可能要好些)
*/

#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参数,就可以满足这一要求。

#define list_prepare_entry(pos, head, member) \
((pos) ? : list_entry(head, typeof(*pos), member))



  

如果遍历不是从链表头开始,而是从已知的某个节点pos开始,则可以使用list_for_each_entry_continue(pos,head,member)

#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节点被释放而造成的断链*/

#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
//表头结点(不同于结点的数据结构)

struct hlist_head {                          
struct hlist_node *first;
};


 



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

struct hlist_node {                                 

struct hlist_node *next, **pprev;
};


 

 

//给struct hlist_head变量来进行初始化的。

#define HLIST_HEAD_INIT { .first = NULL }


 

//定义一个一个struct hlist_head节点,并且给其初始化为NULL

#define HLIST_HEAD(name) struct hlist_head name = {  .first = NULL }


 

 

//给struct hlist_head节点进行初始化的。

#define HLIST_HEAD(name) struct hlist_head name = {  .first = NULL }


 

 

//这个宏函数是对struct hlist_node节点的一个变量进行初始化的操作。这个宏用在删除节点之后对节点的操作当中。

#define INIT_HLIST_NODE(ptr) ((ptr)->next = NULL, (ptr)->pprev = NULL)


 

/*h:struct hlist_node节点指针。
判断h->prev是不是为空,如果pprev的指向是空的话,表示这个节点没有添加到这个链表当中来,如果是空,返回true,否则返回false*/

static inline int hlist_unhashed(const struct hlist_node *h)
{
return !h->pprev;
}


 

 

/*h:struct hlist_head节点指针(hlist链表的头节点)。
判断hlist链表是不是空链表,如果是,返回true,否则返回false。
*/

static inline int hlist_empty(const struct hlist_head *h)
{
return !h->first;
}



 

/*真正意义上实现删除操作的函数。
n:要删除的节点。
对于删除操作的话,要注意n是不是末尾节点,如果是末尾节点的话,next就是NULL,所以就没有指向的pprev,就更不能进行相应的修改了,否则进行修改。
*/

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,表示不可使用的地方
*/

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(初始化节点)。
*/

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节点,类似与头插。
*/

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。
*/

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
*/

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所指地址的这个结构体的首地址
*/

#define hlist_entry(ptr, type, member) container_of(ptr,type,member)


 



/*pos:struct hlist_node类型的一个指针;
head:struct hlist_head类型的一个指针,表示hlist链表的头结点。
这个实际上就是一个for循环,从头到尾遍历链表。
*/

#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链表的头结点。
*/   

#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类型结构体的任何一个变量了。
*/

#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这个地址的下一个元素处开始继续往下遍历。
*/   

#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这个地址处开始继续往下遍历。
*/   

#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类型结构体的任何一个变量了。这个宏函数也是为了防止在遍历的时候删除节点而引入的。
*/

#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