container_of的含义及使用方法

本文详细解读Linux内核中container_of宏的原理与应用,包括typeof、offsetof的使用,以及如何通过该宏从成员变量找到包含结构体。以字符设备驱动程序为例,展示了如何在实际代码中利用container_of宏解决问题。

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

1. 宏定义

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) ({			\
        const typeof( ((type *)0)->member ) *__mptr = (ptr);	\
        (type *)( (char *)__mptr - offsetof(type,member) );})


2. 解读

2.1 typeof

简单地讲,typeof()就是获取变量或数据的数据类型。参考:http://gcc.gnu.org/onlinedocs/gcc/Typeof.html


2.2 offsetof

offsetof宏的定义在<linux/stddef.h>文件中,如下:

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


其意思就是获取TYPE结构体(本文中只需要关心struct即可)中MEMBER成员相对于该结构起始位置的偏移量。下面链接给出的严格解释如下:

       The macro offsetof() returns the offset of the field member from the
       start of the structure type.

       This macro is useful because the sizes of the fields that compose a
       structure can vary across implementations, and compilers may insert
       different numbers of padding bytes between fields.  Consequently, an
       element's offset is not necessarily given by the sum of the sizes of
       the previous elements.

       A compiler error will result if member is not aligned to a byte
       boundary (i.e., it is a bit field).


参考下面两个资料:

直接演示上述链接中的代码:

/* offsetof example */
#include <stdio.h>      /* printf */
#include <stddef.h>     /* offsetof */

struct foo {
  char a;
  char b[10];
  char c;
};

int main ()
{
  printf ("offsetof(struct foo,a) is %d\n",(int)offsetof(struct foo,a));
  printf ("offsetof(struct foo,b) is %d\n",(int)offsetof(struct foo,b));
  printf ("offsetof(struct foo,c) is %d\n",(int)offsetof(struct foo,c));
  
  return 0;
}

运行结果:

offsetof(struct foo,a) is 0
offsetof(struct foo,b) is 1
offsetof(struct foo,c) is 11



2.3 container_of

container_of的算法包括如下几步:

  1. 确定成员变量的地址;
  2. 确定成员相对于container的偏移量;
  3. 成员地址减去这个偏移量,就是container的地址;
  4. 把这个地址强制转化成container类型,即获取container的地址了。


3. 示例

container_of的使用,在Linux内核代码中几乎随处可见。以字符设备驱动程序为例,<linux/cdev.h>中的cdev定义:

struct cdev {
	struct kobject kobj;
	struct module *owner;
	const struct file_operations *ops;
	struct list_head list;
	dev_t dev;
	unsigned int count;
};

其中嵌入了一个kobject对象。再驱动程序的实现代码中,往往需要从kobject出发,找到它的container。下面是<fs/char_dev.c>中的部分代码:

/*
 * Called every time a character special file is opened
 */
int chrdev_open(struct inode * inode, struct file * filp)
{
	struct cdev *p;
	struct cdev *new = NULL;
	int ret = 0;

	spin_lock(&cdev_lock);
	p = inode->i_cdev;
	if (!p) {
		struct kobject *kobj;
		int idx;
		spin_unlock(&cdev_lock);
		kobj = kobj_lookup(cdev_map, inode->i_rdev, &idx);
		if (!kobj)
			return -ENXIO;
		new = container_of(kobj, struct cdev, kobj);
		spin_lock(&cdev_lock);
		p = inode->i_cdev;
		if (!p) {
			inode->i_cdev = p = new;
			inode->i_cindex = idx;
			list_add(&inode->i_devices, &p->list);
			new = NULL;
		} else if (!cdev_get(p))
			ret = -ENXIO;
	} else if (!cdev_get(p))
		ret = -ENXIO;
	spin_unlock(&cdev_lock);
	cdev_put(new);
	if (ret)
		return ret;
	filp->f_op = fops_get(p->ops);
	if (!filp->f_op) {
		cdev_put(p);
		return -ENXIO;
	}
	if (filp->f_op->open) {
		lock_kernel();
		ret = filp->f_op->open(inode,filp);
		unlock_kernel();
	}
	if (ret)
		cdev_put(p);
	return ret;
}

