container_of的含义及使用方法

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. 参考资料

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值