Linux 内核常见的宏(1):offsetof 和 container_of分析

简介: Linux 内核常见的宏(1):offsetof 和 container_of分析

hello, 大家好,今天和大家一起学习 Linux 内核中常见的两个宏 offsetof 和 container_of。对于初学者,很容易弄懵逼。


offsetof


定义:include/linux/stddef.h ;功能:给定一个TYPE结构和其成员,获取其成员相对于首地址的偏移。


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


分析: ((size_t) &((TYPE *)0)->MEMBER),假设TYPE 数据类型如下:


typedef struct student {
    char name[32];
    int age;
};


第一步:我们先看最里层((TYPE*)0)。实际上是将0地址强转为TYPE结构的指针。指向一个TYPE类型的结构体变量,这里就是 student 类型。


第二步:((TYPE*)0)->MEMBER。(TYPE*)0 表示一个TYPE类型的结构体指针。通过指针来访问这个结构体变量的MEMBER元素。student->age


第三步:&((TYPE*)0)->MEMBER 等效于 &(((TYPE*)0)->MEMBER) - &((TYPE *)0)


# 这里取巧 利用了 0地址的结构体指针变量。
&((TYPE*)0)->MEMBER =  &(((TYPE*)0)->MEMBER) - &((TYPE *)0)


代入student,去理解。offsetof(student, age),实际上就是获取相对于student首地址的偏移。


实验验证:


#include <stdio.h>
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
typedef struct _student1 {
    char name[32];
    int age;
} student1_t;
typedef struct _student2 {
    char name[32];
    int age;
    float score;                     
} student2_t;
int main()
{
    size_t addr = 0;
    addr = offsetof(student1_t, age);
    printf("student1_t addr = %d\n", addr);
    addr = offsetof(student2_t, score);
    printf("student2_t addr = %d\n", addr);
    return 0;
}


可以看到,addr输出的值分别对应age 和 score 相对于其结构体首地址的偏移。

思考一个问题,这样做的好处是什么呢?


container_of 宏


在看另外一个宏container_of 宏。


定义:include/linux/kernel.h


/**
 * container_of - cast a member of a structure out to the containing structure
 * @ptr:             the pointer to the member.
 * @type:           the type of the container struct this is embedded in.
 * @member:     the name of the member within the struct.
 *
 */
#define container_of(ptr, type, member) ({        \ 
    const typeof( ((type *)0)->member ) *__mptr = (ptr);    \
    (type *)( (char *)__mptr - offsetof(type,member) );})


分析containr_of 宏,我们还是以student结构体为例代入:


第一步:先看第一段。


const typeof( ((type *)0)->member ) *__mptr = (ptr); 
 // 等价于
 const typeof(A) *__mptr = (ptr);


继续拆开:A =  ((type *)0)->member,和上文offsetof 分析一样。这里指的是student->age.然后作为参数传给typeof。


然后typeof(A) *__mptr = ptr; 等价于 A *__mptr = ptr;


注:typeof() 是gcc的扩展宏,给定一个参数或者变量名,能够自动推导数据类型。此时ptr 就指向 student->age地址处。


第二步:再看第二段。


(type *)( (char *)__mptr - offsetof(type,member) );
#等价于
(type *) (&A - offset)


根据 typeof 推算出 member 所在地址,然后减去 member相对于 type 的偏移,得到结构体变量的首地址。


验证:


#include <stdio.h>
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
/**
 * container_of - cast a member of a structure out to the containing structure
 * @ptr:        the pointer to the member.
 * @type:       the type of the container struct this is embedded in.
 * @member:     the name of the member within the struct.
 *
 */
#define container_of(ptr, type, member) ({                      \
    const typeof( ((type *)0)->member ) *__mptr = (ptr);    \
    (type *)( (char *)__mptr - offsetof(type,member) );})
typedef struct _student1 {
    char name[32];
    int age;
} student1_t;
typedef struct _student2 {
    char name[32];
    int age;
    float score;
} student2_t;
int main()
{
    student1_t s1[2] = {
    {
        .name = "s1",
        .age = 18,
    },
    {
        .name = "s2",
        .age = 20,
    },
    };
    student1_t *ptr = NULL;
    ptr = container_of(&s1[1].age, student1_t, age);
    printf("s1 addr = %p \n", &s1[0]);
    printf("ptr addr = %p \n", ptr);
    printf("s2 addr = %p, s2.age = %d \n", &s1[1], s1[1].age);
    printf("ptr addr = %p, ptr.age = %d \n", ptr, ptr->age);
    return 0;
}


可以看到根据s1[1].age 的地址及成员名称,即可获取到改结构的首地址。即ptr = &s1[1]。



总结


  • offsetof:对于给定的一个结构的成员,获取其成员相对于首地址的偏移。


  • container_of:对于给定结构成员的地址,返回其结构体指针(所有者)首地址。


内核里面为什么要用到这两个宏呢?事实上在内核链表里面大量用到了这两个宏。


struct list_head {
 struct list_head *next, *prev;
};


linux 内核链表结构信息非常简单,只包含list_head信息,我们正式通过内核链表中的list_head 指针,获取所有者的节点信息。


list_entry(ptr,type,member)


  • list_entry 就是 container_of 宏。


  • ptr是指向该数据中list_head成员的指针,也就是存储在链表中的地址值。


  • type是数据项的类型。


  • member则是数据项类型定义中list_head成员的变量名。
