首页 > 系统相关 >linux内核链表 --20240225

linux内核链表 --20240225

时间:2024-02-25 19:33:23浏览次数:16  
标签:head struct -- list 20240225 next 链表 pos

提起linux内核链表,首先一定得弄清楚的两个linux内核常用宏offsetof && container_of offsetof宏

#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
宏解析: 1、size_t在系统中一般指unsigned int,无符号整型 2、(TYPE *)0,把0地址强制转换成type结构体类型的指针,此时0就成了type结构体的首地址,就是结构体指针,那么就可以通过结构体指针来引用该结构体的成员,所以(TYPE *)0)->MEMBER的整体意义便是引用type类型结构体的成员member。 3、&((TYPE *)0)->MEMBER的意义便是去member成员相对于0地址的偏移量 举个例子:
#include <stdio.h>

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

struct example_struct {
    int a;
    int b;
    char c;
};

int main() {
    size_t offset = offsetof(struct example_struct, b);
    printf("Offset of 'b' member: %zu\n", offset);
    return 0;
}
运行结果如下: 0   container_of宏
#define container_of(ptr, type, member) ({          \
    const typeof(((type *)0)->member)*__mptr = (ptr);    \
             (type *)((char *)__mptr - offsetof(type, member)); })
宏解析: 1、typeof 是一个在 C 语言中的编译器扩展,用于获取表达式的类型 2、const typeof(((type *)0)->member)*__mptr = (ptr); 是将ptr复制给mptr,mptr是struct中member的指针 3、(type *)((char *)__mptr - offsetof(type, member)); 是将mptr指针减去offsetof的值,那么就是返回这个struct的指针值,将 mptr 转换为 char * 是为了进行指针运算和偏移量计算 使用demo:
#include <stdio.h>

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

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

struct test_str {
    int a;
    int b;
    int c;
};

int main(void)
{
    struct test_str test;
    printf("test ptr: 0x%p, test.b ptr: 0x%p\n", &test, &test.b);
    printf("offsetof val: 0x%lx\n", offsetof(struct test_str, b));
    //通过test_str中的b指针,来返回test_str结构体指针
    printf("container_of: 0x%p\n", container_of(&test.b, struct test_str, b));
    return 0;
}
运行结果如下: 0   linux内核双向链表 在leetcode刷题一般使用的都是下面的链表使用方式:
struct node {
    unsigned int data;
    struct node *next;
    struct node *prev;
};
linux内核链表结构体如下:
struct list_head {
    struct list_head *next, *prev;
}
  具体到实际使用时候的差异:
//做一个person的管理系统,person包含了age和name
//方式1:
struct person_list {
    int age;
    char *name;
    struct person_list *next, *prev;
};

//方式2:
struct list_head {
    struct list_head *next, *prev;
};
struct person {
    int age;
    char *name;
    struct list_head list;
};
linux内核这种将链表抽象出来的实现方式,是由于Linux内核需要管理非常多的硬件设计,方式1的使用是和业务强相关的。 在linux内核中,则可以通过list来访问person的数据,使用上面提到的offsetof和container_of宏   linux内核链表使用API: 链表初始化:
static inline void
INIT_LIST_HEAD(struct list_head *list)
{
    list->next = list->prev = list;
}

// 链表静态初始化
#define LIST_HEAD_INIT(name) { &(name), &(name) }

// 链表动态初始化   
#define LIST_HEAD(name) \
    struct list_head name = LIST_HEAD_INIT(name)
  添加链表节点:
static inline void
__list_add(struct list_head *entry,
                struct list_head *prev, struct list_head *next)
{
    next->prev = entry;
    entry->next = next;
    entry->prev = prev;
    prev->next = entry;
}
-->
void __list_add(struct list_head *new,
                struct list_head *a,
                struct list_head *b)
{
        if (!__list_add_valid(new, prev, next))
                 return;
        
         b->prev = new;
         new->next = b;
         new->prev = a;
         a->next = new;
}

// 添加链表节点
static inline void
list_add(struct list_head *entry, struct list_head *head)
{
    __list_add(entry, head, head->next);
}

static inline void
list_add_tail(struct list_head *entry, struct list_head *head)
{
    __list_add(entry, head->prev, head);
}
__list_add(new, a, b)过程示意图如下: 0   删除链表节点:
static inline void __list_del(struct list_head *prev, struct list_head *next)
{
    prev->next = next;
    next->prev = prev;
}

// 链表node节点删除操作,在调用 list_del() 后,要手动释放该节点的内存
void list_del(struct list_head *entry)
{
    __list_del(entry->prev, entry->next);
    entry->next = NULL;
    entry->prev = NULL;
}

 

链表遍历:
#define list_for_each(pos, head) \
        for (pos = (head)->next; pos != (head); pos = pos->next)

#define list_for_each_entry(pos, head, member)                \
    for (pos = container_of((head)->next, typeof(*pos), member);        \
     &pos->member != (head);                    \
     pos = container_of(pos->member.next, typeof(*pos), member))

#define list_for_each_safe(pos, n, head) \
        for (pos = (head)->next, n = pos->next; pos != (head); \
                pos = n, n = pos->next)
list_for_each() 和 list_for_each_safe() 的使用区别:
  • 遍历链表时若不删除链表节点,两者都可以使用,效果没有差别;
  • 遍历链表时若需要删除链表节点,则必须使用list_for_each_safe(),否则会出错
  linux内核完整的使用demo:
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/list.h>
#include <linux/slab.h>

