Partie 18 - Les small bins - fonctionnement et exploitation (1/2)
Les small bins : fonctionnement et exploitation (1/2)
Pour ĂȘtre honnĂȘte avec vous, jâai longuement hĂ©sitĂ© Ă Ă©crire les chapitres dĂ©diĂ©s aux small bins et large bins tant leur usage est marginal depuis la prĂ©sence du tcache. Mais bon, qui sait, peut-ĂȘtre quâavoir Ă©crit quelque part la maniĂšre dont ces types de corbeilles fonctionnent sera utile un jour ou lâautre đ€·ââïž.
Utilisation
Nous avons rĂ©cemment analysĂ© le fonctionnement de la unsorted bin au cours des prĂ©cĂ©dents chapitres. Vous vous rappelez sans doute quâun bloc libre de la unsorted bin nâa quâune seule chance dâĂȘtre rĂ©utilisĂ©. Sâil nâest pas utilisĂ© lors de la prochaine allocation (ex : taille dâallocation plus importante), alors il est insĂ©rĂ© soit dans une small bin soit dans une large bin en fonction de sa taille.
Gestion des blocs libres
Taille des blocs gérés
Agencement en mémoire
Voyons quelles sont les tailles de bloc gĂ©rĂ©es par les small bins, cela est trĂšs utile pour savoir si un bloc de la unsorted bin ira dans une small bin ou large bin. Tout dâabord, voici ce que dit le code source au sujet du nombre de small bins :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/*
Indexing
Bins for sizes < 512 bytes contain chunks of all the same size, spaced
8 bytes apart. Larger bins are approximately logarithmically spaced:
64 bins of size 8
32 bins of size 64
16 bins of size 512
8 bins of size 4096
4 bins of size 32768
2 bins of size 262144
1 bin of size what's left
There is actually a little bit of slop in the numbers in bin_index
for the sake of speed. This makes no difference elsewhere.
The bins top out around 1MB because we expect to service large
requests via mmap.
Bin 0 does not exist. Bin 1 is the unordered list; if that would be
a valid chunk size the small bins are bumped up one.
*/
#define NBINS 128
#define NSMALLBINS 64
Les nombres affichĂ©s dans cette section de code de la glibc peuvent prĂȘter Ă confusion đ”âđ«, il faut le reconnaĂźtre. Nous allons dĂ©tailler Ă©tape par Ă©tape la maniĂšre de compter le nombre de
small binsetlarge binspour que vous compreniez comment sâeffectue, au final, leur dĂ©nombrement.
Faisons la somme de toutes les corbeilles listĂ©es dans le commentaire ci-dessus : 64+32+16+8+4+2+1 = 127. Or la valeur de NBINS est 128. On sâattendrait donc Ă ce quâil y ait : 64 small bins et 64 large bins. Mais non, il y a une unitĂ© de diffĂ©rence đ€ âŠ
La fin du commentaire stipule Bin 0 does not exist.. Vous remarquerez, dâailleurs, que les boucles indexĂ©es par rapport Ă NBINS dĂ©marrent toujours Ă 1, comme ici.
Bah faut savoir : soit les tableaux en informatique commencent Ă
0, soit Ă1đ.
Tout Ă fait dâaccord ! Câest trĂšs perturbant pour la comprĂ©hension de lâagencement des small bins et large bins. Mais bon, au moins nous comprenons pourquoi il y en a bien 127 et non 128 au total.
Mais il y a autre chose qui peut sembler Ă©trange : NSMALLBINS vaut 64. Le problĂšme ? Bah câest que les small bins, il nây en a pas 64 mais 62 đ
. Puisque la Bin 0 nâexiste pas, on passe de 64 Ă 63 corbeilles. Reste Ă dĂ©terminer comment nous sommes passĂ©s de 63 Ă 62.
De plus, dâaprĂšs le commentaire Bin 1 is the unordered list, on en dĂ©duit que la premiĂšre corbeille nâest tout autre que la unsorted bin. Ainsi, voici comment se dĂ©composent ces 127 corbeilles :
- 1
unsorted bin; - 62
small bins; - 64
large bins;
Et la boucle est (presque) bouclĂ©e ! Il nous faut dĂ©sormais comprendre le tableau bins qui fait partie de lâarĂšne :
1
2
/* Normal bins packed as described above */
mchunkptr bins[NBINS * 2 - 2];
Normalement le tableau bins devrait vous dire quelque chose étant donné que ses deux premiers éléments pointent vers le premier et dernier bloc de la unsorted bin. Vous avez sûrement dû le voir passer en utilisant la commande arena. Pour rappel, il ressemble à :
Il est important de comprendre lâagencement des small bins et large bins dans ce tableau car ce nâest pas Ă©vident de prime abord.
DĂ©jĂ , calculons sa taille : NBINS * 2 - 2 = 128 * 2 - 2 = 254. 254 câest aussi 127*2. Cette prĂ©cision nâest pas anodine. Vu que chaque corbeille âclassiqueâ (unsorted bin, small bin et large bin) est une liste doublement chaĂźnĂ©e, lâarĂšne a besoin dâavoir un pointeur vers son premier et son dernier Ă©lĂ©ment. Câest pourquoi la glibc utilise un tableau de 127*2 Ă©lĂ©ments pour reprĂ©senter les 127 corbeilles.
Le tableau ci-dessous synthĂ©tise la correspondance entre le numĂ©ro dâune corbeille (qui va de 1 Ă 127) et ses deux index dans le tableau bins :
| Numéro de la corbeille | Ses deux index dans bins | Type de la corbeille |
|---|---|---|
| 1 | { 0 ; 1 } | unsorted bin |
| 2 | { 2 ; 3 } | small bin |
| 3 | { 4 ; 5 } | small bin |
n | { n*2 - 2 ; n*2 - 1 } | small bins |
| 63 | { 0x7c ; 0x7d } ({ 124 ; 125 }) | small bin |
| 64 | { 0x7e ; 0x7f } ({ 126 ; 127 }) | large bin |
| 65 | { 0x80 ; 0x81 } ({ 128 ; 129 }) | large bin |
| n | { n*2 - 2 ; n*2 - 1 } | large bins |
| 127 | { 0xfc ; 0xfd } ({ 252 ; 253 }) | large bin |
Pfiouu đ„” ! Au moins maintenant, nous savons exactement oĂč est stockĂ©e chaque corbeille et la maniĂšre dont se calculent les indices.
Résumé
Les tailles données ci-dessous incluent les métadonnées.
Ă partir du nombre de small bins et de lâespace entre deux small bins en fonction de lâarchitecture, nous obtenons les rĂ©sultats suivants :
Version de la libc <= 2.25
| Version | Espace entre deux corbeilles | Nombre de small bins | Taille min | Taille max (incluse) |
|---|---|---|---|---|
| 32 bits | 8 octets | 62 | 0x10 | 0x1f8 |
| 64 bits | 0x10 octets | 62 | 0x20 | 0x3f0 |
Version de la libc > 2.25
| Version | Espace entre deux corbeilles | Nombre de small bins | Taille min | Taille max (incluse) |
|---|---|---|---|---|
| 32 bits | 0x10 octets | 62 | 0x20 | 0x3e0 |
| 64 bits | 0x10 octets | 62 | 0x20 | 0x3f0 |
Pour rappel, dans les anciennes versions de la glibc (†2.25 (2017)), la contrainte dâalignement des blocs est de 8 octets. De ce fait, vous remarquerez quâaprĂšs la version 2.25, les intervalles de taille en 32 bits et 64 bits ne diffĂšrent pas tellement.
Les blocs non réutilisés de la
unsorted binvont dans unelarge binlorsque leur taille ne leur permet pas dâĂȘtre traitĂ©s par unesmall bin?
Exactement !
Organisation des corbeilles
Concernant la structure des corbeilles, ce sera assez facile à comprendre étant donné que cela est trÚs proche de la structure de la unsorted bin :
- les blocs sont gérés via un systÚme de liste doublement chaßnée circulaire ;
- la liste est de type FIFO (First In First Out). Autrement dit le premier bloc arrivé, est le premier servi.
Lâune des particularitĂ©s de ce type de corbeilles est quâun bloc qui vient dâĂȘtre libĂ©rĂ© ne va jamais directement dans une small bin mais passe dans un premier temps par la unsorted bin. Si ce bloc nâest pas rĂ©utilisĂ© et quâil a une petite taille, il va dans la small bin idoine.
Transition de la unsorted bin vers une small bin
Cas n°1 : un seul bloc
Comme Ă lâaccoutumĂ©e, je vous propose de voir avec un schĂ©ma ce qui se passe lorsquâun bloc nâest pas rĂ©utilisĂ© dans la unsorted bin. Prenons comme support le programme suivant :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include "stdlib.h"
// Version de la glibc : 2.24-9ubuntu2_amd64
int main()
{
void *a = malloc(0x500);
malloc(1);
free(a); // Bloc A -> unsorted bin (taille = 0x500)
malloc(0x500 - 0x100); // Bloc A reste dans unsorted bin (taille = 0x100)
malloc(0x200); // 0x200 > 0x100 => Bloc A -> small bin (taille = 0x100)
return 0;
}
Ci-dessous, les diffĂ©rentes Ă©tapes dâexĂ©cution du programme :
- le bloc
Ade0x500octets est allouĂ© ainsi quâun autre petit bloc afin dâĂ©viter la consolidation avec le bloc du sommet lors de la libĂ©ration ; free(a): Ă©tant donnĂ© que dans cette version de la glibc (2.24) il nây a pas detcacheet que la taille de ce bloc nâest pas gĂ©rĂ©e par unefastbin, le bloc libre est gĂ©rĂ© par launsorted bin;malloc(0x500 - 0x100): un bloc de0x400octets est allouĂ©. Sachant que le bloc libreAa Ă©tĂ© rĂ©utilisĂ©, le reste du bloc (0x100octets) ne quitte pas launsorted bin. De nouveau, le bloc libre de0x100octets a une seule chance dâĂȘtre rĂ©utilisĂ© ;malloc(0x200): la taille de lâallocation demandĂ©e dĂ©passe celle du bloc libre, ce dernier est donc placĂ© dans lasmall binde taille0x100.
Cas n°2 : plusieurs blocs
Analysons désormais le comportement du programme suivant :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include "stdlib.h"
// Version de la glibc : 2.24-9ubuntu2_amd64
int main()
{
void *a = malloc(0x500);
malloc(1);
void *b = malloc(0x500);
malloc(1);
void *c = malloc(0x500);
malloc(1);
free(a); // Bloc A -> unsorted bin (taille = 0x500)
malloc(0x500 - 0x100); // Bloc A reste dans unsorted bin (taille = 0x100)
malloc(0x200); // 0x200 > 0x100 => Bloc A -> small bin (taille = 0x100)
free(b); // Bloc B -> unsorted bin (taille = 0x500)
malloc(0x500 - 0x100); // Bloc B reste dans unsorted bin (taille = 0x100)
malloc(0x200); // 0x200 > 0x100 => Bloc B -> small bin (taille = 0x100)
free(c); // Bloc B -> unsorted bin (taille = 0x500)
malloc(0x500 - 0x100); // Bloc B reste dans unsorted bin (taille = 0x100)
malloc(0x200); // 0x200 > 0x100 => Bloc B -> small bin (taille = 0x100)
return 0;
}
Il sâagit du mĂȘme programme que le prĂ©cĂ©dent, si ce nâest quâĂ la fin de son exĂ©cution, 3 blocs libres seront prĂ©sents dans la small bin de taille 0x100.
Petite question pour voir si vous avez bien compris : quel est le numéro de la
small binqui gĂšre les blocs libres de taille 0x100 ? Quels sont ses deux index dans le membre (tableau)binsde lâarĂšne ?RĂ©ponse :
aWwgcydhZ2l0IGRlIGxhIHNtYWxsIGJpbiBuwrAxNiBkb250IGxlcyBkZXV4IGluZGV4IHNvbnQgeyAweDFlIDsgMHgxZn0u
En exĂ©cutant le programme jusquâau retour du main dans gdb, nous pouvons effectivement constater la prĂ©sence des trois blocs libres de 0x100 octets dans une small bin :
Selon la docâ de la glibc qui stipule que la premiĂšre
bin(n°1) est launsorted bin, le numĂ©ro de cettesmall binest 16 parmi lâensemble des trois corbeilles :unsorted bin+small bins+large bins.Par contre, parmi toutes les
small bins, celle qui gĂšre les blocs de taille0x100est la numĂ©ro 15. DâoĂč la valeuridx=15utilisĂ©e dans gdb.En gros : câest la 16e
bin(unsorted bin,small binsetlarge binsconfondues) et la 15esmall binparmi lessmall bins.Oui, câest trĂšs pĂ©nible de devoir jongler entre diffĂ©rentes maniĂšres dâindexer les corbeilles đźâđš ⊠Ce qui est important est de se rappeler lâorigine de ces diffĂ©rences.
En utilisant la commande arena, nous trouvons rapidement les indices de cette small bin :
En sâintĂ©ressant aux liens entre les diffĂ©rents blocs, vous constaterez que les blocs libres dâun small bin sont liĂ©s de la mĂȘme maniĂšre que des blocs libres de la unsorted bin :
Les listes doublement chaßnées circulaires sont un peu difficiles à lire au début, mais avec un peu de temps, on finit par en saisir la logique.
Structure et mĂ©tadonnĂ©es dâun bloc issu dâune small bin
Ce qui est, au dĂ©part, compliquĂ© avec les small bins, câest le nombre de corbeilles quâil y a et la maniĂšre dont elles sont indexĂ©es et gĂ©rĂ©es par lâarĂšne. Quant Ă lâorganisation des blocs au sein dâune corbeille, câest semblable Ă ce qui est effectuĂ© dans la unsorted bin.
Dâailleurs, mĂȘme la structure dâun bloc dans une small bin est similaire Ă celle des blocs de la unsorted bin :
On passe Ă la suite ?





