您的位置:首页 > 理论基础 > 数据结构算法

linux数据结构—链表

2016-04-20 00:10 351 查看
一、双向循环链表

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;	//修改原来的旧元素的前后元素的指针域
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: