95

当我浏览Linux内核时,我发现一个container_of宏定义如下:

#define container_of(ptr, type, member) ({                      \
        const typeof( ((type *)0)->member ) *__mptr = (ptr);    \
        (type *)( (char *)__mptr - offsetof(type,member) );})

明白container_of是做什么的,但不明白的是最后一句话,就是

(type *)( (char *)__mptr - offsetof(type,member) );})

如果我们使用宏如下:

container_of(dev, struct wifi_device, dev);

最后一句的对应部分是:

(struct wifi_device *)( (char *)__mptr - offset(struct wifi_device, dev);

看起来什么都不做。有人可以在这里填补空白吗?

4

7 回答 7

96

您的使用示例container_of(dev, struct wifi_device, dev);可能有点误导,因为您在那里混合了两个命名空间。

虽然dev您的示例中的第一个是指指针的名称,但第二个dev是指结构成员的名称。

很可能这种混淆引起了所有人的头痛。实际上member,您引用中的参数是指在容器结构中赋予该成员的名称。

以这个容器为例:

struct container {
  int some_other_data;
  int this_data;
}

以及指向您将使用宏获取指针int *my_ptr的成员的指针:this_datastruct container *my_container

struct container *my_container;
my_container = container_of(my_ptr, struct container, this_data);

考虑到结构开头的偏移量this_data对于获得正确的指针位置至关重要。

实际上,您只需this_data从指针中减去成员的偏移量my_ptr即可获得正确的位置。

这正是宏的最后一行所做的。

于 2013-04-05T11:21:15.560 回答
22

最后一句投了:

(type *)(...)

指向给定的type. 指针计算为从给定指针的偏移量dev

( (char *)__mptr - offsetof(type,member) )

使用cointainer_of宏时,您希望检索包含给定字段指针的结构。例如:

struct numbers {
    int one;
    int two;
    int three;
} n;

int *ptr = &n.two;
struct numbers *n_ptr;
n_ptr = container_of(ptr, struct numbers, two);

您有一个指向结构中间的指针(并且您知道这是指向字段two[结构中的字段名称] 的指针),但您想检索整个结构 ( numbers)。因此,您计算结构中字段的偏移量two

offsetof(type,member)

并从给定的指针中减去这个偏移量。结果是指向结构开头的指针。最后,将此指针转换为结构类型以获得有效变量。

于 2013-04-05T11:18:50.633 回答
11

Linux 内核中的 container_of() 宏 -

在代码中管理多个数据结构时,您几乎总是需要将一个结构嵌入到另一个结构中并随时检索它们,而不会被问到有关内存偏移或边界的问题。假设您有一个结构人员,定义如下:

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

通过仅具有年龄或薪水的指针,您可以检索包装(包含)该指针的整个结构。顾名思义,container_of 宏用于查找结构的给定字段的容器。该宏在 include/linux/kernel.h 中定义,如下所示:

#define container_of(ptr, type, member) ({               \ 
   const typeof(((type *)0)->member) * __mptr = (ptr);   \ 
   (type *)((char *)__mptr - offsetof(type, member)); })

不要害怕指针;只需如下所示:

container_of(pointer, container_type, container_field); 

以下是前面代码片段的元素:

  • 指针:这是指向结构中字段的指针
  • container_type:这是包装(包含)指针的结构类型
  • container_field:这是结构内部指针指向的字段的名称

让我们考虑以下容器:

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

现在,让我们考虑它的一个实例,以及指向年龄成员的指针:

struct person somebody; 
[...] 
int *age_ptr = &somebody.age; 

除了指向名称成员 (age_ptr) 的指针外,您还可以使用 container_of 宏来获取指向包装此成员的整个结构 (容器) 的指针,方法是使用以下内容:

struct person *the_person; 
the_person = container_of(age_ptr, struct person, age); 

container_of 考虑了结构开头的年龄偏移量,以获得正确的指针位置。如果从指针age_ptr 中减去字段age 的偏移量,就会得到正确的位置。这是宏的最后一行的作用:

(type *)( (char *)__mptr - offsetof(type,member) ); 

将此应用于一个真实的例子,给出以下内容:

