Gestion de la mémoire Linux - Explication détaillée de l'allocateur de slab

analyse vidéo liée à Linux:

90 minutes pour comprendre l'architecture mémoire Linux, les avantages de numa, la réalisation de slab, et le principe de vmalloc.
Analysez l'architecture du noyau linux sous 5 aspects, afin de ne plus être familier avec le noyau et vous amenez
à implémenter un Système de fichiers du noyau Linux.

Linux a un algorithme d'allocation appelé buddy system, qui résout principalement le problème d'allocation de pages mémoire consécutives. L'algorithme d'allocation de partenaire utilise principalement des pages de mémoire (4 Ko) comme unité d'allocation, ce qui signifie que l'algorithme d'allocation de partenaire peut allouer 2 pages de mémoire d'ordre à la fois (ordre 0, 1, 2 ... 9). Mais parfois, nous n'avons besoin que d'une petite zone de mémoire (par exemple 32 octets). À ce stade, il est inutile d'utiliser l'algorithme d'allocation de partenaire. Afin de résoudre le problème de la petite allocation de mémoire, Linux utilise l'algorithme d'allocation de dalle.

Structure de données associée

L'algorithme de dalle a deux structures de données importantes, l'une est kmem_cache_t et l'autre est slab_t. Examinons d'abord la structure kmem_cache_t:

1. struct kmem_cache_s {
    
     
2.  struct list_head    slabs_full;
3.  struct list_head    slabs_partial;
4.  struct list_head    slabs_free;
5.  unsigned int        objsize;
6.  unsigned int        flags;
7.  unsigned int        num;
8.     spinlock_t          spinlock; 
9.  
10.      /* 2) slab additions /removals */ 
11.  /* order of pgs per slab (2^n) */ 
12.  unsigned int        gfporder; 
13.  
14.  /* force GFP flags, e.g. GFP_DMA */ 
15.  unsigned int        gfpflags; 
16.  
17.     size_t              colour;
18.  unsigned int        colour_off;
19.  unsigned int        colour_next;
20.     kmem_cache_t        *slabp_cache; 
21.  ... 
22.  struct list_head      next;
23.  ...
24. };

Ce qui suit présente les champs les plus importants de la structure kmem_cache_t:

  1. slab_full: dalle entièrement allouée
  2. slab_partial: dalle partiellement allouée
  3. slab_free: dalle qui n'a pas été allouée
  4. objsize: la taille de l'objet stocké
  5. num: le nombre d'objets qu'une dalle peut stocker
  6. gfporder: une dalle est composée de pages mémoire 2gfporder
  7. color / colour_off / colour_next: la taille de la zone de coloration (sera mentionnée plus tard)

La structure slab_t est définie comme suit:

1. typedef struct slab_s {
    
     
2.  struct list_head    list;
3.  unsigned long       colouroff;
4.  void                *s_mem;
5.  unsigned int        inuse;
6.     kmem_bufctl_t        free;
7. } slab_t;

Le but de chaque champ de la structure slab_t est le suivant:

  1. list: chaîne de liens (complet / partiel / vide complet)
  2. colouroff: compensation de couleur
  3. s_mem: l'adresse mémoire de départ de l'objet de stockage
  4. inuse: combien d'objets ont été alloués
  5. free: Utilisé pour connecter des objets libres

Utilisez un graphique pour montrer la relation entre eux, comme suit:
Insérez la description de l'image ici

Besoin d'expliquer ici, une dalle sera divisée en plusieurs objets (peut être compris comme une structure), ces objets sont la plus petite unité allouée par l'algorithme de dalle, et une dalle a généralement une ou plusieurs pages mémoire (mais pas plus de 24 pages) composition.

La dalle de la liste slab_free dans la structure kmem_cache_t est le principal candidat pour la récupération de mémoire. Étant donné que l'objet est alloué et libéré de la dalle, une seule dalle peut figurer dans la liste des dalles. Par exemple, lorsque tous les objets d'une dalle sont alloués, ils sont déplacés de la liste slab_partial vers la liste slab_full. Lorsqu'une dalle de la liste slab_free reçoit un objet, elle sera déplacée de la liste slab_free vers la liste slab_partial. Lorsque tous les objets d'une dalle sont libérés, ils sont déplacés de la liste slab_partial vers la liste slab_free.

