111 votes

Comprendre la macro container_of dans le noyau Linux

Quand je parcourais le noyau Linux, j'ai trouvé une container_of qui est définie comme suit :

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

Je comprends ce que fait container_of, mais ce que je ne comprends pas, c'est la dernière phrase, qui est la suivante

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

Si nous utilisons la macro comme suit :

container_of(dev, struct wifi_device, dev);

La partie correspondante de la dernière phrase serait :

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

ce qui ressemble à ne rien faire. Quelqu'un peut-il combler ce vide ?

108voto

mikyra Points 3595

Votre exemple d'utilisation container_of(dev, struct wifi_device, dev); peut être un peu trompeur car vous mélangez deux espaces de noms.

Alors que le premier dev dans votre exemple fait référence au nom du pointeur le deuxième dev fait référence au nom d'un membre de la structure.

C'est probablement cette confusion qui est à l'origine de tous ces maux de tête. En fait, le member dans votre citation fait référence au nom donné à ce membre dans la structure du conteneur.

Prenons l'exemple de ce conteneur :

struct container {
  int some_other_data;
  int this_data;
}

Et un pointeur int *my_ptr a la this_data vous utiliserez la macro pour obtenir un pointeur sur struct container *my_container en utilisant :

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

En prenant le décalage de this_data au début de la structure est essentielle pour obtenir l'emplacement correct du pointeur.

En fait, il suffit de soustraire le décalage du membre. this_data de votre pointeur my_ptr pour obtenir l'emplacement correct.

C'est exactement ce que fait la dernière ligne de la macro.

26voto

Federico Points 1525

La dernière phrase coule :

(type *)(...)

un pointeur vers un type . Le pointeur est calculé comme un décalage par rapport à un pointeur donné. dev :

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

Lorsque vous utilisez le cointainer_of vous voulez récupérer la structure qui contient le pointeur d'un champ donné. Par exemple :

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

Vous avez un pointeur qui pointe au milieu d'une structure (et vous savez que c'est un pointeur vers le fichier two [ le nom du champ dans la structure ]), mais vous voulez récupérer la structure entière ( numbers ). Ainsi, vous calculez le décalage du dossier two dans la structure :

offsetof(type,member)

et soustraire ce décalage du pointeur donné. Le résultat est le pointeur vers le début de la structure. Enfin, vous convertissez ce pointeur en type de structure pour obtenir une variable valide.

18voto

user3799762 Points 347

Macro conatainer_of() dans le noyau Linux -

Lorsqu'il s'agit de gérer plusieurs structures de données dans un code, vous aurez presque toujours besoin d'intégrer une structure dans une autre et de les récupérer à tout moment sans avoir à répondre à des questions sur les décalages ou les limites de la mémoire. Disons que vous avez une personne struct, telle que définie ici :

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

En n'ayant qu'un pointeur sur l'âge ou le salaire, vous pouvez récupérer la structure entière enveloppant (contenant) ce pointeur. Comme son nom l'indique, la macro container_of est utilisée pour trouver le conteneur du champ donné d'une structure. La macro est définie dans include/linux/kernel.h et ressemble à ce qui suit :

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

N'ayez pas peur des pointeurs ; voyez-les simplement comme suit :

container_of(pointer, container_type, container_field); 

Voici les éléments du fragment de code précédent :

  • pointeur : Il s'agit du pointeur vers le champ de la structure.
  • container_type : Il s'agit du type de structure enveloppant (contenant) le pointeur.
  • container_field : Il s'agit du nom du champ auquel pointeur pointe à l'intérieur de la structure

Considérons le conteneur suivant :

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

Maintenant, considérons une de ses instances, ainsi qu'un pointeur vers le membre age :

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

En plus d'un pointeur sur le membre de nom (age_ptr), vous pouvez utiliser la macro container_of afin d'obtenir un pointeur sur la structure entière (container) qui englobe ce membre en utilisant ce qui suit :

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

container_of prend en compte le décalage de age au début de la structure pour obtenir l'emplacement correct du pointeur. Si vous soustrayez le décalage du champ age du pointeur age_ptr, vous obtiendrez l'emplacement correct. C'est ce que fait la dernière ligne de la macro :

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

En appliquant cela à un exemple réel, on obtient ce qui suit :

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 est principalement utilisée dans les conteneurs génériques du noyau.

C'est tout ce qui concerne la macro container_of dans le noyau.

11voto

shodanex Points 7318

C'est une utilisation d'une extension de gcc, la déclarations, expressions . Si vous voyez la macro comme quelque chose qui renvoie une valeur, alors la dernière ligne serait :

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

Voir la page liée pour une explication des déclarations composées. Voici un exemple :

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

Le résultat est

b 25

5voto

qeatzy Points 547

Un peu de contexte réel permet d'y voir plus clair, ci-dessous utiliser l'arbre rouge-noir comme exemple qui est le manière dont je comprends container_of .

comme Documentation/rbtree.txt états, dans le code du noyau linux, ce n'est pas rb_node qui contient les données mais plutôt

Les nœuds de données d'une arborescence rbtree sont des structures contenant une structure rb_node.

struct vm_area_struct (en fichier include/linux/mm_types.h:284 ) est une telle structure,

dans le même fichier, il y a une macro rb_entry qui est défini comme suit

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

clairement, rb_entry est identique à container_of .

à l'adresse mm/mmap.c:299 définition de la fonction interne browse_rb il y a une utilisation 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 */

maintenant il est clair, dans container_of(ptr, type, member) ,

  • type est la structure du conteneur, ici struct vm_area_struct
  • member est le nom d'un membre de type par exemple, ici vm_rb qui est de type rb_node ,
  • ptr est un pointeur pointant member d'un type par exemple, ici rb_node *nd .

ce que container_of est, comme dans cet exemple,

  • adresse donnée de obj.member (ici obj.vm_rb ), renvoyer le adresse de obj .
  • puisqu'un struct est un bloc de mémoire contiguë, l'adresse de obj.vm_rb moins offset between the struct and member sera l'adresse du conteneur.

include/linux/kernel.h:858 -- définition de container_of

include/linux/rbtree.h:51 -- définition de rb_entry

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

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

Documentation/rbtree.txt: -- Documentation sur l'arbre rouge-noir

include/linux/rbtree.h:36 -- définition de struct rb_node

P.S.

Les fichiers ci-dessus sont dans la version de développement actuelle, à savoir 4.13.0-rc7 .

file:k la moyenne de la kième ligne dans file .

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X