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 brket sbrk?
brkpourrait bien sûr être implémenté avec des sbrkcalculs 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 brkqui 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 brkPOSIX?
brkAuparavant, c'était POSIX, mais il a été supprimé dans POSIX 2001, d'où la nécessité _GNU_SOURCEd'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 brkplace mallocou de mmapnos jours.
brk contre malloc
brkest une ancienne possibilité de mise en œuvre malloc.
mmapest 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 brket malloc?
Si votre mallocest implémenté avec brk, je ne sais pas comment cela peut ne pas faire exploser les choses, car brkne 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 mmapsont 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?