[Avantages de l'article] Matériel d'apprentissage de l'architecte serveur Linux C / C ++ plus groupe 812855908 (données comprenant C / C ++, Linux, technologie golang, Nginx, ZeroMQ, MySQL, Redis, fastdfs, MongoDB, ZK, diffusion multimédia, CDN, P2P, K8S, Docker, TCP / IP, coroutine, DPDK, ffmpeg, etc.)
Insérez la description de l'image ici

initialisation d'allocateur de dalle

L'initialisation de l'allocateur de dalles est complétée par la fonction kmem_cache_init (), comme suit:

1. void __init kmem_cache_init(void) 
2. {
    
     
3.     size_t left_over; 
4.  
5.     init_MUTEX(&cache_chain_sem); 
6.     INIT_LIST_HEAD(&cache_chain); 
7.  
8.     kmem_cache_estimate(0, cache_cache.objsize, 0, 
9.             &left_over, &cache_cache.num); 
10.  if (!cache_cache.num) 
11.         BUG(); 
12.  
13.     cache_cache.colour = left_over/cache_cache.colour_off; 
14.     cache_cache.colour_next = 0; 
15. }

Cette fonction est principalement utilisée pour initialiser la variable cache_cache, cache_cache est une variable de structure de type kmem_cache_t, définie comme suit:

1. static kmem_cache_t cache_cache = {
    
     
2.     slabs_full:       LIST_HEAD_INIT(cache_cache.slabs_full), 
3.     slabs_partial:    LIST_HEAD_INIT(cache_cache.slabs_partial), 
4.     slabs_free:       LIST_HEAD_INIT(cache_cache.slabs_free), 
5.     objsize:          sizeof(kmem_cache_t), 
6.     flags:            SLAB_NO_REAP, 
7.     spinlock:         SPIN_LOCK_UNLOCKED, 
8.     colour_off:       L1_CACHE_BYTES, 
9.     name:             "kmem_cache", 
10. };

Pourquoi avons-nous besoin d'un tel objet? Puisque la structure kmem_cache_t elle-même est aussi un petit objet mémoire, elle devrait également être allouée par l'allocateur de dalle, mais de cette façon le problème de "l'œuf ou la poule vient en premier". Lorsque le système est initialisé, l'allocateur de dalle n'a pas été initialisé, donc l'allocateur de dalle ne peut pas être utilisé pour allouer un objet kmem_cache_t. À ce stade, l'allocateur de dalle ne peut être géré qu'en définissant une variable statique de type kmem_cache_t, donc le cache_cache statique variable Il permet de gérer le distributeur de dalles.

Comme le montre le code ci-dessus, le champ objsize de cache_cache est défini sur la taille de sizeof (kmem_cache_t), donc cet objet est principalement utilisé pour allouer différents types d'objets kmem_cache_t.

La fonction kmem_cache_init () appelle la fonction kmem_cache_estimate () pour calculer combien d'objets de taille cache_cache.objsize peuvent être stockés dans une dalle, et les enregistrer dans le champ cache_cache.num. Il est impossible d'allouer tous les objets d'une dalle. Par exemple: une dalle de 4 096 octets est utilisée pour allouer des objets de 22 octets, qui peuvent être divisés en 186, mais les 4 octets restants ne peuvent pas être utilisés., Donc, cette partie du la mémoire est utilisée comme zone d'ombrage. La fonction de la zone grisée est d'échelonner différentes dalles afin que le processeur puisse mettre en cache les dalles plus efficacement. Bien entendu, cela fait partie de la partie optimisation et n'a pas beaucoup d'impact sur l'algorithme d'allocation de dalles. C'est-à-dire que même si la dalle n'est pas colorée, l'algorithme d'allocation de dalle peut toujours fonctionner.

Application d'objet kmem_cache_t

kmem_cache_t est utilisé pour gérer et allouer des objets, donc lorsque vous souhaitez utiliser l'allocateur de dalles, vous devez d'abord demander un objet kmem_cache_t. La demande d'objet kmem_cache_t est effectuée par la fonction kmem_cache_create ():

1. kmem_cache_t *kmem_cache_create ( 
2.  const char *name, 
3.     size_t size, 
4.     size_t offset, 
5.  unsigned long flags, 
6.  void (*ctor)(void*, kmem_cache_t *, unsigned long), 
7.  void (*dtor)(void*, kmem_cache_t *, unsigned long) 
8. ) {
    
     
9.     ... 
10.     cachep = (kmem_cache_t *) kmem_cache_alloc(&cache_cache, SLAB_KERNEL); 
11.  if (!cachep) 
12.  goto opps; 
13.     memset(cachep, 0, sizeof(kmem_cache_t)); 
14.     ... 
15.  do {
    
    
16.  unsigned int break_flag = 0; 
17. cal_wastage: 
18.         kmem_cache_estimate(cachep->gfporder, size, flags, 
19.                         &left_over, &cachep->num); 
20.  if (break_flag) 
21.  break;
22.  if (cachep->gfporder >= MAX_GFP_ORDER) 
23.  break; 
24.  if (!cachep->num)
25.  goto next; 
26.  if (flags & CFLGS_OFF_SLAB && cachep->num > offslab_limit) {
    
     
27.  /* Oops, this num of objs will cause problems. */ 
28.             cachep->gfporder--; 
29.             break_flag++; 
30.  goto cal_wastage; 
31.         } 
32.  
33.  if (cachep->gfporder >= slab_break_gfp_order) 
34.  break; 
35.  
36.  if ((left_over*8) <= (PAGE_SIZE<<cachep->gfporder)) 
37.  break;    /* Acceptable internal fragmentation. */ 
38. next: 
39.         cachep->gfporder++; 
40.     } while (1); 
41.  
42.  if (flags & CFLGS_OFF_SLAB && left_over >= slab_size) {
    
     
43.         flags &= ~CFLGS_OFF_SLAB; 
44.         left_over -= slab_size; 
45.     } 
46.  
47.  /* Offset must be a multiple of the alignment. */ 
48.     offset += (align-1); 
49.     offset &= ~(align-1); 
50.  if (!offset) 
51.         offset = L1_CACHE_BYTES; 
52.     cachep->colour_off = offset; 
53.     cachep->colour = left_over/offset; 
54.  
55.     cachep->flags = flags; 
56.     cachep->gfpflags = 0; 
57.  if (flags & SLAB_CACHE_DMA) 
58.         cachep->gfpflags |= GFP_DMA; 
59.     spin_lock_init(&cachep->spinlock); 
60.     cachep->objsize = size; 
61.     INIT_LIST_HEAD(&cachep->slabs_full); 
62.     INIT_LIST_HEAD(&cachep->slabs_partial); 
63.     INIT_LIST_HEAD(&cachep->slabs_free); 
64.  
65.  if (flags & CFLGS_OFF_SLAB) 
66.         cachep->slabp_cache = kmem_find_general_cachep(slab_size,0); 
67.     cachep->ctor = ctor; 
68.     cachep->dtor = dtor; 
69.     strcpy(cachep->name, name); 
70.  
71.     down(&cache_chain_sem); 
72.     {
    
     
73.  struct list_head *p; 
74.  
75.         list_for_each(p, &cache_chain) {
    
     
76.             kmem_cache_t *pc = list_entry(p, kmem_cache_t, next); 
77.         } 
78.     } 
79.  
80.     list_add(&cachep->next, &cache_chain); 
81.     up(&cache_chain_sem); 
82. opps: 
83.  return cachep; 
84. }

La fonction kmem_cache_create () est relativement longue, donc le code ci-dessus supprime certains points moins importants pour que le code reflète plus clairement son principe.

Dans la fonction kmem_cache_create (), appelez d'abord kmem_cache_alloc () pour demander un objet kmem_cache_t. Nous voyons que lorsque kmem_cache_alloc () est appelé, la variable cache_cache est passée. Après avoir demandé l'objet kmem_cache_t, il doit être initialisé, principalement pour initialiser tous les champs de l'objet kmem_cache_t: 1) Calculez le nombre de pages nécessaires en fonction de la taille de la dalle. 2) Calculez le nombre d'objets qu'une dalle peut allouer. 3) Calculez les informations de la zone de coloration. 4) Initialisez la liste chaînée slab_full / slab_partial / slab_free. 5) Enregistrez l'objet kmem_cache_t appliqué dans la liste chaînée cache_chain.

Attribution d'objets

Après avoir postulé pour l'objet kmem_cache_t, utilisez la fonction kmem_cache_alloc () pour appliquer pour l'objet spécifié. Le code de la fonction kmem_cache_alloc () est le suivant:

1. static inline void * 
2. kmem_cache_alloc_one_tail (kmem_cache_t *cachep, slab_t *slabp) 
3. {
    
     
4.  void *objp; 
5.  
6.     slabp->inuse++; 
7.     objp = slabp->s_mem + slabp->free*cachep->objsize; 
8.     slabp->free = slab_bufctl(slabp)[slabp->free]; 
9.  
10.  if (unlikely(slabp->free == BUFCTL_END)) {
    
     
11.         list_del(&slabp->list); 
12.         list_add(&slabp->list, &cachep->slabs_full); 
13.     } 
14.  return objp; 
15. }
16.  
17. static inline void * 
18. __kmem_cache_alloc(kmem_cache_t *cachep, int flags) 
19. {
    
     
20.  unsigned long save_flags; 
21.  void* objp; 
22.  struct list_head * slabs_partial, * entry; 
23.     slab_t *slabp;     
24.  
25.     kmem_cache_alloc_head(cachep, flags); 
26. try_again: 
27.     local_irq_save(save_flags); 
28.  
29.     slabs_partial = &(cachep)->slabs_partial;
30.     entry = slabs_partial->next;
31.  
32.  if (unlikely(entry == slabs_partial)) {
    
    
33.  struct list_head * slabs_free;
34.         slabs_free = &(cachep)->slabs_free;
35.         entry = slabs_free->next;
36.  if (unlikely(entry == slabs_free))
37.  goto alloc_new_slab;
38.         list_del(entry);
39.         list_add(entry, slabs_partial);
40.     }
41.  
42.     slabp = list_entry(entry, slab_t, list); 
43.     objp = kmem_cache_alloc_one_tail(cachep, slabp); 
44.  
45.     local_irq_restore(save_flags); 
46.  return objp; 
47.  
48. alloc_new_slab: 
49.     local_irq_restore(save_flags); 
50.  if (kmem_cache_grow(cachep, flags)) 
51.  goto try_again; 
52.  return NULL; 
53. }

Après avoir développé la fonction kmem_cache_alloc () par moi, comme dans le code ci-dessus, les principales étapes de la fonction kmem_cache_alloc () sont: 1) Recherchez la liste slab_partial de l'objet kmem_cache_t pour voir s'il y a une dalle disponible, et si il y a, allouer un objet directement à partir de la dalle. 2) S'il n'y a pas de dalle disponible dans la liste slab_partial, recherchez la dalle disponible dans la liste slab_free. S'il y a une dalle disponible, allouez un objet de la dalle et placez la dalle dans la liste slab_partial. 3) S'il n'y a pas de dalle disponible dans la liste slab_free, appelez la fonction kmem_cache_grow () pour demander une nouvelle dalle pour l'allocation d'objets.

