Hiểu macro container_of trong nhân Linux


81

Khi tôi duyệt hạt nhân Linux, tôi tìm thấy một container_ofmacro được định nghĩa như sau:

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

Tôi hiểu container_of làm gì, nhưng điều tôi không hiểu là câu cuối cùng, đó là

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

Nếu chúng ta sử dụng macro như sau:

container_of(dev, struct wifi_device, dev);

Phần tương ứng của câu cuối cùng sẽ là:

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

trông giống như không làm gì cả. Ai có thể vui lòng lấp đầy khoảng trống ở đây?


câu trả lời này có một ví dụ thực tế và trực quan bằng cách sử dụng cây đỏ-đen rb_node.
qeatzy

Câu trả lời:


86

Ví dụ sử dụng của bạn container_of(dev, struct wifi_device, dev);có thể hơi gây hiểu nhầm vì bạn đang trộn hai không gian tên ở đó.

Trong khi cái đầu tiên devtrong ví dụ của bạn đề cập đến tên của con trỏ, cái thứ hai devđề cập đến tên của một thành viên cấu trúc.

Hầu hết có lẽ sự kết hợp này đang gây ra tất cả những đau đầu. Trên thực tế, membertham số trong trích dẫn của bạn đề cập đến tên được đặt cho thành viên đó trong cấu trúc vùng chứa.

Lấy ví dụ về vùng chứa này:

struct container {
  int some_other_data;
  int this_data;
}

Và một con trỏ int *my_ptrđến this_datathành viên mà bạn sử dụng macro để đưa con trỏ đến struct container *my_containerbằng cách sử dụng:

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

Việc tính đến phần bù của this_dataphần đầu của cấu trúc là điều cần thiết để có được vị trí con trỏ chính xác.

Một cách hiệu quả, bạn chỉ cần trừ phần bù của phần tử this_datakhỏi con trỏ của mình my_ptrđể có được vị trí chính xác.

Đó chính xác là những gì dòng cuối cùng của macro làm.


5
Đối với những người bạn cần giải thích chi tiết hơn: Radek Pazdera đã giải thích container_of macro ( include/linux/kernel.h) thực sự rõ ràng trên blog của anh ấy . BTW : macro list_entry ( include/linux/list.h) từng được định nghĩa rất giống nhau , nhưng bây giờ được định nghĩa là container_of.
patryk.beza 19/02/16

1
Ngoài ra, bài đăng blog này của Greg Kroah-Hartman có thể hữu ích: kroah.com/log/linux/container_of.html
EFraim

1
Về nguyên tắc, đó là một câu trả lời hay, ngoại trừ việc nó không thực sự trả lời phần nào đáng kể của câu hỏi? Ví dụ: dòng mã cuối cùng đạt được điều gì và làm thế nào?
Michael Beer

Khi tôi làm theo ví dụ này, tôi nhận được GCC error: initialization from incompatible pointer type. Điều này có phải do constđịnh nghĩa macro theo chủ đề này không? stackoverflow.com/a/39963179/1256234
Andy J

18

Câu cuối cùng diễn ra:

(type *)(...)

một con trỏ đến một type. Con trỏ được tính toán như bù đắp từ một con trỏ nhất định dev:

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

Khi bạn sử dụng cointainer_ofmacro, bạn muốn truy xuất cấu trúc có chứa con trỏ của một trường nhất định. Ví dụ:

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

Bạn có một con trỏ trỏ vào giữa một cấu trúc (và bạn biết rằng đó là một con trỏ đến two[ tên trường trong cấu trúc ] được đệ trình ), nhưng bạn muốn truy xuất toàn bộ cấu trúc ( numbers). Vì vậy, bạn tính toán phần bù của phần được lưu twotrong cấu trúc:

offsetof(type,member)

và trừ phần bù này khỏi con trỏ đã cho. Kết quả là con trỏ đến điểm bắt đầu của cấu trúc. Cuối cùng, bạn ép kiểu con trỏ này đến kiểu cấu trúc để có một biến hợp lệ.


10

Nó là sự sử dụng phần mở rộng gcc, các biểu thức câu lệnh . Nếu bạn thấy macro là thứ gì đó trả về giá trị, thì dòng cuối cùng sẽ là:

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

Xem trang được liên kết để biết giải thích về câu lệnh ghép. Đây là một ví dụ :

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

Đầu ra là

b 25


7

macro conatainer_of () trong Nhân Linux -

