linux驱动中的链表操作

来源:互联网 发布:淘宝客服经典对话 编辑:程序博客网 时间:2024/04/27 14:58

一、链表的基本概念

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


其中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就能很容易理解这里的代码)




0 0