struct my_struct {
    int data;
    struct list_head list;
};

static struct list_head my_list;

static int my_init(void)
{
    struct my_struct *obj;
    int i;

    // 初始化链表
    INIT_LIST_HEAD(&my_list);

    // 向链表中添加节点
    for (i = 0; i < 5; i++) {
        obj = kmalloc(sizeof(struct my_struct), GFP_KERNEL);
        obj->data = i;
        list_add_tail(&obj->list, &my_list);
    }

    // 遍历链表并打印节点数据
    struct list_head *pos;
    struct my_struct *entry;
    list_for_each(pos, &my_list) {
        entry = list_entry(pos, struct my_struct, list);
        printk(KERN_INFO "Data: %d\n", entry->data);
    }

    return 0;
}

static void my_exit(void)
{
    struct list_head *pos, *q;
    struct my_struct *entry;

    // 删除链表中的所有节点并释放内存
    list_for_each_safe(pos, q, &my_list) {
        entry = list_entry(pos, struct my_struct, list);
        list_del(pos);
        kfree(entry);
    }
}

module_init(my_init);
module_exit(my_exit);

 

参考博客: https://blog.csdn.net/BigElt/article/details/131851307#comments_30341818 https://blog.csdn.net/weixin_37620587/article/details/129893469?  

标签:head,struct,--,list,20240225,next,链表,pos
From: https://www.cnblogs.com/lethe1203/p/18032772

相关文章

  • 《系统科学方法概论》第2章系统方法读后感
    又到了每天分享时间,下面分享《系统科学方法概论》第2章系统方法读后感。系统工程就是一组织建立或者是经营管理某一系统为目的的工程系统工程具有六个基本特征。复杂程度高,有一个目标体系,具有定量化特征,最优化特征,程序化特征,应用范围广。系统工程方法包括两部分内容,其一是搞系统工......
  • 刘谦魔术
    #include<iostream>#include<vector>#include<random>usingnamespacestd;random_devicerd;mt19937_64gen(rd());voidprint(vector<int>&arr){for(auto&x:arr)cout<<x<<"";......
  • maimai GreeN+
    Back2Back[13.9]非常好索尼克歌曲!非常好水13.9!个人感觉唯一难的地方是后面套圈有点蹭,同开头星星用内屏点手指划就很好黄。患部で止まってすぐ溶ける~狂気の優曇華院[13.7]经典歌曲,比较简单,最后双押发狂段很有感觉!Jack-the-Ripper◆[13.9]sasakure的经典牛逼歌曲,正是ja......
  • 加速Python代码的秘密武器,探索Cython的秘密
    首先和大家明确一下这个Cython单词的读法,这个单词Cython以前我也不知道怎么读,老后面要用到这个包的时候,老是不清楚读法,才去搜了下,这个单词是读"赛森",就是前面的cy是读"赛",后面的读法和python后一个读音thon一样。Cython是什么Cython是一个用于将Python代码转换为C或C++代码的编......
  • 用QTimeLine实现滑动动画
    一般在Qt实现动画可以用QAbstractAnimation的子类实现。这里给出一个不一样的例子实现动画,即用QTimeLine实现。功能是有一个QStackedWidget,它有两个子页面。默认显示第一页。点击“动画”按钮播放一段动画使页面第一页滑动到第二页,然后切换到第二页。程序测试环境是VS2017和Qt5.9......
  • 《程序是怎样跑起来的》第七章读后感
    程序运行的环境包括两个主要方面:操作系统和硬件。操作系统决定了程序的运行环境,而硬件则提供必要的计算和存储资源。从程序的运行环境这一角度来考量硬件时,CPU的种类是特别重要的参数。不同的CPU能解释的机器语言的种类也是不同的。机器语言的程序称为本地代码。此外,有些程序会......
  • 机器学习 (周志华西瓜书)笔记
    2.模型评估与选择2.1泛化能力模型在未见样本上表现好评价模型性能时,我们更希望他泛化能力强2.2过拟合和欠拟合泛化误差:在未来样本上的误差经验误差(训练误差):在训练集上的误差过拟合Overfitting:把不该学的学进去了机器学习关键:怎样缓解Overfitting2.3三大问题2.3.1评......
  • leedcode 买卖股票的最佳时机
    暴力解法,最后内存爆了classSolution:defmaxProfit(self,prices):n=len(prices)ifn==1:return0ifn>1000:return3profit=[]foriinrange(n):cur=i+1whilecur......
  • Arrow和ArrowStream格式的区别
    Arrow是Apache软件基金会的一个顶级项目,它提供了一种内存布局格式,用于在不同系统之间高效地共享数据。Arrow旨在提供一种跨平台、跨语言的数据交换格式,以便在大数据处理和分析领域中提高数据处理效率。在Arrow中,数据可以被序列化为不同的格式,其中两种主要格式是Ar......
  • 让计算机思考
    程序就如同是由计算机执行的各种指令罗列起来的文章。计算机内部的CPU,通过对文章的内容进行解析运行,控制连接到计算机的各种外围设备。控制:指CPU和各种设备之间配合进行数据的输入输出处理。程序的使用目的有两类,一类是大家作为工具使用的程序,一类是用程序代替执行人类的思考过......