您的位置:首页 > 运维架构 > Linux

linux驱动中的链表操作

2015-06-12 16:27 549 查看
一、链表的基本概念

链表是一种物理存储单元上非连续的存储结构,数据单元的逻辑顺序是通过链表中的指针链接次序实现的。通常链表有单向链表和双向链表,如下图所示:



其中data表示数据域,用来存放正真的数据。next和prev表示指针域,用来存放节点的首地址。next指针指向下一个节点,而prev指针指向上一个节点。这样单链表能通过next指针单向遍历整个链表中节点,而双向链表则能通过next和prev指针双向地遍历整个链表。除了以上两种,还有单向循环链表和双向循环链表,区别就是在循环链表中最后一个节点的next指针指向了第一个节点,而第一个节点的prev指针则指向了最后一个节点。

二、链表在linux驱动中的应用(以pl330驱动为例)

链表在linux驱动及内核中的使用是非常普遍的,且在include/linux/list.h文件中封装了许多链表的通用操作函数。在驱动中可以直接使用这些函数来进行链表操作。以下以pl330.c驱动程序中链表的使用为例。

1、  几个相关的数据结构

在pl330.c中有如下三个结构体:

struct dma_pl330_desc
{
struct list_head node;
.
.
.
};
struct pl330_dmac
{
struct list_head desc_pool;
.
.
.
};
struct dma_pl330_chan
{
struct list_head work_list;
.
.
.
};
其中list_head结构体定义在include/linux/Types.h文件中:

struct list_head {
struct list_head *next, *prev;
};
pl330_dmac和dma_pl330_chan的desc_pool和work_list成员分别代表了两个链表的头结点。而dma_pl330_desc的node成员则会被添加到这两个链表中。比如现在有3个dma_pl330_desc结构体变量desc1,desc2,desc3,分别对应了3个node成员node1,node2,node3。将这三个node添加到desc_pool链表中后,将是如下组织结构(只是简单的示例):



其中三个虚线框图代表desc1,desc2和desc3结构体变量。在node节点中没有包含数据域,数据实际上是存放在dma_pl330_desc结构体中的。根据图中的这种结构我们就可以通过desc_pool这个链表头节点来遍历到链表中的任何一个node节点,然后使用container_of函数来获取该node节点所在的dma_pl330_desc结构体变量的指针,从而访问到dma_pl330_desc结构体变量的数据。

2、  desc_pool链表的初始化

static int add_desc(struct pl330_dmac*pl330, gfp_t flg, int count)
{
struct dma_pl330_desc *desc;
unsigned long flags;
int i;

desc= kcalloc(count, sizeof(*desc), flg);
if(!desc)
return 0;

spin_lock_irqsave(&pl330->pool_lock,flags);

for(i = 0; i < count; i++) {
_init_desc(&desc[i]);//初始化描述符结构
list_add_tail(&desc[i].node,&pl330->desc_pool); //将这个描述符结构添加到desc_pool链表中
}
spin_unlock_irqrestore(&pl330->pool_lock,flags);

return count;
}
add_desc函数的功能就是先分配count个dma_pl330_desc结构体变量,初始化后将这些变量通过node节点添加到pl330_dmac的desc_pool链表中。node节点添加到desc_pool链表的动作是通过list_add_tail这个函数来完成。当然在这之前desc_pool这个节点首先会通过INIT_LIST_HEAD函数初始化,该函数为:

static inline void INIT_LIST_HEAD(struct list_head *list)
{
list->next = list;
list->prev = list;
}
可知初始化后的desc_pool节点为:(即prev和next指针都指向了desc_pool节点本身)



假设add_desc函数传入的count为3,则for循环就为:

for(i = 0; i < 3; i++) {
_init_desc(&desc[i]);//初始化desc[i],并使INIT_LIST_HEAD初始化它的node节点
list_add_tail(&desc[i].node,&pl330->desc_pool);
}
将desc[0].node添加到desc_pool后,链表形式为:



将desc[1].node添加到desc_pool后,链表形式为:



将desc[2].node添加到desc_pool后,链表形式为:



所以这里desc_pool链表实际上是一种双向循环链表。

3、  链表的一些操作

(1)检查链表是否为空

可用list_empty(&desc_pool)来检查desc_pool链表是否为空,如果为空则返回true。该函数为:

static inline int list_empty(const struct list_head *head)
{
return head->next == head;
}
根据图3就能很容易理解这里。

(2)向desc_pool添加节点

通过list_add_tail(&desc[2].node, &pl330->desc_pool)就可以将desc[2]的node节点添加到desc_pool链表的末尾。该函数为:

