浅谈Linux Kernel 中循环链表的实现
前阵子在弄缓存的时候,我们需要将qemu对于磁盘镜像文件写请求串成一个链表,最终将这个链表里面的写请求全部刷回到镜像文件里面,那么我们便需要一个强健,可靠的链表的接口,于是我们仿照Linux 2.4.0的内核,来造了这么一个链表的轮子。今天抽抽空来记录一下。
链表,估计学过数据结构这门课程的人都对其印象深刻,因为老师们都喜欢将它放在比较靠前的地方讲,很多人都认为链表是一种非常easy的数据结构。因为链表的逻辑非常地简单,每个节点就是分成指针和数据,一头一尾地通过指针将每个节点串起来,没有树形(二叉树,B-树,红黑树,基树等)的数据结构那样复杂的前驱和后继的结构,以及复杂的结构伸展变化的操作。并且链表的各种操作的复杂度也都非常容易估算出来。
可是,要实现一个非常可靠,通用,强健的链表数据结构却是很有难度的。
一般地,我们都会对一个循环链表的节点做出如下的定义:
struct node { int val; struct node *prev; struct node *next; };
代码很简单,构造完一个链表之后会有如下的效果:
这只是普通链表的实现,当我们需要创建另一种数据结构组成的链表的时候,如果还是按照上述的方法来创建一个链表,那么我们就不得不重新定义一个链表的节点
来存储这种数据结构,如果某个应用里面有一万种数据结构,那我们岂不要定义一万种链表的数据结构,及其查询,删除,修改的应用接口?
我们可以尝试着将链表单独地抽离出来,对其进行解耦,这样我们便可以将他当作一套通用的应用接口来使用了。
先来定义一下链表节点的访问入口:
typedef struct cir_list_head { struct cir_list_head *prev, *next; }gc_list;
现在我们为特定的存储数据来定义一个链表的节点:
typedef struct test_list{ gc_list list; int val; } test_list;
注意,我们在这里就将链表节点的访问接口打包插入到了链表节点里面,仔细体会,我们最终所构建的效果如下:
接下来我们来看看如何通过gc_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/