本文共 10870 字,大约阅读时间需要 36 分钟。
linux内核中,数量巨大的数据是靠链表链接起来的,链表结构在内核中起着异常重要的作用。在linux内核中,链表的实现是以一个非常巧妙,非常有新意的方式来实现的,它脱离了传统数据结构课程上所教导的链表的实现方法,而是以一种非常有新意,而且也不缺乏适用性的方式来实现的,下面我就来分析一下linux内核中关于链表实现的方法。
struct list_head {
struct list_head *next, *prev; };这个没什么太多可说的,定义一个双链表的结构
#define LIST_HEAD_INIT(name) { &(name), &(name) }
这个宏的作用是把参数为name的地址,赋值给结构里的头两个变量
#define LIST_HEAD(name) \
struct list_head name = LIST_HEAD_INIT(name) 定义了一个链表类的变量,变量名字命名为所给的参数,然后把链表结构赋值,相当于环起来了。static inline void INIT_LIST_HEAD(struct list_head *list)
{ list->next = list; list->prev = list; }给定一个双链表类型的变量,使之初使化,把它环起来,根据以上的描述,那么就有这样的结论:
LIST_HEAD(foo);
与 struct list_head foo; INIT_LIST_HEAD(&foo); 等效。 #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这个函数是在已知的prev与next之间插一个new的元素。这个操作只是在内部使用,因为只有特定的人,才能确切知道prev与next之间,是不是在之前是相连的,没有其它元素。要是其间有其它元素,这个操作就会导致一些不确定的后果。另外,如果是要做list debug,则这个操作就不在这里实现了,是在list_debug.c这个里面实现的了。
static inline void list_add(struct list_head *new, struct list_head *head)
{ __list_add(new, head, head->next); } 结合上一个函数的说明,这个操作的作用就很明了啦,就是在head这个元素后面,加一个new的元素。static inline void list_add_tail(struct list_head *new, struct list_head *head)
{ __list_add(new, head->prev, head); } 这个函数也很容易看懂,就是在head与head->prev这两个元素之间加一个元素。因为这个链表是双向的,所以head的头一个,其实就是尾了,所以这个函数的名字后面就有个tail了。static inline void __list_del(struct list_head * prev, struct list_head * next)
{ next->prev = prev; prev->next = next; } 这个函数也是一个内部函数,用来删除prev与next这两个元素之间的一个元素。至于为什么,原因与__list_add类似。#ifndef CONFIG_DEBUG_LIST
static inline void list_del(struct list_head *entry) { __list_del(entry->prev, entry->next); entry->next = LIST_POISON1; entry->prev = LIST_POISON2; } #else extern void list_del(struct list_head *entry); #endif 删除指定的元素的头一个元素与后一个元素之间的元素,所以很容易理解,就是删除指定元素的了。删除完后,给元素的两个指针赋值,赋一个特殊的值,用来表示此节点已经被删除。但这里为什么要使用这个特殊的值,而不使用0值呢?因为这是为了调试方便。如果所有的无效指针都是使用零,那么在访问的时候,报页错误的时候,都是提示地址0有页错误,这样没有使用一个特殊的值来进行特殊的定位这样方便,可见,Linux内核中的代码,处处都是妙招啊。同样,如果是debug版的话,这个函数在list_debug.c里实现。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); } 把旧的链表替换了以后,给旧链接初始化,让它成为一个新的,只有一个元素的链表。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); } 把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); } 把list这个元素删掉,然后把它加到head这个链表之尾。static inline int list_is_last(const struct list_head *list,
const struct list_head *head) { return list->next == head; } 判断list这个元素是否为head这个链表的尾,因为是双向链表,所以这样判断很容易。static inline int list_empty(const struct list_head *head)
{ return head->next == head; } 判断head这个链表,是否为空。如果head的下一个元素指向它自己,那么它就是空的了。static inline int list_empty_careful(const struct list_head *head)
{ struct list_head *next = head->next; return (next == head) && (next == head->prev); } 用来判断这个链表是空的,并且不是被正在修改这样的状态之中。不过这个操作,得是在有同步技术支持的时候才起作用,或是只有单进程能执行相关代码的时候才能够起作用。static inline int list_is_singular(const struct list_head *head)
{ return !list_empty(head) && (head->next == head->prev); } 用来判断这个链表是否只有单个元素。判断规则就是head不能为空,并且head的下一个元素与head的上一个元素相同。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; } 这个函数所做的操作,就是把entry放到list列表的左面,当做list链表的prev元素,list链表的next元素指向原先head元素的next元素。entry后面的元素都链到head链表右面,也即后面。我这里做的说明,引用了一个假设,那就是链表的元素是从左往右展开的,左边的元素在前,右边元素在后。起初看这个函数的时候,你肯定是很奇怪的,为什么要这样操作呢?因为它是一个以__开头的函数,有这样的开头字符,就说明这个函数是一个内部函数,既然是内部函数,不是为外面的人调用而写,那么看起来奇怪就不算什么了,这点跟上面我提到的那些特殊的内部函数一样。这个函数主要是为下面的这个函数list_cut_position来服务的。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); } 这个函数的功能,就是把head这个链表里,上至表头(不含),下至entry这个元素(包括),给移到list链表内,组成一个新的链表。然后head链表则与entry后面的元素形成一个新的链表。这里要求entry这个元素,一定要在head这个链表内。如果head里只有一个entry这个元素,则初始化list链表。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; } 这个函数的功能是让原链表的最后一个元素的next指向next参数,让原链表的第一个元素的prev指向prev参数,list这个表头就把架空了,通过它能访问链表,但链表不能访问它,已经链不起来了。static inline void list_splice(const struct list_head *list,
struct list_head *head) { if (!list_empty(list)) __list_splice(list, head, head->next); } 这个函数其实就是把head链表加入到了list链表中来,从中可以可以看出,head是加到了原list链表的头部。static inline void list_splice_tail(struct list_head *list,
struct list_head *head) { if (!list_empty(list)) __list_splice(list, head->prev, head); } 这个函数与上面那个很类似,只不过是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); } } 这个函数与前面的list_splice一样,只不过多了一步,就是把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); } } 这个函数与前面的list_splice_tail功能一样,只不过多了一步,就是把list这个节点初始化了一下。#define list_entry(ptr, type, member) \
container_of(ptr, type, member) 这里有一个关于container_of的用法,我在之前的一篇博客里已经写了这个函数的实现方式与用法,不了解的可以去参考一下。这个函数的作用就是取得链表所在的结构的整个结构。#define list_first_entry(ptr, type, member) \
list_entry((ptr)->next, type, member) 获得ptr所在entry的下一个entry。head的下一个entry就是第一个entry,所以在函数名里,有一个first字样。#define list_for_each(pos, head) \
for (pos = (head)->next; prefetch(pos->next), pos != (head); \ pos = pos->next) 这个函数,定义了一个for循环,来不停地访问pos所指向的entry,pos的初始值设置成了head->next,也即链表的头一个元素。注意,这个for循环后面没有跟分号,所以这个函数后面,还得接上每次for都要执行的语句,如果不止一行,就要加大括号。还有一个非常重要的地方,那就是这个函数的定义里,有一个prefetch指令,这个指令是把数据从内存或是磁盘上预先取到高速缓存中,这样能快速的遍历整个链表。关于这个函数可以参考gcc文档里的详细描述。与这个函数相对应的,还有一个类似的函数定义,为: #define __list_for_each(pos, head) \ for (pos = (head)->next; pos != (head); pos = pos->next) 这两个函数的唯一区别,就是这个函数定义里,没有使用prefetch这样的优化指令,那么这个函数就建议使用在短链表里,比如说大多数情况下,只有零个或是一个元素。因为链表长了,消耗的存储就多,那么所需要的数据被系统从高速缓存置换到内存或是磁盘上的机率就要大,所以长链表的遍历,就需要使用带prefetch指令的,短的就不需要了。#define list_for_each_prev(pos, head) \
for (pos = (head)->prev; prefetch(pos->prev), pos != (head); \ pos = pos->prev) 这个就很容易理解啦,往前遍历,同时使用prefetch指令。#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_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) 类似,这两个函数里面,把当前元素的下一个元素给取出来了,这样的作用,就是保证,你在遍历这个链表的时候,把当前的这个链表里的元素删了,这个链表后面还能接得上,呵呵,其它方面的,我就不用多说了,看函数定义就可以啦。#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)) 类似,这两个函数也是遍历链表里的元素的。一个往前,一个往后。这里也使用了prefetch指令来提前装载内存数据到高速缓存。从这个函数的实现就可以看出,member这个参数,一定得是链表所在结构的链表的名字。#define list_prepare_entry(pos, head, member) \
((pos) ? : list_entry(head, typeof(*pos), member)) 判断pos这个类型是否为空,如果不是为空,则返回这个类型指针的,而且指针是指向head元素所在的结构的,这个并不是第一个元素,而是链表的头,链表的头是空的,并不放数据。#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)) 与下面这个函数 #define list_for_each_entry_continue_reverse(pos, head, member) \ for (pos = list_entry(pos->member.prev, typeof(*pos), member); \ prefetch(pos->member.prev), &pos->member != (head); \ pos = list_entry(pos->member.prev, typeof(*pos), member)) 类似,这两个函数其实与上面的一组函数几乎一样,只不过这里的遍历,是从pos的下一个元素开始的,并不是从head的下一个元素开始的,其它的都与上面的一样。#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)) 从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)) 与 #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)) 都与各自的非safe版本的功能几乎是一样的,只不过,由于safe版本的,增加了对当前元素的后面一个元素的保存,所以如果涉及到要删除当时元素的操作时,后面的链表也可以接上,这个在之前的函数实现里也已经分析过了。这个list实现的后面有一个单链表的实现,叫hlist,按照程序中的注释,这个单链表一般是用在哈希表里的,不过这个链表没有哈希链表里O(1)的访问效率。
我一开始看关于hlist的实现代码后,有一个非常不解的疑问,那就是为什么是单链表,还有一个pprev域呢,而且还是一个struct hlist_node **类型的。其实经过后面的代码阅读,我才知道了,原来这里的pprev并不是指向单链表中当前元素的上一个元素,而是指向它自己。函数通过对pprev这个指针的判断,来判断这个节点是否是哈希的,具体的可以看看hlist_unhashed这个函数的实现。 而且在hlist的实现中,在删除节点以后,旧节点的next与pprev域都被置成了LIST_POISON1与LIST_POISON2,并不是简单的设置成NULL,这样做可以达到一个目的,就是当程序访问已经被删除的节点以后,通过程序打印出来的OOPS错误信息,可以快速地定位出错的位置,这比直接设成NULL要有效得多。这个技术点,我在之前的某篇文章里有讲过,这里就不再多说了,呵呵。关于hlist的函数,我不想再做分析了,因为把关键的pprev的作用,还有前面的双链表的知识都掌握了的话,弄懂单链表的实现方法,弄懂它的用法,还是很容易的。如果对单链表有兴趣的朋友,可以自己试着分析一下,不难的。
转载地址:http://rorvb.baihongyu.com/