La structure d'une dalle est la suivante:
Insérez la description de l'image ici

La partie grise est la zone colorée, la partie verte est la structure de gestion de la dalle, la partie jaune est l'index de la liste liée des objets inactifs et la partie rouge est l'entité de l'objet. Nous pouvons voir que le champ s_mem de la structure de dalle pointe vers l'adresse de départ de la liste d'entités objet.

Lors de l'affectation d'objets, vérifiez d'abord si des objets libres sont disponibles via le champ libre de la structure de dalle. Le champ libre stocke l'index du premier nœud de la liste liée d'objets libres.

Libération d'objet

La sortie de l'objet est relativement simple, principalement en appelant la fonction kmem_cache_free (), et la fonction kmem_cache_free () finira par appeler la fonction kmem_cache_free_one (), le code est le suivant:

1. static inline void 
2. kmem_cache_free_one(kmem_cache_t *cachep, void *objp) 
3. {
    
     
4.     slab_t* slabp; 
5.  
6.     {
    
     
7.  unsigned int objnr = (objp-slabp->s_mem)/cachep->objsize;  
8.         slab_bufctl(slabp)[objnr] = slabp->free; 
9.         slabp->free = objnr; 
10.     } 
11.  
12.  /* fixup slab chains */ 
13.     {
    
     
14.  int inuse = slabp->inuse; 
15.  if (unlikely(!--slabp->inuse)) {
    
     
16.  /* Was partial or full, now empty. */ 
17.             list_del(&slabp->list); 
18.             list_add(&slabp->list, &cachep->slabs_free); 
19.         } else if (unlikely(inuse == cachep->num)) {
    
     
20.  /* Was full. */ 
21.             list_del(&slabp->list); 
22.             list_add(&slabp->list, &cachep->slabs_partial); 
23.         } 
24.     } 
25. }

Lorsque l'objet est libéré, l'index de l'objet est d'abord ajouté à la liste des objets libres de la dalle, puis la dalle est déplacée vers la liste appropriée en fonction de l'utilisation de la dalle. 1) Si tous les objets de la dalle sont libérés, placez la dalle dans la liste slab_free. 2) Si la dalle où se trouve l'objet se trouvait à l'origine dans slab_full, déplacez la dalle vers slab_partial.

Je suppose que tu aimes

Origine blog.csdn.net/qq_40989769/article/details/113050687
conseillé
Classement