Definire le dimensioni dell'heap e dello stack per un microcontrollore ARM Cortex-M4?


11

Ho lavorato su e off su piccoli progetti di sistemi embedded on e off. Alcuni di questi progetti hanno utilizzato un processore base ARM Cortex-M4. Nella cartella del progetto c'è un file startup.s . All'interno di quel file ho notato le seguenti due righe di comando.

;******************************************************************************
;
; <o> Stack Size (in Bytes) <0x0-0xFFFFFFFF:8>
;
;******************************************************************************
Stack   EQU     0x00000400

;******************************************************************************
;
; <o> Heap Size (in Bytes) <0x0-0xFFFFFFFF:8>
;
;******************************************************************************
Heap    EQU     0x00000000

Come si definiscono le dimensioni dell'heap e dello stack per un microcontrollore? Ci sono informazioni specifiche nel foglio dati per guidare per arrivare al valore corretto? In tal caso, cosa si dovrebbe cercare nel foglio dati?


Riferimenti:

Risposte:


12

Stack e heap sono concetti software, non concetti hardware. Ciò che l'hardware fornisce è la memoria. La definizione di zone di memoria, una delle quali si chiama "stack" e una si chiama "heap", è una scelta del programma.

L'hardware aiuta con le pile. La maggior parte delle architetture ha un registro dedicato chiamato puntatore dello stack. L'uso previsto è che quando il programma effettua una chiamata di funzione, i parametri della funzione e l'indirizzo di ritorno vengono inseriti nello stack e vengono visualizzati quando la funzione termina e ritorna al chiamante. Spingere sullo stack significa scrivere all'indirizzo indicato dal puntatore dello stack e decrementare il puntatore dello stack di conseguenza (o incrementare, a seconda della direzione in cui cresce lo stack). Popping significa incrementare (o decrementare) il puntatore dello stack; l'indirizzo di ritorno viene letto dall'indirizzo fornito dal puntatore dello stack.

Alcune architetture (ma non ARM) hanno un'istruzione di chiamata di subroutine che combina un salto con la scrittura all'indirizzo fornito dal puntatore dello stack e un'istruzione di ritorno di subroutine che combina la lettura dell'indirizzo fornito dal puntatore dello stack e il salto a questo indirizzo. Su ARM, il salvataggio e il ripristino dell'indirizzo vengono eseguiti nel registro LR, le istruzioni di chiamata e di ritorno non utilizzano il puntatore dello stack. Vi sono tuttavia istruzioni per facilitare la scrittura o la lettura di più registri all'indirizzo fornito dal puntatore dello stack, per inviare e popare gli argomenti della funzione.

Per scegliere le dimensioni dell'heap e dello stack, l'unica informazione rilevante dall'hardware è la quantità di memoria totale che hai. Quindi fai la tua scelta in base a ciò che desideri memorizzare in memoria (consentendo codice, dati statici e altri programmi).

Un programma in genere utilizza queste costanti per inizializzare alcuni dati in memoria che verranno utilizzati dal resto del codice, come l'indirizzo della parte superiore dello stack, forse un valore da qualche parte per verificare la presenza di overflow dello stack, limiti per l'allocatore di heap , eccetera.

Nel codice che stai osservando , la Stack_Sizecostante viene utilizzata per riservare un blocco di memoria nell'area del codice (tramite una SPACEdirettiva nell'assemblaggio ARM). All'indirizzo superiore di questo blocco viene assegnata l'etichetta __initial_sp, che viene memorizzata nella tabella vettoriale (il processore utilizza questa voce per impostare SP dopo un ripristino del software) e viene esportato per l'uso in altri file di origine. La Heap_Sizecostante viene utilizzata allo stesso modo per riservare un blocco di memoria e le etichette ai suoi confini ( __heap_basee __heap_limit) vengono esportate per l'uso in altri file di origine.

; Amount of memory (in bytes) allocated for Stack
; Tailor this value to your application needs
; <h> Stack Configuration
;   <o> Stack Size (in Bytes) <0x0-0xFFFFFFFF:8>
; </h>

Stack_Size      EQU     0x00000400

                AREA    STACK, NOINIT, READWRITE, ALIGN=3
Stack_Mem       SPACE   Stack_Size
__initial_sp


; <h> Heap Configuration
;   <o>  Heap Size (in Bytes) <0x0-0xFFFFFFFF:8>
; </h>

Heap_Size       EQU     0x00000200

                AREA    HEAP, NOINIT, READWRITE, ALIGN=3
