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成员的变量名。
相关文章
|
10天前
|
Ubuntu Linux 开发者
Ubuntu20.04搭建嵌入式linux网络加载内核、设备树和根文件系统
使用上述U-Boot命令配置并启动嵌入式设备。如果配置正确,设备将通过TFTP加载内核和设备树,并通过NFS挂载根文件系统。
49 15
|
15天前
|
存储 运维 监控
Linux--深入理与解linux文件系统与日志文件分析
深入理解 Linux 文件系统和日志文件分析,对于系统管理员和运维工程师来说至关重要。文件系统管理涉及到文件的组织、存储和检索,而日志文件则记录了系统和应用的运行状态,是排查故障和维护系统的重要依据。通过掌握文件系统和日志文件的管理和分析技能,可以有效提升系统的稳定性和安全性。
33 7
|
17天前
|
监控 安全 Linux
启用Linux防火墙日志记录和分析功能
为iptables启用日志记录对于监控进出流量至关重要
|
1月前
|
算法 Linux
深入探索Linux内核的内存管理机制
本文旨在为读者提供对Linux操作系统内核中内存管理机制的深入理解。通过探讨Linux内核如何高效地分配、回收和优化内存资源,我们揭示了这一复杂系统背后的原理及其对系统性能的影响。不同于常规的摘要,本文将直接进入主题,不包含背景信息或研究目的等标准部分,而是专注于技术细节和实际操作。
|
1月前
|
存储 缓存 网络协议
Linux操作系统的内核优化与性能调优####
本文深入探讨了Linux操作系统内核的优化策略与性能调优方法,旨在为系统管理员和高级用户提供一套实用的指南。通过分析内核参数调整、文件系统选择、内存管理及网络配置等关键方面,本文揭示了如何有效提升Linux系统的稳定性和运行效率。不同于常规摘要仅概述内容的做法,本摘要直接指出文章的核心价值——提供具体可行的优化措施,助力读者实现系统性能的飞跃。 ####
|
1月前
|
监控 算法 Linux
Linux内核锁机制深度剖析与实践优化####
本文作为一篇技术性文章,深入探讨了Linux操作系统内核中锁机制的工作原理、类型及其在并发控制中的应用,旨在为开发者提供关于如何有效利用这些工具来提升系统性能和稳定性的见解。不同于常规摘要的概述性质,本文将直接通过具体案例分析,展示在不同场景下选择合适的锁策略对于解决竞争条件、死锁问题的重要性,以及如何根据实际需求调整锁的粒度以达到最佳效果,为读者呈现一份实用性强的实践指南。 ####
|
1月前
|
缓存 监控 网络协议
Linux操作系统的内核优化与实践####
本文旨在探讨Linux操作系统内核的优化策略与实际应用案例,深入分析内核参数调优、编译选项配置及实时性能监控的方法。通过具体实例讲解如何根据不同应用场景调整内核设置,以提升系统性能和稳定性,为系统管理员和技术爱好者提供实用的优化指南。 ####
|
2月前
|
Linux 网络安全 数据安全/隐私保护
Linux 超级强大的十六进制 dump 工具:XXD 命令,我教你应该如何使用!
在 Linux 系统中,xxd 命令是一个强大的十六进制 dump 工具,可以将文件或数据以十六进制和 ASCII 字符形式显示,帮助用户深入了解和分析数据。本文详细介绍了 xxd 命令的基本用法、高级功能及实际应用案例,包括查看文件内容、指定输出格式、写入文件、数据比较、数据提取、数据转换和数据加密解密等。通过掌握这些技巧,用户可以更高效地处理各种数据问题。
169 8
|
2月前
|
监控 Linux
如何检查 Linux 内存使用量是否耗尽?这 5 个命令堪称绝了!
本文介绍了在Linux系统中检查内存使用情况的5个常用命令:`free`、`top`、`vmstat`、`pidstat` 和 `/proc/meminfo` 文件,帮助用户准确监控内存状态,确保系统稳定运行。
696 6
|
2月前
|
Linux
在 Linux 系统中,“cd”命令用于切换当前工作目录
在 Linux 系统中,“cd”命令用于切换当前工作目录。本文详细介绍了“cd”命令的基本用法和常见技巧,包括使用“.”、“..”、“~”、绝对路径和相对路径,以及快速切换到上一次工作目录等。此外,还探讨了高级技巧,如使用通配符、结合其他命令、在脚本中使用,以及实际应用案例,帮助读者提高工作效率。
119 3