Esempio eseguibile minimo
Cosa fa la chiamata di sistema brk ()?
Chiede al kernel di farti leggere e scrivere in un pezzo contiguo di memoria chiamato heap.
Se non lo chiedi, potrebbe segfault.
Senza 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;
}
Con 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 a monte .
Quanto sopra potrebbe non colpire una nuova pagina e non essere segfault anche senza il brk , quindi ecco una versione più aggressiva che alloca 16 MiB ed è molto probabile che segfault senza 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;
}
Testato su Ubuntu 18.04.
Visualizzazione dello spazio degli indirizzi virtuali
Prima brk :
+------+ <-- Heap Start == Heap End
Dopo brk(p + 2) :
+------+ <-- Heap Start + 2 * sizof(int) == Heap End
| |
| You can now write your ints
| in this memory area.
| |
+------+ <-- Heap Start
Dopo brk(b) :
+------+ <-- Heap Start == Heap End
Per comprendere meglio gli spazi degli indirizzi, è necessario acquisire familiarità con il paging: come funziona il paging x86? .
Perché abbiamo bisogno di entrambi brk e sbrk?
brkpotrebbe ovviamente essere implementato con i sbrkcalcoli + offset, entrambi esistono solo per comodità.
Nel backend, il kernel Linux v5.0 ha una singola chiamata di sistema brkche viene utilizzata per implementare entrambi: https://github.com/torvalds/linux/blob/v5.0/arch/x86/entry/syscalls/syscall_64. TBL # L23
12 common brk __x64_sys_brk
brkPOSIX è ?
brkera POSIX, ma è stato rimosso in POSIX 2001, quindi la necessità _GNU_SOURCEdi accedere al wrapper glibc.
La rimozione è probabilmente dovuta all'introduzione mmap, che è un superset che consente di assegnare più intervalli e più opzioni di allocazione.
Penso che non ci siano casi validi in cui dovresti usare al brkposto malloco al mmapgiorno d'oggi.
brk vs malloc
brkè una vecchia possibilità di implementazione malloc.
mmapè il nuovissimo meccanismo rigorosamente più potente che probabilmente tutti i sistemi POSIX attualmente utilizzano per implementare malloc. Ecco un runnable minimommap allocazione della memoria .
Posso mescolare brk e malloc?
Se il tuo mallocè implementato con brk, non ho idea di come ciò non possa far esplodere le cose, da allorabrk che gestisce solo un singolo intervallo di memoria.
Tuttavia non sono riuscito a trovare nulla al riguardo sui documenti glibc, ad esempio:
Le cose probabilmente funzioneranno lì, suppongo da allora mmap è probabilmente usato per malloc.
Guarda anche:
Ulteriori informazioni
Internamente, il kernel decide se il processo può avere tanta memoria e stacca le pagine di memoria per quell'uso.
Questo spiega come lo stack si confronta con l'heap: qual è la funzione delle istruzioni push / pop usate sui registri nell'assembly x86?