7. KONCEPCJA PAMIĘCI WIRTUALNEJ
Systemy operacyjne i wykonywane pod ich nadzorem programy są zazwyczaj w
stanie wykorzystać
dużo większą przestrzeń adresową, niż umożliwia to fizyczna pamięć operacyjna
wbudowana
w komputer. Ponadto może występować duża różnica pomiędzy możliwościami
adresowania
wynikającymi z formatu rozkazów procesora (i wielkości jego rejestrów), a
możliwościami
wynikającymi z szerokości szyny adresowej.
W przypadku procesorów Intel, tryb rzeczywisty adresowania (wykorzystywany
przez system
operacyjny DOS) umożliwia zaadresowanie jedynie około 1MB pamięci:
Jeden segment: 64 KB (ustalona wielkość).
1 MB + Przesunięcie pomiędzy początkami kolejnych
segmentów: 16 B.
64 KB - Adresowanie: segment : offset , gdzie zarówno
numer segmentu,
16 B jak i wielkość offsetu (przesunięcie względem
początku segmentu)
są liczbami 16-bitowymi, a cały adres - 20-
bitowy (w procesorach
80286 i późniejszych można obsługiwać
nadmiar na 21-szym bicie).
Procesor 80286 pracujący w trybie chronionym (stosujący deskryptorowy tryb
obliczania adresu
fizycznego, umożliwiający ochronę pamięci), ma możliwość zaadresowania 16 MB
pamięci fizycznej
(choć jego sposób obliczania adresu teoretycznie umożliwiałby zaadresowanie 1
GB pamięci).
Procesory o numerach 80386 i wyższych mogą zaadresować 4 GB pamięci
fizycznej (choć
teoretycznie mogłyby adresować nawet do 64 TB pamięci).
1 KB = 2 B = 1024 B 1 MB = 2 B (ponad 1
milion bajtów)
1 GB = 2 B (ponad 1 miliard bajtów) 1 TB = 2 B (ponad 1
bilion bajtów)
Koncepcja pamięci wirtualnej sprowadza się do symulowania większej ilości
pamięci operacyjnej
przy użyciu szybkiej pamięci dyskowej. Może być zrealizowana zarówno w oparciu
o stronicowanie,
jak i o segmentację (stronicowanie jest dużo dogodniejszym narzędziem).
Korzystanie przez system
z pamięci wirtualnej nie powinno być dostrzegane przez programy wykorzystujące
logiczną przestrzeń
adresową. Realizacja pamięci wirtualnej musi być wspierana przez odpowiedni
sprzęt.
10
20
30
40
Aby móc zaimplementować pamięć wirtualną, należy wydzielić na dysku
odpowiednio dużą partycję
przeznaczoną na przestrzeń wymiany
(swap file)
. W przestrzeni wymiany
przechowywane są
fragmenty logicznego obrazu pamięci, które aktualnie nie mieszczą się w pamięci
operacyjnej.
Translacja adresów jest teraz realizowana dwustopniowo:
1) przy użyciu rejestrów procesora i tablic deskryptorów następuje przeliczenie
adresu logicznego na
adres liniowy, który może odpowiadać pewnemu adresowi w fizycznej pamięci
operacyjnej, bądź
być adresem zewnętrznym (pojęcie pamięci wirtualnej w gruncie rzeczy
odpowiada zakresowi
adresów liniowych, który może być dużo większy od zainstalowanej pamięci
fizycznej).
2) w przypadku, gdy adres liniowy jest adresem zewnętrznym, następuje
przerwanie nazywane brakiem
jednostki
(missing item)
, w wyniku którego wykonywany jest podprogram
sprowadzenia z dysku do
pamięci operacyjnej brakującej jednostki (strony lub segmentu), a następnie (po
zaktualizowaniu
wpisów w tablicy deskryptorów) ostateczne obliczenie adresu fizycznego.
W sytuacji, gdy w pamięci fizycznej brakuje miejsca na wpisanie jednostki
sprowadzonej z dysku,
system musi podjąć decyzję, jaką jednostkę czasowo przepisać z pamięci
operacyjnej na dysk.
Ogólnie, wymiana jednostek nie musi być wykonywana bardzo często dzięki
zasadzie lokalności
odniesień (stwierdzającej rzadkość skoków na duże odległości w logicznej
przestrzeni adresowej).
Wydajność działania systemu jest jednak w dużym stopniu zależna od doboru
właściwego algorytmu
wymian.
Jeśli kompilator programów napisanych w pewnym języku został zaprojektowany
pod kątem pracy
w systemie z wymianą, przypada mu ważna rola dostosowania logicznej struktury
skompilowanego
programu do jednostek wymiany. Jest bardzo wskazane, na przykład, żeby duże
struktury danych,
takie jak duże tablice, były przechowywane w jak najmniejszej liczbie jednostek.
Jeśli program używa
podprogramów, najkorzystniej jest, jeśli zajmują one oddzielne jednostki.
Istotną rzeczą jest odseparowanie kodu programu (oraz ewentualnie używanych
stałych) od obszaru
wykorzystywanego przez zmienne programu. Jednostki zajmowane przez kod i
stałe przeznaczone są
tylko do odczytu, zatem przy czasowym usuwaniu ich z pamięci nie jest
potrzebna aktualizacja zapisu
na dysku (cały czas przebywają tam w tym samym miejscu), co znacznie
przyspiesza wykonywanie
procesu.
Aby system operacyjny wiedział, czy zachodzi konieczność aktualizacji zapisu
w przestrzeni
wymiany na dysku w przypadku usuwania jednostki z pamięci operacyjnej, czy
też nie, w opisach
jednostek umieszczane są bity modyfikacji, nazywane też bitami
zabrudzenia
(dirty bit)
.
W przypadku, gdy zawartość jednostki nie podlega modyfikacji, wartość tego
bitu wynosi 0.
Jeśli w jednostce został zmieniony choćby jeden bajt, bit modyfikacji jest
ustawiany na 1.
Jednostki zawierające kod i stałe mają przez cały czas wyzerowany bit
modyfikacji. W przypadku
jednostek zawierających wartości zmiennych może się przez przypadek
również zdarzyć, że od
chwili sprowadzenia ich do pamięci operacyjnej przez pewien czas będą
używane wyłącznie do
odczytu - w tym czasie ich bit modyfikacji pozostaje wyzerowany, co
przyspiesza ewentualną
wymianę.
Uwaga
Dostęp systemu operacyjnego do przestrzeni wymiany na dysku jest na ogół
dużo szybszy, niż
dostęp do systemu plików, gdyż korzysta z dużo prostszych mechanizmów
(może nie stosować
buforowania ani skomplikowanych algorytmów wyszukiwania miejsca zapisu /
odczytu).
Najczęściej stosowanym sposobem implementacji pamięci wirtualnej jest
stronicowanie na żądanie
(demand paging)
. W tym przypadku niektóre strony procesu (jego kodu i
danych) mogą przebywać
w pamięci operacyjnej, a niektóre na dysku. System operacyjny prowadzi
„leniwą” politykę
sprowadzania stron do pamięci - sprowadza je dopiero wtedy, gdy okażą się
potrzebne („na żądanie
procesu”). Problemy, jakie trzeba rozstrzygnąć, to:
1) ile ramek wstępnie przydzielać każdemu procesowi ?
2) jaką politykę wymiany stron prowadzić, gdy nie ma już wolnych ramek w
pamięci ?
Algorytmy, które rozwiązują te problemy, nazywane są odpowiednio
algorytmami wstępnego
przydziału i algorytmami zastępowania. Jeśli na początku działania procesy
nie mają
przydzielanych żadnych ramek, mówimy o czystym stronicowaniu na żądanie.
Wstępny przydział ramek nie musi być taki sam dla wszystkich procesów - może
zależeć od
wielkości jego programu, od priorytetu, logicznej struktury lub danych na temat
jego wcześniejszych
wykonań.
Algorytm zastępowania może być algorytmem zastępowania lokalnego lub
algorytmem zastępowania
globalnego. W pierwszym przypadku każdy proces ma liczbę ramek przydzieloną
na stałe i w jej
granicach operuje wymieniając swoje strony w miarę własnych potrzeb (stosując
na przykład liczniki
użycia ramek, system priorytetów ramek i inne kryteria). W drugim przypadku
procesy mogą
rywalizować o ramki - algorytm wymiany musi pełnić rolę arbitra i brać pod uwagę
na przykład
priorytety procesów, wielkość programów czy logiczną strukturę (podobnie, jak
algorytm przydziału
wstępnego).
W przypadku niewłaściwego doboru algorytmów w systemie mogą występować
różne niekorzystne
zjawiska. Przykładowo, może się zdarzyć, że globalny algorytm wymiany stron
przydzieli pewnemu
niskopriorytetowemu procesowi bardzo małą liczbę ramek. W takiej sytuacji
prawdopodobnie byłoby
najkorzystniejsze czasowe zawieszenie takiego procesu i wznowienie go dopiero
po zmniejszeniu się
obciążenia systemu. Jeśli jednak w takich warunkach proces będzie nadal się
wykonywał, będzie musiał
co chwila wymieniać ramki, i łączny czas dokonywania tych wymian może
przekroczyć łączny czas
jego efektywnej pracy - takie zjawisko nazywamy szamotaniem się procesu
(migotaniem).
Fizyczna realizacja stronicowania na żądanie opiera się na istnieniu w każdej
pozycji tablicy stron
(w każdym opisie strony) bitu poprawności odniesienia, który informuje, czy
dana strona przebywa
aktualnie w pamięci operacyjnej (adres jest adresem wewnętrznym), czy też
przebywa na dysku
(adres jest adresem zewnętrznym). W tym drugim przypadku żądanie dostępu
do strony generuje
przerwanie
(page fault)
. Po sprowadzeniu strony do pamięci (co ewentualnie
może być związane z
wymianą) system aktualizuje tablicę stron i kontynuuje wykonywanie
przerwanego procesu.
Realizacja segmentacji na żądanie wygląda podobnie - również oparta jest na
istnieniu bitów
poprawności odniesienia w deskryptorach segmentów. Jest jednak
algorytmicznie dużo trudniejsza
(a zatem powolniejsza) ze względu na zmienną wielkość dziur w pamięci i
ewentualną potrzebę ich
okresowej komasacji.
Segmentację na żądanie wykorzystywały systemy dedykowane procesorowi
Intel 80286 - na
przykład OS / 2.
Czynnikiem, jaki musi być uwzględniony w przypadku algorytmów wymiany
jednostek pamięci, jest
możliwość współdzielenia tych jednostek. Odnosi się to do:
1) współdzielenia przez procesy wykonywane na tym samym procesorze
głównym;
2) współdzielenia przez procesy wykonywane na różnych procesorach fizycznych
w systemie
wieloprocesorowym;
3) współdzielenia przez proces oraz transmisję danych wykonywaną przez
autonomiczne (działające
asynchronicznie) urządzenie zewnętrzne.
Z oczywistych względów nie można usunąć z pamięci jednostki przed
zakończeniem transmisji
danych. W przypadku współdzielenia fragmentu pamięci przez różne procesy
można usunąć ten
fragment dopiero po zmniejszeniu się stanu jego licznika dowiązań do zera.
Uwzględnienie takiej
sytuacji jest szczególnie trudne w systemie wieloprocesorowym - gospodarka
pamięcią dzieloną
w takim systemie musi być gospodarką scentralizowaną.
Mechanizmem umożliwiającym zabezpieczenie strony przed przedwczesnym
usunięciem jej
z pamięci operacyjnej może być bit blokowania umieszczony w opisie strony.
Ustawienie wartości
tego bitu na 1 stanowi informację dla systemu operacyjnego, że strona czasowo
nie powinna podlegać
usuwaniu w ramach algorytmu wymiany.
Stosowanie bitu blokowania może być przydatne zarówno w sytuacji
oczekiwania na zakończenie
transmisji danych czy współdzielenia strony, jak też w sytuacji, kiedy strona
została dopiero co
sprowadzona do pamięci przez jakiś proces i jeszcze nie była ani razu użyta.
Jeżeli proces ma niski
priorytet, mogłoby się zdarzyć, że inny, wysokopriorytetowy proces będzie
usiłował pozbawić go
od razu miejsca w pamięci. Ponieważ sprowadzenie strony do pamięci jest
czasochłonne, należy
zadbać o to, żeby choć przez pewien czas była ona wykorzystana.
Procesy użytkowników mogą z „egoistycznych pobudek” starać się nadużywać
mechanizmu
blokowania stron w pamięci. Z tego powodu systemy operacyjne często traktują
ustawienie bitu
blokowania jako „blokowanie zalecane”, nie „blokowanie obowiązkowe” i w
sytuacji dużego
spiętrzenia wysokopriorytetowych procesów mogą same podejmować ostateczną
decyzję co do
trzymania strony w pamięci lub też jej usunięcia.
Korzyści z implementacji pamięci wirtualnej:
1) podobnie, jak w przypadku nakładkowania, w pamięci mogą być
przechowywane jedynie
fragmenty kodów programów, a nie całe kody;
2) programiści tworzący bardzo duże programy nie muszą wnikać w organizację
wymiany ich
fragmentów pomiędzy pamięcią operacyjną a pamięcią zewnętrzną;
3) można równolegle wykonywać więcej procesów, niż wynika to z ograniczeń
narzucanych przez
rozmiar pamięci operacyjnej, co polepsza wykorzystanie sprzętu i zwiększa
wydajność systemu.
Procesor Intel 80286 umożliwiał realizację pamięci wirtualnej tylko w oparciu o
mechanizm
segmentacji. Procesory o numerach 80386 i wyższych umozliwiają implementację
pamięci wirtualnej
zarówno w oparciu o segmentację, jak i stronicowanie, przy czym zdecydowanie
zalecany jest ten
drugi sposób.