Virtuális memória
Ez a szócikk nem tünteti fel a független forrásokat, amelyeket felhasználtak a készítése során. Emiatt nem tudjuk közvetlenül ellenőrizni, hogy a szócikkben szereplő állítások helytállóak-e. Segíts megbízható forrásokat találni az állításokhoz! Lásd még: A Wikipédia nem az első közlés helye. |
|
Ez a szócikk vagy szakasz lektorálásra, tartalmi javításokra szorul. |
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).
Fogalmak
[szerkesztés]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]- Operating Systems: Three Easy Pieces, by Remzi H. Arpaci-Dusseau and Andrea C. Arpaci-Dusseau. Arpaci-Dusseau Books, 2014. Relevant chapters: Address Spaces Address Translation Segmentation Introduction to Paging TLBs Advanced Page Tables Swapping: Mechanisms Swapping: Policies
- "Time-Sharing Supervisor Programs"[halott link] by Michael T. Alexander in Advanced Topics in Systems Programming, University of Michigan Engineering Summer Conference 1970 (revised May 1971), compares the scheduling and resource allocation approaches, including virtual memory and paging, used in four mainframe operating systems: CP-67, TSS/360, MTS, and Multics.
- LinuxMM: Linux Memory Management.
- Birth of Linux Kernel Archiválva 2008. április 30-i dátummal a Wayback Machine-ben, mailing list discussion.
- The Virtual-Memory Manager in Windows NT, Randy Kath, Microsoft Developer Network Technology Group, 12 December 1992 a Wayback Machine-ben (archiválva 2010. június 22-i dátummal)