Detailed Explanation of Linux Kernel container_of Macro

Table of contents

foreword

1. container_of macro introduction

2. Example of using the container_of macro

3. Analysis of container_of macro implementation principle

3.1 Storage of structures in memory 

3.2 Calculate the offset of member variables in the structure

3.3 Principle implementation of container_of macro

4. Summary


foreword

The basic knowledge covered in this chapter includes  the typeof keyword  and  statement expressions . If you still don’t know what they are and what their functions are, I suggest you read  the two articles on the function of the typeof keyword  and  the power of statement expressions  to consolidate the relevant basic knowledge.

1. container_of macro introduction

Assuming that everyone understands typeof  and  statement expressions here , let's start to see the beauty of container_of, the first macro in the Linux kernel:

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

As the first macro in the Linux kernel, it definitely deserves its name. Looking at its elegant appearance and built-in eight-pack abs, you will know that it is not easy to mess with. There are macros in macros, as a comprehensive application of GNU C's high-end extension features, so what does it do? Its main function is to obtain the first address of the structure according to the address of a member of the structure. According to the macro definition, it can be seen that this macro has three parameters:

  • type: structure type
  • member: member of the structure
  • ptr: the address of the member member in the structure

That is to say, when we know the type of a structure and the address of a member in the structure, we can directly obtain the first address of the structure. The container_of macro returns the first address of this structure .

2. Example of using the container_of macro

This macro is very important in the kernel. In the kernel, there is often such a requirement: the parameter we pass to a certain function is a member variable of a certain structure, and then in this function, other member variables of this structure may be used, so what at this time What to do? We can use container_of to find the first address of the structure by accessing a certain member of the structure, and then we can access other member variables.

struct _box_t
{
    double length;   // 盒子的长度
    double breadth;  // 盒子的宽度
    double height;   // 盒子的高度
};

int main(void)
{
    struct _box_t box = {30.0, 20.0, 10.0};
    struct _box_t *p_box = NULL;

    p_box = container_of(&box.height, struct _box_t, height);
    printf("%p\n", p_box);
    printf("length: %f\n", p_box->length);
    printf("breadth: %f\n", p_box->breadth);

    return 0;
}

In this program, we define a structure variable box, knowing the address &box.height of its member variable height, we can directly obtain the first address of the box structure variable through the container_of macro, and then directly access other members of the box structure p_box->length and p_box->breadth.

3. Analysis of container_of macro implementation principle

The main knowledge used in the implementation of the container_of macro is: statement expression and typeof, plus the basic knowledge of structure storage. In order to help you better understand this macro, let's review the basics of structure storage first.

3.1 Storage of structures in memory 

We know that as a composite type of data, a structure can have multiple members in it. When we define a structure variable, the compiler must allocate storage space for this variable in memory. In addition to considering factors such as data type and byte alignment, the compiler will allocate a continuous space in memory to store them according to the order of each member in the structure.

struct _box_t
{
    double length;   // 盒子的长度
    double breadth;  // 盒子的宽度
    double height;   // 盒子的高度
};

int main(void)
{
    struct _box_t box = {30.0, 20.0, 10.0};
    printf("&box = %p\n", &box);
    printf("&box.length = %p\n", &box.length);
    printf("&box.breadth = %p\n", &box.breadth);
    printf("&box.height = %p\n", &box.height);

    return 0;
}

In this program, we define a structure with three double data members, we define a variable, and then print the address of the structure and the address of each member variable respectively. The running results are as follows:

&box         = 2b6c3dd0
&box.length  = 2b6c3dd0
&box.breadth = 2b6c3dd8
&box.height  = 2b6c3de0

From the running results, we can see that each member variable in the structure is stored sequentially from the first address of the structure . Each member variable has a fixed offset relative to the first address of the structure. For example, breadth is offset by 8 bytes relative to the first address of the structure. The storage address of height is offset by 16 bytes relative to the first address of the structure.

