【Linux内核】内核常用链表宏解释

这篇具有很好参考价值的文章主要介绍了【Linux内核】内核常用链表宏解释。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

1、list_for_each_entry_safe

list_for_each_entry_safe,链表,linux,数据结构
这段代码是一个宏定义,用于遍历一个链表中所有的元素,并且在遍历过程中可以安全地删除元素。具体来说,这个宏定义的功能是:

  1. 遍历链表中所有的元素,从头节点开始,直到尾节点结束。

  2. 对于每个元素,使用给定的结构体成员变量名找到它所属的结构体对象,并且将该对象的指针赋值给给定的变量名。

  3. 在遍历过程中,可以安全地删除当前元素,因为它在删除前会先保存下一个元素的指针,保证不会影响遍历的正确性。

下面是这个宏定义的详细解释:

  1. 参数解释:
  • pos:用于保存当前遍历到的元素的指针。
  • tmp:用于保存下一个元素的指针,以便在删除当前元素后继续遍历。
  • head:链表的头节点。
  • member:链表节点所在的结构体成员变量名。
  1. 宏定义解释:

首先,使用 __container_of 宏定义将头节点的下一个节点转换为链表节点的指针,再使用 __container_of 宏定义将该节点的成员变量 member 转换为其所属的结构体对象,并且将该对象的指针赋值给 pos 变量。

接着,使用 __container_of 宏定义将 pos 的成员变量 member.next 转换为下一个节点的指针,并且将该节点的指针赋值给 tmp 变量。

然后,使用 & 操作符判断当前节点是否为链表的尾节点。如果不是尾节点,就执行循环体中的代码,否则跳出循环。

在循环体中,首先将 tmp 的成员变量 member.next 转换为下一个节点的指针,并且将该节点的指针赋值给 tmp 变量,以便在删除当前节点后继续遍历。

然后,执行用户自定义的代码,该代码可以访问当前节点所属的结构体对象,并且可以安全地删除当前节点。

举例说明:

假设有一个链表,链表节点的结构体定义如下:

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

其中,list 是链表节点的成员变量名。如果要遍历该链表并且可以安全地删除节点,可以使用 list_for_each_entry_safe 宏定义,代码如下:

struct list_head head;
struct my_node *pos, *tmp;

// 遍历链表中的所有元素,并且可以安全地删除元素
list_for_each_entry_safe(pos, tmp, &head, list) {
// 访问当前节点所属的结构体对象
printf(“data = %d\n”, pos->data);

// 安全地删除当前节点
list_del(&pos->list);
free(pos);

}

在上面的代码中,list_del 函数用于删除当前节点,并且在删除前会先保存下一个节点的指针,以保证遍历的正确性。

2、container_of

list_for_each_entry_safe,链表,linux,数据结构
这是一个非常常见的宏定义,用于从某个结构体成员变量的指针中获取整个结构体的指针。这个宏在内核编程中非常常用,因为内核中经常需要通过成员变量的指针来访问整个结构体的其他成员变量。

宏定义的格式如下:

#define container_of(ptr, type, member) \
    (type *)((char *)(ptr) - (char *) &((type *)0)->member)

宏定义了三个参数:

  • ptr:指向某个结构体成员变量的指针;
  • type:结构体的类型;
  • member:结构体中的成员变量名。

宏的实现原理是利用了C语言中的指针算术运算和结构体的内存布局。在结构体中,每个成员变量的地址相对于结构体的起始地址都是固定的。因此,我们可以通过计算某个成员变量相对于结构体起始地址的偏移量,来计算出整个结构体的地址。

宏定义的实现步骤如下:

  • 将0强制转换为type类型的指针;
  • 取出member成员变量的地址;
  • 用指向该成员变量的指针减去该员变量在结构体中的偏移量,得到整个结构体的起始地址;
  • 将该地址强制转换为type类型的指针,并返回。

