Linux内核双向链表

Linux内核双向链表思想

Linux双向链表简介

Linux 内核双向链表其实是双向循环链表,它使用了面向对象的思想,内核提供一个基类,然后由我们来继承这个基类,进行内容的拓展。那么对于内核来说,只需要提供最简单地链表指向信息,并只用维护这个最简单的链表。
在这里插入图片描述构建双向循环链表
在这里插入图片描述

通过子类找父类

我们要操作链表,肯定需要知道我们定义的结构体的地址,但是内核只知道父类节点的地址,它是怎么通过父类节点的地址找到子类节点的呢?
这主要是通过内核中定义的两个宏。(内核双向链表源码在内核源码目录 include/linux/list.h 中)

#define offsetof(TYPE, MEMBER) ((size_t) & ((TYPE *)0)->MEMBER)

#define container_of(ptr, type, member) ({			\
	const typeof( ((type *)0)->member ) *__mptr = (ptr);	\
	(type *)( (char *)__mptr - offsetof(type,member) );})

看起来很复杂,其实理解起来很简单,先说说 offsetof(TYPE, MEMBER)

  • 第一个参数是我们自定义结构体的名字,如第一张图的 struct stu
  • 第二个参数是我们自定义结构体中父类的变量名,如第一张图的 list

也很好理解,就是找到 MEMBERTYPE 中的偏移量

  • 首先,(TYPE *)0 把零地址转换成 TYPE 类型
  • 然后 (TYPE *)0)->MEMBER 就是访问结构体下的 MEMBER 成员
  • & 符号是取地址符号,就是取出 TYPE 结构体下 MEMBER 成员的地址,因为结构体的基地址是 0,所以 MEMBER 成员的地址就相当于是其在整个结构体中的偏移量
  • size_t 就是转换类型
    在第一张图的例子中
struct list_head {
    struct list_head *next, *prev;
};

struct stu {
    int age;
    struct list_head list;
};

那么 offsetof(struct stu, list) 就应该等于 4,因为 list 成员相对于整个结构体偏移量是一个 int 类型,也就是 4 字节
接下来看看 container_of
先来看看参数

  • ptr 就是父类的地址
  • type 就是自定义结构体的类型
  • member自定义结构体中父类成员的成员名

对于其内容

  • const typeof( ((type *)0)->member ) *__mptr = (ptr); 就相当于定义一个父类类型的指针,并且指向 ptr
  • (type *)( (char *)__mptr - offsetof(type,member) );}) offsetof 得到父类成员的偏移量,mptr 是某个子类节点中父类成员的地址,父类成员地址减去偏移量,就得到了子类节点的地址

总之,通过 container_of 宏,就能得到我们想要得到的自定义的结构体类型的地址

优点

通过这种双向链表的思想,我们对链表的增删改查的遍历操作,相当于在对父类节点进行操作,就不需要对子节点进行操作
当需要设计多个结构体时,就不需要对每一个结构体设计一套增删改查操作
例如,有学生结构体和老师结构体时,如果我们按照以往的思路,设计两个结构体,那么需要对每两个结构体的增删改查都要设计函数
但是,当使用面向对象的思想时,我们通过继承父类来创建学生和老师结构体,只需要一套增删改查的函数即可,因为,对整个链表的增删改查操作,实际上都是对父类节点的操作,并没有通过子类来指向
当有很多个结构体时,这种思想就可以大大减少工作量

实例

接下来,看一个实例
这里是把内核中的 list.h 文件拿出来,把内核相关的东西删除掉,就可以在应用层使用内核的双向链表了
list.h

#ifndef K_LIST_X_LIST_H
#define K_LIST_X_LIST_H
/**
 * 双向循环链表的节点结构
 */
struct list_head {
    struct list_head *next, *prev;
};
/**
 * INIT_LIST_HEAD - 初始化双向循环链表头信息
 * @list: 链表头节点
 * 传递用户自定义结构中链表数据成员
 * */
