本文只是对linux内核中的链表进行分析。内核版本是linux-2.6.32.63。文件在:linux内核/linux-2.6.32.63/include/linux/list.h。本文对list.h文件进行简要分析,有必要的地方还会以图进行说明。

代码分析

链表结构体:

// 有前驱和后继,说明是双链表
struct list_head {
struct list_head *next, *prev;
};

链表头节点相关操作:

// 为head初始化,把head的next和prev都赋值为head的地址
// 因为定义的是宏,所以可以直接把后面的语句替换前面的宏直接看,
// struct list_head name = {&(name),&(name)};,这样会更容易理解
#define LIST_HEAD_INIT(name) { &(name), &(name) } #define LIST_HEAD(name) \
struct list_head name = LIST_HEAD_INIT(name) // 上面是使用宏进行的head初始化(静态初始化,因为宏会在程序预编译时期进行宏名替换)
// 下面这个是在运行时,内嵌到调用函数中。(因为这个是内联函数,调用时直接用函数体内嵌到被调函数中)
static inline void INIT_LIST_HEAD(struct list_head *list)
{
list->next = list;
list->prev = list;
}

链表节点插入操作:

// 这是一个增加插入的公用函数函数实现的是:
// prev <<=>> new <<=>> next,new是要新增的节点,pre和next是相邻的节点
// A <<=>> B 表示A的后继指向B,B的前驱指向A, 后面调用时,根据这个关系就更好理解了。
// 也可以直接看后面的list_add()函数,把结构体带入函数中也会好理解些
// 在内核中有很多这种函数类型:前面带有两个_的(即:__记住是两杠),一般来说这种类型的
// 函数都是不能直接调用的,一定要先通过包装这个函数,然后才能调用。这是个原始函数
#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(),实现从头节点到头结点的next之间插入元素
// head <<=>> new <<=>> head->next
static inline void list_add(struct list_head *new, struct list_head *head)
{
__list_add(new, head, head->next);
} // 从链表尾部插入节点
// 包装了函数:__list_add(),实现从头节点的prev和头结点之间插入元素
// head-prev <<=>> new <<=>> head
static inline void list_add_tail(struct list_head *new, struct list_head *head)
{
__list_add(new, head->prev, head);
}

链表节点删除操作:

// 这是个删除的通用函数,实现得: prev <<=>> next
// 这是让prev和next建立起链接来。可以联系list_del()函数来分析
// 和上面分析一样该函数前缀为__所以一般是用来被包装的原始函数
// 其实这个函数并没有删除这个节点,而是把这个节点从链表上卸下来而已
static inline void __list_del(struct list_head * prev, struct list_head * next)
{
next->prev = prev;
prev->next = next;
} // 这是个删除的函数,参数则是将要删除的节点。
// 调用_list_del() 函数来让entry节点从链表中卸下来,并且让它的前后节点建立连接,
// 然后entry前后指针设置为个特殊的值,设置了这个值后的元素被访问时会引起页故障。
#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 // 这个函数首先调用__list_del() 来让entry节点从链表中卸下来,并且让它的前后节点建立连接,
// 然后调用INIT_LIST_HEAD() 函数使得entry节点变成空节点。
static inline void list_del_init(struct list_head *entry)
{
__list_del(entry->prev, entry->next);
INIT_LIST_HEAD(entry);
}

链表节点移动操作:

// 这个函数首先调用__list_del()函数让list从链表上卸下了,并且让它的前后节点建立连接
// 然后调用list_add()函数 往头部插入该节点。函数的总体意思是:把某个位置上的节点移动到头节点后插入。
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);// 把卸下来的节点插入到链表头节点的尾部
}

链表节点替换操作:

// 这是个替换的通用函数。就是让new节点替换old节点,但
// old指针的前驱和后继都没有改变,就是old节点还是挂在链表上的
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;
} // 这个函数首先调用list_replace() 函数用new替换了old的指针关系。
// 然后调用INIT_LIST_HEAD() 函数让old节点变成空节点
static inline void list_replace_init(struct list_head *old,
struct list_head *new)
{
list_replace(old, new);
INIT_LIST_HEAD(old);
}

链表判空操作和判断是否唯一节点操作:

// 判断list节点是否是该链表中最后的一个节点。
// 因为是环链表,所以若是最后一个节点。则该节点的后继为头节点:list->next = 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;
} // 这个函数和上面的一样,是个判空函数。唯一不同的是这个函数可以防止该该链表
// 同时正在被另外一个cpu操作,以导致head的前驱和后续不一样。其实换个角度来看
// 该函数也可以用来判断该链表是否还在被其他CPU操作
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 和 entry 是在同一个链表上的节点
// 第一步:....<<=>> head <<=>>......<<=>> entry <<=>>.....
// 第二步:设head的next为head_next,entry的next为entry_next
// 第三步:....<<=>> head <<=>> head_next <<=>>.....<<=>> entry <<=>> entry_next <<=>>....
// 第四步:经过函数分割后得两条链表:...<<=>> head <<=>> entry_next <<=>> .....
// 和 ....<<=>> entry <<=>> list <<=>> head_next <<=>> ....
// 函数功能:函数把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;
} // 这是个分割函数,与上面这个函数不同的是,
// 这个函数考虑到了空链表和一个节点的链表情况
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);
}

