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?
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.
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.
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
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,
obj.member
(ici obj.vm_rb
), retourne l'adresse de obj
. 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
.
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:
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.
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