Je peux voir pourquoi vous êtes confus. Le diagramme est un peu déroutant et peut en fait être incorrect.
Tout d'abord, réfléchissons à la raison pour laquelle un noyau a besoin d'un allocateur de mémoire inférieur au niveau des pages. C'est probablement déjà des choses que vous connaissez le plus, mais je vais les parcourir pour être complètes.
Les pages sont «l'unité» type des opérations de mémoire. Lorsqu'une application de l'espace utilisateur alloue de la mémoire ou mappe en mémoire un fichier ou quelque chose comme ça, elle obtient généralement un multiple de la taille de page de la machine. Il y a quelques exceptions notables; Windows utilise 64 Ko comme unité d'allocation de mémoire virtuelle, quelle que soit la taille de page du processeur. Néanmoins, pensons-y de cette façon.
Sur un CPU moderne, en ce qui concerne le code de l'espace utilisateur, il a un espace d'adressage plat. Il s'agit en fait d'une illusion fournie par le système de mémoire virtuelle. Le système d'exploitation fournit des pages de n'importe où dans la RAM (ou peut-être pas du tout dans la RAM, dans le cas de la mémoire échangée ou des fichiers mappés en mémoire) et les mappe dans un espace d'adressage virtuel contigu.
Le point de tout cela est qu'à part quelques cas spéciaux pour le système d'exploitation lui-même (peut-être des tampons DMA, peut-être des structures de données spéciales mises en place au démarrage, oh et l'image du noyau lui-même), le noyau du système d'exploitation n'a probablement jamais à gérer n'importe quel bloc de RAM plus grand qu'une page. Cela simplifie énormément les choses, car cela signifie qu'en ce qui concerne les pages, chaque allocation et désallocation est de la même taille. Il élimine également efficacement la fragmentation externe au niveau macro.
Cependant, les noyaux doivent également implémenter leurs propres structures de données, et pour cela, ils ont besoin d'un autre type d'allocateur de mémoire. Ces structures de données peuvent généralement être considérées comme une collection d'objets individuels (par exemple, un objet peut être un «thread» ou un «mutex»). La taille de ces objets est généralement beaucoup plus petite qu'une page.
Ainsi, par exemple, un objet qui représente les informations d'identification de sécurité d'un processus (pensez à l'ID utilisateur et à l'ID de groupe dans POSIX, par exemple) pourrait ne faire que 16 octets environ, alors qu'un "processus" ou "thread" pourrait être jusqu'à 1 Ko de taille. De toute évidence, vous ne voulez pas utiliser une page entière pour ces petits enregistrements, donc l'idée est d'implémenter un allocateur en haut des pages.
Le système d'allocation de niveau inférieur doit répondre à bon nombre des mêmes problèmes que l'allocateur de niveau page: il doit être raisonnablement rapide (y compris sur les systèmes multicœurs), vous voulez minimiser la fragmentation, etc. Mais plus important encore, il doit être réglable et configurable en fonction du type de structure de données que vous stockez.
Certaines structures de données sont intrinsèquement "de type cache". Par exemple, de nombreux systèmes d'exploitation maintiennent un cache des noms de chemin d'accès aux objets du système de fichiers pour éviter de longues chaînes de recherche de répertoires (appelées "cache de noms" ou "cache de noms" dans Unix). Ces objets ne sont nécessaires que pour les performances, pas pour l'exactitude, vous pouvez donc (en théorie) simplement oublier une page entière remplie d'entrées si la mémoire est insuffisante et vous devez libérer rapidement un cadre de page.
D'autres structures de données peuvent être échangées sur le disque si la mémoire est insuffisante et que vous n'en avez pas besoin rapidement. Mais vous ne voulez pas le faire avec des structures de données qui contrôlent l'échange ou le système de mémoire virtuelle!
Certaines structures de données peuvent être déplacées en mémoire sans pénalité (par exemple, si personne ne s'y réfère avec un pointeur), elles pourraient donc se "compacter" pour éviter la fragmentation si nécessaire.
L'idée principale de l'allocateur de dalles est donc qu'une page ne doit stocker que des structures de données du même "type". Cela coche toutes les cases: chaque objet d'une page a la même taille, donc il n'y a pas de fragmentation externe. Les objets du même "type" ont les mêmes exigences de performances et la même sémantique.
Soit dit en passant, c'est une histoire similaire avec l'allocation. Pour certains types d'objets, il est probablement correct d'attendre s'il n'y a pas de mémoire immédiatement disponible pour allouer cet objet. Un objet qui représente un fichier ouvert pourrait être un exemple; L'ouverture d'un fichier est une opération coûteuse dans le meilleur des cas, donc attendre un peu plus longtemps ne fera pas trop mal.
Pour d'autres types d'objet (par exemple un objet qui représente un événement en temps réel qui doit se produire dans un certain temps à partir de maintenant), vous ne voulez vraiment pas attendre. Il est donc logique que certains types d'objets sur-allouent (disons, ayez quelques pages libres en réserve) afin que les demandes puissent être satisfaites sans attendre.
Ce que vous faites essentiellement, c'est de permettre à chaque type d'objet d'avoir son propre allocateur, qui peut être configuré pour les besoins de cet objet. Ces allocateurs par objet sont appelés de manière confuse "caches". Vous allouez un cache par type d'objet. (Oui, vous implémentez généralement un "cache de caches" également.) Chaque cache ne stocke que des objets du même type (par exemple, uniquement des structures de threads ou uniquement des structures d'espace d'adressage).
Chaque cache, à son tour, gère les "dalles". Une dalle est un cadre de page qui contient un tableau d'objets du même type. Les dalles peuvent être "pleines" (tous les objets utilisés), "vides" (aucun objet utilisé) ou "partielles" (certains objets utilisés).
Les dalles partielles sont probablement les plus intéressantes, car l'allocateur de dalles maintient une liste gratuite pour chaque dalle partielle. (Les dalles pleines et les dalles vides n'ont pas besoin de liste libre.) Les objets sont d'abord attribués à partir de dalles partielles (et probablement à partir des dalles partielles "les plus complètes") pour éviter d'allouer des pages inutiles.
La bonne chose à propos de l'allocation de dalles est que toutes ces options de politique d'allocation (ainsi que la sémantique de la mémoire) peuvent être ajustées pour chaque type d'objet. Certains caches peuvent conserver un pool de dalles vides et d'autres pas. Certains peuvent être échangés vers un stockage secondaire et d'autres non.
Linux dispose de trois types différents d'allocateur de dalles, selon que vous avez besoin ou non de compacité, de compatibilité avec le cache ou de vitesse brute. Il y a eu une bonne présentation à ce sujet il y a quelques années, ce qui explique bien les compromis.
L'allocateur de dalles Solaris (voir le document pour plus de détails ) a quelques détails supplémentaires pour obtenir encore plus de performances. Pour commencer, dans Solaris, tout se fait avec l'allocation des dalles, y compris l'allocation des cadres de page. (Il s'agit de la solution de Solaris pour l'allocation d'objets d'une taille supérieure à une demi-page.) Il gère les objets plus petits en imbriquant les allocateurs de dalles dans l'espace alloué aux dalles.
Certains objets dans Solaris nécessitent une construction et une destruction complexes et coûteuses (par exemple des objets qui ont un verrou de noyau), et ils pourraient donc être "partiellement libres" (c'est-à-dire construits mais non alloués). Solaris optimise également l'allocation gratuite de dalles en conservant des listes gratuites par processeur, garantissant ainsi que certaines opérations sont entièrement sans attente.
Pour prendre en charge l'allocation à usage général (par exemple pour les tableaux dont la taille n'est pas connue au moment de la compilation), la plupart des systèmes d'exploitation de type macro-noyau ont également des caches qui représentent des tailles d' objets plutôt que des types d' objets . FreeBSD, par exemple, maintient des caches pour les objets inconnus dont les tailles sont des puissances de 2 octets, de 4 à 256.
Ce que j'espère que vous pouvez voir, c'est que l'allocation des dalles est un cadre très flexible qui peut être adapté aux besoins de différents types de données. Il ne rivalise pas avec la pagination, mais le complète (bien que dans Solaris, les cadres de page soient alloués avec des dalles).
J'espère que ça aide. Faites-moi savoir si quelque chose doit être clarifié.