分析 container_of 宏

本文详细解释了Linux内核中的container_of宏的功能和使用方法,通过实例演示了如何通过成员变量地址找到整个结构体的首地址,从而访问结构体中的其他成员。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

分析 container_of 宏

功能和使用

这个宏的作用是通过得知结构体中的某个成员变量的地址,然后找到结构体的首地址。

举例说明:

有个结构体:

struct person {
    int age;
    int salary;
    char *name;
};

struct person leo;
int *salary_ptr = &(leon.salary);

得知这个结构体salary的指针:salary_ptr,如何能找到leo这个结构体变量的首地址。找到首地址之后,你可以通过这个首地址再去找该结构体中其他的变量

先想一下,你来设计,你应该如何找到:直观感觉就是往回数,结构体在存储的时候,是顺序存储的,找到salary是第几个变量,然后减去之前的变量占用的空间,就是结构体的首地址了。还要考虑对齐的问题。暂时先把这个思路留在这里。继续往下看

简单写个程序测试一下

Makefile文件

obj-m += container_of.o

all:
	make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules

clean:
	make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean

测试代码:container_of.c

#include <linux/module.h>
#include <linux/kernel.h>

struct person {
    int age;
    int salary;
    char *name;
};

int container_of_init(void)
{
    struct person leo;
    struct person *leo_ptr;

    int *age_ptr = &(leo.age);
    int *salary_ptr = &(leo.salary);
    char **name_ptr = &(leo.name);

    leo_ptr = container_of(age_ptr, struct person, age);
    printk(KERN_INFO "addr of person:0x%p\n", leo_ptr);

    leo_ptr = container_of(salary_ptr, struct person, salary);
    printk(KERN_INFO "addr of person:0x%p\n", leo_ptr);

    leo_ptr = container_of(name_ptr, struct person, *name);
    printk(KERN_INFO "addr of person:0x%p\n", leo_ptr);

    printk(KERN_INFO "addr of age:0x%p, 0x%p\n", &(leo_ptr->age), &(leo.age));
    printk(KERN_INFO "addr of salary:0x%p, 0x%p\n", &(leo_ptr->salary), &(leo.salary));
    printk(KERN_INFO "addr of name:0x%p, 0x%p\n", leo_ptr->name, leo.name);

    return 0;
}

void container_of_exit(void)
{
    return;
}
module_init(container_of_init);
module_exit(container_of_exit);

输出了结果:通过成员变量,都找到了结构体的首地址,并且再通过结构体的首地址,找到了变量的地址。

[158680.247539] addr of person:0x00000000e491801b
[158680.247541] addr of person:0x00000000e491801b
[158680.247542] addr of person:0x00000000e491801b
[158680.247542] addr of age:0x00000000e491801b, 0x00000000e491801b
[158680.247549] addr of salary:0x000000001e76c839, 0x000000001e76c839
[158680.247550] addr of name:0x0000000063561914, 0x0000000063561914

代码分析

container_of宏位于<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) ({				\
	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 *)(__mptr - offsetof(type, member))); })

慢慢来理解这个宏,这个宏由两部分组成,一个是BUILD_BUG_ON_MSG,还有就是首先就是((type *)(__mptr - offsetof(type, member)));

先看BUILD_BUG_ON_MSG,编译告警输出消息。

/**
 * BUILD_BUG_ON_MSG - break compile if a condition is true & emit supplied
 *		      error message.
 * @condition: the condition which the compiler should know is false.
 *
 * See BUILD_BUG_ON for description.
 */
#define BUILD_BUG_ON_MSG(cond, msg) compiletime_assert(!(cond), msg)

是在编译阶段输出告警,举个例子:

正确的指针类型定义:
char **name_ptr = &(leo.name);
错误的指针类型
int **name_ptr = &(leo.name);

由于用了错误的指针,会在编译阶段输出:

./include/linux/compiler.h:324:38: error: call to ‘__compiletime_assert_25’
 declared with attribute error: pointer type mismatch in container_of()

接着继续展开((type *)(__mptr - offsetof(type, member)));

void *__mptr = (void *)(ptr);
((type *)(__mptr - offsetof(type, member))); 

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

将例子中的参数带入

struct person {
    int age;
    int salary;
    char *name;
};
struct person leo;
int *salary_ptr = &(leo.salary);
leo_ptr = container_of(salary_ptr, struct person, salary);
展开后:
(struct person*)((void *)(salary_ptr) - ((size_t))&((struct person*)0->salary));

第1部分

(void *)(salary_ptr):这部分是结构体中salary的位置,用salary_ptr表示,将salary_ptr指针转换为void指针,这个很好理解。

第2部分

((size_t))&((struct person*)0->salary)):这部分就是结构体首地址到salary的偏移量。

