【问题标题】:Understanding container_of macro in the Linux kernel理解 Linux 内核中的 container_of 宏
【发布时间】:2013-03-27 18:32:23
【问题描述】:

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

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

【问题讨论】:

  • this answer一个真实直观的例子使用红黑树rb_node

标签: c linux-kernel c-preprocessor


【解决方案1】:

您的用法示例 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 到结构开头的偏移量对于获取正确的指针位置至关重要。

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

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

【讨论】:

【解决方案2】:

最后一句话:

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

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

【讨论】:

    【解决方案3】:

    这是对 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

    【讨论】:

      【解决方案4】:

      Linux 内核中的conatainer_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 宏。

      【讨论】:

      • 这是更好的解释,至少对于像我这样的内核菜鸟来说。所以赞成这个。
      【解决方案5】:

      一点真实的上下文说得更清楚,下面以红黑树为例,这是 我理解的方式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 是指向type 实例的member 的指针,此处为rb_node *nd

      container_of 所做的是,如本例所示,

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

      附言

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

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

      【讨论】:

        【解决方案6】:

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

        【讨论】:

          【解决方案7】:

          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-02-11
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2011-08-30
            • 1970-01-01
            • 2011-12-04
            • 1970-01-01
            相关资源
            最近更新 更多