最近在看ucore操做系统的实验指导。里面提要一个双向循环链表的数据结构,挺有意思的。node
其实这个数据结构自己并不复杂。在普通链表的基础上加一个前向指针,咱们就获得了双向链表,再把头尾节点连起来就是双向循环链表了。通常的实现方式以下:redis
typedef struct link_node { ele_type element; link_node *prev, *next; } link_node; ... // 相关的操做比较简单,这里就不实现了
可是这样有必定的局限性,就是里面的数据域(element)是固定类型的,若是有不少种成员变量都须要这种链表结构,就免不了重复编码。数据结构
记得以前看redis代码的时候也有这种相似用C语言实现多态的状况,那里面是用一个void *ptr
的指针来指向具体的底层实现。具体实现以下:this
typedef struct link_node_r { void *ptr link_node_r *prev, *next; } link_node_r;
而后,在真正使用的时候把这个类型强转一下。编码
而ucore里面采用了一种不一样的实现方式。实现以下:atom
struct list_entry { struct list_entry *prev, *next; };
能够看到,这个链表里面并无数据域。是的你没有看错,这个数据结构里没有数据域!但是若是没有数据域的话,这个数据结构有什么实用价值呢?这里是把链表域放到须要使用链表的具体结构了,算是逆向思惟吧。 具体实现以下:操作系统
/* * * struct Page - Page descriptor structures. Each Page describes one * physical page. In kern/mm/pmm.h, you can find lots of useful functions * that convert Page to other data types, such as phyical address. * */ struct Page { atomic_t ref; // page frame's reference counter …… list_entry_t page_link; // free list link };
能够看到,这里是须要使用双向循环链表的数据类型应用了list_entry_t
结构,这样子,链表的结构和操做就不须要变了。设计
可是,这里有一个问题。当咱们查找到链表中某一个节点是,怎么获取到其宿主结构,也就是说,要怎么拿到这个对象的其余部分。正常思惟都是从一个结构里拿到它的子结构xx->xxx
或者xx.xxx
。这里咱们要用到一个le2page宏。指针
le2page宏的使用至关简单:code
// convert list entry to page #define le2page(le, member) \ to_struct((le), struct Page, member)
而相比之下,它的实现用到的to_struct宏和offsetof宏则有一些难懂:
/* Return the offset of 'member' relative to the beginning of a struct type */ #define offsetof(type, member) \ ((size_t)(&((type *)0)->member)) /* * * to_struct - get the struct from a ptr * @ptr: a struct pointer of member * @type: the type of the struct this is embedded in * @member: the name of the member within the struct * */ #define to_struct(ptr, type, member) \ ((type *)((char *)(ptr) - offsetof(type, member)))
这里采用了一个利用gcc编译器技术的技巧,即先求得数据结构的成员变量在本宿主数据结构中的偏移量,而后根据成员变量的地址反过来得出属主数据结构的变量的地址。
咱们首先来看offsetof宏,size_t最终定义与CPU体系结构相关,本实验都采用Intel X86-32 CPU,顾szie_t等价于 unsigned int。 ((type )0)->member的设计含义是什么?其实这是为了求得数据结构的成员变量在本宿主数据结构中的偏移量。为了达到这个目标,首先将0地址强制"转换"为type数据结构(好比struct Page)的指针,再访问到type数据结构中的member成员(好比page_link)的地址,便是type数据结构中member成员相对于数据结构变量的偏移量。在offsetof宏中,这个member成员的地址(即“&((type )0)->member)”)实际上就是type数据结构中member成员相对于数据结构变量的偏移量。对于给定一个结构,offsetof(type,member)是一个常量,to_struct宏正是利用这个不变的偏移量来求得链表数据项的变量地址。接下来再分析一下to_struct宏,能够发现 to_struct宏中用到的ptr变量是链表节点的地址,把它减去offsetof宏所得到的数据结构内偏移量,即就获得了包含链表节点的属主数据结构的变量的地址。