前阵子在弄缓存的时候,咱们须要将qemu对于磁盘镜像文件写请求串成一个链表,最终将这个链表里面的写请求所有刷回到镜像文件里面,那么咱们便须要一个强健,可靠的链表的接口,因而咱们仿照Linux 2.4.0的内核,来造了这么一个链表的轮子。今天抽抽空来记录一下。node
链表,估计学过数据结构这门课程的人都对其印象深入,由于老师们都喜欢将它放在比较靠前的地方讲,不少人都认为链表是一种很是easy的数据结构。由于链表的逻辑很是地简单,每一个节点就是分红指针和数据,一头一尾地经过指针将每一个节点串起来,没有树形(二叉树,B-树,红黑树,基树等)的数据结构那样复杂的前驱和后继的结构,以及复杂的结构伸展变化的操做。而且链表的各类操做的复杂度也都很是容易估算出来。git
但是,要实现一个很是可靠,通用,强健的链表数据结构倒是颇有难度的。github
通常地,咱们都会对一个循环链表的节点作出以下的定义:缓存
struct node { int val; struct node *prev; struct node *next; };
代码很简单,构造完一个链表以后会有以下的效果:数据结构
这只是普通链表的实现,当咱们须要建立另外一种数据结构组成的链表的时候,若是仍是按照上述的方法来建立一个链表,那么咱们就不得不从新定义一个链表的节点测试
来存储这种数据结构,若是某个应用里面有一万种数据结构,那咱们岂不要定义一万种链表的数据结构,及其查询,删除,修改的应用接口?spa
咱们能够尝试着将链表单独地抽离出来,对其进行解耦,这样咱们即可以将他看成一套通用的应用接口来使用了。3d
先来定义一下链表节点的访问入口:指针
typedef struct cir_list_head { struct cir_list_head *prev, *next; }gc_list;
如今咱们为特定的存储数据来定义一个链表的节点:code
typedef struct test_list{ gc_list list; int val; } test_list;
注意,咱们在这里就将链表节点的访问接口打包插入到了链表节点里面,仔细体会,咱们最终所构建的效果以下:
咱们但愿可以经过结构体test_list里面的list(gc_list)来获取数据val,也就是说在同一个结构体下,经过其中一个结构体成员来获取另外一个结构体成员的信息,
这个时候,咱们就须要用到一些奇技淫巧:
#define OFFSETOF(type, member) (size_t)&(((type *)0)->member) #define container_of(ptr, type, member) ({ \ const typeof( ((type *)0)->member ) *__mptr = (ptr); \ (type *)( (char *)__mptr - OFFSETOF(type,member) );})
盗用一幅图描述上面的这几行代码的意思,不用太多的文字的解释~~,经过上面的这个接口咱们即可以随意地操控循环链表里面的各类数据啦~,这种封装方法
比较好的地方即是你不再用为每一种链表里面的数据特地去重写一套API,注意这段代码和编译器平台相关(主要都在GCC平台下),并不具有100%的可移植性。
相关连接:
造的一个循环链表的轮子:https://github.com/jusonalien/VM-DB/blob/master/list.h
测试使用代码:https://github.com/jusonalien/VM-DB/blob/master/TestList.c
关于container_of宏的详尽的解释: http://radek.io/2012/11/10/magical-container_of-macro/