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

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, ez egyes folyamatok külön-külön címtartományban futnak (amit az egyes folyamatok szálai közösen használnak).

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 az alábbi képlettel álltjuk elő a fizikai címet:

 

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).)

Ötszintű lapozás: A következő lépés a címtartomány növelésére, az x86-64 architektúra 57 címbitet támogató processzorai 4 KiB-s lapméret esetén így bontják fel a címbiteket: 57=9+9+9+9+9+12.

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 4 GiB logikai címtartomány, 4 KiB lapméret: (zárójelben a bitek száma egy tipikus rendszerben)

  • Present-bit (1): a lap a fizikai memóriában van-e
  • Address (22): lap fizikai címe, ha az 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. (Említsük meg, hogy egyetlen gépi utasítás akár több laphibát is okozhat (egy kétcímes utasítás esetén akár négyet is); minden hiba aktiválja az OS hibakezelő rutinját, aminek a lefutása utána a program újra a megszakadt utasítással folytatódik.)

É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. Megjegyzés: a jelen gyakorlatban a 'segmentation fault' (röviden SEGFAULT) kifejezést 'érvénytelen laphiba' értelemben használjuk.

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).

Véletlenszerűsített címtartomány-felosztás (ASLR - Address space layout randomization): a felhasználói programok esetleges biztonsági hibáinak kihasználását nehezíti az a technika, miszerint a program különböző területei (kód (text), statikus adat (data, bss), verem (stack), dinamikus adat (heap)) minden programfutáskor véletlenszerű helyre kerülnek a virtuális címtartományban. Értelemszerűen minél nagyobb a címtartomány, annál több lehetőség van véletlenszerűsítésre; ha például 16 megabájtos határra akarunk illeszteni egy adatterület, akkor 32 bites címtartományban 256 lehetőségünk van, 64 bitesben 1099511627776.

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:

 

A szegmentálásra gyakran felhozott 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). Ezek a szegmensek azonban inkább 'változó méretű lapoknak' tekinthetők; a gyakorlatban sem a felhasználói programoknak, sem az operációs rendszernek nem voltak praktikusak: az előbbiek számára gyakran túl kicsi volt a 64 KiB-os limit, az utóbbi számára viszont kisebb (pl 1-4 KiB), de rögzített méretű lapok használata lett volna kényelmes.

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álá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.)

Rendszermag (kernel) részére lefoglalt címtartomány: Egyes rendszerekben a logikai címtartomány egy része (tipikusan a legeleje vagy a legvége) fixen le van foglalva a rendszermag számára; például 32 bites Windows rendszereken ez a foglalt rész választhatóan egy vagy két gigabyte (tehát a címtartomány fele vagy negyede) — ez magában foglalja a 'shared code' részére lefoglalt címtartományt is (lásd a következő pontot).

Osztott kód (shared code) részére lefoglalt címtaromány: az osztott kódot (DLL, shared library) támogató rendszerekben tipikusan előre kijelölt címtartományok szolgálnak ezek elhelyezésére, külön az kód- és külön az adatterületeknek: az előbbiek egyszeresek (minden processz ugyanazt a kódot használja), az utóbbiak processzenként egy-egy példányban léteznek. Például IBM AIX-rendszerben a 32 bites címtartomány 16 darab 256 MiB méretű szegmensre van felosztva (ezeket a 0-F hexadecimális számjegyekkel jelölhetjük), ebből egy van fenntartva a kód (a D-szegmens) és egy az adatterületek számára (az F-szegmens). (A kernel helye a 0-szegmens, a felhasználói programé az 1-szegmens, a felhasználói program adatterületei pedig 1-9 szegmens használhatnak (max 2.25GiB).)

Újraindítható és folytatható utasítások: Tipikus esetben egy-egy gépi utasítás végrehajtását a CPU el sem kezdi, ha az abban szereplő memóriacímek közül egy vagy több nem elérhető; ilyen esetekben a processzor kivételt (exception) generál (lásd a laphiba résznél), annak kezelése után újra megpróbálja a kérdéses utasítás végrehajtását. Egyes processzorok rendelkeznek olyan utasításokkal, amelyek nagy memóriaterületek másolására képesek (pl: IBM System/370: MVCL/MVCR; Intel x86: REP MOVS), ezeknél a laphiba a végrehajtás alatt léphet fel, és annak kezelése után a végrehajtás folytatható. (Ennek persze szükséges feltétele, hogy az utasítás minden paramétere egy-egy regiszterben legyen).

Lapozófájl (swapfile, pagefile): Az a lemezterület, amely a fizikai memóriában el nem férő memórialapokat tartalmazza. Ez rendszertől függően lehet fizikai lemez, lemez-partíció, vagy fájl. Utóbbi esetben előre maximális mérettel létrehozott, egybefüggő (nem darabolódott) fájlt érdemes használni (illetve egyes rendszerekben csak ilyet lehet használni). A fizikai memória és a lapozófájl közötti adatmozgatás az operációs rendszer feladata; érdemes megemlíteni, hogy számos rendszerben nem csak a dedikált lapozófájl használható így, hanem más lemezfájlok is, amelyek tartalmát változatlanul (átalakítás nélkül) akarjuk a memóriában viszontlátni, például végrehajtó programok (executable), programkönyvtárak (shared library, DLL), illetve a felhasználó által memóriába lapozott fájlok (memory mapped file).

További információk

szerkesztés