双向循环链表是很常用的一种数据结构。其结构如下图所示:
ucore里面使用了很多双向循环链表组织数据,如空闲页链表等。
一般我们在Java写双向链表可能会这么写:
public class DoubleLink<T> {
T data;
DoubleLink prev;
DoubleLink next;
}
而在C里面可能会这么写:
struct custom_data {
};
struct double_link {
struct custom_data *data;
struct double_link *prev;
struct double_link *next;
};
但是ucore里面的double link定义如下:
struct list_entry {
struct list_entry *prev, *next;
};
C语言里面没有类似Java的泛型,而ucore里面又想把双向循环链表做的比较通过,所以借鉴了Linux内核双向循环链表的实现,list_entry里面只有链表的节点,而没有数据。而是在具体的数据里面包含了链表节点。比如Page的数据结构:
struct Page {
int ref; // page frame's reference counter
uint32_t flags; // array of flags that describe the status of the page frame
unsigned int property; // the num of free block, used in first fit pm manager
list_entry_t page_link; // free list link
};
这样可以通过取巧的方式设计一个比较通用的双向循环链表,以及对链表操作的通用方法。但是,这样会降低代码的可读性,且增加出错的可能。
这里面比较取巧(tricky)的方法就是:le2page,下面就是le2page涉及到的结构体和宏定义。
struct Page {
int ref; // page frame's reference counter
uint32_t flags; // array of flags that describe the status of the page frame
unsigned int property; // the num of free block, used in first fit pm manager
list_entry_t page_link; // free list link
};
struct list_entry {
struct list_entry *prev, *next;
};
typedef struct list_entry list_entry_t;
/* 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)))
// convert list entry to page
#define le2page(le, member) \
to_struct((le), struct Page, member)
可以看到,le2page实现了从一个list_entry节点地址找到此节点对应的Page结构体的起始地址。Page的结构在内存中的布局大概如下图所示:
可以看到,在已知page_link地址情况下,通过page_link相对Page起始地址的偏移量相减可以找到Page地址。
通过计算(0x10AE1000C - offset(page_link在Page结构中的偏移12个字节)) = 0x01AE10000,再把0x01AE10000强转为指针即可。
对应代码里面offset这个宏定义,其实改一下栈地址很直观就能看出来:
#define offsetof(type, member) \
((size_t)(&((type *)0)->member))
对应le2page(le, page_link);
这个page_link相对的偏移量从offsetof就可以算出来:
((size_t)(&((struct Page *)0)->member)),参考下图,如果我们设想Page *p
指针的地址为0x00,那边&p->page_link为0x0C,即偏移量就为0x0C,那么对于给定的Page的链表节点list_entry_t *x,如果其地址为address(x),通过address(x) - 0x0C(offset)就能得到Page的地址。
参考:
ucore操作系统学习(二) ucore lab2物理内存管理分析 - 小熊餐馆 - 博客园 (cnblogs.com)