__heap_base
Heap_Mem        SPACE   Heap_Size
__heap_limit

…
__Vectors       DCD     __initial_sp               ; Top of Stack
                DCD     Reset_Handler              ; Reset Handler
                DCD     NMI_Handler                ; NMI Handler

…

                 EXPORT  __initial_sp
                 EXPORT  __heap_base
                 EXPORT  __heap_limit

Sai come vengono determinati quei valori 0x00200 e 0x000400
Mahendra Gunawardena

@MahendraGunawardena Sta a te determinarli, in base alle esigenze del tuo programma. La risposta di Niall fornisce alcuni suggerimenti.
Gilles 'SO- smetti di essere malvagio' il

7

Le dimensioni dello stack e dell'heap sono definite dall'applicazione, non ovunque nel foglio dati del microcontrollore.

Lo stack

Lo stack viene utilizzato per memorizzare i valori delle variabili locali all'interno delle funzioni, i valori precedenti dei registri della CPU utilizzati per le variabili locali (in modo che possano essere ripristinati all'uscita dalla funzione), l'indirizzo del programma a cui tornare quando si lasciano tali funzioni, inoltre alcune spese generali per la gestione dello stack stesso.

Quando si sviluppa un sistema incorporato, si stima la profondità massima di chiamata che ci si aspetta di avere, si sommano le dimensioni di tutte le variabili locali nelle funzioni in quella gerarchia, quindi si aggiunge un po 'di riempimento per consentire l'overhead di cui sopra, quindi si aggiunge un altro per eventuali interruzioni che potrebbero verificarsi durante l'esecuzione del programma.

Un metodo di stima alternativo (in cui la RAM non è vincolata) è di allocare molto più spazio di stack di quanto non sia mai necessario, riempire lo stack con un valore sentinella, quindi monitorare quanto effettivamente si utilizza durante l'esecuzione. Ho visto le versioni di debug dei runtime del linguaggio C che lo faranno automaticamente. Quindi, quando hai finito di sviluppare, puoi ridurre le dimensioni dello stack, se lo desideri.

Il mucchio

Calcolare la dimensione dell'heap di cui hai bisogno può essere più complicato. L'heap viene utilizzato per le variabili allocate in modo dinamico, quindi se si utilizza malloc()e free()in un programma in linguaggio C, o newe deletein C ++, è lì che vivono tali variabili.

Tuttavia, in particolare in C ++, ci può essere qualche allocazione di memoria dinamica nascosta in corso. Ad esempio, se sono stati allocati staticamente oggetti, la lingua richiede che i loro distruttori vengano chiamati quando il programma esce. Sono a conoscenza di almeno un runtime in cui gli indirizzi dei distruttori sono memorizzati in un elenco di collegamenti allocati dinamicamente.

Quindi, per stimare la dimensione dell'heap di cui hai bisogno, osserva tutta l'allocazione dinamica della memoria in ciascun percorso attraverso l'albero delle tue chiamate, calcola il massimo e aggiungi un po 'di riempimento. Il runtime della lingua può fornire una diagnostica che è possibile utilizzare per monitorare l'utilizzo totale dell'heap, la frammentazione, ecc.


Grazie per la risposta, mi piace come determinare il numero specifico come 0x00400 e così via
Mahendra Gunawardena

5

Oltre alle altre risposte, mi piacerebbe aggiungere che, quando si esegue la memoria RAM tra lo stack e lo spazio heap, è necessario considerare anche lo spazio per i dati statici non costanti (ad es. File globali, statica delle funzioni e per tutto il programma globuli dal punto di vista C, e probabilmente altri per C ++).

Come funziona l'allocazione stack / heap

Vale la pena notare che il file assembly di avvio è un modo per definire la regione; la toolchain (sia l'ambiente di compilazione che l'ambiente di runtime) si preoccupa principalmente dei simboli che definiscono l'inizio dello spazio dello stack (utilizzato per memorizzare il puntatore dello stack iniziale nella tabella vettoriale) e l'inizio e la fine dello spazio dell'heap (utilizzato dalla dinamica allocatore di memoria, generalmente fornito da libc)

Nell'esempio di OP, sono definiti solo 2 simboli, una dimensione dello stack a 1 kB e una dimensione dell'heap a 0 b. Questi valori vengono utilizzati altrove per produrre effettivamente gli spazi stack e heap

