了解Linux内核中的container_of宏

时间:2013-04-05 11:06:34

标签: c linux-kernel c-preprocessor

当我浏览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);

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

7 个答案:

答案 0 :(得分:72)

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

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

最有可能这种混乱引起了所有人的头痛。实际上,引用中的member参数是指容器结构中为该成员指定的名称。

以此容器为例:

struct container {
  int some_other_data;
  int this_data;
}

指向int *my_ptr成员的指针this_data,您可以使用宏来获取指向struct container *my_container的指针:

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

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

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

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

答案 1 :(得分:15)

最后一句话:

(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)

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

答案 2 :(得分:8)

使用gcc扩展名statements expressions。如果您将宏视为返回值的内容,则最后一行将是:

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

答案 3 :(得分:2)

一些真实的背景说清楚,在下使用红黑树作为例子,这是 我理解的方式container_of

作为Documentation/rbtree.txt状态,在linux内核代码中,它不是rb_node包含数据 进入,而不是

  

rbtree树中的数据节点是包含结构的结构   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_entrycontainer_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
  • ptr是指向member个实例的type的指针,此处为rb_node *nd

container_of做的是,如本例所示,

  • 给定obj.member的地址(此处为obj.vm_rb),返回 地址obj
  • 由于struct是一块连续的内存,地址obj.vm_rb减去 offset 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

的定义

<强> P.S。

以上文件属于当前开发版本,即4.13.0-rc7

file:k表示file中的第k行。

答案 4 :(得分:1)

Linux内核中的

conatainer_of()宏-

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

 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); 

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

  • pointer:这是指向结构中字段的指针
  • 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中减去字段年龄的偏移量,则将获得正确的位置。这是宏的最后一行:

(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宏。

答案 5 :(得分:0)

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

答案 6 :(得分:0)

容器_of宏的最简单实现是在下面,它减少了类型和工作的所有复杂检查

__next__

ptr将给出成员的地址,然后减去偏移量差,您将 获取起始地址。

示例用法

__iter__