Khi nói đến việc quản lý một số cấu trúc dữ liệu trong mã, hầu như bạn sẽ luôn cần phải nhúng một cấu trúc này vào một cấu trúc khác và truy xuất chúng bất cứ lúc nào mà không bị đặt câu hỏi về khoảng trống hoặc ranh giới bộ nhớ. Giả sử bạn có một người có cấu trúc, như được định nghĩa ở đây:

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

Bằng cách chỉ có một con trỏ về tuổi hoặc mức lương, bạn có thể truy xuất toàn bộ cấu trúc bao bọc (chứa) con trỏ đó. Như tên đã nói, macro container_of được sử dụng để tìm vùng chứa của trường nhất định của một cấu trúc. Macro được định nghĩa trong include / linux / kernel.h và trông giống như sau:

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

Đừng sợ những con trỏ; chỉ cần xem chúng như sau:

container_of(pointer, container_type, container_field); 

Dưới đây là các phần tử của đoạn mã trước:

  • con trỏ: Đây là con trỏ đến trường trong cấu trúc
  • container_type: Đây là kiểu cấu trúc bao bọc (chứa) con trỏ
  • container_field: Đây là tên của trường mà con trỏ trỏ đến bên trong cấu trúc

Hãy xem xét vùng chứa sau:

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

Bây giờ, hãy xem xét một trong các trường hợp của nó, cùng với một con trỏ đến thành viên tuổi:

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

Cùng với một con trỏ đến thành viên tên (age_ptr), bạn có thể sử dụng macro container_of để lấy một con trỏ đến toàn bộ cấu trúc (vùng chứa) bao bọc thành viên này bằng cách sử dụng như sau:

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

container_of tính đến phần bù của age ở đầu struct để có được vị trí con trỏ chính xác. Nếu bạn trừ phần bù của tuổi trường khỏi con trỏ age_ptr, bạn sẽ nhận được vị trí chính xác. Đây là những gì dòng cuối cùng của macro thực hiện:

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

Áp dụng điều này vào một ví dụ thực tế, sẽ đưa ra kết quả sau:

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

Macro container_of chủ yếu được sử dụng trong các vùng chứa chung trong hạt nhân.

Đó là tất cả về macro container_of trong kernel.


2

Bối cảnh thực tế một chút nói rõ ràng hơn, bên dưới sử dụng cây đỏ-đen làm ví dụ , đó là cách mà tôi hiểu container_of.

như Documentation/rbtree.txtcác trạng thái, trong mã nhân linux, nó không phải rb_node chứa mục nhập dữ liệu, đúng hơn

Các nút dữ liệu trong cây rbtree là cấu trúc chứa thành viên struct rb_node.

struct vm_area_struct(trong tệp include/linux/mm_types.h:284) là một cấu trúc như vậy,

trong cùng một tệp, có một macro rb_entryđược định nghĩa là

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

rõ ràng, rb_entrygiống như container_of.

mm/mmap.c:299định nghĩa hàm bên trong browse_rb, có cách sử dụng 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 */

bây giờ nó đã rõ ràng, trong container_of(ptr, type, member),

  • type là cấu trúc vùng chứa, ở đây struct vm_area_struct
  • memberlà tên của một thành viên của typetrường hợp, ở đây vm_rb, thuộc loại rb_node,
  • ptrlà một con trỏ trỏ memberđến một typethể hiện, ở đây rb_node *nd.

những gì container_ofcần làm là, như trong ví dụ này,

  • địa chỉ đã cho của obj.member(tại đây obj.vm_rb), trả lại địa chỉ của obj.
  • vì cấu trúc là một khối bộ nhớ liền kề, địa chỉ của obj.vm_rbdấu trừ offset between the struct and membersẽ là địa chỉ của vùng chứa.

include/linux/kernel.h:858 -- định nghĩa của container_of

include/linux/rbtree.h:51 -- định nghĩa của rb_entry

mm/mmap.c:299 - sử dụng rb_entry

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

Documentation/rbtree.txt: - Tài liệu về cây đỏ đen

include/linux/rbtree.h:36 -- định nghĩa của struct rb_node

PS

Các tệp trên nằm trong phiên bản phát triển hiện tại, tức là 4.13.0-rc7,.

file:kcó nghĩa là dòng thứ k trong file.



0

Cách triển khai đơn giản nhất của macro Container _of là bên dưới, Nó làm giảm tất cả việc kiểm tra phức tạp về loại và hoạt động

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

ptr sẽ cung cấp địa chỉ của thành viên và chỉ cần trừ đi phần chênh lệch giá trị và bạn sẽ nhận được địa chỉ bắt đầu.

Ví dụ sử dụng

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;
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.