例如,假设有一个结构体struct my_struct,其中包含一个成员变量int my_field。如果我们有一个指向my_field的指针int *ptr,我们可以使用container_of宏来计算整个结构体的地址:

struct my_struct *s = container_of(ptr, struct my_struct, my_field);

这样,指针s就指向了包含my_field成员变量的整个结构体。从而我们可以通过s来访问整个结构体的其他成员变量。

举例:

为了更好地理解这个宏的实现原理,我们可以通过一个例子来说明。假设有如下的结构体定义:

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

现在我们定义一个指向b成员变量的指针int *ptr,我们想要通过ptr来获取整个结构体的指针。我们可以使用container_of宏来计算整个结构体的地址:
假设我们定义了一个指向b成员变量的指针int *ptr,我们想要通过ptr来获取整个结构体的指针。我们可以使用container_of宏来计算整个结构体的地址:

struct my_struct *s = container_of(ptr, struct my_struct, b);

现在我们来逐步分析一下container_of宏的实现过程:

  1. 首先,我们将0强制转换为struct my_struct *类型的指针,得到一个指向结构体的空指针。

    (struct my_struct *)0
    
  2. 然后,我们取出b成员变量的地址,这可以通过&((struct my_struct *)0)->b来实现。由于我们已经将0强制转换为struct my_struct *类型的指针,因此这个表达式实际上是取出了结构体中b成员变量的地址相对于结构体起始地址的偏移量。

    &((struct my_struct *)0)->b
    
  3. 接下来,我们将指向b成员变量的指针ptr强制转换为char *类型的指针,然后减去上一步中计算出的偏移量。由于指针减法的结果是以指针类型的大小为单位的,因此我们需要将结果强制转换为struct my_struct *类型的指针。

    (struct my_struct *)((char *)ptr - (char *)&((struct my_struct *)0)->b)
    

    这个表达式相当于将ptr指向的地址向前偏移了b成员变量相对于结构体起始地址的偏移量,从而得到整个结构体的起始地址。

  4. 最后,我们将上一步中计算出的地址强制转换为struct my_struct *类型的指针,并返回该指针。

    (struct my_struct *)((char *)ptr - (char *)&((struct my_struct *)0)->b)
    

    这样,我们就得到了一个指向整个结构体的指针,从而可以通过该指针来访问结构体的其他成员变量。

3、list_for_each_entry_rcu

list_for_each_entry_safe,链表,linux,数据结构
这是一个宏定义,用于在读取RCU(Read-Copy-Update)保护的链表中遍历每个元素。

具体来说,这个宏定义了一个循环,其中:

  • pos 是当前元素的指针;
  • head 是链表头的指针;
  • member 是链表节点结构体中表示下一个元素的成员变量的名称;
  • cond... 是可选的条件语句,用于在遍历时进行过滤。

宏定义的实现中,首先调用了一个名为 __list_check_rcu 的函数,用于检查 cond 中是否有语法错误。这个函数的具体实现可以参考 Linux 内核源码中的 include/linux/list.h 文件。

然后,在循环中,首先将 pos 初始化为链表头的下一个元素,即第一个要遍历的元素。这里使用了 list_entry_rcu 宏,将链表节点结构体的指针转换为包含该节点的数据结构的指针,以便于访问该元素的数据。

接着,在循环条件中,检查当前元素的下一个元素是否为链表头,如果是,则说明已经遍历完了整个链表,循环结束。

最后,在循环体中,使用 list_entry_rcu 宏获取当前元素的下一个元素,并将其赋值给 pos,继续循环。这里同样使用了包含节点的数据结构的指针,以便于访问下一个元素的数据。

总之,这个宏定义非常方便地实现了在读取RCU保护的链表中遍历每个元素的功能,而且还支持条件过滤,非常实用。

4、list_last_entry

list_for_each_entry_safe,链表,linux,数据结构
这是一个宏定义,用于获取链表中最后一个元素的数据结构指针。

