Comprender la macro container_of en el kernel de Linux

81

Cuando estaba navegando por el kernel de Linux, encontré una container_ofmacro que se define de la siguiente manera:

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

Entiendo lo que hace container_of, pero lo que no entiendo es la última frase, que es

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

Si usamos la macro de la siguiente manera:

container_of(dev, struct wifi_device, dev);

La parte correspondiente de la última oración sería:

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

que parece no hacer nada. ¿Alguien podría llenar el vacío aquí?

jaeyong
fuente
esta respuesta tiene un ejemplo real e intuitivo usando el árbol rojo-negro rb_node.
qeatzy

Respuestas:

86

Su ejemplo de uso container_of(dev, struct wifi_device, dev);puede ser un poco engañoso ya que está mezclando dos espacios de nombres allí.

Mientras que el primero deven su ejemplo se refiere al nombre del puntero, el segundo se devrefiere al nombre de un miembro de la estructura.

Lo más probable es que esta confusión esté provocando todo ese dolor de cabeza. De hecho, el memberparámetro en su cotización se refiere al nombre dado a ese miembro en la estructura del contenedor.

Tomando este contenedor por ejemplo:

struct container {
  int some_other_data;
  int this_data;
}

Y un puntero int *my_ptral this_datamiembro que usaría la macro para obtener un puntero struct container *my_containerusando:

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

Tener en cuenta el desplazamiento de this_dataal principio de la estructura es esencial para obtener la ubicación correcta del puntero.

Efectivamente, solo tiene que restar el desplazamiento del miembro this_datade su puntero my_ptrpara obtener la ubicación correcta.

Eso es exactamente lo que hace la última línea de la macro.

mikyra
fuente
5
Para aquellos de ustedes que necesitan una explicación más detallada: Radek Pazdera explicó container_of macro ( include/linux/kernel.h) muy claramente en su blog . Por cierto : list_entry macro ( include/linux/list.h) solía definirse de manera muy similar , pero ahora se define como container_of.
patryk.beza
1
También esta entrada de blog de Greg Kroah-Hartman puede ser útil: kroah.com/log/linux/container_of.html
EFraim
1
En principio, esa es una buena respuesta, excepto que realmente no responde una parte considerable de la pregunta. Por ejemplo, ¿qué logra la última línea de código y cómo?
Michael Beer
Cuando sigo este ejemplo, usando GCC obtengo error: initialization from incompatible pointer type. ¿Esto se debe a la constdefinición de macro según este hilo? stackoverflow.com/a/39963179/1256234
Andy J
18

La última frase emitida:

(type *)(...)

un puntero a un dado type. El puntero se calcula como desplazamiento de un puntero dado dev:

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

Cuando usa la cointainer_ofmacro, desea recuperar la estructura que contiene el puntero de un campo determinado. Por ejemplo:

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

Tiene un puntero que apunta al medio de una estructura (y sabe que es un puntero al campo two[ el nombre del campo en la estructura ]), pero desea recuperar la estructura completa ( numbers). Entonces, calcula el desplazamiento del archivo twoen la estructura:

offsetof(type,member)

y reste este desplazamiento del puntero dado. El resultado es el puntero al inicio de la estructura. Finalmente, lanza este puntero al tipo de estructura para tener una variable válida.

Federico
fuente
10

Es una utilización de una extensión gcc, las expresiones de las declaraciones . Si ve la macro como algo que devuelve un valor, entonces la última línea sería:

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

Consulte la página vinculada para obtener una explicación de las declaraciones compuestas. Aquí hay un ejemplo :

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

La salida es

b 25

shodanex
fuente
7

macro conatainer_of () en el kernel de Linux -

Cuando se trata de administrar varias estructuras de datos en código, casi siempre necesitará incrustar una estructura en otra y recuperarlas en cualquier momento sin que se le hagan preguntas sobre las compensaciones o límites de la memoria. Digamos que tienes una persona estructura, como se define aquí:

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

Al tener solo un puntero sobre la edad o el salario, puede recuperar la estructura completa que envuelve (contiene) ese puntero. Como dice el nombre, la macro container_of se usa para encontrar el contenedor del campo dado de una estructura. La macro se define en include / linux / kernel.hy se parece a lo siguiente:

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

No tenga miedo de los indicadores; solo véalos de la siguiente manera:

container_of(pointer, container_type, container_field); 

Estos son los elementos del fragmento de código anterior:

  • puntero: este es el puntero al campo en la estructura
  • container_type: este es el tipo de estructura que envuelve (contiene) el puntero
  • container_field: este es el nombre del campo al que apunta el puntero dentro de la estructura

Consideremos el siguiente contenedor:

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

Ahora, consideremos una de sus instancias, junto con un puntero al miembro de edad:

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

Junto con un puntero al miembro de nombre (age_ptr), puede usar la macro container_of para obtener un puntero a toda la estructura (contenedor) que envuelve este miembro usando lo siguiente:

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

container_of tiene en cuenta el desplazamiento de la edad al comienzo de la estructura para obtener la ubicación correcta del puntero. Si resta el desplazamiento de la edad del campo del puntero age_ptr, obtendrá la ubicación correcta. Esto es lo que hace la última línea de la macro:

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

Aplicando esto a un ejemplo real, da lo siguiente:

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

La macro container_of se usa principalmente en contenedores genéricos en el kernel.

Eso es todo sobre container_of macro en el kernel.

Spyder
fuente
2

Un poco de contexto real dice más claro, a continuación use el árbol rojo-negro como ejemplo , que es la forma en que entiendo container_of.

como Documentation/rbtree.txtdice, en el código del kernel de Linux, no es rb_node contiene entrada de datos, sino

Los nodos de datos en un árbol rbtree son estructuras que contienen un miembro struct rb_node.

struct vm_area_struct(en archivo include/linux/mm_types.h:284) es tal estructura,

en el mismo archivo, hay una macro rb_entryque se define como

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

claramente, rb_entryes lo mismo que container_of.

en la mm/mmap.c:299definición de función interna browse_rb, hay un uso de 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 */

ahora está claro, en container_of(ptr, type, member),

  • type es la estructura del contenedor, aquí struct vm_area_struct
  • memberes el nombre de un miembro de typeinstancia, aquí vm_rb, que es de tipo rb_node,
  • ptres un puntero que apunta membera una typeinstancia, aquí rb_node *nd.

lo container_ofque es, como en este ejemplo,

  • dada la dirección de obj.member(aquí obj.vm_rb), devuelva la dirección de obj.
  • dado que una estructura es un bloque de memoria contigua, la dirección obj.vm_rbmenos offset between the struct and memberserá la dirección del contenedor.

include/linux/kernel.h:858 -- definicion de container_of

include/linux/rbtree.h:51 -- definicion de rb_entry

mm/mmap.c:299 -- uso de rb_entry

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

Documentation/rbtree.txt: - Documentación de árbol rojo-negro

include/linux/rbtree.h:36 -- definicion de struct rb_node

PD

Archivos anteriores están en la versión actual de desarrollo, es decir, 4.13.0-rc7.

file:ksignifica kth línea en file.

qeatzy
fuente
0

La implementación más simple del contenedor _ de macro se encuentra a continuación, reduce todas las comprobaciones complejas de tipo y funciona

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

ptr dará la dirección del miembro y solo restará la diferencia de compensación y obtendrá la dirección de inicio.

Uso de ejemplo

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;
}
Alok Prasad
fuente