Stos i stos to pojęcia oprogramowania, a nie pojęcia sprzętu. Sprzęt zapewnia pamięć. Definiowanie stref pamięci, z których jedna nazywana jest „stosem”, a druga „stertą”, jest wyborem twojego programu.
Sprzęt pomaga w stosach. Większość architektur ma dedykowany rejestr zwany wskaźnikiem stosu. Jego zamierzonym zastosowaniem jest to, że gdy program wykonuje wywołanie funkcji, parametry funkcji i adres zwrotny są wypychane na stos i są wyskakujące, gdy funkcja kończy się i wraca do swojego wywołującego. Naciskanie na stos oznacza pisanie pod adresem podanym przez wskaźnik stosu i odpowiednio zmniejszanie wskaźnika stosu (lub zwiększanie, w zależności od kierunku, w którym rośnie stos). Popping oznacza zwiększanie (lub zmniejszanie) wskaźnika stosu; adres zwrotny jest odczytywany z adresu podanego przez wskaźnik stosu.
Niektóre architektury (choć nie ARM) mają instrukcję wywołania podprogramu, która łączy skok z zapisem na adres podany przez wskaźnik stosu oraz instrukcję powrotu podprogramu, która łączy odczyt z adresu podanego przez wskaźnik stosu i przejście do tego adresu. W ARM zapisywanie i przywracanie adresu odbywa się w rejestrze LR, instrukcje wywoływania i zwracania nie używają wskaźnika stosu. Istnieją jednak instrukcje ułatwiające zapisywanie lub odczytywanie wielu rejestrów na adres podany przez wskaźnik stosu, w celu argumentowania funkcji push i pop.
Aby wybrać rozmiar sterty i stosu, jedyną istotną informacją ze sprzętu jest ilość posiadanej pamięci. Następnie dokonujesz wyboru w zależności od tego, co chcesz przechowywać w pamięci (uwzględniając kod, dane statyczne i inne programy).
Program zwykle używa tych stałych do inicjowania niektórych danych w pamięci, które zostaną wykorzystane przez resztę kodu, takich jak adres górnej części stosu, być może wartość gdzieś w celu sprawdzenia przepełnienia stosu, granice dla alokatora stosu itp.
W kodzie, który oglądasz , Stack_Size
stała służy do rezerwowania bloku pamięci w obszarze kodu (poprzez SPACE
dyrektywę w zespole ARM). Górny adres tego bloku ma etykietę __initial_sp
i jest przechowywany w tabeli wektorów (procesor używa tego wpisu do ustawienia SP po resecie oprogramowania), a także eksportowany do użycia w innych plikach źródłowych. Heap_Size
Stała jest podobnie stosowane zarezerwować blok pamięci i etykiet do jej granic ( __heap_base
i __heap_limit
) są eksportowane do wykorzystania w innych plików źródłowych.
; 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
Rozmiary stosu i sterty są definiowane przez aplikację, a nie w dowolnym miejscu w arkuszu danych mikrokontrolera.
Stos
Stos służy do przechowywania wartości zmiennych lokalnych wewnątrz funkcji, poprzednich wartości rejestrów procesora używanych dla zmiennych lokalnych (aby można je było przywrócić po wyjściu z funkcji), adresu programu, do którego należy wrócić przy opuszczaniu tych funkcji, plus trochę narzutów na zarządzanie samym stosem.
Podczas opracowywania systemu osadzonego szacujesz maksymalną oczekiwaną głębokość wywołania, dodajesz rozmiary wszystkich zmiennych lokalnych w funkcjach w tej hierarchii, następnie dodajesz dopełnienie, aby uwzględnić wspomniany wyżej narzut, a następnie dodajesz więcej dla wszelkie przerwy, które mogą wystąpić podczas wykonywania programu.
Alternatywną metodą szacunkową (gdzie pamięć RAM nie jest ograniczona) jest przydzielenie o wiele więcej miejsca na stosie, niż kiedykolwiek będziesz potrzebować, wypełnienie stosu wartością wartownika, a następnie monitorowanie, ile faktycznie używasz podczas wykonywania. Widziałem debugowane wersje środowisk uruchomieniowych języka C, które zrobią to automatycznie. Następnie, gdy zakończysz programowanie, możesz zmniejszyć rozmiar stosu, jeśli chcesz.
Kupa
Obliczenie wielkości stosu, którego potrzebujesz, może być trudniejsze. Sterty służy do dynamicznie alokowanych zmiennych, więc jeśli używasz
malloc()
ifree()
w programie języka C, czynew
idelete
w C ++, to gdzie te zmienne żyć.Jednak szczególnie w C ++ może istnieć pewna dynamiczna alokacja pamięci. Na przykład, jeśli masz statycznie przydzielone obiekty, język wymaga, aby ich destruktory były wywoływane przy zamykaniu programu. Mam świadomość co najmniej jednego środowiska uruchomieniowego, w którym adresy niszczycieli są przechowywane na dynamicznie przydzielanej liście połączonej.
Aby oszacować rozmiar stosu, którego potrzebujesz, spójrz na cały dynamiczny przydział pamięci w każdej ścieżce w drzewie połączeń, oblicz maksimum i dodaj dopełnienie. Środowisko wykonawcze języka może zapewniać diagnostykę, której można użyć do monitorowania całkowitego zużycia sterty, fragmentacji itp.
źródło
Oprócz innych odpowiedzi chciałbym dodać, że podczas tworzenia pamięci RAM między przestrzenią stosu i sterty, należy również wziąć pod uwagę przestrzeń dla statycznych niestałych danych (np. Globale plików, statyka funkcji i cały program globals z perspektywy C i prawdopodobnie inne dla C ++).
Jak działa alokacja stosu / sterty
Warto zauważyć, że plik zestawu startowego jest jednym ze sposobów definiowania regionu; łańcuch narzędzi (zarówno środowisko kompilacji, jak i środowisko wykonawcze) w głównej mierze dbają o symbole, które definiują początek obszaru stosu (używany do przechowywania początkowego wskaźnika stosu w tabeli wektorów) oraz początek i koniec obszaru sterty (używany przez dynamikę alokator pamięci, zazwyczaj dostarczany przez bibliotekę libc)
W przykładzie OP zdefiniowano tylko 2 symbole, rozmiar stosu na 1 kB i rozmiar stosu na 0 kB. Wartości te są używane gdzie indziej, aby faktycznie utworzyć stos i przestrzenie sterty
W przykładzie @Gilles rozmiary są zdefiniowane i używane w pliku zestawu do ustawiania przestrzeni stosu, zaczynając od miejsca, w którym trwa i trwa rozmiar, identyfikowany przez symbol Stack_Mem i ustawiający na końcu etykietę __initial_sp. Podobnie dla sterty, gdzie spacja jest symbolem Heap_Mem (rozmiar 0,5kiB), ale z etykietami na początku i na końcu (__heap_base i __heap_limit).
Są one przetwarzane przez linker, który nie przydzieli niczego w przestrzeni stosu i przestrzeni stosu, ponieważ ta pamięć jest zajęta (przez symbole Stack_Mem i Heap_Mem), ale może umieścić te wspomnienia i wszystkie globale tam, gdzie jest to potrzebne. Etykiety kończą się symbolami bez długości pod podanymi adresami. __Initial_sp jest używany bezpośrednio do tabeli wektorów w czasie łączenia, a __heap_base i __heap_limit przez kod środowiska wykonawczego. Rzeczywiste adresy symboli są przypisywane przez linker na podstawie tego, gdzie je umieścił.
Jak już wspomniałem powyżej, te symbole nie muszą pochodzić z pliku startup.s. Mogą pochodzić z twojej konfiguracji linkera (plik ładowania rozproszonego w Keil, linkerscript w GNU), aw tych możesz mieć dokładniejszą kontrolę nad umieszczaniem. Na przykład możesz zmusić stos do umieszczenia na początku lub na końcu pamięci RAM lub trzymać globale z daleka od stosu lub cokolwiek chcesz. Możesz nawet określić, że HEAP lub STACK zajmują tylko tyle pamięci RAM, ile pozostało po umieszczeniu globali. Pamiętaj jednak, że musisz uważać, aby dodać więcej zmiennych statycznych, aby zmniejszyć inne pamięci.
Jednak każdy zestaw narzędzi jest inny, a sposób zapisania pliku konfiguracyjnego i symboli używanych przez dynamiczny alokator pamięci będzie musiał pochodzić z dokumentacji konkretnego środowiska.
Rozmiar stosu
Jeśli chodzi o sposób określania wielkości stosu, wiele łańcuchów narzędzi może zapewnić maksymalną głębokość stosu, analizując drzewa wywołań funkcji w twoim programie, JEŻELI nie używasz wskaźników rekurencyjnych lub funkcyjnych. Jeśli ich użyjesz, oszacuj rozmiar stosu i wstępnie wypełnij go wartościami kardynalnymi (być może za pomocą funkcji wprowadzania przed głównym), a następnie sprawdź po uruchomieniu programu przez chwilę, gdzie była maksymalna głębokość (czyli tam, gdzie wartości kardynalne koniec). Jeśli w pełni wykorzystałeś swój program do granic swoich możliwości, będziesz dość dokładnie wiedział, czy możesz zmniejszyć stos lub, jeśli program się zawiesi lub nie pozostaną żadne wartości kardynalne, że musisz zwiększyć stos i spróbować ponownie.
Rozmiary sterty
Określenie wielkości sterty jest nieco bardziej zależne od aplikacji. Jeśli dokonujesz alokacji dynamicznej tylko podczas uruchamiania, możesz po prostu dodać miejsce wymagane w kodzie startowym (plus trochę narzuty na zarządzanie pamięcią). Jeśli masz dostęp do źródła swojego menedżera pamięci, możesz dokładnie wiedzieć, co to jest narzut, a może nawet napisać kod, aby przejść pamięć i podać informacje o użytkowaniu. W przypadku aplikacji, które potrzebują dynamicznej pamięci środowiska wykonawczego (np. Przydzielanie buforów dla przychodzących ramek ethernetowych), najlepsze, co mogę zasugerować, to dokładnie wyostrzyć swój rozmiar stosu i dać stosowi wszystko, co pozostało po stosie i statyce.
Uwaga końcowa (RTOS)
Pytanie OP zostało oznaczone jako goły metal, ale chcę dodać notatkę dla RTOS. Często (zawsze?) Każdemu zadaniu / procesowi / wątkowi (po prostu napiszę tutaj zadanie dla uproszczenia) zostanie przypisany rozmiar stosu podczas tworzenia zadania, a oprócz stosów zadań prawdopodobnie będzie mały system operacyjny stos (używany do przerwań i tym podobnych)
Struktury rozliczania zadań i stosy muszą być skądś przydzielone, a często będzie to związane z ogólną przestrzenią sterty aplikacji. W takich przypadkach początkowy rozmiar stosu często nie ma znaczenia, ponieważ system operacyjny użyje go tylko podczas inicjalizacji. Widziałem na przykład, że WSZYSTKIE pozostałe miejsce podczas łączenia ma zostać przydzielone do HEAP i umieszczenie początkowego wskaźnika stosu na końcu stosu, aby wyrósł na stertę, wiedząc, że system operacyjny przydzieli od początku stosu i przydzieli stos systemu operacyjnego tuż przed opuszczeniem stosu initial_sp. Następnie cała przestrzeń jest wykorzystywana do przydzielania stosów zadań i innej dynamicznie alokowanej pamięci.
źródło