具体来说,这个宏定义了三个参数:

  • ptr 是链表的指针,表示要获取最后一个元素的链表;
  • type 是数据结构的类型,表示链表节点所包含的数据结构的类型;
  • member 是链表节点结构体中表示下一个元素的成员变量的名称。

宏定义的实现中,使用了 list_entry 宏,将链表最后一个节点的指针转换为包含该节点的数据结构的指针。具体来说,宏定义使用了链表最后一个节点的前一个节点的指针 ptr->prev,然后将其转换为包含该节点的数据结构的指针,即 list_entry((ptr)->prev, type, member)

这个宏定义非常方便地实现了获取链表中最后一个元素的数据结构指针的功能,可以在需要遍历链表时,从最后一个元素开始往前遍历。需要注意的是,这个宏定义假设链表不为空,否则在空链表上调用它将会导致错误。

5、WARN相关

list_for_each_entry_safe,链表,linux,数据结构这些都是 Linux 内核中的宏定义,在处理错误和警告时使用。

  1. #define WARN_ON_ONCE(condition) WARN_ON(condition)

这个宏定义表示:如果条件 condition 为真,则打印警告信息。与 WARN_ON() 不同的是,这个宏只会打印一次警告信息,即便 condition 多次为真也只会打印一次。

  1. #define WARN_ONCE(condition, format…) WARN(condition, format)

这个宏定义表示:如果条件 condition 为真,则打印警告信息。与 WARN() 不同的是,这个宏只会打印一次警告信息,即便 condition 多次为真也只会打印一次。同时,这个宏还可以传入格式化字符串 format,用于打印更详细的警告信息。

  1. #define WARN_TAINT(condition, taint, format…) WARN(condition, format)

这个宏定义表示:如果条件 condition 为真,则打印警告信息,并将污点标记 taint 与之关联。污点标记是一种标记机制,用于标记内核中的某些数据或操作是不可信的或者有风险的。通过将污点标记与警告信息关联,可以更加精确地追踪和排查问题。

  1. #define WARN_TAINT_ONCE(condition, taint, format…) WARN(condition, format)

这个宏定义表示:如果条件 condition 为真,则打印警告信息,并将污点标记 taint 与之关联。与 WARN_TAINT() 不同的是,这个宏只会打印一次警告信息,即便 condition 多次为真也只会打印一次。同时,这个宏还可以传入格式化字符串 format,用于打印更详细的警告信息。

6、offset_in_page

list_for_each_entry_safe,链表,linux,数据结构
这是一个宏定义,定义的作用是计算指针 p 所指向的地址在所在页面中的偏移量。

具体来说,PAGE_SIZE 是一个常量,表示页面的大小,通常为 4KB。而 offset_in_page(p) 宏定义中的 (unsigned long)p 表示将指针 p 转换为一个无符号长整型数,然后对 PAGE_SIZE 取模运算,得到的结果就是指针 p 所指向的地址在所在页面中的偏移量。

例如,假设 PAGE_SIZE 为 4KB,指针 p 指向地址 0x12345678,那么 offset_in_page(p) 的值就是 0x678,即指针 p 所指向的地址在所在页面中的偏移量为 0x678。这个宏定义通常用于内核中对页面进行操作时,需要计算出页面中某个地址的偏移量,以便进行相应的处理。文章来源地址https://www.toymoban.com/news/detail-787626.html

