嵌入式驱动学习第三周——container_of()宏

前言

   Linux内核编程中,会经常看见一个宏函数container_of,那么这究竟是什么呢,本篇博客记录学习container_of的过程。

   嵌入式驱动学习专栏将详细记录博主学习驱动的详细过程,未来预计四个月将高强度更新本专栏,喜欢的可以关注本博主并订阅本专栏,一起讨论一起学习。现在关注就是老粉啦!

目录

  • 前言
  • 1. 简介
    • 1.1 用途
    • 1.2 举例说明
  • 2. 代码尝试
    • 2.1 结构体定义
    • 2.2 container_of使用
    • 2.3 完整代码
  • 3. linux中的定义
  • 参考资料

1. 简介

1.1 用途

   在代码管理多个数据结构时,几乎总是需要将一个结构嵌入到另一个结构中,并随时检索它们,而不关心内存偏移或边界的问题。

   container_of(ptr, type, member)是一个宏函数,可以通过结构体成员的地址找到结构体的地址。

ptr——结构体成员地址
type——结构体类型
member——结构体成员在结构体里的名字

1.2 举例说明

   虽然其定义看着比较吓人,但是使用起来是比较简单的。

   假设有一个结构体存储了一些成员:

typedef struct _animal {double age;double weight;
}animal;

   然后我们使用container_of就可以根据变量获取结构体:

animal cat = {2, 20};
animal *p_cat = NULL;
...
p_cat = container_of(&cat.age, animal, age);

   就是如此简单,有一个结构体,然后一个结构体指针,那么就可以根据其中的变量来反求出结构体并赋给结构体指针。

2. 代码尝试

   现在我们来用一个完整的例子来看看container的使用,既然出发点是嵌套结构体,那么举的例子就嵌套结构体成员进去。而事实上,内核开发都是结构体套结构体的

2.1 结构体定义

   首先先定义一个结构体表示工程师:

// 一个员工类
typedef struct engineer_Struct {int age;        // 年龄char* name;     // 姓名
}Engineer;

   接下来定义一个公司类,公司类中包含了各种工程师和员工人数,假设现在有c++工程师和java工程师:

// 一个公司类
typedef struct company_Struct {Engineer cpp;           // c艹工程师Engineer java;          // java工程师int employee_count;     // 员工人数
} company;

2.2 container_of使用

   现在我们在入口函数中使用container_of宏,通过成员获取结构体。我们先定义一个c++工程师,年龄为23,名字叫wp1,再定义一个java工程师,年林为32,名字叫wp2。如果container_of宏根据c++找到的结构体能找到java,那么就说明是成功的:

    Engineer wp1 = {23, "wp1"};Engineer wp2 = {32, "wp2"};Company company = {wp1, wp2, 10};Company *com_ptr;com_ptr = container_of(&company.cpp, Company, cpp);		// 使用container_of查找结构体printk("the java engineer's age is %d and name is %s\r\n", com_ptr->java.age, com_ptr->java.name);

   最后就是写下完整的驱动代码,并到板子上实验了

