De acuerdo con el manual de programadores de Linux:
brk () y sbrk () cambian la ubicación de la interrupción del programa, que define el final del segmento de datos del proceso.
¿Qué significa el segmento de datos aquí? ¿Es solo el segmento de datos o datos, BSS y montón combinados?
De acuerdo con wiki:
A veces, las áreas de datos, BSS y montón se denominan colectivamente "segmento de datos".
No veo ninguna razón para cambiar el tamaño de solo el segmento de datos. Si se trata de datos, BSS y almacenamiento colectivo, entonces tiene sentido ya que el almacenamiento dinámico tendrá más espacio.
Lo que me lleva a mi segunda pregunta. En todos los artículos que leí hasta ahora, el autor dice que el montón crece hacia arriba y la pila crece hacia abajo. Pero lo que no explican es qué sucede cuando el montón ocupa todo el espacio entre el montón y la pila.
brk()
llamada al sistema es más útil en lenguaje ensamblador que en C. En C,malloc()
debe usarse en lugar debrk()
para cualquier propósito de asignación de datos, pero esto no invalida la pregunta propuesta de ninguna manera.brk()
ysbrk()
? Las asignaciones son administradas por el asignador de página, en un nivel mucho más bajo.Respuestas:
En el diagrama que publicó, el "corte", la dirección manipulada por
brk
y,sbrk
es la línea punteada en la parte superior del montón.La documentación que ha leído describe esto como el final del "segmento de datos" porque en las bibliotecas tradicionales (precompartidas, pre
mmap
) Unix, el segmento de datos era continuo con el montón; antes del inicio del programa, el núcleo cargaría los bloques de "texto" y "datos" en la RAM comenzando en la dirección cero (en realidad, un poco por encima de la dirección cero, de modo que el puntero NULL realmente no apuntaba a nada) y establecería la dirección de corte en El final del segmento de datos. La primera llamada amalloc
luego se usaríasbrk
para mover la división y crear el montón entre la parte superior del segmento de datos y la nueva dirección de corte más alta, como se muestra en el diagrama, y el uso posterior de lamalloc
usaría para aumentar el montón según sea necesario.Mientras tanto, la pila comienza en la parte superior de la memoria y crece hacia abajo. La pila no necesita llamadas explícitas del sistema para agrandarla; o comienza con la cantidad de RAM asignada como siempre (este era el enfoque tradicional) o hay una región de direcciones reservadas debajo de la pila, a la que el núcleo asigna automáticamente RAM cuando nota un intento de escribir allí (Este es el enfoque moderno). De cualquier manera, puede haber o no una región de "guardia" en la parte inferior del espacio de direcciones que se puede usar para apilar. Si esta región existe (todos los sistemas modernos hacen esto) está permanentemente sin asignar; si cualquierala pila o el montón intenta crecer, obtienes un error de segmentación. Sin embargo, tradicionalmente, el núcleo no hizo ningún intento de imponer un límite; la pila podría crecer en el montón, o el montón podría crecer en la pila, y de cualquier manera garabatearían sobre los datos del otro y el programa se bloquearía. Si tuviera mucha suerte, se estrellaría de inmediato.
No estoy seguro de dónde proviene el número 512GB en este diagrama. Implica un espacio de direcciones virtuales de 64 bits, que es inconsistente con el mapa de memoria muy simple que tiene allí. Un espacio de direcciones real de 64 bits se parece más a esto:
Esto no se puede escalar de forma remota, y no debe interpretarse como exactamente cómo funciona un sistema operativo determinado (después de dibujarlo descubrí que Linux realmente pone el ejecutable mucho más cerca de la dirección cero de lo que pensaba, y las bibliotecas compartidas en direcciones sorprendentemente altas). Las regiones negras de este diagrama no están asignadas (cualquier acceso causa una falla predeterminada inmediata) y son gigantescas en relación con las áreas grises. Las regiones de color gris claro son el programa y sus bibliotecas compartidas (puede haber docenas de bibliotecas compartidas); cada uno tiene un independientesegmento de texto y datos (y segmento "bss", que también contiene datos globales pero se inicializa en todos los bits cero en lugar de ocupar espacio en el ejecutable o la biblioteca en el disco). El montón ya no es necesariamente continuo con el segmento de datos del ejecutable; lo dibujé de esa manera, pero parece que Linux, al menos, no hace eso. La pila ya no está vinculada a la parte superior del espacio de direcciones virtuales, y la distancia entre el montón y la pila es tan grande que no tiene que preocuparse por cruzarla.
La ruptura sigue siendo el límite superior del montón. Sin embargo, lo que no mostré es que podría haber docenas de asignaciones independientes de memoria en algún lugar del negro, hechas con en
mmap
lugar debrk
. (El sistema operativo intentará mantenerlos alejados delbrk
área para que no choquen).fuente
malloc
todavía se basabrk
o si se está utilizandommap
para poder "devolver" bloques de memoria separados?malloc
usan elbrk
área para asignaciones pequeñas y los individualesmmap
para asignaciones grandes (por ejemplo,> 128K). Vea, por ejemplo, la discusión de MMAP_THRESHOLD en la página de manual de Linuxmalloc(3)
.mmap
; Es extremadamente dependiente del sistema operativo.Ejemplo ejecutable mínimo
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
:Con
brk
: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 elbrk
:Probado en Ubuntu 18.04.
Visualización virtual del espacio de direcciones
Antes
brk
:Después
brk(p + 2)
:Después
brk(b)
: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
ysbrk
?brk
por supuesto, podría implementarse consbrk
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 # L23Es
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 demalloc
o en lammap
actualidad.brk
vsmalloc
brk
Es una antigua posibilidad de implementaciónmalloc
.mmap
es el mecanismo más estrictamente más potente que probablemente todos los sistemas POSIX usan actualmente para implementarmalloc
. Aquí hay un ejemplo mínimo demmap
asignación de memoria ejecutable .¿Puedo mezclar
brk
y malloc?Si
malloc
se implementa conbrk
, no tengo idea de cómo eso posiblemente no puede hacer estallar las cosas, ya quebrk
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 paramalloc
.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?
fuente
p
es un puntero para escribirint
, ¿no debería haber sidobrk(p + 2);
?*(p + i) = 1;
brk(p + 2)
lugar de simplemente aumentarlosbrk(2)
? ¿Es realmente necesario brk?brk
syscall).brk
es un poco más conveniente restaurar la pila previamente asignada.Puede usar
brk
ysbrk
usted mismo para evitar la "sobrecarga de malloc" de la que todos siempre se están quejando. Pero no puede usar fácilmente este método en conjunción con,malloc
por lo que solo es apropiado cuando no tiene que hacerfree
nada. Porque no puedes Además, debe evitar cualquier llamada a la biblioteca que pueda usarmalloc
internamente. Es decir.strlen
es probablemente seguro, perofopen
probablemente no lo es.Llama
sbrk
igual que llamaríasmalloc
. Devuelve un puntero al descanso actual e incrementa el descanso en esa cantidad.Si bien no puede liberar asignaciones individuales (porque no hay sobrecarga de malloc , recuerde), puede liberar todo el espacio llamando
brk
con el valor devuelto por la primera llamada asbrk
, rebobinando así el brk .Incluso podría apilar estas regiones, descartando la región más reciente rebobinando el salto al inicio de la región.
Una cosa más ...
sbrk
También es útil en el código de golf porque es 2 caracteres más corto quemalloc
.fuente
malloc
/free
ciertamente puede (y lo hace) devolver la memoria al sistema operativo. Es posible que no siempre lo hagan cuando lo desee, pero eso se debe a que la heurística se ajusta de manera imperfecta para su caso de uso. Más importante aún, no es seguro llamarsbrk
con un argumento distinto de cero en cualquier programa que pueda llamarmalloc
, y casi todas las funciones de la biblioteca C pueden llamarmalloc
internamente. Los únicos que definitivamente no serán las funciones de seguridad de señal asíncrona .malloc
.sbrk
para esto solo es útil para el golf de código, porque el uso manualmmap(MAP_ANONYMOUS)
es mejor en todos los sentidos, excepto en el tamaño del código fuente.Hay un mapeo de memoria privada anónimo designado especial (tradicionalmente ubicado más allá de los datos / bss, pero el Linux moderno realmente ajustará la ubicación con ASLR). En principio, no es mejor que cualquier otra asignación se puede crear con el que es
mmap
, pero Linux tiene algunas optimizaciones que hacen que sea posible ampliar el final de este mapeo (usando labrk
llamada al sistema) hacia arriba con la reducción de costos de cierre en relación con lo quemmap
omremap
incurriría. Esto lo hace atractivo para lasmalloc
implementaciones cuando se implementa el montón principal.fuente
Puedo responder tu segunda pregunta. Malloc fallará y devolverá un puntero nulo. Es por eso que siempre busca un puntero nulo cuando asigna memoria de forma dinámica.
fuente
malloc()
usarábrk()
y / osbrk()
debajo del capó, y usted también puede hacerlo, si desea implementar su propia versión personalizada demalloc()
.El montón se coloca en último lugar en el segmento de datos del programa.
brk()
se usa para cambiar (expandir) el tamaño del montón. Cuando el montón no puede crecer más, cualquiermalloc
llamada fallará.fuente
El segmento de datos es la porción de memoria que contiene todos sus datos estáticos, leídos desde el ejecutable en el lanzamiento y generalmente llenos de cero.
fuente
.bss
sistema operativo inicializa los datos estáticos no inicializados ( ) a todos-bits-cero antes del inicio del programa; esto está realmente garantizado por el estándar C. Supongo que algunos sistemas integrados pueden no molestar (nunca he visto uno, pero no trabajo tan integrado)mmap
, pero supongo.bss
que todavía se pondría a cero. El espacio BSS es probablemente la forma más compacta de expresar el hecho de que un programa quiere algunas matrices zerod..bss
y no es cero no.bss
sería conforme. Pero nada obliga a una implementación de C a usar.bss
en absoluto o incluso tener tal cosa.main
; ese código podría poner a cero el.bss
área en lugar de hacer que el núcleo lo haga, y eso aún se conformaría.malloc usa la llamada al sistema brk para asignar memoria.
incluir
ejecuta este sencillo programa con strace, llamará al sistema brk.
fuente