当前位置:   article > 正文

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) );})
  • 1
  • 2
  • 3
  • 4
  • 5

看起来很复杂,其实理解起来很简单,先说说 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;
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

那么 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
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95

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

  • 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
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52

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

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/繁依Fanyi0/article/detail/575519
推荐阅读
相关标签
  

闽ICP备14008679号