从kobject找到cdev的代码是(省略了部分代码):

	struct cdev *new = NULL;
	if (!p) {
		struct kobject *kobj;
		kobj = kobj_lookup(cdev_map, inode->i_rdev, &idx);
		new = container_of(kobj, struct cdev, kobj);

在该文件中继续往下看,有下面这个函数:

static void cdev_purge(struct cdev *cdev)
{
	spin_lock(&cdev_lock);
	while (!list_empty(&cdev->list)) {
		struct inode *inode;
		inode = container_of(cdev->list.next, struct inode, i_devices);
		list_del_init(&inode->i_devices);
		inode->i_cdev = NULL;
	}
	spin_unlock(&cdev_lock);
}

这里是从cdev找出container,即struct inode对象的指针。其中inode是在<linux/fs.h>中定义的,如下(省略了大部分代码):

struct inode {
	uid_t			i_uid;
	gid_t			i_gid;
	dev_t			i_rdev;

	struct timespec		i_atime;
	struct timespec		i_mtime;
	struct timespec		i_ctime;


	struct list_head	i_devices;
	union {
		struct pipe_inode_info	*i_pipe;
		struct block_device	*i_bdev;
		struct cdev		*i_cdev;
	};

	unsigned long		i_state;
};


4. 参考资料

<think>好的,用户想了解container_of宏的使用示例,特别是在C语言内核编程中的应用。我需要先回忆一下这个宏的作用。container_of宏在Linux内核中非常常见,它用于通过结构体成员的指针来获取整个结构体的指针。这对于内核链表等数据结构非常有用。 首先,用户提供的引用中有多个关于container_of宏的定义和解释,比如引用[2]和[3]详细描述了宏的实现。需要确保正确理解这些宏的组成部分,比如offsetof的作用是计算成员在结构体中的偏移量。然后,container_of通过减去这个偏移量来得到结构体的起始地址。 接下来,我需要构建一个使用示例。用户可能需要一个具体的代码例子,展示如何在实际中应用这个宏。例如,在内核中经常用链表结构,每个节点包含一个struct list_head成员,而container_of可以用于从链表节点找到包含它的结构体实例。 在示例中,可以定义一个包含链表节点的结构体,比如struct my_data,其中有一个list成员。然后创建一个实例,初始化链表节点,并通过container_of宏从list成员的指针获取整个struct my_data的指针。这需要包含正确的头文件,比如linux/kernel.h,但用户可能需要注意内核编程的环境。 同时,用户可能想知道宏的参数含义,比如ptr是结构体成员的指针,type是结构体类型,member是成员的名字。需要详细解释每个参数的作用,并确保示例代码中的参数传递正确。 另外,需要注意在内核编程中,可能需要使用特定的宏定义,比如引用[5]中的offsetof定义,或者内核已经提供的实现。用户的问题可能涉及如何正确引用这些宏,是否需要自行定义还是内核已经包含。 最后,生成的相关问题需要围绕container_of宏的应用场景、实现原理以及可能的问题,比如安全性或偏移计算是否正确。这有助于用户进一步理解和应用该宏。</think>### container_of使用示例 `container_of` 宏在 Linux 内核编程中广泛用于**通过结构体成员指针反向获取整个结构体的指针**,尤其在链表操作中十分常见。 #### 示例代码 ```c #include <linux/kernel.h> #include <linux/types.h> // 定义包含链表节点的数据结构 struct my_data { int value; struct list_head list; // 链表成员 char name[20]; }; // 使用示例 void demo_container_of(void) { struct my_data data = { .value = 42, .name = "test" }; // 已知 list 成员地址,反向获取其父结构体 my_data 的指针 struct list_head *ptr = &data.list; struct my_data *parent = container_of(ptr, struct my_data, list); printk("Value: %d, Name: %s\n", parent->value, parent->name); } ``` #### 参数解析 - `ptr`: 结构体成员的指针(示例中的 `&data.list`) - `type`: 目标结构体类型(示例中的 `struct my_data`) - `member`: 成员在结构体中的名称(示例中的 `list`) #### 实现原理 1. **偏移计算**:通过 `offsetof` 宏计算成员在结构体中的偏移量,例如: $$ \text{offset} = \&((\text{struct my_data} *)0)->list $$ 2. **地址转换**:将成员指针转换为字符指针(确保按字节计算),减去偏移量后得到结构体起始地址: $$ \text{parent} = (\text{struct my_data} *)((char *)ptr - \text{offset}) $$[^2][^5] #### 应用场景 - 内核链表遍历时获取宿主结构体(如进程控制块、设备驱动数据结构)[^4] - 实现面向对象风格的数据封装 - 减少冗余指针存储,优化内存使用
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值