static inline void INIT_LIST_HEAD(struct list_head *list)
{
    list->next = list;
    list->prev = list;
}
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;
}
/**
 * list_add - add a new entry
 * @new: new entry to be added
 * @head: list head to add it after
 *
 * Insert a new entry after the specified head.
 * This is good for implementing stacks.
 */
static inline void list_add(struct list_head *new, struct list_head *head)
{
    __list_add(new, head, head->next);
}
/**
 * list_add_tail - add a new entry
 * @new: new entry to be added
 * @head: list head to add it before
 *
 * Insert a new entry before the specified head.
 * This is useful for implementing queues.
 */
static inline void list_add_tail(struct list_head *new, struct list_head *head)
{
    __list_add(new, head->prev, head);
}
static inline void __list_del(struct list_head * prev, struct list_head * next)
{
    next->prev = prev;
    prev->next = next;
}
static inline void list_del(struct list_head *entry)
{
    __list_del(entry->prev, entry->next);
    entry->next = NULL;
    entry->prev = NULL;
}
#define offsetof(TYPE, MEMBER) ((size_t) & ((TYPE *)0)->MEMBER)

#define container_of(ptr, type, member) ({			\
	const typeof( ((type *)0)->member ) *__mptr = (ptr);	\
	(type *)( (char *)__mptr - offsetof(type,member) );})

#define list_entry(ptr, type, member) \
	container_of(ptr, type, member)

#define list_first_entry(ptr, type, member) \
	list_entry((ptr)->next, type, member)

#define list_next_entry(pos, member) \
	list_entry((pos)->member.next, typeof(*(pos)), member)
/**
 * list_for_each_entry	-	iterate over list of given type
 * @pos:	the type * to use as a loop cursor.
 * @head:	the head for your list.
 * @member:	the name of the list_struct within the struct.
 */
#define list_for_each_entry(pos, head, member)				\
	for (pos = list_first_entry(head, typeof(*pos), member);	\
	     &pos->member != (head);					\
	     pos = list_next_entry(pos, member))
/**
 * list_for_each	-	iterate over a list
 * @pos:	the &struct list_head to use as a loop cursor.
 * @head:	the head for your list.
 */
#define list_for_each(pos, head) \
	for (pos = (head)->next; pos != (head); pos = pos->next)

#endif //K_LIST_X_LIST_H

这里再对头文件里的函数和宏进行一个说明。增删就不多说了,这里讲一下遍历。通常使用的变量方法有两个

  • list_for_each 就相当于遍历的是父类成员,需要自己调用 container_of 获得子节点
  • list_for_each_entry 就是直接遍历的子类节点,可以直接操作子节点

main.c

#include <stdio.h>
#include <stdlib.h>
#include "xlist.h"

struct stu {
    int age;
    struct list_head list;
};

struct stu stu_head;  //头结点(不存储数据)

int main()
{
    struct stu *p;
    struct list_head *find; //父节点遍历指针

    // 初始化头结点
    INIT_LIST_HEAD(&stu_head.list);
    stu_head.age = 0;

    //插入5个数据,使用头插法,类似于栈
    for (int i = 0; i < 5; i++) {
        p = (struct stu *)malloc(sizeof(struct stu));
        p->age = 10 + i;
        list_add(&p->list, &stu_head.list);
    }

    //通过父节点遍历链表
    list_for_each(find, &stu_head.list) {
        p = container_of(find, struct stu, list);  //通过父节点找到子节点
        printf("age is : %d\n", p->age);
        if (p->age == 13) { //遍历到第三个退出训循环
            break;
        }
    }

    // 删除第三个节点
    list_del(&p->list);

    //释放空间
    free(p);
    p = NULL;

    printf("----------------\n");

    //直接遍历子节点
    list_for_each_entry(p, &stu_head.list, list) {
        printf("the stu age is : %d\n", p->age);
    }

    return 0;
}

运行结果
在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值