Pamięć wirtualna
Hierarchia pamięci
W systemie mikroprocesorowym występuje kilka rodzajów pamięci. Na początek możemy podzielić je na pamięć masową PM i pamięć półprzewodnikową PP. Do pamięci masowych należą wszelkiego rodzaju pamięci na nośnikach magnetycznych, takie jak dyski twarde i elastyczne, streamery oraz pamięci optyczne typu CD - ROM czy też DVD - ROM. Rodzaje pamięci półprzewodnikowych to pamięci do zapisu i odczytu dynamiczne i statyczne oraz pamięci typu ROM, a także rejestry wewnątrz mikroprocesora. Pamięci te można uszeregować w hierarchiczną strukturę, biorąc pod uwagę trzy czynniki: pojemność, koszt jednego bitu oraz czas dostępu. Hierarchia pamięci uwzględniająca najistotniejsze składniki systemu przedstawiona jest na rysunku 28.
Jak widzimy, wzrost pojemności układów pamiętających oraz zmniejszanie się ceny l bitu jest (niestety) odwrotnie proporcjonalne do zmniejszania się czasu dostępu, czyli do szybkości działania tych układów. Wynikają z tego określone konsekwencje. Układami pamiętającymi o największej pojemności w systemie są pamięci masowe, na przykład dyski twarde. Są to jednocześnie najtańsze układy pamiętające, zwłaszcza jeżeli uwzględnimy cenę 1 bitu. Niestety są to układy znacznie wolniejsze od pamięci półprzewodnikowych. Ponieważ, pamięć, z którą komunikuje się mikroprocesor, musi być pamięcią szybką, budujemy ją z pamięci półprzewodnikowych. Nie może mieć ona jednak takich pojemności jak dyski twarde ze względów zarówno technologicznych, jak i ekonomicznych. Z podobnych powodów nie możemy pozwolić sobie na zbudowanie całej pamięci operacyjnej z pamięci statycznych, które są szybsze, lecz drogie i trudniej podlegają scalaniu. Wreszcie najszybszymi układami pamiętającymi w systemie są rejestry mikroprocesora, jednak ich pojemność jest najmniejsza. W systemie przyjęto więc rodzaj kompromisu. Mamy pamięć o dużej pojemności, lecz stosunkowo wolną (pamięć masową) i szybszą od niej, lecz droższą i o mniejszej pojemności pamięć operacyjną zbudowaną z pamięci DRAM. Zabieg, który pozwoli traktować programom pamięć masową jako przedłużenie pamięci operacyjnej, doprowadzi nas do pojęcia pamięci wirtualnej. Opisujemy ją w następnym podpunkcie. Podobne rozważania przeprowadzone dla pamięci DRAM i SRAM doprowadzą nas do pojęcia pamięci cache. Jak wiadomo, cache jest pamięcią bardzo szybką, lecz jej pojemność w systemie jest niewielka i wynosi od setek kB do pojedynczych MB. Najszybszymi układami pamiętającymi są rejestry mikroprocesora. Konsekwencją tego są rozwiązania stosowane na przykład w procesorach RISC (duża liczba rejestrów roboczych).
Pamięć ROM w pokazanej hierarchii zachowuje się nietypowo (szczególnie jeśli chodzi o jej pojemność) i została tu umieszczona głównie z powodu jej czasu dostępu, który jest większy (dłuższy) niż dla pamięci DRAM. Wyjaśni to używanie w części systemów tak zwanego shadow BIOS-u.
Rysunek 28 Hierarchia pamięci w systemie komputerowym
Zasada działania pamięci wirtualnej
Jak już stwierdzono, mechanizm pamięci wirtualnej pozwala traktować programom pamięć masową jako przedłużenie pamięci operacyjnej. Zastanówmy się, co to oznacza. Pamięć masowa ma znacznie większą pojemność, co pozwala na używanie w programie dłuższych adresów. Z drugiej strony nie jest możliwe odczytywanie pojedynczych instrukcji w trakcie wykonywania programu z pamięci masowej, która jest dość wolna, a co ważniejsze, jej urządzenia są urządzeniami o dostępie blokowym. Dlatego też stosuje się następujące rozwiązanie. Pewna część informacji wykonywanego programu załadowana jest do pamięci operacyjnej. Pozostała część niemieszcząca się w niej jest przechowywana w pamięci masowej. Jeżeli w trakcie realizacji programu następuje odwołanie do informacji znajdującej się na dysku, to odpowiedni mechanizm powoduje wczytanie brakującego bloku informacji do pamięci operacyjnej, przesyłając inny blok do pamięci masowej w celu zwolnienia miejsca (chyba że blok ten, na przykład fragment programu, jest w niej już zapisany). Na wykonanie tych operacji pozwala mechanizm pamięci wirtualnej. Powinien także spowodować przetłumaczenie (translację) długich adresów (wirtualnych) w programie na krótsze adresy (fizyczne) pamięci operacyjnej. Przy okazji takiej translacji należy też sprawdzać, czy poszukiwana informacja jest dostępna w pamięci operacyjnej.
Podsumowując, mechanizm pamięci wirtualnej jest następujący (w nawiasach podajemy długości adresów występujących w procesorze 80286, w którym po raz pierwszy pojawiły się sprzętowe mechanizmy wspomagające obsługę pamięci wirtualnej):
Program żąda dostępu do określonej informacji, podając (długi, 32-bitowy) adres wirtualny.
Sprawdzana jest obecność poszukiwanej informacji w pamięci operacyjnej. Informacja o obecności konkretnych bloków w pamięci operacyjnej przechowywana jest w specjalnej tablicy.
W przypadku braku poszukiwanej informacji jest ona wczytywana z dysku, a odpowiednie pozycje w tablicach obsługujących pamięć wirtualną są modyfikowane.
Obliczany jest adres fizyczny miejsca przechowywania informacji w pamięci operacyjnej, czyli dokonywana jest translacja adresu wirtualnego na fizyczny (krótki, 24-bitowy). Translacji tej dokonuje się także przy użyciu odpowiedniej tablicy.
Poszukiwana informacja jest dostępna dla procesora, co zamyka cykl działania pamięci wirtualnej.
Translacja adresu wirtualnego na fizyczny oraz sprawdzenie obecności poszukiwanej informacji są realizowane za pomocą specjalnej tablicy przechowywanej w pamięci operacyjnej, zwanej tablicą deskryptorów. Mechanizm jej działania ilustruje rysunek 29.
Przykładowe wartości użyte na rysunku 29 zostały utworzone na potrzeby tego przykładu, w celu jaśniejszego pokazania działania mechanizmu pamięci wirtualnej.
Rysunek 29 Translacja adresu wirtualnego na fizyczny
Załóżmy, ze pojemność pamięci przydzielona jednemu zadaniu wynosi 8 MB, co oznacza, że zezwalamy na używanie w programie 23 - bitowych adresów (223 = 8 M). Pojemność pamięci operacyjnej wynosi 2 MB (pomijamy niewielki dodatek na system operacyjny i tablicę deskryptorów), więc długość adresu fizycznego wynosi 21 bitów. Ponieważ pojemność PAO jest mniejsza niż wielkość programu, tylko część programu będzie do niej załadowana. W naszym przypadku są to 2 MB. Program w całości będzie przechowywany w pamięci masowej, np. na dysku. Program zarówno w PAO, jak i w PM dzielimy na bloki. W naszym przypadku są to bloki o jednakowej wielkości równej 1 MB (jest to pewne uproszczenie w stosunku do rzeczywistej sytuacji, służące jaśniejszemu przedstawieniu zawartości deskryptora). Każdy z bloków ma na dysku unikalny numer, w naszym przypadku jest to liczba 3-bitowa.
W pamięci operacyjnej utworzona jest specjalna tablica, zwana tablicą deskryptorów. Liczba pozycji w tej tablicy musi być równa liczbie bloków w PM, na jakie został podzielony program. Inaczej mówiąc, każdy blok programu musi mieć swój deskryptor. Każdy z deskryptorów składa się z dwóch części: bitu obecności bloku i adresu bazowego (21-bitowego) podającego, w którym miejscu w pamięci operacyjnej został umieszczony dany blok. Bit obecności równy 1 oznacza obecność danego bloku programu w PAO.
W naszym przykładzie do pamięci operacyjnej załadowane są bloki programu o numerach 5 i 3 (numery tych bloków zostały dla większej czytelności rysunku umieszczone zarówno w PM, jak i PAO). W deskryptorach odpowiadającym blokom 3 i 5 bity obecności mają wartość 1. W deskryptorach pozostałych bloków wartość ta wynosi 0. Ponadto w deskryptorze bloku 3 umieszczony jest adres 100000h, co oznacza, że blok 3 rozpoczyna się w pamięci operacyjnej od adresu 100000h. Podobnie dla bloku 5 adres bazowy wynosi 000000h.
Załóżmy teraz, że program odwołuje się do adresu wirtualnego 3FFFFFh. Adres jest dzielony na dwie części: pole selektora i pole przesunięcia. Pole selektora (u nas 3-bitowe) określa, w którym bloku programu znajduje się poszukiwana informacja, czyli wskazuje, którego deskryptora należy użyć podczas translacji adresu wirtualnego na rzeczywisty. Zauważmy, że pole to musi mieć taką długość, aby móc ponumerować wszystkie bloki przydzielone dla jednego programu, czyli aby obsłużyć całą przestrzeń pamięci wirtualnej przydzielonej programowi. W naszym przypadku pole to musi mieć długość 3 bitów, gdyż program może się składać z ośmiu bloków. Przesunięcie określa, jak daleko od początku bloku znajduje się poszukiwana informacja. W naszym przypadku selektor zawiera liczbę 3. Oznacza to, że poszukiwana informacja znajduje się w bloku 3. Kolejność postępowania systemu operacyjnego jest następująca:
Sprawdzany jest bit obecności bloku w deskryptorze bloku 3. W naszym przypadku wynosi on 1, czyli blok jest obecny w PAO.
Odczytywany jest adres bazowy bloku w PAO.
Do odczytanego adresu bazowego dodawane jest przesunięcie pobrane z adresu wirtualnego.
Otrzymany 21-bitowy adres jest fizycznym adresem w PAO, pod którym znajduje się poszukiwana informacja.
Co stanie się w przypadku, gdy bloku zawierającego poszukiwaną informację nie ma w PAO? Załóżmy tym razem, że adres wirtualny wynosi 7F0000h. Odwołujemy się więc do bloku 7, którego nie ma w pamięci. Wykonane muszą być wtedy dodatkowe czynności.
1a. Blok numer 7 wczytywany jest do pamięci operacyjnej w miejscu jednego z obecnych tam bloków. Załóżmy, że blok 7 umieścimy w miejscu bloku 3.
2a. Modyfikowane są deskryptory usuniętego i załadowanego bloku.
Tablica deskryptorów po wykonaniu tej operacji będzie wyglądać tak, jak na rysunku 30.
Dalszy ciąg postępowania jest taki sam jak w poprzednim przypadku. Zauważmy, że w deskryptorze bloku 3 wystarczyło zmodyfikować bit obecności. Powtarzający się adres bazowy nie ma znaczenia, gdyż i tak dla nieobecnego bloku nie jest czytany.
Rysunek 30 Przykładowa zawartość tablicy deskryptorów