1.3 Linux内核中的双链表结构

1.双链表的演化

  • 在C语言中,一个基本的双向链表定义如下:
struct my_list {
	void * mydata;
	struct my_list *next;
	struct my_list *prev;
};
  • 图解如下
    在这里插入图片描述
  • prev称为前驱指针,next称为后继指针。这种结构可以从两个方向遍历链表从而提高效率。
  • 为什么要对双链表作为重点介绍?对于一个双链表
    • 如果减少一个指针域就可以退化成一个单链表
    • 如果我们只能对链表的首尾进行插入和删除就可以退化成一个队结构
    • 如果我们只能对链表的头进行操作就可以退化成一个栈结构
    • 如果前驱和后继表示左右孩子化就可以退化成一个二叉树

2. Linux内核双链表

2.1 定义

  • Linux内核对链表的实现方式与众不同,在链表中并不包含数据,其具体的定义如下:
struct list_head {
	struct list_head *next, *prev;
};
  • 这个链表结构常常被嵌入到其他结构体中,比如:
struct my_list {
	void * mydata;
	struct list_head list;
};
  • 说明:list域隐藏了链表的指针特性以struct list_head为基本对象,可以对链表进行插入、删除、合并以及遍历等各种操作,这些操作位于内核的头文件include/linux/list.h中

2.2 链表的声明和初始化

  • 内核代码list.h中定义了两个宏:
#define LIST_HEAD_INIT(name) { &(name), &(name) }/*仅初始化*/

#define LIST_HEAD(name) \
        struct list_head name = LIST_HEAD_INIT(name) /*声明并初始化*/

2.3 链表中增加一个节点

  • 源码存放在include/linux/list.h中
static inline void list_add();
static inline void list_add_tail();
  • list_add()和list_add_tail()均调用__list_add(),可简化为
static inline void __list_add(struct list_head *new,
                              struct list_head *prev,
                              struct list_head *next)
{
        next->prev = new;
        new->next = next;
        new->prev = prev;
        prev->next = new;
}
  • 图解如下:
    在这里插入图片描述

2.4 遍历链表

#define list_for_each(pos, head) \
        for (pos = (head)->next; pos != (head); pos = pos->next)

2.5 通过成员偏移获得结构体的首地址

list_entry(ptr, type, member) ({ \
const typeof( ((type *)0)->member ) *__mptr = (ptr);    \
(type *)( (char *)__mptr - offsetof(type,member) );})
简化后
list_entry(ptr, type, member) \
((type *)((char *)(ptr) - (unsigned long)(&((type *)0)->member)))
  • ptr代表成员member所在的位置

  • type代表这个结构体的类型

  • member是这个结构体的一个成员

  • 通过这个宏我们可以获得这个结构体的起始地址

  • 如下图
    在这里插入图片描述

  • list_entry应该这么理解呢,它是如何计算出结构体头的地址的呢?

  • 首先要摒弃指针地址加减没有意义的概念。这样我们就可以得到一个公式。

  • 结构体首地址的绝对地址=任意结构体成员的绝对地址-任意结构体成员的相对地址

  • 我们可以很容易的看出(char *)ptr是绝对地址,问题就在于相对地址这么求了。

  • 可以这么想如果把这个结构体放到0地址的地方去算成员的偏移,此时的偏移地址既是绝对地址又是相对地址,此时相对地址等于绝对地址,因为相对地址 + 0地址 = 绝对地址。

  • 所以&((type *)0->member)就是相对地址。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值