【Orange Pi 5与Linux内核编程】-理解Linux内核中的container_of宏

理解Linux内核中的container_of宏

文章目录

  • 理解Linux内核中的container_of宏
    • 1、了解C语言中的struct内存表示
    • 2、Linux内核的container_of宏实现理解
    • 3、Linux内核的container_of使用

Linux 内核包含一个名为 container_of 的非常有用的宏。本文介绍了解 Linux 内核中的 container_of 宏。本文包括一个简单的程序,该程序说明了如何使用此宏,并解释了为什么它如此有用。

1、了解C语言中的struct内存表示

C 中的结构是 C 编程语言的一个强大功能。它们允许您通过将不同的变量分组到一个名称下来创建复杂的数据类型。那么,struct在内存中是如何表示的呢?例如,我们创建一个struct,并访问它的成员地址。

struct Student {
    int age;
    float grade;
    char name[50];
    int weight;
}__attribute__((packed));

请注意,为了方便演示这里使用了结构成员对齐。关于C语言的结构成员对齐,请参考:C语言结构成员对齐、填充和数据打包

那么,结构体Student在内存中的表示如下:

在这里插入图片描述

结构体的成员地址访问如下:

#include <stdlib.h>
#include <string.h>

struct Student {
    int age;
    float grade;
    char name[50];
    int weight;
}__attribute__((packed));

int main(int argc, char* argv[]) {
    struct Student st1;
    st1.age = 17;
    st1.grade = 7;
    st1.weight = 120;
    memset(st1.name, 0, sizeof(st1.name));
    memcpy(st1.name, "Jenson", 6);

    printf("sizeof struct = %d\n", sizeof(struct Student));

    printf("st1's address = %p\n", &st1);
    printf("st1.age = %p\n", &st1.age);
    printf("st1.grade = %p\n", &st1.grade);
    printf("st1.name = %p\n", &st1.name[0]);
    printf("st1.weight = %p\n", &st1.weight);
    printf("-------------------\n");
    printf("age'addr = %#lx\n", ((unsigned long)&st1));
    printf("grade'addr = %#lx\n", ((unsigned long)&st1 + sizeof(st1.age)));
    printf("name'addr = %#lx\n", ((unsigned long)&st1 + sizeof(st1.age) + sizeof(st1.grade)));
    printf("weight's addr = %#lx\n", ((unsigned long)&st1 + sizeof(st1.age) + sizeof(st1.grade) + sizeof(st1.name)));
    return 0;
}

我们将得到如下结果:

sizeof struct = 62
st1's address = 0x7fe806bb28
st1.age = 0x7fe806bb28
st1.grade = 0x7fe806bb2c
st1.name = 0x7fe806bb30
st1.weight = 0x7fe806bb62
-------------------
age'addr = 0x7fe806bb28
grade'addr = 0x7fe806bb2c
name'addr = 0x7fe806bb30
weight's addr = 0x7fe806bb62

如果结构体没有使用__attribute__((packed)),则name成员与weight成员将有两个字节的差异,因为编译器对结构体填充了两个字节,从而达到内存对齐的目的。

2、Linux内核的container_of宏实现理解

在Linux内核中,container_of宏的使用非常普遍,其实现如下:

#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)); }) 

container_of宏接受3个参数:

  • ptr – 指向成员的指针。
  • type – 嵌入其中的容器结构的类型。
  • member – 结构中成员的名称。
  • 它返回成员的容器结构的地址。

为了方便理解,我们将Linux内核的container_of宏应用到用户空间程序中,示例代码如下:

#include  <stdio.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) );})

int main(void)
{
    struct sample {
        int mem1;
        char mem2;
    };
    
    struct sample sample1;
    
    printf("Address of Structure sample1 (Normal Method) = %p\n", &sample1);
    
    printf("Address of Structure sample1 (container_of Method) = %p\n", 
                            container_of(&sample1.mem2, struct sample, mem2));
    
    return 0;
}

因为我们已经知道结构sample1 的地址,那么,程序输出的地址应该相同还是不同? 让我们看看输出。

