Virtuális memória

A Wikipédiából, a szabad enciklopédiából

A virtuális memória egy, az operációs rendszer vagy a számítógép hardvere által nyújtott szolgáltatás (legtöbbször a kettő szoros együttműködése), amit általában egy külső tárolóterület (merevlemez) igénybevételével, a futó program(ok) számára transzparens módon biztosítja, hogy a program végrehajtáskor a központi vagy operatív memória fizikai korlátai észrevétlenek legyenek.

Az operációs rendszer úgy szabadít fel operatív memóriát az éppen futó program számára, hogy a memóriában tárolt, de éppen nem használt blokkokat (lapokat) kiírja a külső tárolóra, amikor pedig ismét szükség van rájuk, visszaolvassa őket. Mivel a merevlemez sebessége töredéke a memória sebességének, nagyon sok múlik azon, hogy a virtuálismemória-kezelő milyen stratégiát alkalmaz az operatív memóriából kimozgatandó lapok kiválasztásakor.

Történelmi háttér[szerkesztés | forrásszöveg szerkesztése]

A virtuális memóriakezelés előzménye az a memórialapozási technológia, amelyet az ismert hobbiszámítógépek is alkalmaznak, amikor a processzor által kezelhető címtartomány (tipikusan 64 KiB) kevés a fizikai memóriák eléréséhez. Szép példa az Enterprise 128: a címtartományt 4 db 16 KiB-os keretre (A-D), a fizikai memóriát pedig max. 256 db ugyanekkora lapra bontották (0-255), és egy kiegészítő címfordító hardver segítségével gondoskodtak arról, hogy minden kerethez minden lap hozzárendelhető legyen, így a kezelhető teljes memória legfeljebb 4 MiB lehetett.

A valódi virtuális memória első megjelenésekor (IBM OS/SVS) a folyamatok továbbra is közös címtartományban futottak, a virtuális memória jelentősége csak annyi volt, hogy a címtartomány a fizikai memóriaméretnél nagyobb lehetett.

A következő lépés (IBM OS/MVS) a folyamatok önálló címtartományba helyezése volt, ami lehetővé tette, hogy a programok fix címre töltődhessenek, illetve kizárta a folyamatok közötti véletlen vagy rosszhiszemű kölcsönhatásokat.

Ma minden korszerű operációs rendszer ilyen szerkezetben működik, amit könnyen ellenőrizhetünk, ha egy egyszerű programmal kiíratjuk egy eljárás, egy statikus adat és egy vermen létező adat címét, többször egymás után/egyszerre elindítva is minden futásnál azonos értékeket kell látnunk, pl:

int main (void)
{
   static int svar;
   int lvar;

   printf ("main=%p svar=%p lvar=%p\n", &main, &svar, &lvar);
   return 0;
 }

BS2000:     main=01000000  svar=010010e0  lvar=01023818
WinXP/i386: main=00401150  svar=0040c470  lvar=0012ff88
linux/i386: main=08048384  svar=08049608  lvar=bfb3d3a4
AIX/32 bit: main=20000c24  svar=20000d44  lvar=2ff22888
AIX/64 bit: main=110000e20 svar=110001038 lvar=ffffffffffff720

Fogalmak[szerkesztés | forrásszöveg szerkesztése]

Lapozás: A memóriakezelésnek az a módja, amelyben a logikai címtartományt (tehát amit a programozó lát) azonos méretű keretekre, a fizikai memóriát ugyanakkora lapokra bontjuk, és a logikai címből (la) az alábbi képlettel álltjuk elő a fizikai címet (pa):

pa = lapt\acute{a}bla \left [ la/lapm\acute{e}ret \right ] * lapm\acute{e}ret + la\ \operatorname{mod}\ lapm\acute{e}ret

A lapméret szokásosan kettő valamelyik hatványa, több rendszerben 4096 (=4 KiB). A laptábla minden bejegyzése tartalmaz egy Present (jelen van) jelzőbitet is, amelynek törölt értéke azt jelenti, hogy ez a cím nem használható, ha a program hivatkozik rá, a futást félbe kell szakítani, és az operációs rendszerhez kell fordulni segítségért.

Kétszintű lapozás: Hogy a laptábla ne nőjön túl nagyra, egyes rendszerek a laptáblát is particionálják, azaz részekre bontják, amelyeket egy fő laptábla ír le. Például ha a 4 GiB-os címtartományt 4 KiB-os lapokra bontjuk (azaz a 32 bites címet 20+12 bitre osztjuk), akkor az egyszintű laptábla 1 048 576 bejegyzésből állna, amelyek nagy része kihasználatlan lenne. Kétszintű lapozásnál felbonthatjuk a logikai címet 32 = 10+10+12 részekre, ekkor az egyes laptáblák mérete 1024 bejegyzés, amelyekből csak annyit kell létrehozni, amennyire tényleg szükség van. (További példa (IBM System/360 16 MiB címtartomány): 24 = 8+5+11, azaz 2 KiB-os lapméret, 256 elemű elsődleges és 32 elemű másodlagos laptábla.) Megjegyzés: ezeket a 'magasabb szintű lapokat' (a példákban a méretük 4 MiB (22 címbit), illetve 64 KiB (16 címbit)), néha pontatlan szóval szegmenseknek nevezik.