2.3 完整代码

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/init.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/of_device.h>#define chrdevTest_CNT      1
#define chrdevTest_NAME     "chrdevTest"// 一个员工类
typedef struct engineer_Struct {int age;        // 年龄char* name;     // 姓名
}Engineer;// 一个公司类
typedef struct company_Struct {Engineer cpp;           // c艹工程师Engineer java;          // java工程师int employee_count;     // 员工人数
} Company;// 设备结构体
struct chrdevTest_dev {dev_t devid;struct cdev cdev;struct class *class;struct device *device;int major;int minor;struct device_node *nd;
};struct chrdevTest_dev chrdevTest;   // 字符设备static int chrdevTest_open(struct inode *inode, struct file *filp) {filp->private_data = &chrdevTest;return 0;
}static ssize_t chrdevTest_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt) {return 0;
}static ssize_t chrdevTest_write(struct file *filp, const char __user* buf, size_t cnt, loff_t* offt) {return 0;
}static ssize_t chrdevTest_release(struct inode* inode, struct file* filp) {return 0;
}static struct file_operations chrdevTest_fops = {.owner = THIS_MODULE,.open = chrdevTest_open,.read = chrdevTest_read,.write = chrdevTest_write,.release = chrdevTest_release,
};// 入口函数
static int __init containerTest_init(void)
{Engineer wp1 = {23, "wp1"};Engineer wp2 = {32, "wp2"};Company company = {wp1, wp2, 10};Company *com_ptr;printk("this is init function\r\n");com_ptr = container_of(&company.cpp, Company, cpp);printk("the java engineer's age is %d and name is %s\r\n", com_ptr->java.age, com_ptr->java.name);if (chrdevTest.major) {chrdevTest.devid = MKDEV(chrdevTest.major, 0);register_chrdev_region(chrdevTest.devid, chrdevTest_CNT, chrdevTest_NAME);} else {alloc_chrdev_region(&chrdevTest.devid, 0, chrdevTest_CNT, chrdevTest_NAME);     // 申请设备号chrdevTest.major = MAJOR(chrdevTest.devid);     // 获取主设备号chrdevTest.minor = MINOR(chrdevTest.devid);     // 获取次设备号}printk("chrdevTest major=%d, minor=%d\r\n", chrdevTest.major, chrdevTest.minor);// cdevchrdevTest.cdev.owner = THIS_MODULE;cdev_init(&chrdevTest.cdev, &chrdevTest_fops);// 添加cdevcdev_add(&chrdevTest.cdev, chrdevTest.devid, chrdevTest_CNT);// 创建类chrdevTest.class = class_create(THIS_MODULE, chrdevTest_NAME);if (IS_ERR(chrdevTest.class)) {return PTR_ERR(chrdevTest.class);}// 创建设备chrdevTest.device = device_create(chrdevTest.class, NULL, chrdevTest.devid, NULL, chrdevTest_NAME);if (IS_ERR(chrdevTest.device)) {return PTR_ERR(chrdevTest.device);}return 0;
}// 出口函数
static void __exit containerTest_exit(void)
{printk("this is exit function\r\n");cdev_del(&chrdevTest.cdev);unregister_chrdev_region(chrdevTest.devid, chrdevTest_CNT);device_destroy(chrdevTest.class, chrdevTest.devid);class_destroy(chrdevTest.class);
}module_init(containerTest_init);
module_exit(containerTest_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("wp");

   编写makefile文件,makefile内容如下所示。同时将生成的模块文件.ko放入到设备对应的文件夹下

KERNELDIR := /home/wp/Linux/IMX6ULL/alientek_linux		此处是你的linux内核地址(要修改)
CURRENT_PATH := $(shell pwd)
obj-m := chrdevTest.o									此处是你要生成的文件(要修改)build: kernel_moduleskernel_modules:$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean

   最后输入以下指令加载驱动

depmod
insmod chrdevTest.ko

   加载完驱动后,可以看到我们之前用printk想要显示的内容如下所示:

在这里插入图片描述

   根据输出的信息,可以发现,输出了java工程师的年龄是32,名字叫wp2,通过container_of宏成功根据cpp工程师找到了结构体,并重新根据结构体找到了java工程师的信息

   最后可以卸载驱动:

rmmod chrdevTest.ko

在这里插入图片描述

3. linux中的定义

   在知晓了其怎么使用后,下面可以来探究一下其在linux中的源码是如何实现的

   其在linux源码中的定义如下,定义在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) );})

   我们来拆分一下每一句,首先是第一句:

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

   首先来看"0"指针。假设结构体变量的起始地址为"0",那么具体成员的地址其实就是相对于结构体的偏移量。(type *)0是把"0"强制转换成结构体类型的地址;((type *)0)->member是以0为起始地址的结构体成员变量member。那么((type *)0)->member定义出来的变量就是成员变量的地址

   由前面的铺垫,我们知道了ptr是一个结构体成员变量member的地址,所以ptr的类型得是一个指向member数据类型的指针。通过typeof( ((type *)0)->member )就可以获取到结构体成员member的数据类型,所以这句话就是把临时变量__mptr定义为结构体成员的类型

   但是有个缺点,那就是如果ptr和成员变量类型不一致,就会报warning,这一点在后续的内核版本中得到了解决。

   后续版本中,拆分为了两句话,第一句是先定义了一个void指针 __mptr,然后用了一个断言BUILD_BUG_ON_MSG,断言判断就是__same_type,检查如果ptr与实际类型不一致,就参数warning,直接中断编译。

