linux数据结构—链表
2016-04-20 00:10
351 查看
一、双向循环链表
linux中的双向循环链表采用了与传统的链表不一样的方式来实现。
传统的双向循环链表:含有3个域,一个数据域,一个前向指针,一个后向指针;前向指针指向前一个链表元素,后一个指向后一个链表元素。这样就将所有的元素给链接起来了,而且它是循环的,能够向前或向后遍历。
linux的双向循环链表:采用2个域,一个前向指针,一个后向指针;前向指针指向前一个链表元素,后一个指向后一个链表元素。这样就将所有的链表元素给链接起来了,而且它是循环的,能够向前或向后遍历。
这里我们可能发现问题了,只将链表元素链接起来,根本就没有数据,我要你何用?这个就是linux设计巧妙地地方:linux源码中实际上很多地方都要用到双向循环链表,为了提高代码的重用和防止对于每一个涉及到有链表操作的结构都插入指针来处理,linux用一种通用的链表元素嵌入到这样的数据结构中,然后用一个常数时间操作的宏(下面会重点介绍)来获得你想要的被嵌入链表元素的结构的指针,就可以对数据进行操作了。
二、源码分析
链表结构体
静态初始化一个链表,包含一个元素,其前向指针和后向指针都指向自己
动态初始化一个链表,包含一个元素,其前向指针和后向指针都指向自己
通用的向链表中加入一个元素:在*prev和*next之间插入一个元素new
利用上面的函数,在*head后插入一个元素*new
利用上面的函数,在*head前插入一个元素*new
通用地向链表中加入一个元素:在*prev和*next之间插入一个元素new。但是它与_list_add的区别在于当出现下一个情况时只能使用_list_add_rcu了。当链表在遍历过程中插入一个元素时(这个在多处理器不难见到),这时执行_list_add由于编译器和CPU执行的优化,打乱执行顺序,先执行prev->next = new,刚好另一个进程遍历到这里,对不起,这里断掉了或者其他一些不正常的情况,仔细想想可能很糟糕,与new也有关。这样导致操作的失败。如果加入内存屏障smp_wmb(),那么前两句肯定先执行完,这个也就是插入新结点,但是没有更新前后结点,那么再执行后面的时候,无论先执行那一句,都能保证遍历的完成,只是是否遍历到*new的问题(可以分析出)。至此我们终于弄清了这个函数的作用了。
利用上面的函数,在*head后插入一个元素*new,并防止如上遍历的问题
利用上面的函数,在*head前插入一个元素*new,并防止如上遍历的问题
通用的删除的函数:删除*prev和*next之间的元素,但是对删除的元素不做任何处理
删除元素*prev,并将该元素的指针域指向不可用的地址
删除元素*prev,并将该元素的前向指针指向不可用的地址。同样采用上面的例子—遍历,这里没有设置内存屏障,假如执行了entry->next = LIST_POISON1后比那里到这里怎么办,对不起,遍历失败,还产生一个异常,所以这里不要这一句,也就是顺向地遍历不会出问题的,但是按照这种分析逆向的遍历我认为还是会出现问题的
用元素*new替代元素*old:这里面也使用内存屏障,其分析方法跟list_add_rcu一样,先保证修改替代的新元素,这个之后无论下面的那条指令先执行,都不会让同时进行的遍历失败
linux中的双向循环链表采用了与传统的链表不一样的方式来实现。
传统的双向循环链表:含有3个域,一个数据域,一个前向指针,一个后向指针;前向指针指向前一个链表元素,后一个指向后一个链表元素。这样就将所有的元素给链接起来了,而且它是循环的,能够向前或向后遍历。
linux的双向循环链表:采用2个域,一个前向指针,一个后向指针;前向指针指向前一个链表元素,后一个指向后一个链表元素。这样就将所有的链表元素给链接起来了,而且它是循环的,能够向前或向后遍历。
这里我们可能发现问题了,只将链表元素链接起来,根本就没有数据,我要你何用?这个就是linux设计巧妙地地方:linux源码中实际上很多地方都要用到双向循环链表,为了提高代码的重用和防止对于每一个涉及到有链表操作的结构都插入指针来处理,linux用一种通用的链表元素嵌入到这样的数据结构中,然后用一个常数时间操作的宏(下面会重点介绍)来获得你想要的被嵌入链表元素的结构的指针,就可以对数据进行操作了。
二、源码分析
链表结构体
struct list_head { struct list_head *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)
通用的向链表中加入一个元素:在*prev和*next之间插入一个元素new
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; }
利用上面的函数,在*head后插入一个元素*new
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); }
通用地向链表中加入一个元素:在*prev和*next之间插入一个元素new。但是它与_list_add的区别在于当出现下一个情况时只能使用_list_add_rcu了。当链表在遍历过程中插入一个元素时(这个在多处理器不难见到),这时执行_list_add由于编译器和CPU执行的优化,打乱执行顺序,先执行prev->next = new,刚好另一个进程遍历到这里,对不起,这里断掉了或者其他一些不正常的情况,仔细想想可能很糟糕,与new也有关。这样导致操作的失败。如果加入内存屏障smp_wmb(),那么前两句肯定先执行完,这个也就是插入新结点,但是没有更新前后结点,那么再执行后面的时候,无论先执行那一句,都能保证遍历的完成,只是是否遍历到*new的问题(可以分析出)。至此我们终于弄清了这个函数的作用了。
static inline void __list_add_rcu(struct list_head * new,struct list_head * prev, struct list_head * next) { new->next = next; new->prev = prev; //先初始化要添加的元素,这个保证先操作后在执行后面的操作 smp_wmb(); //内存屏障 next->prev = new; //将新元素加入到链表中 prev->next = new; }
利用上面的函数,在*head后插入一个元素*new,并防止如上遍历的问题
static inline void list_add_rcu(struct list_head *new, struct list_head *head) { __list_add_rcu(new, head, head->next); }
利用上面的函数,在*head前插入一个元素*new,并防止如上遍历的问题
static inline void list_add_tail_rcu(struct list_head *new,struct list_head *head) { __list_add_rcu(new, head->prev, head); }
通用的删除的函数:删除*prev和*next之间的元素,但是对删除的元素不做任何处理
static inline void __list_del(struct list_head * prev, struct list_head * next) { next->prev = prev; prev->next = next; }
删除元素*prev,并将该元素的指针域指向不可用的地址
static inline void list_del(struct list_head *entry) { __list_del(entry->prev, entry->next); entry->next = LIST_POISON1; entry->prev = LIST_POISON2; }
删除元素*prev,并将该元素的前向指针指向不可用的地址。同样采用上面的例子—遍历,这里没有设置内存屏障,假如执行了entry->next = LIST_POISON1后比那里到这里怎么办,对不起,遍历失败,还产生一个异常,所以这里不要这一句,也就是顺向地遍历不会出问题的,但是按照这种分析逆向的遍历我认为还是会出现问题的
static inline void list_del_rcu(struct list_head *entry) { __list_del(entry->prev, entry->next); entry->prev = LIST_POISON2; }
用元素*new替代元素*old:这里面也使用内存屏障,其分析方法跟list_add_rcu一样,先保证修改替代的新元素,这个之后无论下面的那条指令先执行,都不会让同时进行的遍历失败
static inline void list_replace_rcu(struct list_head *old, struct list_head *new){ new->next = old->next; new->prev = old->prev; //修改替代的新元素的指针域 smp_wmb(); new->next->prev = new; new->prev->next = new; //修改原来的旧元素的前后元素的指针域 }
相关文章推荐
- 数据结构 ---- 链表
- 数据结构4.1--部分排序算法
- 用Doxygen和Graphviz给Contiki文档添加类图和调用图
- 重学数据结构系列之——八大排序算法
- 数据结构之循环链表
- 啊哈!算法【转】
- 菜鸟nginx源代码剖析数据结构篇(八) 缓冲区链表ngx_chain_t
- 数据结构--二叉树的遍历
- 数据结构算法应用C++语言描述——(1)C++基础知识
- MySQL索引背后的数据结构及算法原理
- (转载)动态规划之背包问题(一)
- 【数据结构】二叉树的实现(如:默认成员函数、(叶子)节点数、深度、四种遍历)
- 数据结构学习安排
- 数据结构串之块链串
- 数据结构串之堆串
- 数据结构之串——顺序串
- 数据结构和算法16 之堆排序
- 数据结构和算法15 之二叉树排序
- 关于在牛客网上做题的解析之数据结构(每日20道题,记录错误和不懂的题)
- 广义表(非线性结构)