驱动-兼容不同设备-container_of

驱动兼容不同类型设备
在 Linux 驱动开发中,container_of 宏常被用来实现一个驱动兼容多种不同设备的架构。这种设计模式在 Linux 内核中非常常见,特别
是在设备驱动模型中。linux内核的主要开发语言是C,但是现在内核的框架使用了非常多的面向对象的思想,这就面临了一个用C语言
来实现面向对象编程的问题


参考资料

在字符设备这块内容,所有知识点都是串联起来的,需要整体来理解,缺一不可,建议多了解一下基础知识
驱动-申请字符设备号
驱动-注册字符设备
驱动-创建设备节点
驱动-字符设备驱动框架
驱动-杂项设备
驱动-内核空间和用户空间数据交换
驱动-文件私有数据

典型应用场景

  • 同一厂商的不同型号设备
  • 功能相似但寄存器布局不同的设备
  • 需要维护设备特定数据的场合

原理:利用结构体中元素指针获取结构体指针

Kobject是linux设备驱动模型的基础,也是设备模型中抽象的一部分。

linux内核为了兼容各种形形色色的设备,就需要对各种设备的共性进行抽象,抽象出一个基类,其余的设备只需要继承此基类就可以了。

而此基类就是kobject(暂且把它看成是一个类),但是C语言没有面向对象语法。

在C++中这样的操作非常简单,继承基类就可以了,而在C语言中需要将基类的结构体指针嵌入到派生的类中,那么为什么将基类指针嵌入就可以得到派生类的指针呢?

这个实现是一个宏:container_of。

container_of 函数

container_of 在 Linux 内核中是一个常用的宏, 用于从包含在某个结构中的指针获得结构本
身的指针, 通俗地讲就是通过结构体变量中某个成员的首地址进而获得整个结构体变量的首地
址。 那么可以使用这个函数获取不同设备的地址, 来对不同的设备进行操作, 从而一个驱动可
以兼容不同的设备。

container_of 

函数原型:
    container_of(ptr,type,member)
函数作用:
   通过结构体变量中某个成员的首地址获取到整个结构体变量的首地址。
参数含义:
  ptr 是结构体变量中某个成员的地址。
  type 是结构体的类型
  member 是该结构体变量的具体名字

container_of 宏的作用是通过结构体内某个成员变量的地址和该变量名, 以及结构体类型。
找到该结构体变量的地址

理解

C程序例子

 #include <stdio.h>
 
 struct Base{
   
                       //定义一个Base类
     int var;
     char *string;
 };
 
 struct Derived{
   
                    //定义一个派生类
     int var;
     struct Base base;           //派生类中包含了struct Base类
 };

 #define offsetof(TYPE, MEMBER)	((size_t)&((TYPE *)0)->MEMBER)       //offset_of宏
 #define container_of(ptr, type, member) ({
   
   				\            //阉割版container_of,省去了类型检查
     void *__mptr = (void *)(ptr);                     \
     ((type *)(__mptr - offsetof(type, member))); })
 
 struct Base *base_p;                  
 struct Derived test_derived;       
 
 
 struct Derived *get;
 int main()
 {
   
   
    base_p = &test_derived.base;                                     //赋值给指针
    printf("Derived addr = %x\n",(unsigned int)&test_derived);
    printf("=================================\n");
    get = container_of(base_p,struct Derived,base);                 //使用base_p指针获取派生类的首地址
    printf("get Derived addr = %x\n",(unsigned int)get);

    return 0;
 }

输出结果:


输出如下:
Derived addr = 601050
=================================
get Derived addr = 601050

源码分析:最重要的就是理解 container_of 函数

    get = container_of(base_p,struct Derived,base);                 //使用base_p指针获取派生类的首地址

  
  


  • base_p :ptr 是结构体变量中某个成员的地址。 这个base_p 是怎么定义的 struct Base *base_p; Base 又是在哪里定义的呢? 作为派生类Deviced 的成员变量 struct Base base

  • struct Derived: type 是结构体的类型,不就是派生类的结构体类型吗? 这个函数就是要返回这个结构体类型的指针

  • base: member 是该结构体变量的具体名字。 就是派生类里面 定义的成员变量名字。

我们这里讨论和接下来讨论的目的就是要理解这个函数container_of的作用,理解参数和返回类型 以及理解实际应用的价值。

实验

使用 container_of 函数编写一个驱动兼容不同设备

源码程序 file.c

#include <linux/init.h>
#include <linux/module.h>
#include <linux/kdev_t.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>

struct device_test
{
   
   