void *__mptr = (void *)(ptr);					\
BUILD_BUG_ON_MSG(!__same_type(*(ptr), ((type *)0)->member) &&	\!__same_type(*(ptr), void),			\"pointer type mismatch in container_of()");	\

   下面来看第二句:

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

   拿结构体某个成员 member 的地址,减去这个成员在结构体 type 中的偏移,结果就是结构体 type 的首地址。container_of 最后就会返回这个地址值给宏的调用者。

   这个offset宏定义如下所示:

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

   这个宏中,将0强制转换为一个指向TYPE的结构体常来那个指针,然后通过这个常量指针访问成员获取member地址,其大小在数值上等于member在结构体中的偏移

参考资料

[1] Linux container_of() 函数详解
[2] Linux 内核 container_of 宏详解
[3] container of()函数用法简介
[4] 话说Linux内核链表之“container_of“(二)

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Claude3 正式发布,支持多模态(附注册使用教程)

免费使用教程请看到最后&#xff01;&#xff01;&#xff01;&#xff01;&#xff01; AnthropicAI 官推发布消息&#xff0c;正式推出Claude 3&#xff0c;沉寂了很久的Anthropic 终于亮剑放了大招。Claude 3 系列模型&#xff0c;包括Claude 3 Opus、Claude 3 Sonnet 和 C…

鸿蒙Next学习-Flex布局

