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

news2024/12/23 2:51:56

前言

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

    // cdev
    chrdevTest.cdev.owner = THIS_MODULE;
    cdev_init(&chrdevTest.cdev, &chrdevTest_fops);

    // 添加cdev
    cdev_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_modules

kernel_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.coloradmin.cn/o/1510068.html

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

相关文章

1688平台最关键的接口接入实例|获得1688商品详情| 按关键字搜索商品| 按图搜索1688商品(拍立淘)| 获得淘口令真实url

参数说明 通用参数说明 version:API版本key:调用key,测试key:test_api_keyapi_name:API类型[item_get,item_search]cache:[yes,no]默认yes&#xff0c;将调用缓存的数据&#xff0c;速度比较快result_type:[json,xml,serialize,var_export]返回数据格式&#xff0c;默认为jsonl…

【JavaScript 漫游】【034】AJAX

文章简介 本篇文章为【JavaScript 漫游】专栏的第 034 篇文章&#xff0c;对浏览器模型的 XMLHttpRequest 对象&#xff08;AJAX&#xff09;的知识点进行了总结。 XMLHttpRequest 对象概述 浏览器与服务器之间&#xff0c;采用 HTTP 协议通信。用户在浏览器地址栏键入一个网…

Cisco Packet Tracer模拟器实现交换机的vlan配置、生成树技术及模拟器路由设置

1. 内容 1.对交换机进行Vlan配置&#xff0c;完成交换机Vlan的划分、交换机间相同Vlan的通信以及三层交换机的配置。 2.实现交换机的生成树技术&#xff0c;在两个交换机上配置生成树协议&#xff0c;实现Vlan的负载均衡 3.对路由器进行设置&#xff0c;包括模拟器中路由器的…

案例分析篇07:数据库设计相关28个考点(23~28)(2024年软考高级系统架构设计师冲刺知识点总结系列文章)

专栏系列文章推荐: 2024高级系统架构设计师备考资料(高频考点&真题&经验)https://blog.csdn.net/seeker1994/category_12593400.html 【历年案例分析真题考点汇总】与【专栏文章案例分析高频考点目录】(2024年软考高级系统架构设计师冲刺知识点总结-案例分析篇-…

基于yolov5的草莓成熟度检测系统,可进行图像目标检测,也可进行视屏和摄像检测(pytorch框架)【python源码+UI界面+功能源码详解】

功能演示&#xff1a; 基于yolov5的草莓成熟度检测系统&#xff0c;系统既能够实现图像检测&#xff0c;也可以进行视屏和摄像实时检测_哔哩哔哩_bilibili &#xff08;一&#xff09;简介 基于yolov5的草莓成熟度系统是在pytorch框架下实现的&#xff0c;这是一个完整的项目…

力扣226.翻转二叉树(二叉树的先序遍历)

文章目录 题目描述思路复杂度Code 题目描述 思路 利用二叉树的先序遍历&#xff0c;每次递归遍历时将当前节点的左右子节点交换即可 复杂度 时间复杂度: O ( n ) O(n) O(n)&#xff1b;其中 n n n为树节点的个数 空间复杂度: O ( h e i g h ) O(heigh) O(heigh)&#xff1b;其…

虚位以待!OpenHarmony开发者激励计划持续招募中

虚位以待&#xff01;OpenHarmony开发者激励计划持续招募中 自2022年5月7日&#xff0c;OpenHarmony开发者激励计划启动招募以来&#xff0c;就正式公开邀请广大开发者们参与 OpenHarmony 生态共建。随着社区的快速成长&#xff0c;目前已有累计超过7000名贡献者&#xff0c;产…

7个帮您恢复文件的Android 数据恢复推荐

您的 Android 设备上保存哪些类型的数据&#xff1f;如果您像大多数人一样&#xff0c;那么您可能已经列出了文档、照片、视频和音频文件。如果您使用智能手机或平板电脑的时间足够长&#xff0c;我们愿意打赌您对 Android 数据丢失有第一手经验。 对您来说幸运的是&#xff0…

目标检测数据集:手机顶盖焊缺陷检测数据集

✨✨✨✨✨✨目标检测数据集✨✨✨✨✨✨ 本专栏提供各种场景的数据集,主要聚焦:工业缺陷检测数据集、小目标数据集、遥感数据集、红外小目标数据集,该专栏的数据集会在多个专栏进行验证,在多个数据集进行验证mAP涨点明显,尤其是小目标、遮挡物精度提升明显的数据集会在该…

