vidrio portacostilla para herrajes catalogo angulo c linux system-calls brk

portacostilla - ¿Qué hace la llamada al sistema brk()?



angulo portacostilla (9)

  1. La llamada al sistema que maneja la asignación de memoria es sbrk(2) . Aumenta o disminuye el espacio de direcciones del proceso en un número específico de bytes.

  2. La función de asignación de memoria, malloc(3) , implementa un tipo particular de asignación. malloc() , que probablemente usará la llamada al sistema sbrk() .

La llamada al sistema sbrk(2) en el kernel asigna un espacio adicional en nombre del proceso. La función de biblioteca malloc() gestiona este espacio desde el nivel de usuario .

De acuerdo con el manual de programadores de Linux:

brk () y sbrk () cambian la ubicación del salto de 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 la wiki:

A veces, las áreas de datos, BSS y montón se denominan colectivamente como el "segmento de datos".

No veo ninguna razón para cambiar el tamaño del segmento de datos. Si se trata de datos, BSS y montón colectivamente, entonces tiene sentido, ya que montón obtendrá 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 que ¿qué sucede cuando el montón ocupa todo el espacio entre el montón y la pila?


El montón se coloca al final 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, cualquier llamada malloc fallará.


El segmento de datos es la porción de memoria que contiene todos sus datos estáticos, leídos desde el ejecutable al inicio y normalmente llenos a cero.


Existe una asignación de memoria privada anónima especial designada (tradicionalmente ubicada más allá de los datos / bss, pero la versión moderna de Linux realmente ajustará la ubicación con ASLR). En principio, no es mejor que cualquier otro mapeo que pueda crear con mmap , pero Linux tiene algunas optimizaciones que hacen posible expandir el final de esta asignación (usando la brk brk syscall) hacia arriba con un costo de bloqueo reducido en comparación con mmap o mremap . Esto lo hace atractivo para las implementaciones malloc para usar al implementar el montón principal.


Puede usar brk y sbrk para evitar la "sobrecarga de malloc" de la que todos siempre se quejan. Pero no puedes usar este método fácilmente junto con malloc por lo que solo es apropiado cuando no tienes que free nada. Porque no puedes. Además, debe evitar cualquier llamada a la biblioteca que pueda usar malloc internamente. Es decir. strlen es probablemente seguro, pero fopen probablemente no lo es.

Llama a sbrk como llamarías malloc . Devuelve un puntero al salto actual e incrementa el salto en esa cantidad.

void *myallocate(int n){ return sbrk(n); }

Aunque no puede liberar asignaciones individuales (porque no hay malloc-overhead , recuerde), puede liberar todo el espacio llamando a brk con el valor devuelto por la primera llamada a sbrk , rebobinando así el brk .

void *memorypool; void initmemorypool(void){ memorypool = sbrk(0); } void resetmemorypool(void){ brk(memorypool); }

Incluso podría apilar estas regiones, descartando la región más reciente al rebobinar 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 que malloc .


Puedo responder tu segunda pregunta. Malloc fallará y devolverá un puntero nulo. Es por eso que siempre se busca un puntero nulo cuando se asigna dinámicamente la memoria.


Veo muchas respuestas parciales pero ninguna respuesta completa. Aquí está la foto que posteó de nuevo:

El " brk " - 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 Unix tradicional (bibliotecas precompartidas, pre- mmap ) el segmento de datos era continuo con el montón; antes del inicio del programa, el kernel cargaría los bloques de "texto" y "datos" en la RAM comenzando en la dirección cero (en realidad un poco más arriba en la dirección cero, para que el puntero NULL realmente no apuntara a nada) y configurara la dirección de corte en el final del segmento de datos. La primera llamada a malloc usaría sbrk para mover la sbrk y crear el montón entre la parte superior del segmento de datos y la nueva dirección de sbrk más alta, como se muestra en el diagrama, y ​​el uso posterior de malloc lo usaría para hacer el montón más grande según sea necesario.

Mientras tanto, la pila comienza en la parte superior de la memoria y crece. La pila no necesita llamadas explícitas al sistema para hacerlo más grande; o bien comienza con la cantidad de RAM asignada que pueda tener (este era el enfoque tradicional) o hay una región de direcciones reservadas debajo de la pila, a la cual el kernel 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 la pila. Si esta región existe (todos los sistemas modernos lo hacen), está permanentemente sin asignar; Si la pila o el montón intentan crecer, se obtiene un error de segmentación. Tradicionalmente, sin embargo, el kernel no intentó forzar un límite; la pila podría convertirse en el montón, o el montón podría crecer en la pila, y de cualquier manera ellos garabatearían sobre los datos de los demás y el programa colapsaría. Si fueras muy afortunado, se estrellaría de inmediato.

No estoy seguro de dónde viene 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 es remotamente escalable, y no debe interpretarse exactamente como un SO dado hace cosas (después de dibujarlo descubrí que Linux en realidad 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 mapeadas (cualquier acceso provoca un segfault inmediato) 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 segmento de datos y texto independiente (y un segmento "bss", que también contiene datos globales, pero se inicializa a 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 lo hace. 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 demostré es que podría haber docenas de asignaciones de memoria independientes en negro en alguna parte, hechas con mmap lugar de brk . (El SO intentará mantener estos muy lejos del área de brk para que no colisionen).


malloc usa la llamada del sistema brk para asignar memoria.

incluir

int main(void){ char *a = malloc(10); return 0; }

ejecuta este sencillo programa con strace, llamará al sistema brk.


Ejemplo ejecutable mínimo

¿Qué hace la llamada al sistema brk ()?

Pide al kernel que te permita leer y escribir en un trozo contiguo de memoria denominado montón.

Si no preguntas, puede que te falle.

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; }

Probado en Ubuntu 14.04.

Visualización del espacio de direcciones virtuales

Antes de brk :

+------+ <-- Heap Start == Heap End

Después de brk(p + 2) :

+------+ <-- Heap Start + 2 * sizof(int) == Heap End | | | You can now write your ints | in this memory area. | | +------+ <-- Heap Start

Después de 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?

Más información

brk a ser POSIX, pero fue eliminado en POSIX 2001, por lo tanto, la necesidad de _GNU_SOURCE para acceder al contenedor glibc.

La eliminación probablemente se deba a la introducción de mmap , que es un superconjunto que permite asignar un rango múltiple y más opciones de asignación.

Internamente, el kernel decide si el proceso puede tener esa cantidad de memoria y destina páginas de memoria para ese uso.

brk y mmap son los mecanismos subyacentes más comunes que libc usa para implementar malloc en sistemas POSIX.

Esto explica cómo la pila se compara con el montón: ¿Cuál es la función de las instrucciones push / pop usadas en los registros en el ensamblaje x86?