    dev_t dev_num;         // 设备号
    int major;             // 主设备号
    int minor;             // 次设备号
    struct cdev cdev_test; // cdev
    struct class *class;   // 类
    struct device *device; // 设备
    char kbuf
<think> 我们正在讨论ESP-IDF中的`__containerof`宏。根据之前的引用,我们并没有直接关于`__containerof`的信息,但是我们可以根据一般知识和ESP-IDF的编程实践来回答。 `__containerof`宏是一个常用的技巧,用于从结构体成员的指针获取整个结构体的指针。这在Linux内核中称为`container_of`,在ESP-IDF中可能有类似的实现。 原理: 假设我们有一个结构体类型`struct my_struct`,其中有一个成员`member`,类型为`type_member`。如果我们有一个指向`member`的指针`ptr`(即`ptr = &my_struct_instance.member`),那么我们可以通过以下方式获取整个结构体的起始地址: ``` 整个结构体的起始地址 = (char *)ptr - offsetof(struct my_struct, member) ``` 这里,`offsetof`宏用于计算成员在结构体中的偏移量。 在ESP-IDF中,`__containerof`宏通常定义如下(或类似): ```c #define __containerof(ptr, type, member) ((type *)((char *)(ptr) - offsetof(type, member))) ``` 这样,当我们知道一个结构体成员的指针`ptr`,结构体类型`type`,以及成员名`member`时,就可以得到整个结构体的指针。 使用示例: 假设我们有以下结构体: ```c typedef struct { int a; double b; char c; } my_struct_t; ``` 现在我们有一个指向`b`的指针: ```c my_struct_t my_struct_instance; double *ptr_b = &my_struct_instance.b; ``` 我们可以使用`__containerof`宏来获取整个结构体的指针: ```c my_struct_t *container = __containerof(ptr_b, my_struct_t, b); ``` 这样,`container`就指向了`my_struct_instance`的起始地址。 注意事项: 1. 确保`ptr`确实是指向结构体中某个成员的指针。 2. 确保传入的`type`是结构体的类型,`member`是成员的确切名称。 3. 这个宏在链表、队列等数据结构的实现中非常常见,比如在FreeRTOS或ESP-IDF的驱动程序中。 在ESP-IDF的具体位置: 由于`__containerof`是一个基础宏,它可能在ESP-IDF的多个组件中出现。常见的定义位置可能在`esp_rom.h`或`esp_common.h`等头文件中,但具体位置可能会随着版本变化而变化。我们可以通过搜索ESP-IDF的源代码来找到确切的定义。 根据引用[4]中提到了`freertos/FreeRTOS.h`等头文件,但并没有直接提到`__containerof`。因此,我们可能需要查看ESP-IDF的源代码。 实际上,在ESP-IDF中,这个宏可能被定义在`esp_system.h`或`rom/ets_sys.h`等头文件中。但更常见的是,它可能被定义在某个内部头文件中。 另外,在ESP-IDF中,我们也可以使用标准库中的`offsetof`(定义在`stddef.h`)来实现相同的功能,但`__containerof`宏提供了便利。 总结: `__containerof`宏是一个常用的C语言技巧,用于通过结构体成员的指针获取整个结构体的指针。它在ESP-IDF中的实现与Linux内核中的`container_of`类似。使用时需要确保指针的有效性,以及类型和成员名的正确性。 相关问题: 1. `__containerof`宏在ESP-IDF中的具体定义位置在哪里? 2. 使用`__containerof`宏时需要注意哪些问题? 3. 除了`__containerof`,ESP-IDF中还有哪些类似的宏或函数? </think> ### esp-idf 中 `__containerof` 的使用和原理 `__containerof` 是 ESP-IDF 中一个关键的宏定义,主要用于 **通过结构体成员的地址反向定位整个结构体的起始地址**,常见于驱动开发和底层数据结构实现中。 #### 基本原理 宏定义如下(简化版): ```c #define __containerof(ptr, type, member) \ ((type *)((char *)(ptr) - offsetof(type, member))) ``` 其中: - `ptr`:结构体成员的指针 - `type`:结构体类型 - `member`:成员在结构体中的名称 **工作原理**: 1. 通过 `offsetof(type, member)` 计算成员在结构体中的偏移量 2. 将成员指针 `ptr` 转换为字节型指针 `(char *)` 3. `(char *)ptr - offset` 得到结构体起始地址 4. 最终转换为目标结构体指针 `(type *)` #### 典型使用场景 在驱动开发中,常见于 **回调函数获取上下文**: ```c // 定义结构体(设备上下文) typedef struct { rmt_channel_t channel; gpio_num_t gpio_num; uint8_t mem_block_num; // 引用[1]提到的内存块数量 } led_strip_t; // 回调函数 static void rmt_isr_handler(void* arg) { // 通过RMT寄存器指针获取设备上下文 led_strip_t* strip = __containerof((RMTMEM.chan[0].data32), led_strip_t, rmt_data); strip->channel = 0; // 操作通道 // ... 其他操作 } ``` 此处通过 RMT 内存块地址(`RMTMEM.chan[0].data32`)反向定位到 `led_strip_t` 结构体[^1][^4]。 #### 关键作用 1. **逆向定位** 解决"已知成员地址,求父结构体地址"的问题,避免全局变量 2. **驱动封装** 在初始化时绑定硬件资源(如引用[1]的 `rmt_tx_config_t` 配置): ```c void led_strip_init(rmt_config_t* config) { led_strip_t* strip = malloc(sizeof(led_strip_t)); strip->gpio_num = config->gpio_num; // 配置引脚 // 注册中断时将成员地址传入 rmt_isr_register(rmt_isr_handler, &strip->rmt_data); } ``` 3. **内存安全** 相比手动计算偏移量,`__containerof` 通过编译期检查确保类型安全。 #### 注意事项 1. **指针有效性** 传入的 `ptr` 必须是结构体中**真实存在的成员地址**,否则引发内存错误。 2. **对齐问题** 当结构体包含非字节对齐成员(如 `uint32_t`)时,需确保编译器未插入填充字节(可用 `#pragma pack(1)`)。 3. **替代方案** C11 标准支持 `container_of` 宏(需包含 `stddef.h`),但 ESP-IDF 使用 `__containerof` 保持跨平台兼容性。 #### 相关引用 - 在 RMT 驱动配置中用于管理通道资源(引用[1]的 `channel` 配置)[^1] - 在 WS2812 驱动示例中关联中断上下文(引用[4]的 `rmt_struct.h` 使用场景)[^4] --- ### 相关问题 1. ESP-IDF 中 `offsetof` 的实现原理是什么? 2. 如何通过 `__containerof` 实现多级结构体的嵌套定位? 3. 在中断服务函数中使用 `__containerof` 需要注意哪些线程安全问题? 4. 对比 Linux 内核的 `container_of` 宏,ESP-IDF 的实现有何特殊优化?
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

野火少年

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值