Linux内核链表代码分析

Linux内核设计与实现中看到的链表相关内容进行记录
内容参考: C语言高级用法—container_of().

Linux内核中链表的实现

Linux中将链表节点插入数据结构而非在数据结构中塞入链表

链表结构如下
struct list_head{
	struct list_head *next;
	struct list_head *prev;
};

以数据结构举例如下

struct fox{
	int data;
	struct list_head list;
}

container_of

函数原型如下

/**
 * container_of - cast a member of a structure out to the containing structure
 *
 * @ptr:        the pointer to the member.
 * @type:       the type of the container struct this is embedded in.
 * @member:     the name of the member within the struct.
 *
 */
#define container_of(ptr,type,member)({							\
	const typeof( ((type *) 0->member  ) *__mptr =(ptr);	\
	(type *)( (char *)__mptr - offsetof( type,member ) );})

参数如下
ptr:结构体变量中某个成员的地址
type:结构体类型
member:该结构体变量的具体名字

接下来一步步分析代码

typeof()

typeof() 是GUN C提供的一种特性,可参考C-Extensions,它可以取得变量的类型,或者表达式的类型。
typeof() 返回参数的类型,如果参数为函数,不执行函数而是得到函数返回类型

struct a* foo(){
	...
	do something
}

typeof(foo()) bar;

bar得到的类型为 a*;
在宏的用法中

#define max(x, y) ({                \
    typeof(x) _max1 = (x);          \
    typeof(y) _max2 = (y);          \
    (void) (&_max1 == &_max2);      \//如果调用者传参时,两者类型不一致,在编译时就会发出警告。
    _max1 > _max2 ? _max1 : _max2; })

首先定义和x相同类型的变量 _max1,再将x的值赋给_max1

(void) (&_max1 == &_max2);

typeof( ((type *)0)->member )

ANSI C标准允许值为0的常量被强制转换成任何一种类型的指针,并且转换的结果是个NULL,因此((type *)0)的结果就是一个类型为*type 的NULL指针.

如果利用这个NULL指针来访问type的成员当然是非法的,但*typeof( ((type *)0)->member )*是想取该成员的类型,编译器不会生成访问type成员的代码,类似的代码&( ((type *)0)->member )在最前面有个取地址符&,它的意图是想取member的地址,所以编译器同样会优化为直接取地址。

struct apple{
    int weight;
    int color;
};

int main(int argc, char *argv[])
{
    int weight = 5;

    typeof(((struct apple *)0)->weight) *ap1 = &weight;//定义一个指针变量ap1, ap1的类型为apple成员weight的类型。
    printf("ap1's value:%d\n", *ap1);
}

ap1’s value:5

offset_of()

offset_of()的作用是获取结构体中某个成员相对于该结构体首元素地址的偏移量。

#define offset_of(type, memb) \
        ((unsigned long)(&((type *)0)->memb))

内核中链表的遍历

如前所示,内核将链表节点嵌入数据结构,已有list的地址情况下,为了获取数据结构的成员,通过container_of()可以获取到父结构中包含的变量。
举例如下

{
    struct ipstore ip1;
    struct ipstore *p1;

    p1 = container_of(&ip1.list, struct ipstore, list);

    printf("ip1's addr:0x%0x\n", &ip1);
    printf("p1's  addr:0x%0x\n", p1);
}

ip1’s addr:0xa5fe1fe0
p1’s addr:0xa5fe1fe0

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
一、linux内核链表 1、普通链表的数据区域的局限性 之前定义数据区域时直接int data,我们认为我们的链表中需要存储的是一个int类型的数。但是实际上现实编程中链接中的节点不可能这么简单,而是多种多样的。 一般实际项目中的链表,节点中存储的数据其实是一个结构体,这个结构体中包含若干的成员,这些成员加起来构成了我们的节点数据区域。 2、一般性解决思路:即把数据区封装为一个结构体 (1)因为链表实际解决的问题是多种多样的,所以内部数据区域的结构体构成也是多种多样的。 这样也导致了不同程序当中的链表总体构成是多种多样的。 我们无法通过一套泛性的、普遍适用的操作函数来访问所有的链表,意味着我们设计一个链表就得写一套链表的操作函数(节点创建、插入、删除、遍历……)。 (2)实际上深层次分析会发现 不同的链表虽然这些方法不能通用需要单独写,但是实际上内部的思路和方法是相同的,只是函数的局部地区有不同。 实际上链表操作是相同的,而涉及到数据区域的操作就有不同 (3)问题 能不能有一种办法把所有链表中操作方法里共同的部分提取出来用一套标准方法实现,然后把不同的部分留着让具体链表的实现者自己去处理。 3、内核链表的设计思路 (1)内核链表中实现一个纯链表的封装,以及纯链表的各种操作函数 纯链表就是没有数据区域,只有前后向指针; 各种操作函数是节点创建、插入、删除、遍历。 这个纯链表本身自己没有任何用处,它的用法是给我们具体链表作为核心来调用。 4、list.h文件简介 (1)内核中核心纯链表的实现在include/linux/list.h文件中 (2)list.h中就是一个纯链表的完整封装,包含节点定义和各种链表操作方法。 二、内核链表的基本算法和使用简介 1、内核链表的节点创建、删除、遍历等 2、内核链表的使用实践 (1)问题:内核链表只有纯链表,没有数据区域,怎么使用? 使用方法是将内核链表作为将来整个数据结构的结构体的一个成员内嵌进去。类似于公司收购,实现被收购公司的功能。 这里面要借助container_of宏。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值