由于只需要一个偏移量,那将结构体的首地址设置成从0开始,(struct person*)0,就构造了这个结构体,但是地址是0,但是这个结构体构造还是不变的,(struct person*)0->salary表示了结构体首部到salary的偏移量。

这样取出来是结构体的成员,还要对这个成员进行取地址运算&,得到成员的地址。将这个地址转换为(size_t)类型,这样就表示了结构体首部到salary的偏移。

计算偏移量

用第1部分减去第2部分,就得到了结构体的首地址,最后将该地址,转换成(struct person*)返回。

<think>嗯,用户问的是container_of的用法,尤其是在定时器回调函数中如何通过timer成员获取包含它的struct my_data结构体指针。首先,我得回忆一下container_of的作用。这个在Linux内核中很常见,用于根据结构体成员的地址找到整个结构体的起始地址。比如,如果有一个结构体包含某个成员,知道了这个成员的地址,就可以通过container_of得到外层结构体的指针。 用户提到了定时器回调函数,可能需要结合引用中的timer_list结构体。比如在引用[3]的例子中,定时器回调函数需要访问包含timer_list的结构体数据。这时候就需要用container_of来获取外层结构体的指针,从而访问其他成员。 接下来,我需要解释container_of的三个参数:成员指针、结构体类型、成员名称。内部通过计算成员在结构体中的偏移量,用成员地址减去偏移得到结构体的起始地址。这里可能需要展开的定义,比如用typeof获取类型,然后用指针运算。 然后,用户可能需要具体的代码示例。比如定义一个struct my_data,里面包含timer_list。在回调函数中,使用container_of从timer_list变量获取my_data的指针。需要结合引用[3]中的例子,比如在xxx_func中使用mod_timer时,可能需要访问my_data的其他数据,这时候就需要通过container_of来获取整个结构体的指针。 还要注意类型转换和指针运算的正确性,确保的参数正确,成员名称正确。另外,在定时器回调函数中,data字段可能已经被用来传递参数,但使用container_of更灵活,可以直接从timer_list本身获取外层结构体,这可能更安全。 可能的相关问题包括container_of的原理、在链表中的应用、错误使用的后果,以及内核中其他类似机制。比如引用[2]中的work_struct结构体,也可能用到类似的方法来获取外层结构体。 需要确认用户是否了解结构体在内存中的布局,以及指针运算的基础。如果用户不熟悉,可能需要简单说明结构体成员的偏移概念。另外,确保示例代码正确,符合内核编程的规范,比如使用正确的和函数。 总结回答的结构:先解释container_of的作用和参数,再给出定时器回调中的示例,说明步骤,最后列出相关问题。引用中提供的timer_list和work_struct的例子可以作为应用场景的补充说明。</think>### C语言中`container_of`的用法解析 #### 1. `container_of`的作用 `container_of`是Linux内核中广泛使用的一种通过结构体成员地址反向获取包含它的外层结构体指针的工具。其核心功能是:**已知某个结构体成员的地址和类型,计算出外层结构体的起始地址**。在定时器回调、工作队列、链表操作等场景中尤为常见[^1][^2][^3]。 #### 2. 定义与参数说明 ```c #define container_of(ptr, type, member) ({ \ const typeof(((type *)0)->member) *__mptr = (ptr); \ (type *)((char *)__mptr - offsetof(type, member)); }) ``` - **`ptr`**:已知结构体成员的地址(如`timer_list`指针) - **`type`**:外层结构体类型(如`struct my_data`) - **`member`**:成员在结构体中的名称(如`timer`) #### 3. 定时器回调中的典型用法 假设存在以下结构体定义: ```c struct my_data { int value; struct timer_list timer; // 内嵌的定时器成员 }; ``` 在定时器回调函数中,通过`timer_list`成员获取外层结构体指针: ```c void timer_callback(struct timer_list *t) { // 通过container_of反向获取外层结构体指针 struct my_data *data = container_of(t, struct my_data, timer); // 操作外层结构体成员 data->value += 1; mod_timer(&data->timer, jiffies + HZ); // 重新激活定时器[^3] } ``` #### 4. 关键步骤分析 1. **成员地址对齐**:`typeof(((type *)0)->member)`获取成员的类型,通过指针类型转换确保地址计算正确 2. **偏移量计算**:`offsetof(type, member)`计算成员在结构体中的偏移量 3. **指针运算**:`(char *)__mptr - offset`将成员指针回退到外层结构体起始地址 #### 5. 注意事项 - **类型安全**:必须确保`ptr`指向的确实是`type`结构体中声明的`member`成员 - **内存布局**:依赖结构体成员在内存中的连续存储特性 - **替代方案**:可通过`timer.data`字段直接传递指针(但`container_of`更灵活)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值