list.h之我见

 

      链表是一种常见的、组织有序数据的数据结构,它通过指针将一系列的数据节点连接成一条数据链,是线性表的一种实现方式。与数组相比,链表具有更好的动态性,建立联表示无须知道结点的总数,可以随机分配空间,可以高效的链表中的任意位置插入或者删除数据。按照结点指针域的组织以及各节点之间的联系方式,链表又可以分为单链表、循环链表、双向循环链表等多种类型。以下是常见的这几种链表的结点的数据结构。
单链表:每个结点仅有一个指针指向后继结点。

typedef struct node { dataType data; struct node *next; }slnode;

双链表:每个结点都有一个指向前驱结点的指针和一个指向后继结点的指针,但链表的首尾不相接,所以首结点的前驱为空,尾结点的后继为空。


typedef struct node { dataType data; struct node *prev; struct node *next; }dlnode; 

双向循环链表:每个结点都有一个指向前驱结点的指针和一个指向后继结点的指针,且首尾相接。

typedef struct node { dataType data; struct node *prev; struct node *next; }dlnode;

在linux内核中使用了大量的链表结构来组织数据,那么内核链表是如何定义和操作的呢?我们现在就走进list.h文件,赶快学习一下吧!
首先看一下list_head结构体:

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

struct list_head是表示双向链表的结点的结构体,它里面没有定义数据域,只包含指针域。它将作为另一个结构体的成员,该结构体包含数据域。
双向链表的定义:

struct my_list{ void *mydata; struct list_head list; };

list字段,隐藏了链表的指针特性,但正是它,把我们要链接的数据组织成了链表。
接着,看看对链表可以进行那些操作。
1. 初始化链表

static inline void INIT_LIST_HEAD(struct list_head *list) { list->next = list; list->prev = list; }

INIT_LIST_HEAD函数将链表初始化为一个空链表,当前链表只含有头结点,头结点的prev和next域均指向头结点,其值等于头指针list。

#define LIST_HEAD_INIT(name) {&(name), &name} #define LIST_HEAD(name) / struct list_head name = LIST_HEAD_INIT(name)

第二个宏是一个嵌套定义的宏,它在定义一个新的宏的时候调用了已定义的第一个宏。宏#define LIST_HEAD(name)生成了一个头结点,宏#define LIST_HEAD_INIT(name)是对头结点name所表示的链表初始化,即将name的地址直接分别赋给那name.prevname.next,形成一个空链表。因此,宏LIST_HEAD(name)的作用就是定义并初始化一个空的双向循环链表。
2. 链表结点的插入
在内核中有两个插入函数:

static inline void list_add(struct list_head *new, struct list_head *head); 在链表头插入(头结点后插入),实现了栈功能 static inline void list_add_tail(struct list_head *new, struct list_head *head);在链表尾部插入(头结点后插入),实现了队列功能

以上两个插入函数均调用了下面的函数:

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

list.h之我见_第1张图片

下面我们来看一下list_add和list_add_tail函数:

static inline void list_add(struct list_head *new, struct list_head *head) { __list_add(new, head, head->next); } static inline void list_add_tail(struct list_head *new, struct list_head *head) { __list_add(new, head->prev, head); }

可见,这两个函数通过巧妙地调用_list_add函数来实现元素的头插和尾插。
3. 链表节点的删除

static inline void __list_del(struct list_head * prev, struct list_head * next) { next->prev = prev; prev->next = next; }

该函数分别让欲删除结点的prev和next结点越级指向彼此。
但这个函数并不是正式的删除函数,它只是一个被调用的函数,调用函数即具体的删除结点函数如下,entry为要删除的节点的指针。

static inline void list_del(struct list_head *entry) { __list_del(entry->prev, entry->next); entry->next = LIST_POISON1; entry->prev = LIST_POISON2; }

这个函数属于不安全的删除,想要安全的删除,那么请调用下面的函数,它最后会让entry的prev和next指针均指向自己。

Static inline void list_del_init(struct list_head *entry) { _list_del(entry->prev, entry->next); Init_list_head(entry); }

4. 链表结点的替换
替换操作很简单,实质就是在old->prev和old->next之间插入new结点。