struct family { 
    struct person *father; 
    struct person *mother; 
    int number_of_sons; 
    int family_id; 
} f; 

/*   
 * Fill and initialise f somewhere   */      [...]

 /* 
  * pointer to a field of the structure 
  * (could be any (non-pointer) member in the structure) 
  */ 
   int *fam_id_ptr = &f.family_id; 
   struct family *fam_ptr; 

   /* now let us retrieve back its family */ 
   fam_ptr = container_of(fam_id_ptr, struct family, family_id); 

container_of 宏主要用于内核中的通用容器。

这就是内核中的 container_of 宏。

于 2019-01-21T11:06:22.743 回答
10

它是对 gcc 扩展的利用,即statements 表达式。如果您将宏视为返回值的东西,那么最后一行将是:

return (struct wifi_device *)( (char *)__mptr - offset(struct wifi_device, dev);

有关复合语句的解释,请参见链接页面。这是一个例子:

int main(int argc, char**argv)
{
    int b;
    b = 5;
    b = ({int a; 
            a = b*b; 
            a;});
    printf("b %d\n", b); 
}

输出是

b 25

于 2013-04-05T11:31:08.967 回答
3

一点真实的上下文说得更清楚,下面以红黑树为例,这是我理解的方式container_of

如前所述Documentation/rbtree.txt,在 linux 内核代码中,它不是 rb_node 包含数据条目,而是

rbtree 树中的数据节点是包含 struct rb_node 成员的结构。

struct vm_area_struct(在文件中include/linux/mm_types.h:284)就是这样一个结构,

在同一个文件中,有一个宏rb_entry定义为

#define rb_entry(ptr, type, member) container_of(ptr, type, member)

显然,rb_entry与 相同container_of

mm/mmap.c:299内部函数定义browse_rb中,有一个用法rb_entry

static int browse_rb(struct mm_struct *mm)
{
    /* two line code not matter */
    struct rb_node *nd, *pn = NULL; /*nd, first arg, i.e. ptr. */
    unsigned long prev = 0, pend = 0;

    for (nd = rb_first(root); nd; nd = rb_next(nd)) {
        struct vm_area_struct *vma;
        vma = rb_entry(nd, struct vm_area_struct, vm_rb);   
        /* -- usage of rb_entry (equivalent to container_of) */
        /* more code not matter here */

现在很清楚,在container_of(ptr, type, member),

  • type是容器结构,这里struct vm_area_struct
  • membertype实例成员的名称,此处vm_rb为 ,类型为rb_node
  • ptrmember是一个指向type实例的指针,这里rb_node *nd.

container_of什么是,就像在这个例子中一样,

  • 给定obj.member(此处obj.vm_rb)的地址,返回 的地址obj
  • 由于结构是一块连续的内存,减号 的地址将是容器的地址。obj.vm_rboffset between the struct and member

include/linux/kernel.h:858 -- 定义container_of

include/linux/rbtree.h:51 -- 定义rb_entry

mm/mmap.c:299 -- 用法rb_entry

include/linux/mm_types.h:284 --struct vm_area_struct

Documentation/rbtree.txt: -- 红黑树的文档

include/linux/rbtree.h:36 -- 定义struct rb_node

附言

以上文件为当前开发版本,即4.13.0-rc7.

file:k表示 中的第 k 行file

于 2017-08-28T16:32:40.417 回答
3

对于理解 linux 内核中的 container_of 宏非常有用的链接。 https://linux-concepts.blogspot.com/2018/01/understanding-containerof-macro-in.html

于 2019-05-14T13:18:00.367 回答
0

Container _of 宏的最简单实现如下,它减少了所有复杂的类型检查和工作

#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
#define container_of(ptr, type, member) ((type *)((char *)(ptr) - offsetof(type, member))) 

ptr 将给出成员的地址并减去偏移差,您将获得起始地址。

示例用法

struct sample {
    int mem1;
    char mem2;
    int mem3;
};
int main(void)
{

struct sample sample1;

printf("Address of Structure sample1 (Normal Method) = %p\n", &sample1);
printf("Address of Structure sample1 (container_of Method) = %p\n", 
                        container_of(&sample1.mem3, struct sample, mem3));

return 0;
}
于 2019-12-30T02:29:46.360 回答