linux中的数据结构之链表

1. list_head

linux的内核实现中使用了很多数据结构,如链表,队列,红黑树等。其中链表的实现很独特,它将链表的前后指针提了出来,定义为一个结构体,然后包含该结构体的数据结构就成为了链表上的一员。即实现了链表指针与数据的分离,使其更具有通用性。其数据结构如下:

struct list_head{
    struct list_head *pre;
    struct list_head *next;
}

例如,在通常版本中,如果定义一个列车的链表,是这样的:

struct train{
    char *color;
    int capacity;
    //...other member
    struct train *pre;
    struct train *next;
}

很明显,它在某个特定的结构体(train)当中定义了指向该类型结构体的指针(train *)。而如果使用linux内核的做法,是这样的:

struct train{
    char *color;
    int capacity;
    //...other memner
    list_head *node;
}

node中存放着链表的前后指针,而不代表任何具体的数据类型,它不关心节点上的数据是什么,只关注前后节点,这种抽象是很独特的且很巧妙的。

2. 获取数据

那么问题可能来了,如果链表的指针只是单纯地指向list_head这样的数据结构,它如何访问链表节点上的数据呢,在上例中,就是如何访问train的成员呢?

显然linux考虑过这个问题,于是使用了两个更为巧妙的宏来实现对节点数据的访问:offsetofcontainer_of

2.1 offsetof

offsetof用来获得结构体中成员的偏移量,宏定义如下:

//定义在 stddef.h 中
#define offsetof(type, member) (size_t)&(((type*)0)->member)

很巧妙吧,它将地址0强制转换成type类型的指针,也就是说该结构体从地址0处开始存放,然后访问其成员member,并对其取地址,取得的地址就是该成员member 相对0地址的偏移量。

2.2 container_of

container_of 提供了从成员变量地址获得结构体地址的方法,宏定义如下:

//定义在 linux/kernel.h 中
#define container_of(ptr, type, member)({ \
    const typeof(((type *)0)->member) *__mptr = (ptr); \
    (type *) ((char *)__mptr - offsetof(type, member));})

依然很巧妙,使用一个member 类型的指针__mptr保存member 的实际地址,然后用该实际地址减去member 在结构体type 中的偏移量,即可获得结构体的实际地址。其中typeof 操作符提供了编译时的多态,它是gcc的一个扩展,用于获取指定表达式结果的类型[1]

3. 待续

好了,现在我们拥有了独立于数据之外的链表,以及能够从成员获取结构体地址的宏,之前的问题也可以解决了:linux通过获取list_head的地址,可以得到结构体的地址,进而顺利访问结构体的成员。
那么还有一个疑问:结构体的偏移量是在编译时期就确定的吗?回答是是的,要不然这些宏不可能起作用,事实上是在ABI阶段就确定了。

链表的添加和删除操作等以后有机会在看一下,毕竟这是链表最重要的操作了。

参考资料

[1] gcc typeof在kernel中的使用
[2]《linux内核设计与实现》
[3] offsetof与container_of宏

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值