static inline void list_replace(struct list_head *old, struct list_head *new) { new->next = old->next; new->next->prev = new; new->prev = old->prev; new->prev->next = new; }

若要安全替换,可以调用:

static inline void list_replace_init(struct list_head *old, struct list_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节点移动到链表末尾。

static inline void list_move(struct list_head *list, struct list_head *head) { __list_del_entry(list); list_add(list, head); } static inline void list_move_tail(struct list_head *list, struct list_head *head) { __list_del_entry(list); list_add_tail(list, head); }

6. 测试函数
接下来的几个测试函数,可以见名之意。
list_is_last函数是测试结点list是否为链表head的最后一个节点。

static inline int list_is_last(const struct list_head *list, const struct list_head *head) { return list->next == head; }

下面的两个函数是测试head链表是否为空链表。

static inline int list_empty(const struct list_head *head) { return head->next == head; } static inline int list_empty_careful(const struct list_head *head) { struct list_head *next = head->next; return (next == head) && (next == head->prev); }

list_empty_careful函数,他比list_empty函数"仔细"在那里呢?前者只 是认为只要一个结点的next指针指向头指针就算为空,但是后者还要去检查头节点的prev指针是否也指向头结点。另外,这种仔细也是有条件的,只有当其 他cpu的链表操作只list_del_init()时,否则仍然不能保证安全。

static inline void list_del_init(struct list_head *entry) { __list_del_entry(entry); INIT_LIST_HEAD(entry); }

下面的函数list_is_singular是测试head链表是否只有一个结点:这个链表既不能是空而且head前后的两个结点都得是同一个结点。

static inline int list_is_singular(const struct list_head *head) { return !list_empty(head) && (head->next == head->prev); }

7. 链表的分割(一分为二)
下面的函数是将原链表头结点head后至entry(包括entry结点)之间的所有结点与entry后面的其它结点切割开,使他们成为一个以list为头结点的新链表。但事实切割之前我们必须进行判断,如果head本身是一个空链表则失败;如果head是一个单节点链表也失败;如果entry恰好就是头结点head,那么直接初始化list,因为根据切割规则得到的链表将是一个空链表;如果这些条件都不符合,那么就放心的切割吧!

static inline void list_cut_position(struct list_head *list,struct list_head *head, struct list_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); }

具体的切割代码请看下面的函数:

 

static inline void __list_cut_position(struct list_head *list,struct list_head *head, struct list_head *entry) { struct list_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; }

8. 链表的合并
下面是一个基本的合并函数,它的功能是将list链表(不包括头结点)插入到另一个链表的prev和next两结点之间。

static inline void __list_splice(const struct list_head *list,struct list_head *prev,struct list_head *next) { struct list_head *first = list->next; struct list_head *last = list->prev; first->prev = prev; prev->next = first; last->next = next; next->prev = last; }

 

理解了基本的合并函数,那么将它封装起来就得到了下面的两个合并函数。这里的合并类似于添加和删除功能,有头部合并和尾部合并。

static inline void list_splice(const struct list_head *list,struct list_head *head) { if (!list_empty(list)) __list_splice(list, head, head->next); } static inline void list_splice_tail(struct list_head *list, struct list_head *head) { if (!list_empty(list)) __list_splice(list, head->prev, head); }

合并两个链表后,list还指向原来的链表,因此应该初始化,在上述两个函数末尾添加一句INIT_LIST_HEAD(list);后,就安全了。
故安全的合并便是下面的两个函数:

static inline void list_splice_init(struct list_head *list,struct list_head *head) { if (!list_empty(list)) { __list_splice(list, head, head->next); INIT_LIST_HEAD(list); } } static inline void list_splice_tail_init(struct list_head *list, struct list_head *head) { if (!list_empty(list)) { __list_splice(list, head->prev, head); INIT_LIST_HEAD(list); } }

9. 链表的遍历
下面我们来学习内核链表的遍历,在list.h文件中有许多的遍历链表的宏,开始看的时候可能有点头大,但从前面的分析的思想来看,我们只要抓住基本的函数,再对基本的函数进行封装便得到扩展函数,那么宏也是如此。
基本宏,pos为索引,head为链表头指针。

