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 brk
y sbrk
?
brk
por supuesto, podría implementarse con sbrk
cálculos de + desplazamiento, ambos existen solo por conveniencia.
En el backend, el kernel de Linux v5.0 tiene una sola llamada al sistema brk
que 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 brk
POSIX?
brk
solía ser POSIX, pero se eliminó en POSIX 2001, por lo tanto, la necesidad de _GNU_SOURCE
acceder 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 brk
lugar de malloc
o en la mmap
actualidad.
brk
vs malloc
brk
Es una antigua posibilidad de implementación malloc
.
mmap
es 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 mmap
asignación de memoria ejecutable .
¿Puedo mezclar brk
y malloc?
Si malloc
se implementa con brk
, no tengo idea de cómo eso posiblemente no puede hacer estallar las cosas, ya que brk
solo 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 mmap
que 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?