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
brky,sbrkes 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 amallocluego se usaríasbrkpara 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 lamallocusarí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
mmaplugar debrk. (El sistema operativo intentará mantenerlos alejados delbrkárea para que no choquen).fuente
malloctodavía se basabrko si se está utilizandommappara poder "devolver" bloques de memoria separados?mallocusan elbrkárea para asignaciones pequeñas y los individualesmmappara 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
brkysbrk?brkpor supuesto, podría implementarse consbrkcá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 # L23Es
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 demalloco en lammapactualidad.brkvsmallocbrkEs una antigua posibilidad de implementaciónmalloc.mmapes el mecanismo más estrictamente más potente que probablemente todos los sistemas POSIX usan actualmente para implementarmalloc. Aquí hay un ejemplo mínimo demmapasignación de memoria ejecutable .¿Puedo mezclar
brky malloc?Si
mallocse implementa conbrk, no tengo idea de cómo eso posiblemente no puede hacer estallar las cosas, ya quebrksolo 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 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
pes 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?brksyscall).brkes un poco más conveniente restaurar la pila previamente asignada.Puede usar
brkysbrkusted 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,mallocpor lo que solo es apropiado cuando no tiene que hacerfreenada. Porque no puedes Además, debe evitar cualquier llamada a la biblioteca que pueda usarmallocinternamente. Es decir.strlenes probablemente seguro, perofopenprobablemente no lo es.Llama
sbrkigual 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
brkcon 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 ...
sbrkTambién es útil en el código de golf porque es 2 caracteres más corto quemalloc.fuente
malloc/freeciertamente 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 llamarsbrkcon un argumento distinto de cero en cualquier programa que pueda llamarmalloc, y casi todas las funciones de la biblioteca C pueden llamarmallocinternamente. Los únicos que definitivamente no serán las funciones de seguridad de señal asíncrona .malloc.sbrkpara 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 labrkllamada al sistema) hacia arriba con la reducción de costos de cierre en relación con lo quemmapomremapincurriría. Esto lo hace atractivo para lasmallocimplementaciones 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, cualquiermallocllamada 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
.bsssistema 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.bssque 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..bssy no es cero no.bsssería conforme. Pero nada obliga a una implementación de C a usar.bssen 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