Gestión de memoria (II)

Pila del usuario (user stack)


En un procesador x86-64, el registro %rsp (puntero de pila o stack pointer en Inglés) referencia en todo momento a la cima/cabecera de la pila del usuario. Para alojar (desalojar) memoria en la misma, basta disminuir (incrementar) el valor del puntero.

La llamada a una función introduce, por lo general, un nuevo marco de pila (stack frame en Inglés) en la pila del usuario, disminuyendo el valor del registro %rsp convenientemente. Así, supongamos que una función f() invoca a otra función g() (ver la figura adjunta). En primer lugar, se introduce en el marco de f() la dirección de retorno donde el programa debe continuar su ejecución tras finalizar g(). Se crea entonces un nuevo marco de pila para g(), en el que se almacenan copias de los valores contenidos en los registros no volátiles que vayan a utilizarse, los argumentos y las variables locales que no puedan contenerse directamente en los registros y/o los argumentos para las funciones invocadas posteriormente por g(). Al finalizar la ejecución de g(), el puntero de pila será incrementado hasta su valor original.

El tamaño máximo habitual para la pila en un sistema Linux es de 8 MiB.

Con carácter general, el puntero de pila permanece estático a lo largo del cuerpo de una función, lo que permite su uso como referencia para recorrer la pila en combinación con metadatos generados por el compilador y almacenados en el ejecutable [1]. En contraste con la arquitectura x86, pues, no es necesaria la utilización del puntero de marco %ebp (frame pointer) para referenciar el fondo de la pila.

El modo de funcionamiento de la pila, que sigue el esquema LIFO (Last In, First Out), es simple y determinista. Es más, la reutilización constante de la pila tiende a mantenerla activa en la memoria caché de la CPU, por lo que el acceso a sus datos resulta enormemente eficiente [2].

Cabe citar dos errores de software (bugs) comunes en la gestión de la pila, a saber:
  • Si el uso de memoria sobrepasase el tamaño máximo permitido para la pila (fenómeno conocido como stack overflow) se produciría una violación de acceso (segmentation fault), lo que conduciría a una interrupción inesperada del proceso (crash del proceso). Esto puede ocurrir, por ejemplo, si se agotan los recursos de la pila al almacenar en ella variables de gran tamaño (en cuyo caso debiera procederse a alojarlas en el free store, cuyo tamaño típico es del orden del GiB), o bien durante la ejecución de algoritmos recursivos infinitos o demasiado profundos.
  • Sobrescribir un array más allá de su cota superior puede corromper la pila, siendo éste un error difícil de detectar en un proceso de depuración (debugging). Esta última situación es fácilmente evitable de hacerse uso de las técnicas de programación propias del lenguaje C++ moderno (pensemos, por ejemplo, en el empleo de la plantilla de clase std::array<> y los bucles for basados en rango frente a los arrays estáticos tradicionales del lenguaje C).


Referencias bibliográficas:
  1. Stack reconstruction - https://blogs.msdn.microsoft.com/ntdebugging/2010/05/12/x64-manual-stack-reconstruction-and-stack-walking/
  2. Stack and cache memory - https://stackoverflow.com/questions/23760725/c-stack-memory-and-cpu-cache
  3. Bryant R. E. and O'Hallaron D. R., 'Computer Systems: A Programmer's Perspective', 2nd Edition. Addison-Wesley (2010).

No hay comentarios:

Publicar un comentario