Ejemplo ejecutable mínimo
¿Qué hace la llamada al sistema brk ()?
Pide al núcleo que le permita leer y escribir en un fragmento contiguo de memoria llamado montón.
Si no pregunta, podría dejarlo por defecto.
Sin 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 aguas arriba .
Es posible que lo anterior no llegue a una nueva página y no se convierta en segfault incluso sin el brk, por lo que aquí hay una versión más agresiva que asigna 16MiB y es muy probable que segfault sin el 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;
}
Probado en Ubuntu 18.04.
Visualización virtual del espacio de direcciones
Antes brk:
+------+ <-- Heap Start == Heap End
Después brk(p + 2):
+------+ <-- Heap Start + 2 * sizof(int) == Heap End
| |
| You can now write your ints
| in this memory area.
| |
+------+ <-- Heap Start
Después brk(b):
+------+ <-- Heap Start == Heap End
Para comprender mejor los espacios de direcciones, debe familiarizarse con la paginación: ¿Cómo funciona la paginación x86? .
¿Por qué necesitamos ambos brky sbrk?
brkpor supuesto, podría implementarse con sbrkcálculos de + desplazamiento, ambos existen solo por conveniencia.
En el backend, el kernel de Linux v5.0 tiene una sola llamada al sistema brkque se usa para implementar ambos: https://github.com/torvalds/linux/blob/v5.0/arch/x86/entry/syscalls/syscall_64. tbl # L23
12 common brk __x64_sys_brk
Es brkPOSIX?
brksolía ser POSIX, pero se eliminó en POSIX 2001, por lo tanto, la necesidad de _GNU_SOURCEacceder al contenedor glibc.
La eliminación probablemente se deba a la introducción mmap, que es un superconjunto que permite asignar rangos múltiples y más opciones de asignación.
Creo que no hay un caso válido en el que deba usar en brklugar de malloco en la mmapactualidad.
brk vs malloc
brkEs una antigua posibilidad de implementación malloc.
mmapes el mecanismo más estrictamente más potente que probablemente todos los sistemas POSIX usan actualmente para implementar malloc. Aquí hay un ejemplo mínimo de mmapasignación de memoria ejecutable .
¿Puedo mezclar brky malloc?
Si mallocse implementa con brk, no tengo idea de cómo eso posiblemente no puede hacer estallar las cosas, ya que brksolo administra un único rango de memoria.
Sin embargo, no pude encontrar nada al respecto en los documentos de glibc, por ejemplo:
Las cosas probablemente funcionarán allí, supongo, ya mmapque probablemente se usen para malloc.
Ver también:
Más información
Internamente, el núcleo decide si el proceso puede tener tanta memoria y asigna páginas de memoria para ese uso.
Esto explica cómo la pila se compara con el montón: ¿Cuál es la función de las instrucciones push / pop utilizadas en los registros en el ensamblaje x86?