c linux-kernel c-preprocessor

Comprender container_of macro en el kernel de Linux



linux-kernel c-preprocessor (4)

Es una utilización de una extensión de 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); }

El resultado es

b 25

Cuando estaba explorando el kernel de Linux, encontré una macro container_of 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 qué hace container_of do, pero lo que no entiendo es la última oración, 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. ¿Podría alguien llenar el vacío aquí?


La última oración emitida:

(type *)(...)

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

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

Cuando utiliza el cointainer_of macro, desea recuperar la estructura que contiene el puntero de un campo dado. 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 en el medio de una estructura (y sabe que es un puntero a los two archivados [ el nombre del campo en la estructura ]), pero desea recuperar toda la estructura ( numbers ). Entonces, calcula el desplazamiento de los two archivados en la estructura:

offsetof(type,member)

y resta esta compensación del puntero dado. El resultado es el puntero al inicio de la estructura. Finalmente, coloca este puntero al tipo de estructura para tener una variable válida.


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

Mientras que el primer desarrollador en su ejemplo se refiere al nombre del puntero, el segundo desarrollador se refiere al nombre de un miembro de la estructura.

Lo más probable es que esta confusión provoque todo ese dolor de cabeza. De hecho, el parámetro member en su presupuesto 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_ptr para el miembro this_data que usaría la macro para obtener un puntero al struct container *my_container usando:

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

Teniendo en cuenta el desplazamiento de this_data al comienzo de la estructura es esencial para obtener la ubicación correcta del puntero.

Efectivamente, solo tiene que restar el desplazamiento del miembro this_data de su puntero my_ptr para obtener la ubicación correcta.

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


Un pequeño contexto real dice más claro, debajo del uso del árbol rojo-negro como ejemplo , que es la forma en que entiendo container_of .

como dice Documentation/rbtree.txt , en código de núcleo de Linux, no es rb_node contener entrada de datos, sino

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

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

en el mismo archivo, hay una macro rb_entry que se define como

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

claramente, rb_entry es lo mismo que container_of .

en mm/mmap.c:299 dentro de la definición de función 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
  • member es el nombre de un miembro de la instancia de type , aquí vm_rb , que es de tipo rb_node ,
  • ptr es un puntero que señala el member de una instancia de type , aquí rb_node *nd .

qué container_of hacer es, como en este ejemplo,

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

include/linux/kernel.h:858 - definición de container_of

include/linux/rbtree.h:51 - definición 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 del árbol rojo-negro

include/linux/rbtree.h:36 - definición de struct rb_node

PD

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

file:k significa kth línea en el file .