Qual è la direzione della crescita dello stack nella maggior parte dei sistemi moderni?


Risposte:


147

La crescita dello stack di solito non dipende dal sistema operativo stesso, ma dal processore su cui è in esecuzione. Solaris, ad esempio, funziona su x86 e SPARC. Mac OSX (come hai detto) funziona su PPC e x86. Linux gira su tutto, dal mio grande clamoroso System z al lavoro a un piccolo orologio da polso .

Se la CPU fornisce qualsiasi tipo di scelta, la convenzione ABI / chiamata utilizzata dal sistema operativo specifica quale scelta è necessario fare se si desidera che il codice chiami il codice di tutti gli altri.

I processori e la loro direzione sono:

  • x86: inattivo.
  • SPARC: selezionabile. Lo standard ABI utilizza down.
  • PPC: giù, credo.
  • System z: in una lista collegata, non ti sto prendendo in giro (ma ancora inattivo, almeno per zLinux).
  • ARM: selezionabile, ma Thumb2 ha codifiche compatte solo per down (LDMIA = incremento dopo, STMDB = decremento prima).
  • 6502: inattivo (ma solo 256 byte).
  • RCA 1802A: come preferisci, soggetta all'implementazione SCRT.
  • PDP11: giù.
  • 8051: su.

Mostrando la mia età su quegli ultimi, il 1802 era il chip utilizzato per controllare le prime navette (percependo se le porte erano aperte, sospetto, in base alla potenza di elaborazione che aveva :-) e il mio secondo computer, il COMX-35 ( seguendo il mio ZX80 ).

Dettagli PDP11 raccolti da qui , dettagli 8051 da qui .

L'architettura SPARC utilizza un modello di registro a finestra scorrevole. I dettagli architettonicamente visibili includono anche un buffer circolare di finestre di registro che sono valide e memorizzate nella cache internamente, con trappole in caso di overflow / underflow. Vedi qui per i dettagli. Come spiega il manuale di SPARCv8, le istruzioni SAVE e RESTORE sono come le istruzioni ADD più la rotazione della finestra di registro. L'uso di una costante positiva invece del solito negativo darebbe uno stack in crescita.

La suddetta tecnica SCRT è un'altra: il 1802 utilizzava alcuni o sedici registri a 16 bit per SCRT (tecnica di chiamata e ritorno standard). Uno era il contatore del programma, puoi usare qualsiasi registro come il PC con l' SEP Rnistruzione. Uno era il puntatore dello stack e due erano sempre impostati per puntare all'indirizzo del codice SCRT, uno per la chiamata, uno per il ritorno. Nessun registro è stato trattato in modo speciale. Tieni presente che questi dettagli provengono dalla memoria, potrebbero non essere completamente corretti.

Ad esempio, se R3 fosse il PC, R4 era l'indirizzo di chiamata SCRT, R5 era l'indirizzo di ritorno SCRT e R2 era lo "stack" (virgolette poiché è implementato nel software), SEP R4imposterebbe R4 come PC e inizierebbe a eseguire SCRT codice di chiamata.

Quindi memorizzerebbe R3 sullo "stack" R2 (penso che R6 sia stato utilizzato per l'archiviazione temporanea), regolandolo verso l'alto o verso il basso, prenda i due byte dopo R3, li carichi in R3, quindi SEP R3esegua ed esegua al nuovo indirizzo.

Per tornare, ciò SEP R5estrarrebbe il vecchio indirizzo dallo stack R2, aggiungerne due (per saltare i byte dell'indirizzo della chiamata), caricarlo in R3 e SEP R3iniziare a eseguire il codice precedente.

Inizialmente molto difficile da capire dopo tutto il codice basato sullo stack 6502/6809 / z80, ma comunque elegante in un modo che sbatte la testa contro il muro. Inoltre una delle caratteristiche più vendute del chip era una suite completa di 16 registri a 16 bit, nonostante il fatto che ne abbiate subito perso 7 (5 per SCRT, due per DMA e interrupt dalla memoria). Ahh, il trionfo del marketing sulla realtà :-)

System z è in realtà abbastanza simile, utilizzando i suoi registri R14 e R15 per la chiamata / ritorno.


3
Per aggiungere all'elenco, ARM può crescere in entrambe le direzioni, ma può essere impostato su una o sull'altra da una particolare implementazione di silicio (o può essere lasciato selezionabile dal software). I pochi con cui ho avuto a che fare sono sempre stati in modalità crescita.
Michael Burr

1
Nella piccola parte del mondo ARM che ho visto finora (ARM7TDMI) lo stack è interamente gestito dal software. Gli indirizzi di ritorno sono memorizzati in un registro che viene salvato dal software se necessario, e le istruzioni pre / post-incremento / decremento consentono di metterlo e altre cose nello stack in entrambe le direzioni.
starblue

1
Uno dei HPPA, lo stack è cresciuto! Abbastanza raro tra le architetture ragionevolmente moderne.
tml

2
Per i curiosi, ecco una buona risorsa su come funziona lo stack su z / OS: www-03.ibm.com/systems/resources/Stack+and+Heap.pdf
Dillon Cower

1
Grazie @paxdiablo per la tua comprensione. A volte le persone lo prendono come un affronto personale quando fai un commento del genere, specialmente quando è più vecchio. So solo che c'è una differenza perché in passato ho fatto lo stesso errore. Stai attento.
CasaDeRobison

23

In C ++ (adattabile 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);
    }
}