Address of Structure sample1 (Normal Method) = 0x7feeaf2598
Address of Structure sample1 (container_of Method) = 0x7feeaf2598

可以知道,示例程序输出的结果是相同的,那么,container_of宏是如何工作的呢?让我们看看内核中的代码:

/**
 * 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) );})

下面,我们将一步一步来解释这个宏是如何工作的?首先,让我们看第一行代码:

 const typeof( ((type *)0)->member ) *__mptr = (ptr);

对于const关键字在这里就不做解释了。在这行代码中,我们对typeof感兴趣。

typeof()

typeof是非标准 GCC 扩展。GCC 扩展的类型,允许我们引用表达式的类型,并可用于声明变量。例如:

int x = 5;
typeof(x) y = 6;
printf("%d %d\n", x, y);

零指针引用

但是零指针取消引用呢?获取成员的类型是一个小小的指针魔术。它不会崩溃,因为表达式本身永远不会被计算。编译器关心的只是它的类型。如果我们要求回问地址,也会发生同样的情况。编译器同样不关心值,它只是将成员的偏移量添加到结构的地址中,在本例中为 0,并返回新地址。如下面代码所示:

struct s {
        char m1;
        char m2;
};

/* This will print 1 */
printf("%d\n", &((struct s*)0)->m2);
#include  <stdio.h>
int main(void)
{
    struct sample {
        int mem1;
        char mem2;
    };
    printf("Offset of the member = %d\n", &((struct s*)0)->mem2);
    return 0;
}

因此,我们从:

const typeof( ((type *)0)->member ) *__mptr = (ptr)

这行代码可以知道,它创建了局部常量指针变量。正如示例代码所实现的那样:

const typeof( ((type *)0)->member ) *__mptr = (ptr)—–> const char * __mptr = &sample1.mem2

接下来,让我们分析container_of宏的第二行代码:

 (type *)( (char *)__mptr - offsetof(type,member) )

offsetof()宏

offsetof 是一个宏,它将成员的字节偏移量返回到结构的开头。宏如下所示:

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

它甚至是标准库的一部分(可在 stddef.h 中找到)。

它返回一个名为 MEMBER 的成员的地址,该成员的类型为 TYPE 的结构,从地址 0(恰好是我们正在寻找的偏移量)存储在内存中。

现在我们需要根据我们的原始示例转换这个宏(offsetof(),container_of())。这些宏将取代我们的示例,如下所示。

container_of(&sample1.mem2, struct sample, mem2)
            ||
            ||
            \/
const char * __mptr = &sample1.mem2;
struct sample * ((char*)  __mptr - &((struct sample*)0)->mem2)
            ||
            ||
            \/
const char* __mptr = 0x7FFD0D058784;   //(Address of mem2 is 0x7FFD0D058784)
struct sample * (0x7FFD0D058784 - 4)
            ||
            ||
            \/
struct sample* (0x7FFD0D058780)        //This is the address of the container structure

3、Linux内核的container_of使用

container_of宏在Linux内核中的使用示伪代码例如下:

struct foo {
    spinlock_t lock;
    struct workqueue_struct *wq;
    struct work_struct offload;
    (...)
};

static void foo_work(struct work_struct *work)
{
    struct foo *foo = container_of(work, struct foo, offload);

    (...)
}

static irqreturn_t foo_handler(int irq, void *arg)
{
    struct foo *foo = arg;

    queue_work(foo->wq, &foo->offload);
    (...)
}

