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就能很容易理解这里的代码)
- linux驱动中的链表操作
- Linux设备驱动中的异步操作
- linux 驱动mmap操作
- Linux驱动中的platform
- Linux驱动中的platform
- Linux驱动中的platform
- linux驱动中的request_irq
- Linux驱动中的platform
- Linux驱动中的platform
- linux驱动中的ioctrl
- Linux驱动中的platform
- Linux驱动中的platform
- Linux驱动中的platform
- Linux驱动中的platform
- Linux驱动中的platform
- Linux驱动中的platform
- linux驱动中的ENODEV
- linux中的按键驱动
- Levenberg–Marquardt algorithm
- Android app进入前的闪屏界面代码
- 给程序员新人的一封信
- MySQL 中的自定义函数和存储过程 简单实例
- [leetcode] Construct Binary Tree from Preorder and Inorder Traversal
- linux驱动中的链表操作
- wxPython源码编译和安装
- POJ 1094 Sorting It All Out
- 页面的缓存与不缓存设置
- 如何让一个div跟随鼠标移动
- Administrator privileges required for OLE Remote Procedure Call debugging: this feature will not wo
- Oracle 数据泵使用详解
- hdu 4412 DP
- #pragma clang diagnostic