上面的原始链表拆分函数单看代码是比较难理解的,下面画了图,看图方便理解下:



链表整合操作:

// 这个函数的实现有点不好解释,如果要想理解这个函数的意思最好是根据后面的list_splice()函数来。
// 先说下前提:list是个单独的链表;prev和next是个链表中相邻的2个节点
// 而这个函数实现的是把list和prev这个链表相整合成一个链表。prev和next中断开连接list前后2个节点
// 但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;
}

原始链表整合操作图:

 // 这个函数是先考虑list是否为空表,然后调用上面的整合函数,从头部整合进去。
// 但这个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);
} // 同上个函数,只是从尾部整合进去
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_splice()函数中list的前驱和后继没有修改的问题。
// 该函数调用INIT_LIST_HEAD(list)来是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_tail()这个函数功能是一样的,只是这个函数对list进行了处理。
// 让list变成了空节点。其实有点不理解的是list_splice_tail()函数为什么不对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链表中一个精髓,访问包含节点的结构体中其他数据项
// 后面会详细的分析这个宏的具体使用
//container_of宏用来根据成员的地址来获取结构体的地址。
/**
* 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) #define list_first_entry(ptr, type, member) \
list_entry((ptr)->next, type, member)

list_entry的理解

我们来看一下container_of的宏定义:

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

其次,offsetof的宏定义:

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

TYPE是结构体类型,例如:

struct TYPE
{
//...
struct list_head member;
//...
};

其次,MEMBER就是TYPE中的list_head变量member

那么:

(TYPE *)0是将0强制转换成TYPE型指针,则该指针一定指向0地址(数据段基址)。

&((TYPE *)0)->MEMBER这句话其实是&(((TYPE *)0)->MEMBER),通过该指针访问TYPE的MEMBER成员并得到其地址。

相对于结构体的起始地址0,那么&((TYPE *)0)->MEMBER就是相对于起始地址之间的偏移量,这个偏移量对于所有的TYPE型变量都是成立的。

offsetof(TYPE, MEMBER)就表示这个偏移量。

对于container_of中,

const typeof(((type *)0)->member) * __mptr = (ptr);

由于下面我们要对指针进行强制类型转换,所以这里我们又申请一个指针,指向和ptr相同的位置。

这里的ptr指的是实际list_head member的地址。

(char *)__mptr

由于offsetof()函数求得的是偏移字节数,所以这里(char *)__mptr使得指针的加减操作步长为1Byte,然后二者相减便可以得到TYPE变量的起始地址,最后通过(type *)类型转换,将该地址转换为TYPE类型的指针。

链表节点的遍历操作:

参数相关释义:

/**
* @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.
*/
// 这是个遍历宏,从头往后遍历,算是个比较简单的函数。
// prefetch()是个预取值指令,目的是提高运行效率
#define list_for_each(pos, head) \
for (pos = (head)->next; prefetch(pos->next), pos != (head); \
pos = pos->next)
// 这个函数功能同上,只是没有prefetch()
#define __list_for_each(pos, head) \
for (pos = (head)->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在遍历过程中有可能被删除掉
// 如果还是和上面的遍历函数一样,那假若pos被删除了,则整个程序就会出错停止运行。而现在用个临时变量n
// 可以把数据存放在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)
// 函数功能同上面那个,只是遍历是从head->prev(尾部)那端开始
#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)
// 这是个有数据项的遍历,
// typedef struct pos{
// type date;
// struct head_list member;
// }pos;
// list_entry(&ptr,typeof(pos),ptr);这是个由结构体变量中的某个成员而获取到
// 整个结构体变量的地址指针方法。typeof(pos)是获取到pos的类型
// 这里应该是在创建第一个节点时,让head = &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))
// 函数功能同上,只是从member.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存在,则不做操作,不存在则通过head来虚拟个pos节点
#define list_prepare_entry(pos, head, member) \
((pos) ? : list_entry(head, typeof(*pos), member))
// 这也是遍历数据项的函数,和前面的函数不同的是,这个函数不是从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))
// 这个函数功能和list_for_each_entry_continue()和像,只是遍历的起点不一样。
// list_for_each_entry_continue()是从该节点开始,这个函数则是从该节点的下个节点开始。
#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))
// 这个和上个遍历删除节点的函数类似。多了个临时变量n,
// 所以可以防止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))

linux内核之链表操作解析的更多相关文章

  1. linux 内核的链表操作(好文不得不转)

    以下全部来自于http://www.ibm.com/developerworks/cn/linux/kernel/l-chain/index.html 无任何个人意见. 本文详细分析了 2.6.x 内 ...

  2. Linux内核单链表

    主要说明Linux内核中单链表操作的关键思想,需要注意的地方 1. 假设 为了说明关键思想,对数据结构进行了精简 2. 数据结构定义 struct ListNode { int val; ListNo ...

  3. Linux内核【链表】整理笔记(1)

    我们都知道Linux内核里的双向链表和学校里教给我们的那种数据结构还是些不一样.Linux采用了一种更通用的设计,将链表以及其相关操作函数从数据本身进行剥离,这样我们在使用链表的时候就不用自己去实现诸 ...

  4. linux内核中链表代码分析---list.h头文件分析(一)【转】

    转自:http://blog.chinaunix.net/uid-30254565-id-5637596.html linux内核中链表代码分析---list.h头文件分析(一) 16年2月27日17 ...

  5. linux内核中链表代码分析---list.h头文件分析(二)【转】

    转自:http://blog.chinaunix.net/uid-30254565-id-5637598.html linux内核中链表代码分析---list.h头文件分析(二) 16年2月28日16 ...

  6. Linux内核【链表】整理笔记(2)

    关于链表我们更多时候是对其进行遍历的需求,上一篇博文里我们主要认识了一下和链表操作比较常用的几个内核API接口,其入参全都是清一色的struct list_head{}类型.至于链表的遍历,内核也有一 ...

  7. 基于tiny4412的Linux内核移植 --- aliases节点解析

    作者信息 作者: 彭东林 邮箱:pengdonglin137@163.com QQ:405728433 平台简介 开发板:tiny4412ADK + S700 + 4GB Flash 要移植的内核版本 ...

  8. Linux内核中链表实现

    关于双链表实现,一般教科书上定义一个双向链表节点的方法如下: struct list_node{ stuct list_node *pre; stuct list_node *next; ElemTy ...

  9. Linux内核【链表】整理笔记(2) 【转】

    转自:http://blog.chinaunix.net/uid-23069658-id-4725279.html 关于链表我们更多时候是对其进行遍历的需求,上一篇博文里我们主要认识了一下和链表操作比 ...

随机推荐

  1. Leetcode::JumpGame

    Description: Given an array of non-negative integers, you are initially positioned at the first inde ...

  2. SuperMap iClient

    SuperMap iClient 7C——网络客户端GIS开发平台 产品新特性   SuperMap iClient 7C是空间信息和服务的可视化交互开发平台,是SuperMap服务器系列产品的统一客 ...

  3. Eclipse RCP /Plugin移除Search对话框

    RCP:如何移除Search对话框中不需要的项 2013-08-18 22:31 by Binhua Liu, 231 阅读, 0 评论, 收藏, 编辑 前言 很久没写文章了,准备写一系列关于Ecli ...

  4. Jquery EasyUI tabs处理

    一 获取选中的 Tab 1.   // 获取选中的 tab panel 和它的 tab 对象 2.  var pp = $('#tt').tabs('getSelected'); 3.  var ta ...

  5. Java核心技术 卷Ⅰ 基础知识(1)

    第三章 Java的基本程序设计结构 Java对大小写敏感 命名规范为骆驼命名法,不能使用保留字 main方法必须声明为public 如果main方法正常退出,java应用程序的退出代码为0,表示成功运 ...

  6. Tomcat v7.0 Server at localhost are already in use,tomcat提示端口被占用,tomcat端口已经被使用,tomcat端口占用

    Tomcat v7.0 Server at localhost are already in use, tomcat提示端口被占用,tomcat端口已经被使用 >>>>> ...

  7. C#开发学习——存储过程

    举个例子: 带输入参数的存储过程计算班级中英语和数学不及格的人数      if(exists(select * from sys.objects where name='usp_GetFailCou ...

  8. shell编程其实真的很简单(二)

    上篇我们学会了如何使用及定义变量.按照尿性,一般接下来就该学基本数据类型的运算了. 没错,本篇就仍是这么俗套的来讲讲这无聊但又必学的基本数据类型的运算了. 基本数据类型运算 操作符 符号 语义 描述 ...

  9. selenium自动化过程中遇到的小问题(未完待续)

    1.chrome浏览器调用不起来 代码没出错的情况下,检查下chrome浏览器的版本与chromedriver.exe的版本是否匹配;下面的表格是根据网上及官网整理的chromedriver与chro ...

  10. To the end

    身为一名初二狗的我也走过了半年.不管怎么说人生中也没有几个半年嘛.从九月到现在快四个月了,我也离中考越来越近了/郁闷/.但是还是要好好过唔.不过我想起这半学期还是挺充实的,至少没有浪费太多的时间.有些 ...