到了这里,关于【Linux内核】内核常用链表宏解释的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Linux内核代码中常用的数据结构

    Linux内核代码中广泛使用了数据结构和算法,其中最常用的两个是链表和红黑树。 Linux内核代码大量使用了链表这种数据结构。链表是在解决数组不能动态扩展这个缺陷而产生的一种数据结构。链表所包含的元素可以动态创建并插入和删除。 链表的每个元素都是离散存放的,

    2024年02月12日
    浏览(32)
  • 【Linux操作系统】举例解释Linux系统编程中文件io常用的函数

    在Linux系统编程中,文件IO操作是非常常见和重要的操作之一。通过文件IO操作,我们可以打开、读取、写入和关闭文件,对文件进行定位、复制、删除和重命名等操作。本篇博客将介绍一些常用的文件IO操作函数。 1.1 原型、参数及返回值说明 1.1.1 原型: open()函数是Linux系统

    2024年02月12日
    浏览(46)
  • 深入讲解Linux内核中常用的数据结构和算法

    Linux内核代码中广泛使用了数据结构和算法,其中最常用的两个是链表和红黑树。 Linux内核代码大量使用了链表这种数据结构。链表是在解决数组不能动态扩展这个缺陷而产生的一种数据结构。链表所包含的元素可以动态创建并插入和删除。链表的每个元素都是离散存放的,

    2023年04月16日
    浏览(39)
  • 内核链表的使用

    目录 📎list.rar 链表的作用 使用list.h使用例 练习 答案: 链表在动态内存分配、插入删除操作、构建其他数据结构、高效查找、缓存和回收等方面发挥着重要作用。它是一种灵活、高效的数据结构,适用于各种场景和问题的解决方案。 下面是一个简单的链表应用: 假设学生

    2024年02月11日
    浏览(30)
  • 【脚踢数据结构】内核链表

    (꒪ꇴ꒪ ),Hello我是 祐言QAQ 我的博客主页:C/C++语言,Linux基础,ARM开发板,软件配置等领域博主🌍 快上🚘,一起学习,让我们成为一个强大的攻城狮! 送给自己和读者的一句鸡汤🤔: 集中起来的意志可以击穿顽石! 作者水平很有限,如果发现错误,可在评论区指正,感谢🙏

    2024年02月13日
    浏览(36)
  • freertos内核原理学习 Day1(链表)

    目录 1.freertos列表与列表操作 1.1链表各节点定义(头文件list.h中) 1.1.1普通节点定义 1.1.2mini节点定义 1.1.3根节点定义 1.2链表操作(源文件list.c中) 1.2.1链表节点初始化  1.2.2链表根节点初始化   1.2.3插入节点到链表尾部   1.2.4将节点按“升序”排列后插入到链表中   1.2.

    2024年01月23日
    浏览(35)
  • 利用C++超详细解释数据结构中的链表

    链表(Linked List)是一种常见的数据结构,它可以动态地插入和删除元素,不需要像数组那样预先分配固定大小的内存。链表中的每个元素称为节点(Node),每个节点包含一个数据值和一个指向下一个节点的指针。本教学将涵盖以下知识点: 单向链表(Singly Linked List) 双向

    2024年02月04日
    浏览(30)
  • 基于内核链表和JSON的MQTT的使用

    假设学生结构体信息如下,封装一个 单链表的插入接口 和 遍历输出的接口 ,在主函数中利用封装的接口生成一个学生链表,并遍历输出链表的学生信息。 2.1 定义 linux内核链表与众不同,他不是把将数据结构塞入链表,而是将链表节点塞入数据。链表实现定义在linux/list.h

    2024年02月15日
    浏览(32)
  • 内核链表在用户程序中的移植和使用

    相比于下面这样初始化,前面初始化的好处是,处理链表的时候,不用判空了。太厉害了。 基本功能就这些了

    2024年02月15日
    浏览(35)
  • 嵌入式内核链表list_head,如何管理不同类型节点的实现

             在Linux内核中,提供了一个用来创建双向循环链表的结构 list_head。虽然linux内核是用C语言写的,但是list_head的引入,使得内核数据结构也可以拥有面向对象的特性,通过使用操作list_head 的通用接口很容易实现代码的重用,有点类似于C++的继承机制(希望有机会写篇

    2024年02月20日
    浏览(49)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包