web-dev-qa-db-fra.com

Comprendre la macro container_of dans le noyau Linux

En parcourant le noyau Linux, j'ai trouvé une macro 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 

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

qui ressemble à ne rien faire .. Quelqu'un pourrait-il remplir le vide ici?

58
jaeyong

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

Alors que la première dev dans votre exemple fait référence au nom du pointeur, la seconde dev fait référence au nom d'un membre de structure.

Très probablement, cette confusion provoque tout ce mal à la tête. En fait, le paramètre member de votre devis fait référence au nom donné à ce membre dans la structure de conteneur.

Prenant ce conteneur par exemple:

struct container {
  int some_other_data;
  int this_data;
}

Et un pointeur int *my_ptr vers le membre this_data vous utiliseriez 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);

Il est essentiel de prendre en compte le décalage de this_data au début de la structure pour obtenir l'emplacement correct du pointeur.

Effectivement, il vous 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.

70
mikyra

La dernière phrase jeté:

(type *)(...)

un pointeur sur une type donnée. Le pointeur est calculé en tant que décalage par rapport à un pointeur donné dev:

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

Lorsque vous utilisez la macro cointainer_of, vous souhaitez récupérer la structure contenant 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 qu'il s'agit d'un pointeur vers la variable two [ le nom du champ dans la structure ]), mais vous souhaitez récupérer la structure entière (numbers). Donc, vous calculez le décalage de la valeur two dans la structure:

offsetof(type,member)

et soustrayez ce décalage du pointeur donné. Le résultat est le pointeur sur le début de la structure. Enfin, vous convertissez ce pointeur sur le type de structure pour avoir une variable valide.

15
Federico

C'est une utilisation d'une extension gcc, les expressions expressions . Si vous voyez la macro comme un élément renvoyant une valeur, la dernière ligne est la suivante:

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

Voir la page liée pour une explication des instructions 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); 
}

La sortie est

b 25

8
shodanex

Un peu de contexte réel dit plus clairement, au-dessous de utilisez l’arbre rouge-noir comme exemple , qui est la façon dont je comprends container_of.

comme Documentation/rbtree.txt déclare, dans le code du noyau Linux, ce n'est pas rb_node contenir des données entrée, plutôt 

Les nœuds de données dans un arbre rbtree sont des structures contenant un struct membre rb_node.

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

dans le même fichier , il existe une macro rb_entry définie comme

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

clairement, rb_entry est identique à container_of.

en mm/mmap.c:299 dans la définition de fonction browse_rb, il existe un usage 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, en container_of(ptr, type, member),

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

ce que container_of fait est, comme dans cet exemple, 

  • adresse donnée de obj.member (ici obj.vm_rb), retourne l'adresse de obj.  
  • puisqu’une structure est un bloc de mémoire contiguë, 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 de l'arbre rouge-noir

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

P.S.

Les fichiers ci-dessus sont en version de développement actuelle, c'est-à-dire 4.13.0-rc7.

file:k signifie kème ligne dans file.

1
qeatzy

macro conatainer_of () dans le noyau Linux -

Quand il s'agit de gérer plusieurs structures de données dans le code, vous aurez presque toujours besoin d'intégrer une structure dans une autre et de les récupérer à tout moment sans être interrogé sur les décalages de mémoire ou les limites. Disons que vous avez une personne structurée, telle que définie ici:

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

En ayant uniquement un pointeur sur l'âge ou le salaire, vous pouvez récupérer l'intégralité de la structure 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 se présente comme 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. juste les voir 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 sur le champ dans la structure
  • conteneur_type: il s'agit du type de structure qui enveloppe le pointeur
  • container_field: c'est le nom du champ sur lequel le pointeur pointe à l'intérieur de la structure

Considérons le conteneur suivant:

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

Considérons maintenant l'une de ses instances, avec un pointeur sur le membre age:

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

Avec un pointeur sur le nom membre (age_ptr), vous pouvez utiliser la macro conteneur_of pour obtenir un pointeur sur la structure entière (conteneur) qui enveloppe ce membre en utilisant les éléments suivants:

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

container_of prend en compte le décalage d'âge au début de la structure pour obtenir l'emplacement correct du pointeur. Si vous soustrayez le décalage de l'âge du champ du pointeur age_ptr, vous obtiendrez l'emplacement correct. Voici 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:

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 au sujet de la macro container_of dans le noyau.

1
Upen

Lien très utile pour comprendre la macro conteneur_of dans le noyau Linux. https://linux-concepts.blogspot.com/2018/01/understanding-containerof-macro-in.html

0
Anand Kumar