链表数据结构
linux有标准的链表数据结构,在<linux/list.h>中声明
struct list_head{
struct list_head *next;
struct list_head *prev;
}
那么定义链表可以通过如下语句
struct MyStruct{
int a;
int b;
struct list_head list;
}
内核还提供了一组链表操作例程。
使用宏container_of(ptr,type,member)可以方便的从链表指针找到父结构中的任何变量,如上面的例子中就是通过list找到MyStruct。函数实现如下:
#define container_of(ptr, type, member) ({
const typeof( ((type *)0)->member ) *__mptr = (ptr);
(type *)( (char *)__mptr - offsetof(type,member) );})
实现的具体结构如图所示,实现功能为:已知结构体type,且member成员的地址ptr,求type的首地址
上述代码第三行中offsetof的原型解释
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
首先引入一个特殊的实例代码((int)&((struct test *)0)->k)解释:
struct test *代表指向该结构体起始地址的指针,将0强制转换为此类型,则令起始指针为0,然后->指向的为成员变量的指针,那么(struct test *)0)->k就代表起始指针为0的test结构体中,k元素的指针,那么也就是代表k元素与结构体起始地址的偏移量。
那么offsetof(type,member)代表的则是size
然后通过__mptr-size,就是type的首地址,那么第二行的作用是??为什么要把原生的ptr,转换为一个_mptr呢?
首先来看_mptr的定义,类型为 typeof( ((type )0)->member ),也就是member成员的类型指针,ptr也是member成员类型指针。
在开发过程中,如果输入的参数错误,ptr和member类型不匹配,那么编译时的赋值操作两端编译不一样就会报错!!(所以编译时候的warnning还是要看的!)
链表操作函数
list_entry
获取链表的入口点,提供了更加标准化的接口,其实就是container_of的直接调用
#define list_entry(ptr,type,member) container_of(ptr,type,member)
INIT_LIST_HEAD定义一个链表
list_add(struct list_head *new,struct list_head *head)
list_add_tail(struct list_head *new,struct list_head *head)
list_del(struct list_head *entry)
list_del_init()
list_move(struct list_head *list,struct list_head *head)
list_move_tail(struct list_head *list,struct list_head *head)
list_empty(struct list_head *head)
list_splice(struct list_head *list,struct list_head *head)
list_splice_init(struct list_head *list,struct list_head *head)
上述函数均为外部链表函数,其中还需要去获取list_head中的prev和next指针,如果已经拿到了prev和next指针,可以直接调用内部函数,如__list_del(prev,next)
链表遍历
list_for_each(p,list)
p为预先定义的临时变量,用来存储当前所遍历的元素。
list为整个链表的list_head,用来表示整个链表。
仅仅获取到list_head往往是无意义的,真正的遍历过程中,需要通过list_entry来获取链表的真正结构体
struct list_head *p;
struct MyStruct *f;
list_for_each(p,&MyList){
f = list_entry(p,MyStruct,MyList);
}
内核当然也提供了更直接的遍历方法
list_for_each_entry(pos,head,member)
list_for_each_entry_reverse(pos,head,member)
通过prev进行遍历,与上述的方法基本相同
list_for_each_entry_safe(pos,next,head,member)
使用带safe的方法可以实现稳定的遍历,遍历的过程中,可以删除链表中的某一项而无需担心链表断开,因为safe的实现过程中,每访问一个链表元素都已经将该元素进行暂存,访问完毕后直接再读出即可。
看实现代码上的区别