嵌入式驱动学习第三周——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);

    // 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“(二)

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

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

相关文章

【C++】stack、queue模拟实现+仿函数

stack、queue模拟实现仿函数 stack定义stack模拟实现 queue定义queue模拟实现 priority_queue定义priority_queue模拟实现 deque定义底层分析 容器适配器定义种类 仿函数控制类里面数据的比较逻辑回调函数仿函数两者区别 铁汁们&#xff0c;今天给大家分享一篇stack、queue模拟…

Vue+SpringBoot打造服装店库存管理系统

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 数据中心模块2.2 角色管理模块2.3 服装档案模块2.4 服装入库模块2.5 服装出库模块 三、系统设计3.1 用例设计3.2 数据库设计3.2.1 角色表3.2.2 服装档案表3.2.3 服装入库表3.2.4 服装出库表 四、系统展示五、核心代码5.…

算法打卡day17|二叉树篇06|Leetcode 654.最大二叉树、617.合并二叉树、700.二叉搜索树中的搜索、98.验证二叉搜索树

算法题 Leetcode 654.最大二叉树 题目链接:654.最大二叉树 大佬视频讲解&#xff1a;最大二叉树视频讲解 个人思路 大概思路就是在数组中 找最大值的节点作为当前节点&#xff0c;用最大值的index切割左右子树的区间&#xff0c;往复循环到数组元素为0&#xff1b; 解法 递…

【数据结构】二叉搜索树底层刨析

