¿Cuál es la dirección del crecimiento de la pila en la mayoría de los sistemas modernos?
Estoy preparando algunos materiales de capacitación en C y quiero que mis ejemplos se ajusten al modelo de pila típico.
¿En qué dirección crece una pila C en Linux, Windows, Mac OSX (PPC y x86), Solaris y los Unix más recientes?
El crecimiento de la pila no suele depender del sistema operativo en sí, sino del procesador en el que se ejecuta. Solaris, por ejemplo, se ejecuta en x86 y SPARC. Mac OSX (como mencionaste) se ejecuta en PPC y x86. Linux se ejecuta en todo, desde mi gran System z que toca la bocina en el trabajo hasta un pequeño y insignificante reloj de pulsera .
Si la CPU ofrece algún tipo de elección, la convención de llamada/ABI utilizada por el sistema operativo especifica qué elección debe hacer si desea que su código llame al código de todos los demás.
Los procesadores y su dirección son:
- x86: abajo.
- SPARC: seleccionable. El ABI estándar se reduce.
- PPC: abajo, creo.
- System z: en una lista vinculada, no bromeo (pero aún está abajo, al menos para zLinux).
- ARM: seleccionable, pero Thumb2 tiene codificaciones compactas solo para abajo (LDMIA = incremento después, STMDB = disminución antes).
- 6502: inactivo (pero solo 256 bytes).
- RCA 1802A: como desee, sujeto a la implementación SCRT.
- PDP11: abajo.
- 8051: arriba.
Mostrando mi edad en los últimos, el 1802 fue el chip utilizado para controlar los primeros transbordadores (sospecho que detectaba si las puertas estaban abiertas, según la potencia de procesamiento que tenía :-) y mi segunda computadora, la COMX-35 ( siguiendo mi ZX80 ).
Detalles de PDP11 obtenidos de aquí , 8051 detalles de aquí .
La arquitectura SPARC utiliza un modelo de registro de ventana deslizante. Los detalles arquitectónicamente visibles también incluyen un búfer circular de ventanas de registro que son válidas y almacenadas en caché internamente, con trampas cuando se desborda o se desborda. Consulte aquí para obtener más detalles. Como explica el manual SPARCv8 , las instrucciones GUARDAR y RESTAURAR son como instrucciones AGREGAR más rotación de la ventana de registro. Usar una constante positiva en lugar de la negativa habitual daría como resultado una pila en crecimiento.
La técnica SCRT antes mencionada es otra: el 1802 utilizó algunos de sus dieciséis registros de 16 bits para SCRT (técnica estándar de llamada y devolución). Uno era el contador del programa, podías usar cualquier registro como PC con las SEP Rn
instrucciones. Uno era el puntero de la pila y dos estaban configurados para apuntar siempre a la dirección del código SCRT, uno para llamada y otro para devolución. Ningún registro recibió un tratamiento especial. Tenga en cuenta que estos detalles son de memoria, es posible que no sean del todo correctos.
Por ejemplo, si R3 era la PC, R4 era la dirección de llamada SCRT, R5 era la dirección de retorno SCRT y R2 era la "pila" (comillas tal como está implementada en el software), SEP R4
configuraría R4 como la PC y comenzaría a ejecutar SCRT. código de llamada.
Luego almacenaría R3 en la "pila" de R2 (creo que R6 se usó para almacenamiento temporal), ajustándolo hacia arriba o hacia abajo, tomaría los dos bytes que siguen a R3, los cargaría en R3, luego lo haría SEP R3
y se ejecutaría en la nueva dirección.
Para regresar, sacaría SEP R5
la dirección anterior de la pila de R2, le agregaría dos (para omitir los bytes de dirección de la llamada), la cargaría en R3 y SEP R3
comenzaría a ejecutar el código anterior.
Es muy difícil entenderlo inicialmente después de todo el código basado en pila 6502/6809/z80, pero sigue siendo elegante en una forma de golpearse la cabeza contra la pared. Además, una de las características más vendidas del chip fue un conjunto completo de 16 registros de 16 bits, a pesar de que inmediatamente se perdieron 7 de ellos (5 para SCRT, dos para DMA e interrupciones de memoria). Ahh, el triunfo del marketing sobre la realidad :-)
En realidad, System z es bastante similar y utiliza sus registros R14 y R15 para llamada/retorno.
En C++ (adaptable a C) stack.cc :
static int
find_stack_direction ()
{
static char *addr = 0;
auto char dummy;
if (addr == 0)
{
addr = &dummy;
return find_stack_direction ();
}
else
{
return ((&dummy > addr) ? 1 : -1);
}
}
Solo una pequeña adición a las otras respuestas, que hasta donde puedo ver no han tocado este punto:
Hacer que la pila crezca hacia abajo hace que todas las direcciones dentro de la pila tengan un desplazamiento positivo en relación con el puntero de la pila. No hay necesidad de compensaciones negativas, ya que solo apuntarían al espacio de pila no utilizado. Esto simplifica el acceso a las ubicaciones de la pila cuando el procesador admite el direccionamiento relativo al puntero de la pila.
Muchos procesadores tienen instrucciones que permiten accesos con un desplazamiento solo positivo respecto de algún registro. Entre ellas se incluyen muchas arquitecturas modernas, así como algunas antiguas. Por ejemplo, ARM Thumb ABI proporciona accesos relativos al puntero de pila con un desplazamiento positivo codificado dentro de una única palabra de instrucción de 16 bits.
Si la pila creciera hacia arriba, todas las compensaciones útiles relativas al puntero de la pila serían negativas, lo cual es menos intuitivo y menos conveniente. También está en desacuerdo con otras aplicaciones de direccionamiento relativo a registros, por ejemplo para acceder a campos de una estructura.
La ventaja de crecer hacia abajo es que en los sistemas más antiguos la pila normalmente estaba en la parte superior de la memoria. Los programas generalmente llenaban la memoria comenzando desde abajo, por lo que este tipo de administración de memoria minimizaba la necesidad de medir y colocar la parte inferior de la pila en algún lugar sensato.