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
?
brk
potrebbe ovviamente essere implementato con i sbrk
calcoli + offset, entrambi esistono solo per comodità.
Nel backend, il kernel Linux v5.0 ha una singola chiamata di sistema brk
che 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
brk
POSIX è ?
brk
era POSIX, ma è stato rimosso in POSIX 2001, quindi la necessità _GNU_SOURCE
di 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 brk
posto malloc
o al mmap
giorno 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?