ubuntu 18.04安装教程(详细有效)

文章目录 一、下载ubuntu 18.04镜像二、安装ubuntu1. 点击下载好的Vmware Workstation&#xff0c;点击新建虚拟机&#xff0c;选择 “自定义(高级)”&#xff0c;之后下一步。2. 默认配置&#xff0c;不需要更改&#xff0c;点击下一步。3. 选择 “安装程序光盘映像文件(iso)(…

用信号的方式回收僵尸进程

当子进程退出后&#xff0c;会给父进程发送一个17号SIGCHLD信号&#xff0c;父进程接收到17号信号后&#xff0c;进入信号处理函数调用waitpid函数回收僵尸进程若多个子进程同时退出后&#xff0c;这是切回到父进程&#xff0c;此时父进程只会处理一个17号信号&#xff0c;其他…

vulhub中Weblogic SSRF漏洞复现

Weblogic中存在一个SSRF漏洞&#xff0c;利用该漏洞可以发送任意HTTP请求&#xff0c;进而攻击内网中redis、fastcgi等脆弱组件。 访问http://your-ip:7001/uddiexplorer/&#xff0c;无需登录即可查看uddiexplorer应用。 SSRF漏洞测试 SSRF漏洞存在于http://your-ip:7001/ud…

魅族录屏功能在哪里?这篇文章告诉你答案

“魅族录屏功能在哪里&#xff1f;作为一名魅族手机的新用户&#xff0c;我对这款手机非常满意。然而&#xff0c;最近我在工作中需要用到录屏功能&#xff0c;却找不到如何开启的方法。想请教各位魅族手机的达人们&#xff0c;能否分享一下录屏功能的开启方法&#xff1f;非常…

Android studio Gradle下载失败,如何手动配置解决该问题详解

前些天发现了一个蛮有意思的人工智能学习网站,8个字形容一下"通俗易懂&#xff0c;风趣幽默"&#xff0c;感觉非常有意思,忍不住分享一下给大家。 &#x1f449;点击跳转到教程 前言&#xff1a; 今天在打开公司一个项目时&#xff0c;突然要重新下载相关的gradle&am…

JWT实战之升级Java JWT

概述 关于JWT的基础概念&#xff0c;如JWT组成部分&#xff0c;以及入门实战&#xff0c;如&#xff1a;如何生成Token、如何解析Token、怎么加入自定义字段等&#xff0c;可参考JWT入门教程。 如前文提到的blog所述&#xff0c;大多数公司都会使用如下&#xff08;版本&…

C#,入门教程(26)——数据的基本概念与使用方法

上一篇: C#,入门教程(25)——注释(Comments)你会吗?看多图演示,学真正注释。https://blog.csdn.net/beijinghorn/article/details/124681888 本文所述的知识基本上适用于C/C++,java等其他语言。 数据是程序的基础,算法是程序的栋梁。 徒弟们交作业的之后,一般都会有…

【Sql】数据库的三范式?MySQL数据库引擎有?InnoDB与MyISAM的区别

目录 数据库的三范式&#xff1f; MySQL数据库引擎有&#xff1f; InnoDB与MyISAM的区别 数据库的三范式&#xff1f; 第一范式&#xff1a;是数据库最基本的要求&#xff0c;列不可再分 第二范式&#xff1a;行可以唯一区分&#xff0c;主键约束 第三范式&#xff1a;是在…

【io.net】问题汇总

【io.net】问题汇总 大家最近挖挖的如火如荼&#xff0c;可是不论是社区活动积分和参与挖矿积分&#xff0c;大家遇到了很多类似问题&#xff0c;重复解决。 因此我这里整理了一下常见的相关问题&#xff0c;大家可以一站式找到解决方案。解决方案主要分为运营和挖矿两个两面…

【CMake G++ GCC】在 Linux 环境中编译 C++ 源码

在 Linux 环境中编译 C 源码 C 技术栏 文章演示了在 乌班图 环境下编译mathematical-expression-cpp 动态库源码,&#xff01; 目录 文章目录 在 Linux 环境中编译 C 源码目录介绍前置准备apt 更新下载并进入源码包 开始编译源码包修改 CMakeList 文件开始进行 makeFile 的生…