相关文章
|
20天前
|
存储 Linux 数据处理
探索Linux操作系统的内核与文件系统
本文深入探讨了Linux操作系统的核心组件,包括其独特的内核结构和灵活的文件系统。文章首先概述了Linux内核的主要功能和架构,接着详细分析了文件系统的工作原理以及它如何支持数据存储和检索。通过比较不同的文件系统类型,本文旨在为读者提供一个关于如何根据特定需求选择合适文件系统的参考框架。
|
1天前
|
Linux 数据处理 开发者
深入解析Linux中的paste命令:数据处理与分析的得力助手
`paste`命令在Linux中是数据处理的利器,它按列拼接多个文件内容,支持自定义分隔符和从标准输入读取。例如,合并`file1.txt`和`file2.txt`,使用`paste file1.txt file2.txt`,默认以制表符分隔;若要使用逗号分隔,可运行`paste -d &#39;,&#39; file1.txt file2.txt`。当文件行数不同时,较短文件后会填充空白行。结合管道符与其他命令使用,如`cat file1.txt | paste -s`,可按行合并内容。注意文件大小可能影响性能。
|
1天前
|
Linux 编译器 C语言
Linux 中 EXPORT_SYMBOL宏详解
Linux 中 EXPORT_SYMBOL宏详解
|
2天前
|
Linux 编译器 测试技术
探索Linux中的objcopy命令:数据处理与分析的得力助手
`objcopy`是GNU工具集中的实用程序,用于复制和转换二进制目标文件,如ELF到S-record。它支持格式转换、内容提取和修改,如移除调试信息。命令参数包括指定输入/输出格式和复制特定段。示例用途有:`objcopy -O binary input.elf output.bin`(ELF转二进制)和`objcopy -j .text input.elf output.o`(复制.text段)。使用时注意文件格式、备份原始文件并查阅文档。对于处理和分析二进制数据,`objcopy`是不可或缺的工具。
|
2天前
|
移动开发 数据挖掘 Linux
探索Linux命令之nl:数据处理与分析的得力助手
`nl`命令是Linux下用于为文本文件添加行号的工具,支持自定义格式和空行处理。它可以显示行首或行尾的行号,并能处理逻辑页。常用参数包括`-b`(控制空行行号)、`-n`(设定行号位置和是否补零)、`-w`(设定行号宽度)。示例用法如`nl -b a -n rz -w 3 filename.txt`。在处理大文件时需谨慎,并注意备份原始文件。nl是数据分析时的实用工具。
|
6天前
|
Linux
查看linux内核版本
在Linux中查看内核版本可使用`uname -r`、`cat /proc/version`、`lsb_release -a`(若安装LSB)、`/etc/*release`或`/etc/*version`文件、`dmesg | grep Linux`、`cat /sys/class/dmi/id/product_name`、`hostnamectl`、`kernrelease`(如果支持)、`rpm -q kernel`(RPM系统)和`dpkg -l linux-image-*`(Debian系统)。
21 4
|
7天前
|
安全 Linux 数据处理
探索Linux的kmod命令:管理内核模块的利器
`kmod`是Linux下管理内核模块的工具,用于加载、卸载和管理模块及其依赖。使用`kmod load`来加载模块,`kmod remove`卸载模块,`kmod list`查看已加载模块,`kmod alias`显示模块别名。注意需有root权限,且要考虑依赖关系和版本兼容性。最佳实践包括备份、查阅文档和使用额外的管理工具。
|
5天前
|
数据挖掘 Linux 数据处理
探索Linux下的Lua命令:轻量级脚本语言在数据处理和分析中的应用
**探索Linux上的Lua:轻量级脚本语言用于数据处理。Lua通过命令行解释器执行,适用于游戏开发、数据分析及自动化。特点包括小巧、高效、可扩展和动态类型。使用`lua`或`luajit`,配合-e、-l、-i参数执行脚本或互动模式。示例:执行`hello.lua`脚本打印&quot;Hello, Lua!&quot;。最佳实践涉及版本兼容、性能优化、使用C API、测试和文档编写。**
|
5天前
|
JSON 运维 安全
深入探索Linux的lsns命令:处理与分析Linux命名空间
`lsns`命令是Linux中用于查看命名空间信息的工具,帮助管理和隔离系统资源。它显示命名空间的状态、类型、进程和挂载点,适用于性能优化、故障排查。命令特点包括丰富的参数选项(如 `-t`、`-p`、`-n`),清晰的表格输出和JSON格式支持。示例:列出所有命名空间用`lsns`,列出网络命名空间用`lsns -t net`。使用时注意权限,结合其他工具,并考虑版本兼容性。
|
19天前
|
运维 NoSQL Ubuntu
深入理解Linux中的"crash"命令:内核崩溃的调试利器
`crash`是Linux内核崩溃调试工具,用于分析内核崩溃转储文件,提供GDB-like的交互式CLI。通过加载`vmcore`文件和内核映像,管理员可以查看系统状态、调用栈、内存布局等。安装`crash`可使用包管理器,如`apt-get`或`yum/dnf`。尽管有学习曲线且依赖转储文件,但`crash`在系统故障排查中极其重要。