static inline void list_add_tail(struct list_head *new, struct list_head *head)
{
__list_add(new,head->prev, head);
}
而__list_add函数为:

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;
}
根据图5~6就可以很容易理解这段代码。

(3)从desc_pool删除节点

可以使用list_del_init(&desc[2]->node)将desc[2]的node节点从desc_pool移除,并将该节点初始化为空。该函数为:

static inline void list_del_init(struct list_head *entry)
{
__list_del_entry(entry);
INIT_LIST_HEAD(entry);
}
__list_del_entry的函数体为:

static inline void __list_del_entry(struct list_head *entry)
{
__list_del(entry->prev,entry->next);
}
而__list_del为:

static inline void __list_del(struct list_head * prev, struct list_head * next)
{
next->prev= prev;
prev->next= next;
}
该函数执行完后,desc_pool链表就会从图6变为图5。

 (4)将节点从一个链表移动到另一个链表

可以使用list_move_tail(&desc[2]->node, &work_list)将desc[2]的node节点从desc_pool移动到work_list链表中。该函数会先将desc[2]的node节点从desc_pool删除掉,然后再添加到work_list链表中。

static inline void list_move_tail(struct list_head *list,
struct list_head *head)
{
__list_del_entry(list);
list_add_tail(list,head);
}
(5)遍历desc_pool链表

链表只是一种组织数据的方法,我们实际应用中真正关心的是数据本身。比如在这里我们想要获取的是dma_pl330_desc结构体数据,而不是node节点。那么我们怎么通过desc_pool链表头节点来获取dma_pl330_desc结构体数据desc[0]、desc[1]和desc[2]呢?如下所示我们可以通过list_for_each_entry来实现这个目标:

list_for_each_entry(desc, & desc_pool, node) //----A
{
···
}
其中desc是dma_pl330_desc类型的指针,list_for_each_entry实际上是一个宏定义,代表了一个for循环,为:

</pre><pre>


#define list_for_each_entry(pos, head,member)                              \
for(pos = list_first_entry(head, typeof(*pos), member);        \
&pos->member != (head);                                          \
pos = list_next_entry(pos, member))
所以可知代码段A等价于:
for(desc = list_first_entry(& desc_pool, typeof(*desc), node);           //-----B
& desc -> node!= (& desc_pool);                                    //-----C
desc = list_next_entry(desc, node))                                //-----D
{
````
}
list_first_entry的定义为:
#define list_first_entry(ptr, type, member)\
list_entry((ptr)->next,type, member)


而list_entry定义为:

#define list_entry(ptr, type, member) \
container_of(ptr,type, member)
所以最终的调用为:container_of((& desc_pool)->next, dma_pl330_desc, node)

这一句代码会返回一个指向dma_pl330_desc结构体变量的指针,在该变量中包含了(& desc_pool)->next指针所指向的节点node。结合图6可知B段代码会返回结构体数据desc[0]的指针。

C段代码的作用是判断当前desc->node是否与desc_pool相同,如果是一样,说明当前的这个desc指针是无效的,退出循环。

D段代码中list_next_entry定义为:

#define list_next_entry(pos, member) \
list_entry((pos)->member.next,typeof(*(pos)), member)
所以最终也就是调用container_of(desc->node.next, dma_pl330_desc, node),即获取下一个dma_pl330_desc变量。所以综上所述,list_for_each_entry(desc,& desc_pool, node)会顺序遍历desc_pool链表,并通过desc指针来返回desc[0] 、desc[1]和desc[2]的指针。

 注:如果在处理过程中涉及到了链表中节点的删除操作,则需要使用list_for_each_entry_safe(pos, n, head, member)来代替list_for_each_entry(pos,head, member)。

(6)将work_list链表添加到desc_pool链表

假设在work_list链表中链接了desc[3]和desc[4]如下所示:



那么可以使用list_splice_tail_init(&work_list,&desc_pool)将work_list中所有的节点移动到desc_pool链表中,并将work_list初始化为空。list_splice_tail_init函数定义为:

static inline void list_splice_tail_init(struct list_head *list,
struct list_head *head)
{
if(!list_empty(list)) {  //判断list链表是否为空。
__list_splice(list,head->prev, head);
INIT_LIST_HEAD(list);//初始化list链表为空
}
}
其中节点的移动实际是通过__list_splice(&work_list, (&desc_pool)->prev, &desc_pool)来完成的,__list_splice函数定义为:

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;
}
执行完后,work_list为空链表,而desc_pool链表如下所示。(结合图6、图7和图8就能很容易理解这里的代码)

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  linux 链表 list list_head