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:
+---------+
| stack | function-local variables, return addresses, return values, etc.
| | often grows downward, commonly accessed via "push" and "pop" (but can be
| | accessed randomly, as well; disassemble a program to see)
+---------+
| shared | mapped shared libraries (C libraries, math libs, etc.)
| libs |
+---------+
| hole | unused memory allocated between the heap and stack "chunks", spans the
| | difference between your max and min memory, minus the other totals
+---------+
| heap | dynamic, random-access storage, allocated with 'malloc' and the like.
+---------+
| bss | Uninitialized global variables; must be in read-write memory area
+---------+
| data | data segment, for globals and static variables that are initialized
| | (can further be split up into read-only and read-write areas, with
| | read-only areas being stored elsewhere in ROM on some systems)
+---------+
| text | program code, this is the actual executable code that is running.
+---------+
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:
+-----------+ top of memory
| extended | above the high memory area, and up to your total memory; needed drivers to
| | be able to access it.
+-----------+ 0x110000
| high | just over 1MB->1MB+64KB, used by 286s and above.
+-----------+ 0x100000
| upper | upper memory area, from 640kb->1MB, had mapped memory for video devices, the
| | DOS "transient" area, etc. some was often free, and could be used for drivers
+-----------+ 0xA0000
| USER PROC | user process address space, from the end of DOS up to 640KB
+-----------+
|command.com| DOS command interpreter
+-----------+
| DOS | DOS permanent area, kept as small as possible, provided routines for display,
| kernel | *basic* hardware access, etc.
+-----------+ 0x600
| BIOS data | BIOS data area, contained simple hardware descriptions, etc.
+-----------+ 0x400
| interrupt | the interrupt vector table, starting from 0 and going to 1k, contained
| vector | the addresses of routines called when interrupts occurred. e.g.
| table | interrupt 0x21 checked the address at 0x21*4 and far-jumped to that
| | location to service the interrupt.
+-----------+ 0x0
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ś:
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” 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
i delete
, 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:
W którym momencie stos jest używany do wykonywania instrukcji? Instrukcje idą z pamięci RAM, na stos, do rejestrów?
Stos (w systemach / językach, które je mają i używają) jest najczęściej używany w następujący sposób:
int mul( int x, int y ) {
return x * y; // this stores the result of MULtiplying the two variables
// from the stack into the return value address previously
// allocated, then issues a RET, which resets the stack frame
// based on the arg list, and returns to the address set by
// the CALLer.
}
int main() {
int x = 2, y = 3; // these variables are stored on the stack
mul( x, y ); // this pushes y onto the stack, then x, then a return address,
// allocates space on the stack for a return value,
// then issues an assembly CALL instruction.
}
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:
f( g( h( i ) ) );
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.]