Entry Component struct FlexCase {build() {//需要在构造参数上传Flex({ direction: FlexDirection.Row,justifyContent:FlexAlign.Center }) {//flex布局Row().width(100).height(100).backgroundColor(Color.Red)Row().width(100).height(100).backgroundColor(Color.Yellow…

L1-7 机工士姆斯塔迪奥【Java】

在 MMORPG《最终幻想14》的副本“乐欲之所瓯博讷修道院”里&#xff0c;BOSS 机工士姆斯塔迪奥将会接受玩家的挑战。 你需要处理这个副本其中的一个机制&#xff1a;NM 大小的地图被拆分为了 NM 个 11 的格子&#xff0c;BOSS 会选择若干行或/及若干列释放技能&#xff0c;玩家…

办公自动化的得力助手 —— 定时执行专家

目录 一、软件简介 二、办公应用场景 1、自动化文件处理 2、定时提醒与日程管理 3、网络操作与远程控制 4、系统维护与优化 三、使用体验 四、总结 在快节奏的现代办公环境中&#xff0c;如何高效地管理任务、节省时间并提升工作效率成为了每个职场人士关注的焦点。今天…

字符设备驱动编写

文章目录 环境一、添加驱动&#xff08;/sys/bus/i2c/drivers/mpu6050_1&#xff09;驱动和设备树扯上关系二、注册一个&#xff08;种/类&#xff1f;&#xff09;字符设备&#xff08;/proc/devices&#xff0c;243 mpu6050_1&#xff09;三、手动创建一个字符设备&#xff0…

web部署 三

案例: 1/在其中一个网站目录下创建\software文件夹&#xff0c;里面放txtppt/mp4/iso,文件类型。 2/web站点目录浏览启动应用。 3/用win10客户机浏览software目录下文件&#xff0c;并下载。txtppt/mp4/iso&#xff0c;发现问题并解决。 首先我们先建立一个software的文件夹并…

微服务初识

1.认识微服务 随着互联网行业的发展&#xff0c;对服务的要求也越来越高&#xff0c;服务架构也从单体架构逐渐演变为现在流行的微服务架构。这些架构之间有怎样的差别呢&#xff1f; 1.1.单体架构 单体架构&#xff1a;将业务的所有功能集中在一个项目中开发&#xff0c;打…

力扣串题:反转字符串中的元音字母

​​​​​​​ 双指针&#xff0c;注意判断是否为元音的操作 bool IsVowel(char s){if(sa||se||si||so||su||sA||sE||sI||sO||sU) return true;return false; }char * reverseVowels(char * s){int len strlen(s),i0;while(i<len-1){if(IsVowel(s[i])&&IsVowel(s…

【C语言】五种方法实现C语言中大小写字母的转化

文章目录 &#x1f4dd;tolower/toupper函数&#x1f309;tolower&#x1f320; toupper &#x1f320; ASCII码关系&#x1f309;位操作&#x1f309;宏定义 &#x1f320;小巧第五位&#x1f6a9;总结 &#x1f4dd;tolower/toupper函数 &#x1f309;tolower tolower函数是…

YOLOv7改进 | 更换主干网络之PP-LCNet

前言:Hello大家好,我是小哥谈。PP-LCNet是一个由百度团队针对Intel-CPU端加速而设计的轻量高性能网络。它是一种基于MKLDNN加速策略的轻量级卷积神经网络,适用于多任务,并具有提高模型准确率的方法。与之前预测速度相近的模型相比,PP-LCNet具有更高的准确性。此外,对于计…

掘根宝典之C++普通迭代器和反向迭代器详解

简介 迭代器是一种用于遍历容器元素的对象。它提供了一种统一的访问方式&#xff0c;使程序员可以对容器中的元素进行逐个访问和操作&#xff0c;而不需要了解容器的内部实现细节。 C标准库里每个容器都定义了迭代器&#xff0c;这迭代器的名字就叫容器迭代器 迭代器的作用类…

数字电子技术笔记——组合逻辑功能

1.Adder&#xff08;加法器&#xff09; Half-Adder&#xff08;半加器&#xff09; Full-Adder&#xff08;全加器&#xff09; 74LS283(4-bit parallel adders) 74LS283 4-bit parallel adders 81 input 41 output carry look-ahead adder &#xff08;超前进位加法器&a…

牛客 NC266925 我不是大富翁(dp)

原题 首先记录这一道题的目的是提醒自己&#xff1a;动态规划的属性并不是只有 m a x max max&#xff0c; m i n min min 和 c o u n t count count&#xff0c;同时还有布尔类型的dp 这题不能考虑在距离的维度上思考&#xff0c;比如说看走几步走到哪里了&#xff0c;如果…

C++进阶之路---手把手带你学习AVL树

顾得泉&#xff1a;个人主页 个人专栏&#xff1a;《Linux操作系统》 《C从入门到精通》 《LeedCode刷题》 键盘敲烂&#xff0c;年薪百万&#xff01; 一、AVL树的概念 二叉搜索树虽可以缩短查找的效率&#xff0c;但如果数据有序或接近有序二叉搜索树将退化为单支树&#…

图像处理与视觉感知---期末复习重点(3)

文章目录 一、空间域和频率域二、傅里叶变换三、频率域图像增强 一、空间域和频率域 1. 空间域&#xff1a;即所说的像素域&#xff0c;在空间域的处理就是在像素级的处理&#xff0c;如在像素级的图像叠加。通过傅立叶变换后&#xff0c;得到的是图像的频谱&#xff0c;表示图…

【深度学习笔记】9_9 语义分割和数据集

注&#xff1a;本文为《动手学深度学习》开源内容&#xff0c;部分标注了个人理解&#xff0c;仅为个人学习记录&#xff0c;无抄袭搬运意图 9.9 语义分割和数据集 在前几节讨论的目标检测问题中&#xff0c;我们一直使用方形边界框来标注和预测图像中的目标。本节将探讨语义分…

深度学习基础知识之Atrous卷积(空洞卷积)

太久不看代码确实生疏了&#xff0c;盯着一堆不同的dilation&#xff0c;不知道有什么作用&#xff0c;论文中说是Atrous卷积&#xff0c;原来就是空洞卷积的意思。 Dilated/Atrous Convolution 空洞卷积&#xff08;膨胀卷积/扩张卷积&#xff09; 空洞卷积是一种不增加参数量…

Web 服务器-Tomcat

文章目录 Web服务器一、Tomcat简介二、基本使用三、在IDEA中创建Maven Web项目四、在IDEA中使用Tomcat Web服务器 一、Tomcat简介 二、基本使用 三、在IDEA中创建Maven Web项目 四、在IDEA中使用Tomcat

外包干了9天,技术退步明显。。。。。

先说一下自己的情况&#xff0c;本科生&#xff0c;2018年我通过校招踏入了南京一家软件公司&#xff0c;开始了我的职业生涯。那时的我&#xff0c;满怀热血和憧憬&#xff0c;期待着在这个行业中闯出一片天地。然而&#xff0c;随着时间的推移&#xff0c;我发现自己逐渐陷入…

【C#】.net core 6.0 使用第三方日志插件Log4net,日志输出到控制台或者文本文档

欢迎来到《小5讲堂》 大家好&#xff0c;我是全栈小5。 这是《C#》系列文章&#xff0c;每篇文章将以博主理解的角度展开讲解&#xff0c; 特别是针对知识点的概念进行叙说&#xff0c;大部分文章将会对这些概念进行实际例子验证&#xff0c;以此达到加深对知识点的理解和掌握。…