Znam ogólną teorię, ale nie mogę dopasować się do szczegółów.
Wiem, że program znajduje się w dodatkowej pamięci komputera. Gdy program rozpocznie wykonywanie, jest całkowicie kopiowany do pamięci RAM. Następnie procesor pobiera jednocześnie kilka instrukcji (w zależności od wielkości magistrali), umieszcza je w rejestrach i wykonuje je.
Wiem również, że program komputerowy wykorzystuje dwa rodzaje pamięci: stos i stertę, które są również częścią podstawowej pamięci komputera. Stos jest używany do pamięci niedynamicznej, a sterty do pamięci dynamicznej (na przykład wszystko związane znew
operatorem w C ++)
Nie rozumiem, jak te dwie rzeczy się łączą. W którym momencie stos jest używany do wykonywania instrukcji? Instrukcje idą z pamięci RAM, na stos, do rejestrów?
źródło
Odpowiedzi:
To zależy od systemu, ale współczesne systemy operacyjne z pamięcią wirtualną mają tendencję do ładowania obrazów procesów i przydzielania pamięci w taki sposób:
Jest to ogólna przestrzeń adresowa procesu w wielu popularnych systemach pamięci wirtualnej. „Dziura” jest rozmiarem całkowitej pamięci, pomniejszonym o przestrzeń zajmowaną przez wszystkie pozostałe obszary; daje to dużą ilość miejsca na powiększenie sterty. Jest to również „wirtualne”, co oznacza, że mapuje na twoją rzeczywistą pamięć za pomocą tabeli translacji i może być faktycznie przechowywane w dowolnym miejscu w rzeczywistej pamięci. Odbywa się to w ten sposób, aby zabezpieczyć jeden proces przed dostępem do pamięci innego procesu i sprawić, aby każdy proces myślał, że działa na pełnym systemie.
Zauważ, że pozycje np. Stosu i sterty mogą być w innej kolejności w niektórych systemach (patrz odpowiedź Billy'ego O'Neala) więcej informacji na temat Win32 można poniżej).
Inne systemy mogą się bardzo różnić. Na przykład DOS działał w trybie rzeczywistym , a przydzielanie pamięci podczas uruchamiania programów wyglądało zupełnie inaczej:
Widać, że DOS zezwalał na bezpośredni dostęp do pamięci systemu operacyjnego, bez żadnej ochrony, co oznaczało, że programy w przestrzeni użytkownika mogły zasadniczo bezpośrednio uzyskiwać dostęp lub zastępować wszystko, co im się podoba.
Jednak w przestrzeni adresowej procesu programy wyglądały podobnie, tyle że były one opisywane jako segment kodu, segment danych, sterta, segment stosu itp. I było nieco inaczej odwzorowane. Ale większość ogólnych obszarów wciąż tam była.
Po załadowaniu programu i niezbędnych współdzielonych bibliotek do pamięci oraz rozprowadzeniu części programu do odpowiednich obszarów, system operacyjny zaczyna wykonywać proces wszędzie tam, gdzie jest jego główna metoda, a program przejmuje stamtąd, wykonując wywołania systemowe w razie potrzeby, gdy potrzebuje ich.
Różne systemy (osadzone, cokolwiek) mogą mieć bardzo różne architektury, takie jak systemy bez stosów, systemy architektury Harvard (z kodem i danymi przechowywanymi w osobnej pamięci fizycznej), systemy, które faktycznie utrzymują BSS w pamięci tylko do odczytu (początkowo ustawione przez programista) itp. Ale to jest ogólna istota.
Powiedziałeś:
„Stos” i „stos” są po prostu abstrakcyjnymi pojęciami, a nie (koniecznie) fizycznie odrębnymi „rodzajami” pamięci.
Stos jest jedynie last in, first out struktura danych. W architekturze x86 można ją rozwiązać losowo, używając przesunięcia względem końca, ale najczęściej używanymi funkcjami są PUSH i POP, odpowiednio, do dodawania i usuwania elementów. Jest powszechnie używany do zmiennych lokalnych funkcji (tak zwane „automatyczne przechowywanie”), argumentów funkcji, adresów zwrotnych itp. (Więcej poniżej)
„Kupa” to tylko pseudonim na fragmencie pamięci, która może zostać przydzielona na żądanie, i skierowana jest losowy (co oznacza, można uzyskać dostęp do dowolnej lokalizacji w nim bezpośrednio). Jest powszechnie używany w strukturach danych, które alokujesz w czasie wykonywania (w C ++, używając
new
idelete
, imalloc
i przyjaciele w C itp.).Stos i sterta w architekturze x86 znajdują się fizycznie w pamięci systemowej (RAM) i są mapowane poprzez alokację pamięci wirtualnej do przestrzeni adresowej procesu, jak opisano powyżej.
Te rejestry (wciąż x86) fizycznie przebywania wewnątrz procesora (w przeciwieństwie do pamięci RAM) i ładowane są przez procesor, z obszaru TEXT (i może być również ładowany z zewnątrz w pamięci lub w innych miejscach, w zależności od instrukcji procesorowych są faktycznie wykonywane). Są to po prostu bardzo małe, bardzo szybkie lokalizacje pamięci na chipie, które są wykorzystywane do wielu różnych celów.
Układ rejestru jest w dużym stopniu zależny od architektury (w rzeczywistości rejestry, zestaw instrukcji i układ / projekt pamięci są dokładnie tym, co rozumie się przez „architekturę”), więc nie będę się na nim rozwijać, ale zalecam wziąć kurs języka asemblera, aby lepiej je zrozumieć.
Twoje pytanie:
Stos (w systemach / językach, które je mają i używają) jest najczęściej używany w następujący sposób:
Napisz prosty program taki jak ten, a następnie skompiluj go do asemblera (
gcc -S foo.c
jeśli masz dostęp do GCC) i spójrz. Montaż jest dość łatwy do naśladowania. Widać, że stos służy do zmiennych lokalnych funkcji oraz do wywoływania funkcji, przechowywania ich argumentów i zwracanych wartości. Dlatego też, gdy robisz coś takiego:Wszystkie te są kolejno wywoływane. To dosłownie budowanie stosu wywołań funkcji i ich argumentów, wykonywanie ich, a następnie wyskakiwanie w miarę, jak zawija (lub podnosi;). Jednak, jak wspomniano powyżej, stos (na x86) faktycznie znajduje się w przestrzeni pamięci procesu (w pamięci wirtualnej), więc można nią manipulować bezpośrednio; nie jest to osobny krok podczas wykonywania (lub przynajmniej jest prostopadły do procesu).
Do Twojej wiadomości, powyższa jest konwencją wywoływania C , również używaną przez C ++. Inne języki / systemy mogą wypychać argumenty na stos w innej kolejności, a niektóre języki / platformy nawet nie używają stosów i działają na różne sposoby.
Zauważ też, że nie są to rzeczywiste wiersze wykonywanego kodu C. Kompilator przekonwertował je na instrukcje języka maszynowego w pliku wykonywalnym.
Są one następnie (ogólnie) kopiowane z obszaru TEXT do potoku CPU, a następnie do rejestrów CPU i stamtąd wykonywane.[To było niepoprawne. Zobacz poprawkę Bena Voigta poniżej.]źródło
Sdaz uzyskał niesamowitą liczbę pozytywnych opinii w bardzo krótkim czasie, ale niestety utrwala błędne przekonanie o tym, jak instrukcje poruszają się przez procesor.
Pytanie zadane:
Sdaz powiedział:
Ale to źle. Z wyjątkiem szczególnego przypadku kodu samodmodyfikującego instrukcje nigdy nie wprowadzają ścieżki danych. I nie są, nie mogą być wykonywane z ścieżki danych.
Te rejestry CPU x86 są:
Rejestry ogólne EAX EBX ECX EDX
Segment rejestruje CS DS ES FS GS SS
Indeks i wskaźniki ESI EDI EBP EIP ESP
Wskaźnik EFLAGS
Istnieją również rejestry zmiennoprzecinkowe i SIMD, ale do celów niniejszej dyskusji sklasyfikujemy je jako część koprocesora, a nie procesora. Jednostka zarządzania pamięcią wewnątrz procesora ma również własne rejestry, ponownie potraktujemy to jako oddzielną jednostkę przetwarzającą.
Żaden z tych rejestrów nie jest używany dla kodu wykonywalnego.
EIP
zawiera adres instrukcji wykonawczej, a nie samą instrukcję.Instrukcje przechodzą przez procesor zupełnie inną ścieżkę niż dane (architektura Harvarda). Wszystkie obecne maszyny mają architekturę Harvarda wewnątrz procesora. Większość tych dni to także architektura Harvarda w pamięci podręcznej. x86 (wspólny komputer stacjonarny) to architektura Von Neumanna w pamięci głównej, co oznacza, że dane i kod są przenikane w pamięci RAM. To nie ma znaczenia, ponieważ mówimy o tym, co dzieje się w procesorze.
Klasyczna sekwencja nauczana w architekturze komputerowej to fetch-decode-execute. Kontroler pamięci wyszukuje instrukcję zapisaną pod adresem
EIP
. Bity instrukcji przechodzą przez pewną kombinacyjną logikę, aby utworzyć wszystkie sygnały sterujące dla różnych multiplekserów w procesorze. Po kilku cyklach arytmetyczna jednostka logiczna osiąga wynik, który jest taktowany do miejsca docelowego. Następnie pobierana jest kolejna instrukcja.Na nowoczesnym procesorze wszystko działa trochę inaczej. Każda przychodząca instrukcja jest tłumaczona na całą serię instrukcji mikrokodu. Umożliwia to potokowanie, ponieważ zasoby używane przez pierwszą mikroinstrukcję nie są później potrzebne, więc mogą rozpocząć pracę nad pierwszą mikroinstrukcją od następnej instrukcji.
Podsumowując, terminologia jest nieco mylona, ponieważ rejestr jest terminem inżynierii elektrycznej dla kolekcji D-flipflops. A instrukcje (a zwłaszcza mikroinstrukcje) mogą bardzo dobrze być tymczasowo przechowywane w takiej kolekcji flipflopów. Ale nie to ma na myśli, gdy informatyk, inżynier oprogramowania lub programiści używają terminu rejestr . Oznaczają one rejestry ścieżki danych, jak wymienione powyżej, i nie są one używane do transportu kodu.
Nazwy i liczba rejestrów ścieżek danych są różne dla innych architektur CPU, takich jak ARM, MIPS, Alpha, PowerPC, ale wszystkie wykonują instrukcje bez przekazywania ich przez ALU.
źródło
Dokładny układ pamięci podczas wykonywania procesu jest całkowicie zależny od używanej platformy. Rozważ następujący program testowy:
W systemie Windows NT (i jego dzieciach) ten program generalnie generuje:
Na pudełkach POSIX będzie napisane:
Model pamięci UNIX jest dość dobrze wyjaśniony tutaj przez @Sdaz MacSkibbons, więc nie powtórzę tego tutaj. Ale to nie jedyny model pamięci. Powodem, dla którego POSIX wymaga tego modelu, jest wywołanie systemowe sbrk . Zasadniczo, w pudełku POSIX, aby uzyskać więcej pamięci, proces mówi Kernelowi, aby przesunął dzielnik między „dziurą” a „stertą” dalej do regionu „dziury”. Nie ma sposobu na zwrócenie pamięci do systemu operacyjnego, a sam system operacyjny nie zarządza stertą. Biblioteka środowiska wykonawczego C musi to zapewnić (przez malloc).
Ma to również wpływ na rodzaj kodu faktycznie używanego w plikach binarnych POSIX. Pudełka POSIX (prawie powszechnie) używają formatu pliku ELF. W tym formacie system operacyjny jest odpowiedzialny za komunikację między bibliotekami w różnych plikach ELF. Dlatego wszystkie biblioteki używają kodu niezależnego od pozycji (to znaczy, że sam kod można załadować do różnych adresów pamięci i nadal działać), a wszystkie połączenia między bibliotekami są przekazywane przez tabelę przeglądową, aby dowiedzieć się, gdzie kontrola musi przejść do przejścia wywołania funkcji biblioteki. To powoduje dodatkowe obciążenie i może zostać wykorzystane, jeśli jedna z bibliotek zmieni tabelę wyszukiwania.
Model pamięci systemu Windows jest inny, ponieważ używany przez niego kod jest inny. Windows używa formatu pliku PE, który pozostawia kod w formacie zależnym od pozycji. Oznacza to, że kod zależy od tego, gdzie dokładnie w pamięci wirtualnej jest ładowany kod. W specyfikacji PE znajduje się flaga, która informuje system operacyjny, gdzie dokładnie w pamięci biblioteka lub plik wykonywalny chciałby zostać zmapowany podczas działania programu. Jeśli programu lub biblioteki nie można załadować pod preferowanym adresem, moduł ładujący systemu Windows musi zmienićbiblioteka / plik wykonywalny - w zasadzie przesuwa kod zależny od pozycji, aby wskazywał nowe pozycje - co nie wymaga tabel odnośników i nie może być wykorzystane, ponieważ nie ma tablic odnośników do zastąpienia. Niestety wymaga to bardzo skomplikowanej implementacji w module ładującym systemu Windows i wymaga znacznego czasu uruchamiania, jeśli obraz wymaga zmiany bazy. Duże komercyjne pakiety oprogramowania często modyfikują swoje biblioteki, aby celowo uruchamiać pod różnymi adresami, aby uniknąć zmiany bazy; sam system Windows robi to z własnymi bibliotekami (np. ntdll.dll, kernel32.dll, psapi.dll itp. - wszystkie mają domyślnie różne adresy początkowe)
W systemie Windows pamięć wirtualna jest uzyskiwana z systemu przez wywołanie VirtualAlloc i jest zwracana do systemu przez VirtualFree (OK, technicznie VirtualAlloc farmuje się do NtAllocateVirtualMemory, ale to szczegół implementacji) (Porównaj to z POSIX, gdzie pamięć nie może odzyskać). Proces ten jest powolny (a IIRC wymaga alokacji na porcje wielkości strony fizycznej; zwykle 4 KB lub więcej). System Windows udostępnia również własne funkcje sterty (HeapAlloc, HeapFree itp.) Jako część biblioteki znanej jako RtlHeap, która jest zawarta jako część samego systemu Windows, na której środowisko wykonawcze C (to znaczy
malloc
zazwyczaj jest wdrażane i przyjaciele).System Windows ma również kilka starszych interfejsów API alokacji pamięci z czasów, gdy miał do czynienia ze starymi modelami 80386, a funkcje te są teraz wbudowane w interfejs RtlHeap. Więcej informacji o różnych interfejsach API sterujących zarządzaniem pamięcią w systemie Windows znajduje się w tym artykule MSDN: http://msdn.microsoft.com/en-us/library/ms810627 .
Należy również zauważyć, że oznacza to, że w systemie Windows pojedynczy proces (i zwykle ma) ma więcej niż jedną stertę. (Zazwyczaj każda biblioteka współdzielona tworzy własną stertę).
(Większość tych informacji pochodzi z „Bezpiecznego kodowania w C i C ++” Roberta Seacorda)
źródło
Stos
W architekturze X86 CPU wykonuje operacje na rejestrach. Stos jest używany tylko ze względów wygody. Możesz zapisać zawartość swoich rejestrów w stosie przed wywołaniem podprogramu lub funkcji systemowej, a następnie załadować je z powrotem, aby kontynuować operację tam, gdzie opuściłeś. (Możesz to zrobić ręcznie bez stosu, ale jest to często używana funkcja, więc ma wsparcie procesora). Ale możesz zrobić prawie wszystko bez stosu w komputerze.
Na przykład mnożenie liczb całkowitych:
Mnoży rejestr AX z rejestrem BX. (Wynik będzie w DX i AX, DX zawierający wyższe bity).
Maszyny oparte na stosie (takie jak JAVA VM) używają stosu do swoich podstawowych operacji. Powyższe mnożenie:
Spowoduje to wyświetlenie dwóch wartości z góry stosu i pomnożenie tem, a następnie wypchnięcie wyniku z powrotem do stosu. Stos jest niezbędny dla tego rodzaju maszyn.
Niektóre języki programowania wyższego poziomu (takie jak C i Pascal) używają tej późniejszej metody do przekazywania parametrów do funkcji: parametry są wypychane na stos w kolejności od lewej do prawej, a następnie wstawiane przez treść funkcji, a zwracane wartości są wypychane z powrotem. (Jest to wybór dokonywany przez producentów kompilatorów i rodzaj nadużywania sposobu, w jaki X86 używa stosu).
Kupa
Sterta to kolejna koncepcja, która istnieje tylko w dziedzinie kompilatorów. To zabiera ból związany z obsługą pamięci za zmiennymi, ale nie jest to funkcja procesora ani systemu operacyjnego, to tylko wybór sposobu utrzymania bloku pamięci, który jest wydawany przez system operacyjny. Możesz to zrobić na wiele sposobów, jeśli chcesz.
Dostęp do zasobów systemowych
System operacyjny ma publiczny interfejs umożliwiający dostęp do jego funkcji. W DOS parametry są przekazywane do rejestrów CPU. System Windows używa stosu do przekazywania parametrów funkcji systemu operacyjnego (Windows API).
źródło