Nell'esempio @Gilles, le dimensioni sono definite e utilizzate nel file di assieme per impostare uno spazio di stack che inizia ovunque e che dura la dimensione, identificato dal simbolo Stack_Mem e imposta un'etichetta __initial_sp alla fine. Allo stesso modo per l'heap, dove lo spazio è il simbolo Heap_Mem (0,5 kB di dimensione), ma con etichette all'inizio e alla fine (__heap_base e __heap_limit).

Questi vengono elaborati dal linker, che non alloca nulla nello spazio dello stack e nello spazio dell'heap perché quella memoria è occupata (dai simboli Stack_Mem e Heap_Mem), ma può posizionare quei ricordi e tutti i globi ovunque ne abbia bisogno. Le etichette finiscono per essere simboli senza lunghezza agli indirizzi indicati. __Initial_sp viene utilizzato direttamente per la tabella vettoriale al momento del collegamento e __heap_base e __heap_limit dal codice di runtime. Gli indirizzi effettivi dei simboli sono assegnati dal linker in base a dove sono stati posizionati.

Come ho accennato in precedenza, questi simboli non devono effettivamente provenire da un file startup.s. Possono provenire dalla configurazione del tuo linker (Scatter Load file in Keil, linkerscript in GNU) e in quelli puoi avere un controllo più fine sul posizionamento. Ad esempio, puoi forzare lo stack all'inizio o alla fine della RAM, oppure tenere i globali lontani dall'heap o da qualunque cosa tu voglia. Puoi anche specificare che HEAP o STACK occupano solo la RAM rimasta dopo il posizionamento dei globi. NOTA, tuttavia, è necessario fare attenzione che l'aggiunta di più variabili statiche che ridurrà l'altra memoria.

Tuttavia, ogni toolchain è diverso e il modo in cui scrivere il file di configurazione e quali simboli utilizzerà l'allocatore di memoria dinamica dovrà provenire dalla documentazione del proprio ambiente specifico.

Ridimensionamento dello stack

Per quanto riguarda come determinare la dimensione dello stack, molte toolchain possono darti una profondità massima dello stack analizzando gli alberi delle chiamate di funzione del tuo programma, SE non usi ricorsione o puntatori di funzione. Se li usi, stimando una dimensione dello stack e pre-riempendolo con i valori cardinali (forse tramite la funzione di immissione prima del principale) e quindi controllando dopo che il programma è stato eseguito per un po 'di tempo in cui si trovava la profondità massima (che è dove i valori cardinali fine). Se hai completamente esercitato il tuo programma fino ai suoi limiti, saprai in modo abbastanza preciso se puoi ridurre lo stack o, se il tuo programma si arresta in modo anomalo o non sono rimasti valori cardinali, devi aumentare lo stack e riprovare.

Dimensionamento dell'heap

Determinare la dimensione dell'heap dipende un po 'più dall'applicazione. Se esegui l'allocazione dinamica solo durante l'avvio, puoi semplicemente aggiungere lo spazio richiesto nel codice di avvio (oltre a un sovraccarico per la gestione della memoria). Se hai accesso alla fonte del tuo gestore della memoria, puoi sapere esattamente qual è l'overhead e possibilmente anche scrivere il codice per percorrere la memoria per darti informazioni sull'utilizzo. Per le applicazioni che richiedono memoria di runtime dinamica (ad es. Allocazione di buffer per frame ethernet in entrata) il meglio che posso suggerire è di affinare con cura il vostro stack e dare a Heap tutto ciò che resta dopo stack e statica.

Nota finale (RTOS)

La domanda di OP è stata taggata per bare-metal, ma voglio aggiungere una nota per RTOS. Spesso (sempre?) A ogni attività / processo / thread (scriverò semplicemente l'attività qui per semplicità) verrà assegnata una dimensione dello stack quando l'attività viene creata, oltre a stack di attività, probabilmente ci sarà un piccolo sistema operativo stack (usato per interrupt e simili)

Le strutture di contabilità delle attività e le pile devono essere allocate da qualche parte, e questo sarà spesso dallo spazio heap complessivo dell'applicazione. In questi casi, le dimensioni iniziali dello stack spesso non contano, poiché il sistema operativo lo utilizzerà solo durante l'inizializzazione. Ho visto, ad esempio, specificare TUTTO lo spazio rimanente durante il collegamento da allocare all'HEAP e posizionare il puntatore dello stack iniziale alla fine dell'heap per crescere nell'heap, sapendo che il sistema operativo si allocerà a partire dall'inizio dell'heap e assegnerà lo stack del sistema operativo appena prima di abbandonare lo stack initial_sp. Quindi tutto lo spazio viene utilizzato per allocare stack di attività e altra memoria allocata dinamicamente.

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.