本文共 1768 字,大约阅读时间需要 5 分钟。
linux链表
在研究linux内核或基于linux的代码时,我们常会遇到LIST_HEAD、INIT_LIST_HEAD等宏定义,以及list_for_each、list_entry等函数。本文将介绍这些常用宏定义和函数的意义。
struct list_head
在linux中,链表采用双向循环链表结构,定义如下:
struct list_head{struct list_head *next, *prev;}
需要注意的是,链表节点中并不存储数据域。与linux内核中的KObjetc类似,链表节点是嵌入在数据结构中的。这类结构在内核中较为普遍,主要用于链表操作的一种轻量级实现方式。
初始化链表
初始化一个链表节点需要使其自身的prev和next指针都指向自己。有两种常见方法:
跟define 宏:
#define INIT_LIST_HEAD(name) { &(name), &(name) }
或者直接声明变量:
struct list_head name = INIT_LIST_HEAD(name);
这种初始化方式让节点自身环环相扣,成为一个自己的循环链表。
这是一个宏定义,展开为:
do {(ptr->next) = (ptr);(ptr->prev) = (ptr);} while (0);
前提是ptr必须先申明为struct list_head类型。这两种方式在功能上没有区别,只在使用方式上有细微差别。
链表遍历
定义:
#define list_for_each(pos, head)for (pos = (head)->next; prefetch(pos->next), pos != (head); pos = pos->next)
__list_for_each
#define __list_for_each(pos, head)for (pos = (head)->next; pos != (head); pos = pos->next)
两者的区别在于是否带有prefetch。前者为了提高遍历效率进行预取操作,而后者则是简单的遍历。有时为了发挥性能优势,我们需要使用带有prefetch的版本。
for (pos = (head)->next, n = pos->next; pos != head; pos = n, n = pos->next)
这种遍历方式不同于list_for_each,主要是为了在遍历过程中能够安全地删除节点。list_for_each不能在循环体内删除节点,而list_for_entry则可能导致死循环或panic错误。因而在需要删除的场合,应该使用list_for_each_safe。
list_entry函数
list_entry(pos, type, member)
该函数用于根据链表指针找到包含该指针的数据结构体。它实际上就是container_of函数的另一种表达方式。container_of函数通常用于 Shoblemaker结构体与其链表指针之间的转换。
list_for_each_entry
这个宏定义的作用是将list_for_each_entry和list_entry结合起来,以便更方便地同时获取链表节点和数据结构体。它的进一步扩展:
#define list_for_each_entry(pos, head, member)for (pos = list_entry( (head)->next, typeof(*pos), member );prefetch(pos->member->next), &pos->member != head;pos = list_entry(pos->member->next, typeof(*pos), member))
这个递归式的定义方式在语言实现上有些特殊表现,需要仔细理解其对->符号的处理。
总之,理解这些链表处理宏定义和函数,是掌握linux内核链表操作的重要第一步。熟练掌握这些技巧在实际编写代码时能够发挥重要作用。
转载地址:http://newfk.baihongyu.com/