static int foo_probe(...)
{
    struct foo *foo;

    foo->wq = create_singlethread_workqueue("foo-wq");
    INIT_WORK(&foo->offload, foo_work);
    (...)
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/723493.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

如何用Vue3和ApexCharts打造引人注目的3D径向条形图

本文由ScriptEcho平台提供技术支持 项目地址&#xff1a;传送门 使用 ApexCharts 构建美观的 Vue.js 径向条形图 应用场景 径向条形图是一种用于可视化单一数据点及其与目标或理想值的关系的图表类型。它在显示进度、完成率或其他类似度量时非常有用。 基本功能 这段代码…

有了MES、ERP,质量管理为什么还需要QMS?

在制造业&#xff0c;质量管理始终是企业管理中永恒的主题。品质管理要想做得更好&#xff0c;企业必须掌握足够多、足够有用的数据和信息&#xff0c;实现质量管理信息化。很多中小企业也很困惑&#xff0c;是否有必要上线QMS质量管理系统&#xff1f; 一、为什么企业需要QMS的…

曾从钦:共同做大露酒产业蛋糕,共建露酒产业命运共同体

执笔 | 尼 奥 编辑 | 扬 灵 6月15日&#xff0c;由中国酒业协会主办、五粮液股份公司承办的以“文化焕新&#xff0c;价值绽放”为主题的第三届中国露酒T5峰会在四川省宜宾市召开&#xff0c;参会企业对当前露酒产业现状、结构性矛盾、品类价值表达等议题进行深入探讨和交…

SpringBoot整合H2数据库并将其打包成jar包、转换成exe文件二(补充)

SpringBoot整合H2数据库并将其打包成jar包、转换成exe文件二&#xff08;补充&#xff09; 如果你想在cmd命令窗口内看到程序运行&#xff0c;即点开弹出运行窗口&#xff0c;关闭时exe自动关闭。 需要再launch4j上进行如下操作&#xff1a; 这样转换好的exe就可以有控制台了…

如何解决input输入时存在浏览器缓存问题?

浏览器有时会在你输入表单过后缓存你的输入&#xff0c;有时候能提供方便。 但是在某些新建或新页面情况下出现历史的输入信息&#xff0c;用户体验很差。 解决方案 设置 autocomplete关闭 &#xff1a;<input type"text" autocomplete"off">增加…

一个轻量级的TTS模型实现

1.环境 python 版本 3.9 2.训练数据集 本次采用LJSpeech数据集&#xff0c;百度网盘下载地址 链接&#xff1a;https://pan.baidu.com/s/1DDFmPpHQrTR_NvjAfwX-QA 提取码&#xff1a;1234 3.安装依赖 pip install TTS 4.工程结构 5代码部分 decoder.py import torch f…

【ARM】如何通过Keil MDK查看芯片的硬件信息

【更多软件使用问题请点击亿道电子官方网站】 1、文档目标&#xff1a; 解决在开发过程中对于开发项目所使用的的芯片的参数查看的问题 2、问题场景&#xff1a; 在项目开发过程中&#xff0c;经常需要对于芯片的时钟、寄存器或者一些硬件参数需要进行确认。大多数情况下是需…

视频监控管理平台智能边缘分析一体机安防监控平台离岗检测算法

在工业自动化和智能制造的背景下&#xff0c;智能边缘分析一体机的应用日益广泛。这些设备通常在关键岗位上执行监控、分析和数据处理任务。然而&#xff0c;设备的稳定运行至关重要&#xff0c;一旦发生故障或离岗&#xff0c;可能会导致生产线停滞甚至安全事故。因此&#xf…

被拷打已老实!面试官问我 #{} 和 ${} 的区别是什么?

引言&#xff1a;在使用 MyBatis 进行数据库操作时&#xff0c;#{} 和 ${} 的区别是面试中常见的问题&#xff0c;对理解如何在 MyBatis 中安全有效地处理 SQL 语句至关重要。正确使用这两种占位符不仅影响应用的安全性&#xff0c;还涉及到性能优化。 题目 被拷打已老实&…

期货高手交易精髓

1.准 所谓准&#xff0c;就是要看的准&#xff0c;做得准&#xff1b;看&#xff0c;不管是看月线&#xff0c;周线&#xff0c;日线&#xff1b;都能很好的分析当下市场行情处于什么样的走势&#xff0c;是上涨&#xff1f;下跌&#xff1f;还是震荡。未来一段时间可能会怎么…

发表EI会议论文需要注意哪些问题?

发表EI会议论文需要注意哪些问题&#xff1f; 今天整理了一些资料给需要发表EI会议论文的同学&#xff0c;避免踩坑&#xff0c;少走弯路&#xff01;具体需要主要的问题有以下几点&#xff1a;

C语言实现五子棋教程

Hi~&#xff01;这里是奋斗的小羊&#xff0c;很荣幸您能阅读我的文章&#xff0c;诚请评论指点&#xff0c;欢迎欢迎 ~~ &#x1f4a5;&#x1f4a5;个人主页&#xff1a;奋斗的小羊 &#x1f4a5;&#x1f4a5;所属专栏&#xff1a;C语言 &#x1f680;本系列文章为个人学习…

为什么Mid journey很容易就能做出很有氛围感的图而SD却容易做图很丑?

前言 6月12日&#xff0c;Midjourney更新了一项新的功能——模型个性化&#xff0c;这一项功能最重要的作用就是能够让生成的图像更加符合你自己的审美标准。就像每个艺术家都有自己的独特风格一样&#xff0c;有了这项模型个性化功能的加持&#xff0c;每个人都能生成具有鲜明…

安卓手机数据快速找回!2个视频恢复大师,助你还原视频

我们的手机成为了储存信息的海洋。但与此同时&#xff0c;也带来了一个不容忽视的问题&#xff1a;一旦手机中的视频资料丢失&#xff0c;我们该如何高效地找回呢&#xff1f;现在很多程序都能够有效地找回手机视频&#xff0c;本文将为您揭示这些视频恢复大师的神奇能力&#…

STM32F407之移植LVGL(8.4.0)

STM32F407之移植LVGL(8.4.0) 目录 概述 1 系统软硬件 1.1 软件版本信息 1.2 ST7796-LCD 1.3 MCU IO与LCD PIN对应关系 1.4 MCU IO与Touch PIN对应关系 2 认识LVGL 2.1 LVGL官网 2.2 下载V8.4.0 3 移植LVGL 3.1 硬件驱动实现 3.2 添加LVGL库文件 3.3 移植和硬件相关…

gma 2 教程(三)坐标参考系统:4.内置单位和子午线

安装 gma&#xff1a;pip install gma 内置单位 gma内置单位主要包括地理坐标系的角度单位和投影坐标系的线性单位两大类。 角度单位 内置常用的角度单位&#xff08;在crs.AngularUnits下&#xff09;名称及值见下表&#xff1a; 内置角度单位中文名值&#xff08;弧度&…

C盘臃肿怎么办?用这招给C盘彻底瘦身!C盘专清!

随着大家使用电脑的时间越来越长&#xff0c;电脑积累的垃圾就越来越多&#xff0c;特别是作为C盘的核心部位。C盘承载了整个系统运行的框架&#xff0c;各种软件运行的临时数据存储等作用&#xff0c;慢慢的就变得臃肿起来了。 当C盘被垃圾堆得越来越多的时候&#xff0c;我们…

NPDP含金量、考试内容、报考要求、适合人群?

01.NPDP核心价值解读 NPDP认证的核心价值在于整合产品开发管理的理论与实践&#xff0c;包含新产品开发策略、研发流程管理、市场研究、销规划、团队管理、项目管理等等&#xff0c;理论体系和知识内容穿插在产品发展的全过程。 对于职场打工人来说&#xff0c;拥有NPDP证书证…

2024年通信安全员ABC证证考试题库及通信安全员ABC证试题解析

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 2024年通信安全员ABC证证考试题库及通信安全员ABC证试题解析是安全生产模拟考试一点通结合&#xff08;安监局&#xff09;特种作业人员操作证考试大纲和&#xff08;质检局&#xff09;特种设备作业人员上岗证考试大…

阿里云PAI大模型评测最佳实践

作者&#xff1a;施晨、之用、南茵、求伯、一耘、临在 背景信息 内容简介 在大模型时代&#xff0c;随着模型效果的显著提升&#xff0c;模型评测的重要性日益凸显。科学、高效的模型评测&#xff0c;不仅能帮助开发者有效地衡量和对比不同模型的性能&#xff0c;更能指导他…