Exemple exécutable minimal
Que fait l'appel système brk ()?
Demande au noyau de vous permettre de lire et d'écrire dans un bloc de mémoire contigu appelé le tas.
Si vous ne le demandez pas, cela pourrait vous déranger.
Sans brk
:
#define _GNU_SOURCE
#include <unistd.h>
int main(void) {
/* Get the first address beyond the end of the heap. */
void *b = sbrk(0);
int *p = (int *)b;
/* May segfault because it is outside of the heap. */
*p = 1;
return 0;
}
Avec brk
:
#define _GNU_SOURCE
#include <assert.h>
#include <unistd.h>
int main(void) {
void *b = sbrk(0);
int *p = (int *)b;
/* Move it 2 ints forward */
brk(p + 2);
/* Use the ints. */
*p = 1;
*(p + 1) = 2;
assert(*p == 1);
assert(*(p + 1) == 2);
/* Deallocate back. */
brk(b);
return 0;
}
GitHub en amont .
Ce qui précède pourrait ne pas frapper une nouvelle page et pas segfault même sans le brk
, voici donc une version plus agressive qui alloue 16 Mo et est très susceptible de segfault sans le brk
:
#define _GNU_SOURCE
#include <assert.h>
#include <unistd.h>
int main(void) {
void *b;
char *p, *end;
b = sbrk(0);
p = (char *)b;
end = p + 0x1000000;
brk(end);
while (p < end) {
*(p++) = 1;
}
brk(b);
return 0;
}
Testé sur Ubuntu 18.04.
Visualisation de l'espace d'adressage virtuel
Avant brk
:
+------+ <-- Heap Start == Heap End
Après brk(p + 2)
:
+------+ <-- Heap Start + 2 * sizof(int) == Heap End
| |
| You can now write your ints
| in this memory area.
| |
+------+ <-- Heap Start
Après brk(b)
:
+------+ <-- Heap Start == Heap End
Pour mieux comprendre les espaces d'adressage, vous devez vous familiariser avec la pagination: comment fonctionne la pagination x86? .
Pourquoi avons-nous besoin des deux brk
et sbrk
?
brk
pourrait bien sûr être implémenté avec des sbrk
calculs de + offset, les deux existent juste pour des raisons de commodité.
Dans le backend, le noyau Linux v5.0 a un seul appel système brk
qui est utilisé pour implémenter les deux: https://github.com/torvalds/linux/blob/v5.0/arch/x86/entry/syscalls/syscall_64. tbl # L23
12 common brk __x64_sys_brk
Est-ce brk
POSIX?
brk
Auparavant, c'était POSIX, mais il a été supprimé dans POSIX 2001, d'où la nécessité _GNU_SOURCE
d'accéder au wrapper glibc.
La suppression est probablement due à l'introduction mmap
, qui est un sur-ensemble qui permet d'allouer plusieurs plages et plus d'options d'allocation.
Je pense qu'il n'y a pas de cas valable où vous devriez utiliser à la brk
place malloc
ou de mmap
nos jours.
brk
contre malloc
brk
est une ancienne possibilité de mise en œuvre malloc
.
mmap
est le nouveau mécanisme strictement plus puissant que tous les systèmes POSIX utilisent actuellement pour implémenter malloc
. Voici un exemple d'allocation de mémoire exécutable minimalemmap
.
Puis-je mélanger brk
et malloc?
Si votre malloc
est implémenté avec brk
, je ne sais pas comment cela peut ne pas faire exploser les choses, car brk
ne gère qu'une seule plage de mémoire.
Je n'ai cependant rien trouvé à ce sujet sur la documentation de la glibc, par exemple:
Les choses fonctionneront probablement là-bas, je suppose, car elles mmap
sont probablement utilisées malloc
.
Voir également:
Plus d'informations
En interne, le noyau décide si le processus peut avoir autant de mémoire et réserve des pages de mémoire pour cet usage.
Ceci explique comment la pile se compare au tas: Quelle est la fonction des instructions push / pop utilisées sur les registres dans l'assemblage x86?