通用双向循环链表学习

双向循环链表是很常用的一种数据结构。其结构如下图所示:

双向循环链表

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的地址。

image-20220225165227145

参考:

双向循环链表

ucore操作系统学习(二) ucore lab2物理内存管理分析 - 小熊餐馆 - 博客园 (cnblogs.com)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值