14
Wow, è passato molto tempo dall'ultima volta che ho visto la parola chiave "auto".
paxdiablo

9
(& dummy> addr) non è definito. Il risultato di fornire due puntatori a un operatore relazionale è definito solo se i due puntatori puntano all'interno della stessa matrice o struttura.
sigjuice

2
Cercare di indagare sul layout del proprio stack - qualcosa che C / C ++ non specifica affatto - è "non portabile" per cominciare, quindi non me ne preoccuperei. Tuttavia, sembra che questa funzione funzioni correttamente solo una volta.
effimero

9
Non è necessario utilizzare a staticper questo. Invece potresti passare l'indirizzo come argomento a una chiamata ricorsiva.
R .. GitHub STOP HELPING ICE

5
inoltre, utilizzando a static, se lo chiami più di una volta, le chiamate successive potrebbero non riuscire ...
Chris Dodd

7

Il vantaggio di crescere verso il basso è che nei sistemi più vecchi lo stack era tipicamente in cima alla memoria. I programmi in genere riempivano la memoria partendo dal basso, quindi questo tipo di gestione della memoria riduceva al minimo la necessità di misurare e posizionare il fondo dello stack in un posto ragionevole.


3
Non un "vantaggio", davvero una tautologia.
Marchese di Lorne

1
Non una tautologia. Il punto è avere due regioni di memoria in crescita che non interferiscono (a meno che la memoria non sia comunque piena), come ha sottolineato @valenok.
YvesgereY

6

Lo stack cresce su x86 (definito dall'architettura, pop incrementa il puntatore dello stack, push decrement.)


5

In MIPS e in molte moderne architetture RISC (come PowerPC, RISC-V, SPARC ...) non ci sono istruzioni pushe pop. Queste operazioni vengono eseguite esplicitamente regolando manualmente il puntatore dello stack, quindi caricando / memorizzando il valore relativamente al puntatore regolato. Tutti i registri (eccetto il registro zero) sono di uso generale, quindi in teoria qualsiasi registro può essere un puntatore allo stack e lo stack può crescere in qualsiasi direzione il programmatore desidera

Detto questo, lo stack in genere si riduce sulla maggior parte delle architetture, probabilmente per evitare il caso in cui i dati dello stack e del programma o dei dati dell'heap crescano e si scontrino tra loro. Ci sono anche i grandi motivi di indirizzamento menzionati nella risposta di sh- . Alcuni esempi: MIPS ABI cresce verso il basso e usa $29(AKA $sp) come puntatore dello stack, anche RISC-V ABI cresce verso il basso e usa x2 come puntatore dello stack

In Intel 8051 lo stack cresce, probabilmente perché lo spazio di memoria è così piccolo (128 byte nella versione originale) che non c'è heap e non è necessario mettere lo stack in cima in modo che venga separato dall'heap in crescita dal basso

Puoi trovare ulteriori informazioni sull'utilizzo dello stack in varie architetture in https://en.wikipedia.org/wiki/Calling_convention

Guarda anche


2

Solo una piccola aggiunta alle altre risposte, che per quanto posso vedere non hanno toccato questo punto:

Se lo stack cresce verso il basso, tutti gli indirizzi all'interno dello stack hanno un offset positivo rispetto al puntatore dello stack. Non c'è bisogno di offset negativi, poiché indicherebbero solo lo spazio dello stack inutilizzato. Ciò semplifica l'accesso alle posizioni dello stack quando il processore supporta l'indirizzamento relativo allo stackpointer.

Molti processori hanno istruzioni che consentono l'accesso con un offset solo positivo relativo ad alcuni registri. Questi includono molte architetture moderne, così come alcune vecchie. Ad esempio, ARM Thumb ABI fornisce accessi relativi allo stackpointer con un offset positivo codificato all'interno di una singola parola di istruzione a 16 bit.

Se lo stack crescesse verso l'alto, tutti gli offset utili relativi allo stackpointer sarebbero negativi, il che è meno intuitivo e meno conveniente. Inoltre è in contrasto con altre applicazioni di indirizzamento relativo al registro, ad esempio per accedere ai campi di una struttura.


2

Sulla maggior parte dei sistemi, lo stack si riduce e il mio articolo su https://gist.github.com/cpq/8598782 spiega PERCHÉ cresce. È semplice: come disporre due blocchi di memoria in crescita (heap e stack) in un blocco fisso di memoria? La soluzione migliore è metterli alle estremità opposte e lasciarli crescere l'uno verso l'altro.


quel succo sembra essere morto ora :(
Ven

@Ven - Posso arrivarci
Brett Holman

1

Cresce verso il basso perché la memoria allocata al programma ha i "dati permanenti", cioè il codice per il programma stesso in basso, quindi l'heap nel mezzo. Hai bisogno di un altro punto fisso da cui fare riferimento alla pila, in modo da rimanere in cima. Ciò significa che lo stack cresce fino a diventare potenzialmente adiacente agli oggetti nell'heap.


0

Questa macro dovrebbe rilevarlo in fase di esecuzione senza UB:

#define stk_grows_up_eh() stk_grows_up__(&(char){0})
_Bool stk_grows_up__(char *ParentsLocal);

__attribute((__noinline__))
_Bool stk_grows_up__(char *ParentsLocal) { 
    return (uintptr_t)ParentsLocal < (uintptr_t)&ParentsLocal;
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.