文章目录 1. 二叉搜索树的实现2. 二叉搜索树的应用3. 改造二叉搜索树为 KV 结构4. 二叉搜索树的性能分析 1. 二叉搜索树的实现 namespace key {template<class K>struct BSTreeNode{typedef BSTreeNode<K> Node;Node* _left;Node* _right;K _key;BSTreeNode(const…

【1】Python零基础起步

什么是编程(Programming) 编程是编定程序的中文简称&#xff0c;就是让计算机代码解决某个问题&#xff08;目的&#xff09;&#xff0c;对某个计算体系规定一定的运算方式&#xff0c;使计算体系按照该计算方式运行&#xff0c;并最终得到相应结果的过程&#xff08;手段&am…

Java Day 10 io流

IO流 1、前置知识 字符集1.1 标准ASCII1.2 GBK编码1.3 UTF-321.4 UTF-81.5 编码和解码方法 2、IO流2.1 流的分类2.2 FileInputStream2.2.1 常用方法 2.3 FileOutputStram2.3.1 常用方法2.3.2 文件复制案例 2.4 释放资源的方式2.4.1 try-catch-finally2.4.2 try-with-resource 1…

ftp和fxp哪个传传输快,传输大文件该怎么选择?

在当今数字化时代&#xff0c;大文件传输已成为日常工作和商业活动中不可或缺的一部分。无论是跨国公司的数据交换&#xff0c;还是个人用户的大型媒体文件分享&#xff0c;选择一个高效的传输协议至关重要。FTP和FXP是两种常用的文件传输方式&#xff0c;但在传输大文件时&…

nginx gzip性能优化 —— 筑梦之路

对比使用和不使用gzip static处理 1. 不使用 gzip static 时的 gzip 处理 如果你不使用 gzip_static 而只是 "gzip on"&#xff0c;它每次都会被压缩并发送。 虽然它实际上可能缓存在内存中&#xff0c;但传统观点是 "每次都会执行压缩处理&#xff0c;因此 CP…

【SRE系列之docker容器】--dockerfile镜像优化

dockerfile镜像优化 1.1 镜像优化方法 系统镜像采用ubuntu或者alpine&#xff0c;会比centos少1G左右编写业务镜像时从官网拉取镜像&#xff0c;其余配置根据业务需求再配置编写dockerfile时把不用的安装包卸载或者删除尽量减少run命令的使用&#xff08;一个run命令&#xf…

【兆易创新GD32H759I-EVAL开发板】图像处理加速器(IPA)的应用

GD32H7系列的IPA&#xff08;Image Pixel Accelerator&#xff09;是一个高效的图像处理硬件加速器&#xff0c;专门设计用于加速图像处理操作&#xff0c;如像素格式转换、图像旋转、缩放等。它的优势在于能够利用硬件加速来实现这些操作&#xff0c;相比于软件实现&#xff0…

YOLOv9实例分割教程|(二)验证教程

专栏地址&#xff1a;目前售价售价59.9&#xff0c;改进点30个 专栏介绍&#xff1a;YOLOv9改进系列 | 包含深度学习最新创新&#xff0c;助力高效涨点&#xff01;&#xff01;&#xff01; 一、验证 打开分割验证文件&#xff0c;填入数据集配置文件、训练好的权重文件&…

go语言基础笔记

1.基本类型 1.1. 基本类型 bool int: int8, int16, int32(rune), int64 uint: uint8(byte), uint16, uint32, uint64 float32, float64 string 复数&#xff1a;complex64, complex128 复数有实部和虚部&#xff0c;complex64的实部和虚部为32位&#xff0c;complex128的实部…

yocto是个什么东东

yocto不是个什么东东 在我们了解Yocto项目是什么之前&#xff0c;让我们先了解一下它不是什么。 Yocto项目不是用于现有硬件的软件开发工具包&#xff08;SDK&#xff09;&#xff0c;而是用于构建这样一个工具包。 Yocto项目不是可以部署到硬件上的系统二进制镜像&#xff…

客服销冠偷偷用的提效神器!无广很实用

近期发现我的同事每天上班必登录的一款软件——客服宝聊天助手&#xff0c;用过才发现&#xff1a;真客服办公的提效神器&#xff01;感兴趣的小伙伴请往下看~一、客服宝的简介&#xff1a;客服宝聊天助手&#xff0c;是一款跨平台快捷回复工具。自带多种功能&#xff0c;有效帮…

leetcode判断子序列

本题中&#xff0c;我们可以删除原始字符串的一些字符但是不能改变其他字符的位置&#xff0c;这种求子序列的题都可以用动态规划来解决。 首先我们要确定dp数组的定义&#xff0c;这里我们将dp数组定义为dp[i][j] 表示以下标i-1为结尾的字符串s&#xff0c;和以下标j-1为结尾的…

蓝桥杯[OJ 1621]挑选子串-CPP-双指针

目录 一、题目描述&#xff1a; 二、整体思路&#xff1a; 三、代码&#xff1a; 一、题目描述&#xff1a; 二、整体思路&#xff1a; 要找子串&#xff0c;则必须找头找尾&#xff0c;找头可以遍历连续字串&#xff0c;找尾则是要从头的基础上往后遍历&#xff0c;可以设头…

OSCP靶场--BlackGate

OSCP靶场–BlackGate 考点(1.redis rce 2. CVE-2021-4034提权) 1.nmap扫描 ┌──(root㉿kali)-[~/Desktop] └─# nmap -sV -sC -p- 192.168.163.176 --min-rate 2500 Starting Nmap 7.92 ( https://nmap.org ) at 2024-03-14 03:32 EDT Nmap scan report for 192.168.163.…

MongoDB实战面试指南:常见问题一网打尽

码到三十五 &#xff1a; 个人主页 心中有诗画&#xff0c;指尖舞代码&#xff0c;目光览世界&#xff0c;步履越千山&#xff0c;人间尽值得 ! MongoDB是一款流行的非关系型数据库&#xff0c;以其高效、可扩展的特性受到开发者的青睐。了解MongoDB的架构、存储引擎和数据结…

python 基础知识点(蓝桥杯python科目个人复习计划63)

今日复习内容&#xff1a;做题 例题1&#xff1a;蓝桥骑士 问题描述&#xff1a; 小蓝是蓝桥王国的骑士&#xff0c;他喜欢不断突破自我。 这天蓝桥国王给他安排了N个对手&#xff0c;他们的战力值分别为a1,a2,...,an&#xff0c;且按顺序阻挡在小蓝的前方。对于这些对手小…

剪辑设计软件如何跨系统使用?PC也能用Mac Final Cut

我猜你工作中&#xff0c;常常遇到这样那样的麻烦&#xff1a; 临时接手一个项目&#xff0c;之前的同事用Final Cut&#xff0c;而你是Windows系统&#xff1b; 临时有紧急需求要调整&#xff0c;而本地电脑却没有工作软件/性能不给力&#xff1b; 那这样的情况&#xff0c…