3.2 Calculate the offset of member variables in the structure

For a structure data type, in the same compilation environment, the offset of each member relative to the first address of the structure is fixed . We can modify the above program. When the first address of the structure is 0, the address of each member in the structure is numerically equal to the offset of each member of the structure relative to the first address of the structure.

struct _box_t
{
    double length;   // 盒子的长度
    double breadth;  // 盒子的宽度
    double height;   // 盒子的高度
};

int main(void)
{
    printf("&length = %p\n", &((struct _box_t*)0)->length);
    printf("&breadth = %p\n", &((struct _box_t*)0)->breadth);
    printf("&height = %p\n", &((struct _box_t*)0)->height);

    return 0;
}

In the above program, we did not directly define the structure variable, but converted the number 0 into a constant pointer pointing to the structure type of _box_t through mandatory type conversion, and then printed each part of the structure pointed to by the constant pointer. member address. The result of the operation is as follows:

&length  = ox0
&breadth = 0x8
&height  = 0x10

Because the constant pointer is 0, it can be regarded as the first address of the structure is 0, so the address of each member variable in the structure is the offset of the member relative to the first address of the structure. The implementation of the container_of macro is implemented using this trick.

3.3 Principle implementation of container_of macro

The overall implementation principle of the container_of macro is shown in the figure: 

efe1de9c2b1242fc8f90430cdccfa57f.png

From a syntactic point of view, the implementation of the container_of macro consists of a statement expression:

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

The value of the statement expression is the value of the last expression :

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

The meaning of the above statement is to take the address of a member of the structure and subtract the offset of this member in the structure type, and the result is the first address of the structure type. Because the value of the statement expression is equal to the value of the last expression, the result is also the value of the entire statement expression, and container_of will finally return this address value to the caller of the macro.

The offset macro is defined in the kernel to calculate the offset of a member of the structure within the structure , and its definition is as follows:

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

This macro has two parameters, one is the structure type TYPE, and the other is the member MEMBER of the structure. The technique it uses is the same as the offset of the 0 address constant pointer we calculated above: cast 0 to a pointer to TYPE The structure constant pointer, and then access the member through this constant pointer to obtain the address of the member MEMBER, whose size is numerically equal to the offset of MEMBER in the structure TYPE.

Because the member data type of the structure can be any data type, in order to make this macro compatible with various data types. We define a temporary pointer variable __mptr, which is used to store the address of the structure member MEMBER, that is, store the value of ptr. So how to get the ptr pointer type? By the following way:

typeof( ((type *)0)->member ) *__mptr = (ptr);

The parameter ptr of the above macro represents the address of a structure member variable MEMBER , so the type of ptr is a pointer to the MEMBER data type . In order to ensure that the pointer type of the temporary variable __mptr is also a pointer variable pointing to the MEMBER type, use the typeof keyword to obtain the data type of the structure member member through the typeof( ((type *)0)->member ) expression, and then Use typeof( ((type *)0)->member ) *__mptr to define a pointer variable pointing to this type. 

Note : At the end of the statement expression, because the first address of the structure is returned, the data type must also be converted to TYPE *, that is, a pointer to the TYPE structure type is returned , so you will use  the offset in the last expression A type cast (TYPE *) was seen in the macro .

4. Summary

  • After the overall analysis of the container_of macro, what does this process inspire us?
  • For any complex technology, we can decompose it step by step from top to bottom, and then use the basic knowledge we have learned to analyze it bit by bit: first analyze small modules, and then conduct comprehensive analysis.
  • For example, the definition of the container_of macro uses knowledge points such as structure storage, statement expressions, and typeof.
  • When we have mastered these basic knowledge and have an analysis method, we can confidently and calmly analyze it ourselves when we encounter such similar macros in the kernel in the future, instead of always relying on the search for a needle in a haystack on the Internet.
  • This is your core competency and your chance to stand out from other engineers.

Guess you like

Origin blog.csdn.net/m0_37383484/article/details/129244244