Háromszintű lapozás: Itt a címeket értelemszerűen négy csoportra bontjuk, és háromszintű laptáblát használunk. Például az Intel processzorok PAE üzemmódjában a 32 bites címet 2+9+9+12 bites részekre bontjuk, azaz a táblázatok mérete rendre 4, 512, 512 bejegyzés. (A fizikai címtartomány 36 bites (64 GiB).) Megjegyzés: ebben a módban a táblabejegyzések nem 32, hanem 64 bitesek, azaz azonos méretű táblázat csak feleannyi bejegyzést tud tárolni, ezért vált szükségessé egy újabb szint bevezetése.

Négyszintű lapozás: Egy további lépés a bővülő címtartomány kezelésére, például az x86-64 architektúra 48 bites változatai a 48 címbitet így bontják fel 4 KiB-s lapméret esetén: 48=9+9+9+9+12. (A fizikai címtartomány 52 bites (4 PiB = 4 194 304 GiB).)

Laptábla: Az a táblázat, amely a logikai címeket fizikai címekre fordítja. Egy-egy bejegyzés egy memórialapra vonatkozik, tartalma tipikusan (zárójelben a bitek száma egy tipikus rendszerben (4 GiB logikai címtartomány, 4 KiB lapméret)

  • Present-bit (1): a lap a fizikai memóriában van-e
  • Address (22): lap fizikai címe, ha a fizikai memóriában van (az alsó 10 bit mindig nulla, azokat nem tároljuk), egyébként tetszőleges (az OS használhatja, hogy megtalálja a lap tartalmát a lemezen)
  • Védelem (1-3): a lap írható/olvasható/végrehajtható állapota
  • Operációs rendszer számára fenntartott (maradék): az OS például fenntarthat egy bitet annak jelölésére, hogy a lap rezidens, azaz nem szabad kilapozni a memóriából.

Laphiba (page fault): Az előbb írt eset, amikor a laptábla bejegyzés jelzi, hogy az elérni kívánt lap nincs a fizikai memóriában, illetve a logikai cím nagyobb egy előre megadott korlátnál. Ha a memória-hozzáférési igényt az operációs rendszer jogosnak ítéli, akkor intézkedik, hogy a kérdéses lap a fizikai memóriába betöltődjön (ehhez esetleg ugyanezen, vagy valamelyik másik processz egy lapját fel kell áldozni), és a laptáblabejegyzést aktualizálja.

Érvénytelen laphiba (invalid page fault): Ha az operációs rendszer úgy ítéli meg, hogy a folyamat olyan memóriacímet próbált elérni, amihez nem tartozott memória, vagy tiltott módon akar hozzáférni a memóriához (lásd a memóriavédelemnél), akkor a program futását megszakítja. Szegmentálás esetén az is érvénytelen laphibát okoz, ha a logikai cím offset része nagyobb a szegmens méreténél.

Kettős laphiba: Az a szerencsétlen eset, amikor a laphibát kezelő rutin is laphibát okoz. Ilyenkor rendszerint csak az újraindítás segíthet.

Memóriavédelem: Gyakran kombinálják a virtuális memóriakezelést védelmi rendszerrel, ekkor az egyes lapokat/szegmenseket olvasható, írható és végrehajtható attribútummal (illetve ezek kombinációival) látjuk el. Ilyen védelem esetén egy olyan memória-hozzáférés, amely egy adatterületet próbál végrehajtani, vagy egy nem írható területre akar írni, érvénytelen laphibát okoz.
Megjegyzés: bizonyos esetekben szükséges lehet, hogy a veremre kódrészleteket helyezhessünk, vagyis hogy a verem egyszerre írható és végrehajtható attribútumú legyen, ez viszont biztonsági kockázatot jelenthet (ha a támadónak sikerül rávennie egy programot, hogy egy vermen elhelyezett puffert 'túlírjon', akkor megrongálhatja a vermet, és tetszőleges kódot végrehajtathat).

Osztott memória (Shared/Common Memory): Minden korszerű rendszer külön virtuális címtartományba helyezi az egyes folyamatokat (MVS), hogy azok ne zavarhassák egymást, viszont külön eszközt biztosít arra, hogy a folyamatok, ha akarnak, használhassanak megosztott memóriatartományokat.

Szegmentálás: A memóriakezelésnek az a módja, amely a programozó közreműködésével történik (szemben a lapozással, amely a programozó számára transzparens), a logikai címek szegmens és offset részből állnak. Az egyes szegmensek mérete különböző, ezért a szegmenstábla nem csak cím-, hanem méretinformációkat is tartalmaz. A címfordítás képlete:

pa = szegmenst\acute{a}bla \left [ la_{szegmens} \right ] + la_{offszet}

A szegmentálásra jó példa az Intel 80286-os CPU védett üzemmódja, melyben a fizikai címtartomány 16 MiB, a szegmensek maximális mérete 64 KiB, maximális számuk 8192 (folyamatonként).

Napjainkban a szegmentálás a lapozásnál jóval ritkábban használatos, ha mégis alkalmazzák, azt rendszerint a lapozással kombinálva teszik.

Rezidens lap (szegmens): Olyan memórialap (szegmens), amit az operációs rendszer sosem enged a fizikai memóriából kilapozni. Ilyenek a rendszermag legalapvetőbb szintű adat és programterületei (például a megszakításkezelő, lapozó és ütemező rutinok), a lap- illetve szegmenstáblák, IO-pufferek; egyes rendszerekben a felhasználói processzek is rezidenssé tehetnek (korlátozott méretű) memóriatartományokat. (Jellemző példa: gondos programozó a titkos kommunikációhoz generált kulcsot rezidens memóriában tárolja, hogy az ne kerülhessen lemezre.)

Hibakeresés támogatása: A programhibák megtalásában segít, ha egy inicializálatlan pointer használata érvénytelen laphibát okoz. Mivel az ilyen pointerek gyakran nulla (NULL, nil) értékez tartalmaznak, elterjedt megoldás, hogy a virtuális címtartomány elején és végén 4 KiB-t (vagy akár 64 KiB-t) lefoglalunk, és sosem lapozunk oda fizikai memóriát. Vannak olyan hibakereső eszközök (pl. Electric Fence), amik hasonló technikát használnak a dinamikusan foglalt memóriatartományok túlcímzésének leleplezésére: a kért memóriát úgy helyezik el, hogy a vége egy memórialap végén legyen, közvetlenül mögötte pedig üres legyen a virtuális memória, vagyis a túlcímzés érvénytelen laphibát okozzon.

Egyszeres vagy többszörös virtuális memória (angol rövidítéssel SVS illetve MVS). A virtuális memória első megvalósításai csak egyetlen virtuális címtartományt biztosítottak, az összes folyamat ezen osztozott. Az utolsó elterjedt rendszer, ami ezen az elven az alapult, a 16-bites Windows 3.x volt, az annál korszerűbb rendszerek minden folyamatnak külön virtuális címtartományt biztosítanak. (Vesd össze az Osztott memória szócikkel.)

Linuxos megközelítésben[szerkesztés | forrásszöveg szerkesztése]

A Linux támogatja a virtuális memória (virtual memory) kezelését. Ez azt jelenti, hogy képes lemezterületet RAM bővítésként kezelni, azaz a felhasználható memória mérete ennek megfelelően nő. A kernel a pillanatnyilag nem használt memóriablokkokat kiírja a merevlemezre, így a felszabaduló memória más célra felhasználható lesz. Amint az eredeti tartalomra újra szükség lesz, a kernel visszaolvassa a memóriába. Ez teljesen láthatatlan a felhasználó számára; a Linux alatt futó programok csak a nagy memóriaterületet látják és nem veszik észre, hogy bizonyos részei időnként a lemezre kerülnek. Természetesen a merevlemez írása és olvasása sokkal lassabb, mint a valódi memória használata, ami a programok futását lassítja. (A memória és a merevlemez olvasási és írási sebessége közt pár ezerszeres szokott lenni a különbség.)

A merevlemez azon részét, amelyet a virtuális memória használ cserehelynek, vagy swap partíciónak nevezzük.

Windows NT-s megközelítésben[szerkesztés | forrásszöveg szerkesztése]

A virtuális memória segítségével minden alkalmazásnak lehetősége van arra, hogy elérje a számára szükséges memória címterületet. A Windows NT ezt úgy oldja meg, hogy minden alkalmazásnak a rendelkezésére bocsátja a szükséges virtuális memóriaterületet, majd pedig ha szükséges belapozza ezt a területet a fizikai memóriába (RAM-ba). A lapozás 4 KB-os blokkokban történik, amiket „lapoknak” nevezünk. Minden virtuális memóriához 4 GB-os címtér tartozhat. Mivel kevés rendszer tartalmaz elég RAM-ot ahhoz, hogy minden alkalmazásnak ki lehessen osztani 4 GB-ot, az operációs rendszer felosztja a fizikai RAM oldalakat a virtuális memória terek között. Érdemes a méretét manuálisan megadni, elkerülve a lemez töredezettségét (alapértelmezésként a Windows dinamikusan állítja azt). Továbbá ha egy fizikailag különálló lemezre állítjuk be (nem külön partíció) akkor némi sebességnövekedést érhetünk el a párhuzamos feldogozásnak köszönhetően.