#define __list_for_each(pos, head) / for (pos = (head)->next; pos != (head); pos = pos->next)

但这个宏只是不停的移动索引,得到新的结构体的指针,那么与该结构体组成的新结构体中的数据成员怎么得到呢?

           
要找到数据结点的位置,必须明白下面的宏:

#define list_entry(ptr, type, member) / ((type*)((char*)(ptr)-(unsigned long)(&((type*)0)->member)))

其中ptr为list_head结构体指针,type为你所定义的结构体类型,member是结构体中list_head结构体成员变量的名。type的作用是为了强制转换,即宏中两次用到(type *)指针ptr指向结构体type中的成员member;通过指针ptr,返回结构体type的起始地址,如图:

           

为了方便大家阅读,我把上面的结构体写成这样

( (type*) ( (char*)(ptr) - (unsigned long) (&((type*)0)->member) ) )

这个表达式最终的结果就是type 型的地址,((size_t) &(type *)0)->member)把0地址转化为type结构的指针,然后获取该结构中member成员的指针,并将其强制转换为size_t类型。于是,由于结构从0地址开始定义,因此,这样求出member的成员地址,实际上就是它在结构中的偏移量.然后ptr减去这个偏移量就得到了所要的结构体指针。
下面这个宏会得到链表中第一个结点的地址。

#define list_first_entry(ptr, type, member) / list_entry((ptr)->next, type, member)

真正遍历的宏登场了,整个便利过程看起来很简单,可能你对prefetch()陌生,它的作用是预取节点,以提高速度。

#define list_for_each(pos, head) / for (pos = (head)->next; prefetch(pos->next), pos != (head); / pos = pos->next)

我们再回头看一开始我们举例的那个基本的便利宏。注意它和上述便利宏的区别就是没有prefetch(),因为这个宏适合比较少结点的链表。
接下来这个遍历宏貌似长相和上面那几个稍有不同,不过理解起来也不困难,倒着(从最后一个结点)开始遍历链表。

#define list_for_each_prev(pos, head) / for (pos = (head)->prev; prefetch(pos->prev), pos != (head); / pos = pos->prev)

下面两个宏是上述两个便利宏的安全版,我们看它安全在那里?它多了一个与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) #define list_for_each_prev_safe(pos, n, head) / for (pos = (head)->prev, n = pos->prev; / prefetch(pos->prev), pos != (head); / pos = n, n = pos->prev)

用在list_for_each宏进行遍历的时候,我们很容易得到pos,我们都知道pos存储的是当前结点前后两个结点的地址。而通过 list_entry宏可以获得当前结点的地址,进而得到这个结点中其他的成员变量。而下面两个宏则可以直接获得每个结点的地址,在for循环中,首先通过list_entry来获得第一个结点的地址;&pos->member != (head)其实就是&pos->list!=(head);它是用来检测当前list链表是否到头了;最后在利用list_entry宏 来获得下一个结点的地址。这样整个for循环就可以依次获得每个结点的地址,进而再去获得其他成员。理解了list_for_each_entry宏,那 么list_for_each_entry_reverse宏就显而易见了。

#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)) #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结点开始遍历。

#define list_for_each_entry_from(pos, head, member) / for (; prefetch(pos->member.next), &pos->member != (head); / pos = list_entry(pos->member.next, typeof(*pos), member))

接下来几个宏又分别是上述几个宏的安全版。

#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)) #define list_for_each_entry_safe_continue(pos, n, head, member) / for (pos = list_entry(pos->member.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)) #define list_for_each_entry_safe_from(pos, n, head, member) / for (n = list_entry(pos->member.next, typeof(*pos), member); / &pos->member != (head); / pos = n, n = list_entry(n->member.next, typeof(*n), member)) #define list_for_each_entry_safe_reverse(pos, n, head, member) / for (pos = list_entry((head)->prev, typeof(*pos), member), / n = list_entry(pos->member.prev, typeof(*pos), member); / &pos->member != (head); / pos = n, n = list_entry(n->member.prev, typeof(*n), member))

好了,今天的学习到此结束!

你可能感兴趣的:(数据结构,list,struct,测试,each,linux内核)