11. Memóriagazdálkodás (Balogh Ádám és Iványi Antal) A mai számítógépekre az jellemzo, hogy memóriájuk több különbözo kapacitású, hozzáférési ideju és költségu szintbol áll. A processzor számára közvetlenül elérheto szintet zikai memóriának (vagy röviden memóriának), a további szinteket pedig háttértárnak nevezzük. Rendszerint több program fut egyidejuleg, amelyeknek együttes tárigénye nagyobb, mint a zikai memória kapacitása. Ezért a memóriát a folyamatok között el kell osztani. Ebben a fejezetben a memóriagazdálkodás alapveto algoritmusaival foglakozunk. A 11.1. alfejezetben a statikus és dinamikus partícionálás, majd a 11.2. alfejezetben a lapozás legismertebb módszereit tekintjük át. A 11.3. alfejezetben az operációs rendszerek történetének legnevezetesebb anomáliáit a FIFO lapcserélési algoritmus, az átfedéses memória és a listás ütemezo algoritmusok meglepo tulajdonságait elemezzük. Végül a 11.4. alfejezetben annak az optimalizálási feladatnak a közelíto algoritmusait tekintjük át, melyben adott méretu fájlokat kell minimális számú mágneslemezen elhelyezni. 11.1. Partícionálás A memória programok közötti elosztásának egyszeru módja, hogy a teljes címtartományt részekre bontjuk, és minden folyamat egy ilyen részt kap. Ezeket a részeket partícióknak nevezzük. Ehhez a megoldáshoz nincs szükség különösebb hardvertámogatásra, csupán az kell, hogy egy programot különbözo címekre lehessen betölteni, azaz a programok áthelyezhetok legyenek. Erre azért van szükség, mert nem tudjuk garantálni, hogy egy program mindig ugyanabba a partícióba kerüljön, hiszen összességében szinte mindig több futtatható program van, mint amennyi a memóriába befér. Ráadásul azt sem tudjuk meghatározni, hogy mely programok futhatnak egyszerre, és melyek nem, hiszen a folyamatok általában egymástól függetlenek, sokszor különbözo felhasználók a tulajdonosaik. Így még az is elofordulhat, hogy egyszerre többen futtatják ugyanazt a programot, és a példányok különbözo adatokkal dolgoznak, tehát nem lehetnek ugyanott a memóriában. Az áthelyezés szerencsére könnyen elvégezheto, ha a szerkesztoprogram nem abszolút, hanem relatív címekkel dolgozik, azaz nem a pontos memóriabeli helyeket, hanem egy kezdocímhez viszonyított eltolásokat használ minden címzésnél. Ezt a módszert báziscímzésnek nevezzük,
11.1. Partícionálás 457 ahol a kezdocímet az úgynevezett bázisregiszter tartalmazza. A legtöbb processzor ismeri ezt a címzési módot, ezért a program nem lesz lassabb, mintha abszolút címeket használna. Báziscímzés használatával az is elkerülheto, hogy a program egy hiba vagy szándékos felhasználói magatartás folytán valamely másik, alacsonyabb memóriabeli címeken elhelyezkedo program adatait kiolvassa, esetleg módosítsa. Ha a módszert kiegészítjük egy másik, úgynevezett határregiszter használatával, amely a legnagyobb megengedett eltolást, azaz a partíció méretét adja meg, akkor azt is biztosítani tudjuk, hogy egy program számára a többi, nála magasabb memóriabeli címeken elhelyezkedo program se legyen hozzáférheto. A partícionálást régebben gyakran alkalmazták nagygépes operációs rendszerekben. A modern operációs rendszerek többsége azonban virtuális memóriakezelést használ, amihez viszont már különleges hardvertámogatásra van szükség. A partícionálás, mint memóriafelosztási módszer azonban nemcsak operációs rendszerekben alkalmazható. Ha a gépi kódhoz közeli szinten programozunk, akkor elofordulhat, hogy különbözo, változó méretu adatszerkezeteket amelyek dinamikusan jönnek létre, illetve szunnek meg kell elhelyeznünk egy folytonos memóriaterületen. Ezek az adatszerkezetek hasonlítanak a folyamatokhoz, azzal a kivétellel, hogy olyan biztonsági problémákkal, mint a saját területen kívülre való címzés, nem kell foglalkoznunk. Ezért az alább felsorolandó algoritmusok nagy része kisebb-nagyobb módosításokkal hasznunkra lehet alkalmazások fejlesztésénél is. Alapvetoen kétféleképpen lehet felosztani egy címtartományt partíciókra. Az egyik módszer, hogy kezdetben a még üres címtartományt osztjuk fel elore meghatározott méretu és számú részre, és ezekbe próbáljuk meg folyamatosan belehelyezni a folyamatokat vagy más adatszerkezeteket, illetve eltávolítjuk belolük, ha már nincs rájuk szükség. Ezek a rögzített partíciók, hiszen mind helyük, mind méretük elore, az operációs rendszer vagy az alkalmazás indulásakor rögzítve van. A másik módszer, hogy folyamatosan jelölünk ki darabokat a címtartomány szabad részérol az újonnan keletkezo folyamatok vagy adatszerkezetek számára, illetve megszunésükkor újra szabaddá nyilvánítjuk azokat. Ezt a megoldást hívjuk a dinamikus partíciók módszerének, mivel a partíciók dinamikusan keletkeznek, illetve szunnek meg. Mindkét módszernek vannak mind elonyei, mind hátrányai, és megvalósításuk teljesen különbözo algoritmusokat igényel. Ezeket tekintjük át a következokben. 11.1.1. Rögzített partíciók A rögzített partíciók módszerénél a címtartomány felosztása elore rögzített, és futás közben nem változtatható. Operációs rendszerek esetén ez úgy történik, hogy a rendszergazda egy táblázatban meghatározza a partíciókat, és a rendszer következo betöltodésekor a felosztás alapja ez a táblázat. Amikor az elso felhasználói alkalmazás elindul, a címtartomány már partícionálva van. Alkalmazásoknál a partícionálást akkor kell elvégezni, amikor még egyetlen adatszerkezet sincs a felosztandó memóriaterületen. Ezután a kész partíciókban lehet elhelyezni a különbözo méretu adatszerkezeteket. A továbbiakban csakis az operációs rendszerekbeli megvalósítást vizsgáljuk, a feladat és az algoritmusok átfogalmazását adott alkalmazáshoz az Olvasóra bízzuk, hiszen ezek alkalmazásfajtánként egymástól nagyon eltéroek is lehetnek. A címtartomány felosztásakor azt kell gyelembe venni, hogy mekkora folyamatok és milyen arányban fognak a rendszerbe érkezni. Nyilvánvalóan van egy maximális méret, amelyet túllépo folyamatok nem futhatnak az adott rendszerben. A legnagyobb partíció mé-
458 11. Memóriagazdálkodás (Balogh Ádám és Iványi Antal) rete ezzel a maximummal egyezik meg. Az optimális partícionáláshoz gyakran statisztikai felméréseket kell végezni, és a rendszer következo újraindítása elott a partíciók méretét ezen statisztikák alapján kell módosítani. Ennek mikéntjével most nem foglalkozunk. Mivel konstans számú (m) partíciónk van, adataikat tárolhatjuk egy vagy több állandó hosszúságú vektorban. A partíciók konkrét helyével absztrakt szinten nem foglalkozunk: feltételezzük, hogy ezt egy konstans vektor tárolja. Egy folyamatot úgy helyezünk el egy partícióban, hogy sorszámát rögzítjük a folyamat leírójában. Természetesen a konkrét megvalósítás ettol eltérhet. A partíciók méretét a méret[1.. m] vektor tartalmazza. Folyamatainkat 1-tol n-ig számozzuk. Azt, hogy egy partícióban épp mely folyamat fut, a part[1.. m] vektor tartalmazza, melynek párja a hely[1.. n] vektor, ami azt adja meg, hogy egy folyamat mely partícióban fut. Egy folyamat vagy fut, vagy pedig várakozik arra, hogy bekerüljön egy partícióba, és ott futhasson. Ezt az információt a vár[1.. n] vektor tartalmazza: ha az i-edik folyamat vár, akkor vár[i] = IGAZ, egyébként pedig vár[i] = HAMIS. A folyamatok helyigénye különbözik. A helyigény[1.. n] azt adja meg, hogy legalább mekkora partíció szükséges a folyamatok futtatásához. Ha különbözo méretu partícióink és különbözo helyigényu folyamataink vannak, akkor nyilván nem szeretnénk, hogy a kis folyamatok a nagy partíciókat foglalják el, miközben a kisebb partíciók üresen maradnak, hiszen oda nem férnének be a nagyobb folyamatok. Ezért azt kell elérnünk, hogy minden partícióba az kerüljön betöltésre a várakozó folyamatok közül, amely oda befér, és nincs nála nagyobb, ami szintén beférne. Ezt biztosítja a következo algoritmus: LEGNAGYOBB-BEFÉRO(hely, helyigény, méret, part, vár) 1 for j 1 to m 2 do if part[j] = 0 3 then BETÖLT-LEGNAGYOBB(hely, helyigény, méret, j, part, vár) A legnagyobb olyan folyamat megtalálása, amelynek helyigénye nem nagyobb egy adott méretnél, egy egyszeru feltételes maximumkeresés. Amennyiben egyáltalán nem találunk a feltételnek megfelelo folyamatot, kénytelenek vagyunk a partíciót üresen hagyni. BETÖLT-LEGNAGYOBB(hely, helyigény, méret, p, part, vár) 1 max 0 2 ind 0 3 for i 1 to n 4 do if vár[i] = IGAZ és max < helyigény[i] méret[p] 5 then ind i 6 max helyigény[i] 7 if ind > 0 8 then part[p] ind 9 hely[ind] p 10 vár[ind] HAMIS A folyamatokat partícióba tölto algoritmusok helyességének alapveto kritériuma, hogy
11.1. Partícionálás 459 ne töltsenek nagyobb folyamatot egy partícióba, mint amekkora belefér. Ezt a feltételt teljesíti a fenti algoritmus, hiszen visszavezetheto a feltételes maximumkeresés tételére pontosan az említett feltétellel. Egy másik nagyon fontos kritérium, hogy ne töltsön egy partícióba több folyamatot, illetve ne töltsön egyetlen folyamatot több partícióba. Az elso eset azért zárható ki, mert csakis azokra a partíciókra hajtjuk végre a BETÖLT-LEGNAGYOBB algoritmust, amelyekre part[ j] = 0, és ha betöltünk egy folyamatot a p-edik partícióba, akkor part[p]-nek értékül adjuk a betöltött folyamat sorszámát, ami pozitív egész. A második eset bizonyítása is hasonló: a feltételes maximumkeresés feltétele kizárja azokat a folyamatokat, amelyekre vár[i] = HAMIS, és ha az ind-edik folyamatot egynél többször betöltjük valamely partícióba, akkor vár[ind]-nek HAMIS értéket adunk. Az, hogy az algoritmus nem tölt nagyobb folyamatot egyik partícióba sem, mint amekkora belefér, illetve hogy egyetlen partícióba sem tölt egynél több folyamatot és hogy egyetlen folyamatot sem tölt egynél több partícióba, még nem elég. Ezeket az üres algoritmus is teljesíti. Ezért mi többet követelünk: mégpedig azt, hogy ne hagyjon szabadon egyetlen partíciót sem, ha van olyan folyamat, amely beleférne. Ehhez szükségünk van egy invariánsra, amely az egész ciklus során fennáll, és a ciklus végén biztosítja ezt a feltételt is. Legyen az invariánsunk az, hogy j darab partíció megvizsgálása után nem létezik olyan pozitív k j, amelyre part[k] = 0, és amelyre van olyan pozitív i n, hogy vár[i] = IGAZ és helyigény[i] méret[k]. Teljesül: Az algoritmus elején j = 0 darab partíciót vizsgáltunk meg, így egyáltalán nem létezik pozitív k j. Megmarad: Ha az invariáns teljesül j-re a ciklusmag elején, akkor eloször is azt kell látnunk, hogy ugyanerre a j-re a ciklus végén is teljesülni fog. Ez nyilvánvaló, hiszen az elso j darab partícióhoz nem nyúlunk a ( j + 1)-edik vizsgálata során, a bennük levo folyamatokra pedig vár[i] = HAMIS, ami nem felel meg a BETÖLT-LEGNAGYOBB algoritmusban található feltételes maximumkeresés feltételének. A ( j + 1)-edik partícióra pedig azért fog teljesülni az invariáns a ciklusmag végén, mert ha van a feltételnek megfelelo folyamat, azt a feltételes maximumkeresés biztosan megtalálja, hiszen a feltételes maximumkeresésünk feltétele megegyezik az invariánsunk egyes partíciókon értelmezett követelményével. Befejezodik: Mivel a ciklus egyesével halad végig egy rögzített intervallumon, biztosan be is fog fejezodni. Mivel a ciklusmag pontosan annyiszor hajtódik végre, mint ahány partíció van, a ciklus befejezodésekor igaz lesz, hogy nem létezik olyan pozitív k m, amelyre part[k] = 0, és amelyre van olyan pozitív i n, hogy vár[i] = IGAZ és helyigény[i] méret[k], azaz nem hagytunk szabadon egyetlen olyan partíciót sem, amelyben lenne folyamat, ami belefér. A LEGNAGYOBB-BEFÉRO algoritmus 1 3. soraiban lévo ciklus mindig teljes egészében lefut, tehát a ciklusmag Θ(m)-szer hajtódik végre. A ciklusmag egy feltételes maximumkeresést hajt végre az üres partíciókon vagy azokon, amelyekre part[ j] = 0. Mivel a BETÖLT-LEGNAGYOBB 4. sorában lévo feltételt minden j-re ki kell értékelni, ezért a feltételes maximumkeresés Θ(n) lépést igényel. Bár azokra a partíciókra, amelyekre part[ j] > 0, a betölto eljárás nem hívódik meg, a futási ido szempontjából legrosszabb esetben akár minden partíció üres lehet, ezért az algoritmus összes lépésszáma Θ(mn). Az, hogy az algoritmus minden üres partícióba betölt egy várakozó folyamatot, ami
460 11. Memóriagazdálkodás (Balogh Ádám és Iványi Antal) befér, sajnos nem mindig elegendo. Gyakran szükség van arra is, hogy egy folyamat valamilyen megadott határidon belül memóriához jusson. Ezt azonban a fenti algoritmus akkor sem biztosítja, ha az egyes folyamatok futási idejére felso korlátot tudunk adni. Amikor ugyanis újra és újra lefuttatjuk az algoritmust, mindig jöhetnek újabb folyamatok, amik kiszoríthatják a régóta várakozókat. Tekintsünk erre egy példát. 11.1. példa. Legyen két partíciónk, 5 kb és 10 kb méretekkel. Kezdetben két folyamatunk van, egy 8 és egy 9 kb helyigényu. Mindkét folyamat futása 2 2 másodpercig tart. Azonban az elso másodperc végén megjelenik egy újabb, szintén 9 kb helyigényu és 2 másodperc futási ideju folyamat, és ez megismétlodik 2 másodpercenként, azaz a harmadik, az ötödik stb. másodpercekben. Ha most megvizsgáljuk az algoritmusunkat, akkor láthatjuk, hogy annak mindig két folyamat közül kell választania, és mindig a 9 kb helyigényu lesz a nyertes. A 8 kb-os, noha nincs más partíció, amelybe beférne, soha nem fog memóriához jutni. Ahhoz, hogy a fent említett követelményt is teljesítsük, módosítani kell az algoritmusunkat: gyelembe kell vennünk, hogy mely folyamatok azok, amelyek már túl hosszú ideje várakoznak, és elonyben kell részesítenünk oket minden más folyamattal szemben akkor is, ha kisebb a helyigényük, mint azoknak. Az új algoritmusunk ugyanúgy megvizsgál minden partíciót, mint az elozo. LEGNAGYOBB-VAGY-RÉGÓTA-VÁRAKOZÓ-BEFÉRO(hely, helyigény, küszöb, méret, part, vár) 1 for j 1 to m 2 do if part[j] = 0 3 then BETÖLT-LEGNAGYOBB-VAGY-RÉGÓTA-VÁRAKOZÓ(hely, helyigény, küszöb, méret, j, part, vár) Azonban a betöltésnél meg kell vizsgálnunk minden folyamatra, hogy az mennyi ideje várakozik. Mivel az algoritmust mindig akkor futtatjuk, amikor egy vagy több partíció felszabadul, nem tudjuk a konkrét idot vizsgálni, csak azt, hogy hányszor nem tettük be egy olyan partícióba, amelybe befért volna. Ehhez módosítanunk kell a feltételes maximumkeresés algoritmusát: azokon az elemeken is muveletet kell végeznünk, amelyek ugyan teljesítik a feltételt (memóriára várakoznak és be is férnének), de nem a legnagyobbak ezek közül. Ez a muvelet egy számláló növelése. A számlálóról feltételezzük, hogy a folyamat keletkezésekor a 0 értéket veszi fel. Ezenkívül a feltételt is kicsit módosítanunk kell: ha a számláló értéke egy elemnél túl magas (azaz egy bizonyos küszöb feletti), és az eddig talált legnagyobb helyigényu folyamat számlálójánál is nagyobb, akkor lecseréljük azt erre az elemre. Az, hogy az algoritmus nem helyez több folyamatot ugyanabba a partícióba, ugyanúgy látható, mint az elozo algoritmusnál, hiszen a külso ciklust és az abban található elágazás feltételét nem változtattuk meg. A másik két kritérium bizonyításához, azaz hogy nem kerül egy folyamat több partícióba vagy olyan partícióba, amibe nem fér, azt kell látnunk, hogy a feltételes maximumkeresés feltételét úgy alakítottuk át, hogy ez a tulajdonság megmaradt. Látható, hogy a feltételt kettébontottuk, így elso része pontosan ez, amit követelünk, és ha ez nem teljesül, az algoritmus biztosan nem helyezi be a folyamatot a partícióba. Az a tulajdonság is megmarad, hogy nem hagyunk üresen partíciót, hiszen a feltételt, amely alapján kiválasztottuk a folyamatot, nem szukítettük, hanem csak bovítettük, így ha az elozo meg-
11.1. Partícionálás 461 talált minden folyamatot, ami megfelel a kritériumnak, akkor az új algoritmus is megtalálja ezeket. Csupán a kritériumnak megfelelo folyamatok közötti sorrend az, amit változtattunk. A ciklusok lépésszáma sem változott, mint ahogy az sem, hogy a belso ciklust milyen feltétel mellett kell elkezdeni végrehajtani. Tehát az algoritmus lépésszámának nagyságrendje is ugyanannyi, mint az eredeti algoritmusé. Meg kell még vizsgálnunk, hogy az algoritmus teljesíti-e azt a feltételt, hogy egy folyamat csak meghatározott ideig várakozhat memóriára, amennyiben feltételezzük, hogy ismerünk valamilyen p felso korlátot a folyamatok futási idejére (ellenkezo esetben a probléma megoldhatatlan, hiszen minden partíciót elfoglalhat egy-egy végtelen ciklus). Továbbá azt is minden esetben fel kell tételeznünk, hogy a rendszer nincs túlterhelve, azaz tudunk egy q felso becslést adni a várakozó folyamatok számára minden idopillanatban. Ekkor látható, hogy egy folyamatnak egy adott partícióba kerüléshez legrosszabb esetben meg kell várnia az elotte álló folyamatokat, azaz azokat, amelyeknek a számlálója nagyobb, mint az övé (legfeljebb q darab), valamint legfeljebb küszöb darab nála nagyobb folyamatot. Így valóban tudunk egy felso korlátot adni arra, hogy egy folyamat legfeljebb mennyi ideig várakozhat memóriára: (q + küszöb)p ideig. Az algoritmus pszeudokódja a következo. BETÖLT-LEGNAGYOBB-VAGY-RÉGÓTA-VÁRAKOZÓ(hely, helyigény, küszöb, méret, p, part, vár) 1 max 0 2 ind 0 3 for i 1 to n 4 do if vár[i] = IGAZ és helyigény[i] méret[p] 5 then if (pont[i] > küszöb és pont[i] > pont[ind]) vagy helyigény[i] > max 6 then pont[ind] pont[ind] + 1 7 ind i 8 max helyigény[i] 9 else pont[i] pont[i] + 1 10 part[j] ind 11 hely[ind] p 12 vár[ind] HAMIS 11.2. példa. Az elozo példánkban a 8 kb helyigényu folyamatnak küszöb + 1 = k darab másikat kell megvárnia, melyek mindegyike 2 másodpercig tart, azaz a 8 kb helyigényu folyamatnak pontosan 2k másodpercet kell várnia arra, hogy bekerüljön a 10 kb méretu partícióba. Eddigi algoritmusainkban a folyamatok abszolút helyigényét vettük a rangsorolás alapjául, azonban ez a módszer nem igazságos: ha egy partícióba két folyamat is beleférne, és egyik sem férne bele kisebb partícióba, nem számít a méretkülönbség, hiszen elobb-utóbb úgyis ugyanabban, vagy egy másik, a vizsgáltnál nem kisebb partícióban kell elhelyezni a kisebbet is. Így az abszolút helyigény helyett annak a legkisebb partíciónak a méretét kellene gyelembe venni rangsoroláskor, amelybe befér az adott folyamat. Sot, ha a partíciók méretük szerint növekvoen rendezve vannak, akkor elég a partíció sorszáma is a rendezett listában. Ezt a sorszámot nevezzük a folyamat ragjának. A rangok számítását a következo
462 11. Memóriagazdálkodás (Balogh Ádám és Iványi Antal) algoritmus végzi. RANG-SZÁMÍT(helyigény, méret, rang) 1 rend RENDEZ(méret) 2 for i 1 to n 3 do u 1 4 v m 5 rang[i] (u + v)/2 6 while rend[rang[i]] < helyigény[i] vagy rend[rang[i] + 1] > helyigény[i] 7 do if rend[rang[i]] < helyigény[i] 8 then u rang[i] + 1 9 else v rang[i] 1 10 rang[i] (u + v)/2 11 return rang Jól látható, hogy ez az algoritmus elobb rendezi a partíciókat méretük szerint növekvo sorrendbe, majd minden folyamathoz kiszámítja annak rangját. Ezt azonban csak kezdetben kell megtennünk, valamint akkor, ha új folyamat jön. Ez utóbbi esetben azonban már csak az új folyamatokra kell végrehajtanunk a belso ciklust. A rendezést sem kell újra végrehajtanunk, hiszen a partíciók nem változnak a rendszerünk muködése során. Az egyetlen, amire szükség van, a folyamat beillesztése a megfelelo két partíció közé, amelyek közül a nagyobba befér, a kisebbe már nem. Ez egy logaritmikus kereséssel megoldható, amirol tudjuk, hogy helyes is. Már csak a rangszámítás lépésszámát kell megmondanunk: az összehasonlításos rendezés Θ(m lg m), a logaritmikus keresés pedig Θ(lg m), amit n darab folyamatra kell végrehajtanunk. Így az összes lépésszám Θ((n + m) lg m). A rangok kiszámítása után ugyanazt kell tennünk, mint eddig. RÉGÓTA-VÁRAKOZÓ-VAGY-KISEBBE-NEM-FÉR(hely, helyigény, méret, part, vár) 1 for i 1 to m 2 do if part[i] = 0 3 then BETÖLT-RÉGÓTA-VÁRAKOZÓ-VAGY-KISEBBE-NEM(hely, helyigény, méret, i, part, vár) Különbség egyedül a folyamatot egy adott partícióba tölto algoritmusban van: nem a méret, hanem a rang vektoron kell a feltételes maximumkeresést végrehajtani. Az algoritmus helyessége az algoritmus elozo változatának és a rangszámítás algoritmusának helyességébol következik. Lépésszámának nagyságrendje is az elozo változatéval egyezik meg. 11.3. példa. Az elozo példát tekintve látszik, hogy a 8 kb helyigényu és a 9 kb helyigényu folyamatok mindegyike csak a 10 kb méretu partícióba fér be, az 5 kb méretube nem. Ezért a rangjuk is meg fog egyezni (ketto lesz), így érkezési sorrendben kerülnek elhelyezésre, tehát a 8 kb méretu eloször vagy másodszor kapja meg az általa igényelt memóriaterületet.
11.1. Partícionálás 463 BETÖLT-RÉGÓTA-VÁRAKOZÓ-VAGY-KISEBBE-NEM(hely, helyigény, méret, p, part, vár 1 max 0 2 ind 0 3 for i 1 to n 4 do if vár[i] és helyigény[i] méret[p] 5 then if (pont[i] > küszöb és pont[i] > pont[ind]) vagy rang[i] > max 6 then pont[ind] pont[ind] + 1 7 ind i 8 max rang[i] 9 else pont[i] pont[i] + 1 10 part[p] ind 11 hely[ind] p 12 vár[ind] HAMIS 11.1.2. Dinamikus partíciók A dinamikus partícionálás egészen máshogy történik, mint a rögzített. Ennél a módszernél nem azt kell eldönteni, hogy egy üres partícióban mely folyamatot helyezzük el, hanem azt, hogy egy folyamatot hol helyezünk el egy adott, több különbözo méretu darabból álló memóriaterületen, azaz hol hozunk neki létre dinamikusan egy partíciót. Ebben a részben szintén az operációs rendszerek terminológiáját fogjuk használni, de az algoritmusok természetesen átfogalmazhatók alkalmazások szintjén megadott feladatok megoldására is. Ha a folyamatok elindulásuk után mind egyszerre érnének véget, nem lenne probléma, hiszen az üres memóriaterületet alulról felfelé folyamatosan tölthetnénk fel. A helyzet azonban szinte mindig bonyolultabb, hiszen a folyamatok egymástól gyakran nagy mértékben különböznek, így a futásidejük sem azonos. Ezáltal viszont az elfoglalt memóriaterület nem feltétlenül lesz folytonos, hanem a foglalt partíciók között szabad partíciók jelennek meg. Mivel a memóriabeli másolás rendkívül költséges muvelet, a gyakorlatban nem célravezeto a foglalt partíciókat a memória aljára tömöríteni. Gyakran a tömörítés nem is oldható meg a bonyolult relatív címzések miatt. Tehát a szabad terület, amelyen el kell helyezni az új folyamatokat, nem lesz összefüggo. Nyilvánvaló, hogy a folyamatokat egy-egy szabad partíció elején célszeru elhelyezni, az viszont kevésbé, hogy melyik szabad partícióban a sok közül. Az egyes partíciókat legegyszerubb egy láncolt listában tárolni. Természetesen sok más, esetleg hatékonyabb tárolási módszer is elképzelheto, az itt felsorolt algoritmusok bemutatásához azonban ez elegendo. A p címu partíció elejét az eleje[p], a méretét a méret[p] tartalmazza, azt pedig, hogy mely folyamat fut az adott partícióban, a part[p] változóban tároljuk. Ha a folyamat azonosítója 0, akkor üres, különben pedig foglalt partícióról van szó. A láncolt listában következo partíció címe köv[p]. Ahhoz, hogy dinamikusan létrehozzunk egy megfelelo méretu partíciót, elobb ketté kell vágnunk egy legalább akkora, szabad partíciót. Ezt végzi el a következo algoritmus.
464 11. Memóriagazdálkodás (Balogh Ádám és Iványi Antal) KETTÉVÁG-PARTÍCIÓ(határ, eleje, köv, méret, p, q) 1 eleje[q] eleje[p] + határ 2 méret[q] méret[p] határ 3 méret[p] határ 4 köv[q] köv[p] 5 köv[p] q A rögzített partíciók módszerénél látott algoritmusokkal szemben, melyek a partíciókhoz választották a folyamatokat, itt fordított szemlélettel dolgozunk. Itt a folyamatok listáján megyünk végig, és minden várakozó folyamathoz keresünk egy olyan szabad partíciót, amelybe befér. Amennyiben befért, a partíció elejébol levágjuk a szükséges darabot, és a folyamathoz rendeljük, a folyamat leírójában pedig jelöljük, hogy mely partícióban fut. Ha nem fért be egyik szabad partícióba sem, akkor az adott körben a folyamatot nem tudtuk elhelyezni. ELHELYEZ(vár) 1 for j 1 to n 2 do if vár[j] = IGAZ 3 then -FIT(j) A pszeudokódban lévo a FIRST, NEXT, BEST, KORLÁTOS-BEST, WORST és KORLÁTOS-WORST értékeket veheti fel. A megfelelo szabad partíció kiválasztására több lehetoség is kínálkozik. Az egyik, hogy a partíciók listáján elindulva az elso olyan szabad partícióban helyezzük el a folyamatot, amelyben elfér. Ez lineáris keresés segítségével könnyen megoldható. FIRST-FIT(f, eleje, fej, p, helyigény, köv, méret, part, vár) 1 p fej[p] 2 while vár[f] = IGAZ és p NIL 3 do if part[p] = 0 és méret[p] helyigény[f] 4 then KETTÉVÁG-PARTÍCIÓ(helyigény[f], eleje, köv, méret, p, q) 5 part[p] f 6 hely[f] p 7 vár[f] HAMIS 8 p köv[p] Az algoritmus helyességéhez itt is több dolgot kell belátnunk. Az elso most is az, hogy ne töltsünk egy folyamatot kisebb partícióba, mint amekkorába beférne. Ennek teljesülése következik a lineáris keresés tételébol, mivel annak feltételében ez a kritérium is szerepel. Ennél a módszernél is fontos, hogy egyetlen folyamat se kerüljön több partícióba, illetve egyetlen partícióba se kerüljön több folyamat. Ezen kritérium teljesülésének bizonyítása szó szerint megegyezik a rögzített partíciónál ismertetettel, azzal a különbséggel, hogy itt
11.1. Partícionálás 465 nem a feltételes maximumkeresés, hanem a lineáris keresés tételének helyességére vezetünk vissza. Természetesen most sem elegendoek ezek a feltételek, hiszen az üres algoritmus is teljesíti mindhármat. Azt fogjuk még bizonyítani, hogy az algoritmus minden olyan folyamatot elhelyez a memóriában, amelyhez tartozik olyan partíció, amelybe befér. Ehhez ismét szükségünk lesz egy invariánsra: j darab folyamat megvizsgálása után nem létezik olyan pozitív k j, amelyre vár[k], és amelyre van olyan p partíció, hogy part[p] = 0 és méret[p] helyigény[k]. Teljesül: Az algoritmus elején j = 0 darab folyamatot vizsgáltunk meg, így egyáltalán nem létezik pozitív k j. Megmarad: Ha az invariáns teljesül j-re a ciklusmag elején, akkor eloször is azt kell látnunk, hogy ugyanerre a j-re a ciklus végén is teljesülni fog. Ez nyilvánvaló, hiszen az elso j darab folyamathoz nem nyúlunk a ( j + 1)-edik vizsgálata során, az oket tartalmazó partíciókra pedig part[p] > 0, ami nem felel meg a FIRST-FIT algoritmusban található lineáris keresés feltételének. A ( j + 1)-edik folyamatra pedig azért fog teljesülni az invariáns a ciklusmag végén, mert ha van a feltételnek megfelelo szabad blokk, azt a lineáris keresés biztosan megtalálja, hiszen a lineáris keresésünk feltétele megegyezik az invariánsunk egyes partíciókon értelmezett követelményével. Befejezodik: Mivel a ciklus egyesével halad végig egy rögzített intervallumon, biztosan be is fog fejezodni. Mivel a ciklusmag pontosan annyiszor hajtódik végre, mint ahány folyamat van, a ciklus befejezodésekor igaz lesz, hogy nem létezik olyan pozitív k n, amelyre vár[k] és amelyre van olyan p partíció, hogy part[p] = 0 és méret[p] helyigény[i], azaz nem hagytunk várakozni egyetlen olyan folyamatot sem, amelyhez lenne partíció, amibe belefér. Az algoritmus lépésszáma most is könnyen kiszámítható. Mindenképp végignézzük az n darab folyamatot. Ha például minden folyamat vár és a partíciók foglaltak, akkor az algoritmus lépésszáma Θ(nm). A lépésszám kiszámításánál azonban nem vettünk gyelembe néhány fontos szempontot. Az egyik az, hogy m nem állandó, hanem az algoritmus többszöri lefuttatása után várhatóan no, mivel a folyamatok egymástól függetlenek, különbözo idopillanatokban indulnak, illetve érnek véget, és méretük is jelentosen eltérhet egymásétól. Ezért gyakrabban vágunk ketté egy partíciót, minthogy kettot egyesíthetnénk. Ezt a jelenséget a memória elaprózódásának nevezzük. Tehát a legrosszabb eset lépésszáma az algoritmus többszöri futtatása során folyamatosan no. Ráadásul a lineáris keresés mindig a legelso megfelelo méretu partíciót vágja ketté, így egy ido után sok kis partíció lesz a memóriaterület elején, amelyekbe nem tölthetjük be a folyamatok többségét. Így az átlagos lépésszám is növekedni fog. Ez utóbbi problémára megoldást jelenthet, ha a keresést nem mindig a partíciók listájának elejérol kezdjük, hanem annak a partíciónak a másik felétol, amelynek elso felébe a legutóbbi folyamatot töltöttük, és ha a lista végére érünk, az elejérol folytatjuk mindaddig, amíg a folyamat be nem fért valamelyik partícióba, vagy újra el nem értük a kiindulási elemet, azaz ciklikusan járjuk be a partíciók listáját.
466 11. Memóriagazdálkodás (Balogh Ádám és Iványi Antal) NEXT-FIT(f, fej, utolsó, méret, helyigény, köv, part, vár) 1 if utolsó[p] NIL 2 then p köv[utolsó[p]] 3 else p fej[p] 4 while vár[f] = IGAZ és p utolsó[p] 5 do if p = NIL 6 then p fej[p] 7 if part[p] = 0 és méret[p] helyigény[f] 8 then KETTÉVÁG-PARTÍCIÓ(helyigény[f], eleje, köv, méret, p, q) 9 part[p] f 10 hely[f] p 11 vár[f] HAMIS 12 utolsó[p] p 13 p köv[p] A NEXT-FIT algoritmus helyességének bizonyítása lényegében megegyezik a FIRST-FITével, és lépésszámának nagyságrendje is azonos. Gyakorlatilag itt is lineáris keresésrol van szó a belso ciklusban, csupán az intervallumot forgatjuk el mindig a végén. Ez az algoritmus azonban egyenletesen járja be a szabad területek listáját, így nem aprózza el az elejét. Ennek következtében az átlagos lépésszám is várhatóan kisebb lesz, mint a FIRST-FIT algoritmusé. Ha csak annyit vizsgálunk minden partícióról, hogy elfér-e benne a folyamat, akkor elofordulhat, hogy kis folyamatok számára vágunk el nagy partíciókat, így a késobb érkezo nagyobb folyamatoknak már nem jut megfelelo méretu partíció. A nagy partíciók pazarlását el tudjuk kerülni, ha minden folyamatot a legkisebb olyan partícióban helyezünk el, amelyben elfér. BEST-FIT(f, fej, helyigény, köv, part, vár) 1 min 2 ind NIL 3 p fej[p] 4 while p NIL 5 do if part[p] = 0 és helyigény[ f ] méret[p] < min 6 then ind p 7 min méret[p] 8 p köv[p] 9 if ind NIL 10 then KETTÉVÁG-PARTÍCIÓ(helyigény[ f ], eleje, köv, méret, ind, q) 11 part[ind] f 12 hely[f] ind 13 vár[f] HAMIS Az algoritmus helyességének összes kritériuma belátható jelen esetben is, ugyanúgy, mint az elozoekben. Az egyetlen különbség a FIRST-FIT-hez képest, hogy most lineáris keresés helyett feltételes minimumkeresést alkalmazunk. Nyilvánvaló az is, hogy ez az algo-
11.1. Partícionálás 467 ritmus egyetlen folyamat számára sem fog nagyobb partíciót kettévágni, mint amekkorát a meglévok közül minimálisan szükséges. Nem mindig jó azonban, ha minden folyamatot a legkisebb olyan szabad helyre teszünk be, ahová befér. Ekkor ugyanis a partíció üresen maradó része gyakran annyira kicsi, hogy az már csak nagyon kevés folyamat számára használható. Ez két okból is hátrányos. Egyrészt ezeket a partíciókat minden folyamat elhelyezésekor újra és újra megvizsgáljuk, hiszen benne vannak a szabad partíciók listájában. Másrészt pedig, hogy sok kis partíció együtt nagy területet foglalhat el, amit azonban nem tudunk hasznosítani, mert nem összefüggo. Tehát valamilyen módon ki kell védenünk azt, hogy az üresen maradó partíciók túlságosan kicsik legyenek. A túlságosan kicsi fogalom jelenthet egy konstanst is, de lehet az elhelyezendo folyamat helyigényének függvénye is. (Például legalább még egyszer akkora legyen a szabad terület, mint az elhelyezendo folyamat.) Mivel a korlátot az egész partícióra, és nem a megmaradó részre vizsgáljuk, ezért ezt mindig egy a folyamattól függo függvényként kezeljük. Természetesen, ha a nincs olyan partíció, amely ennek a kiegészíto kritériumnak is megfelel, akkor helyezzük el a folyamatot a legnagyobb partícióban. Így a következo algoritmust kapjuk. KORLÁTOS-BEST-FIT(f, méret, fej, helyigény, köv) 1 min 2 ind NIL 3 p fej[p] 4 while p NIL 5 do if part[p] = 0 és méret[p] helyigény[ f ] és ((méret[p] < min és méret[p] KORLÁT(helyigény[ f ])) vagy ind = NIL vagy (min < KORLÁT(helyigény[ f ]) és méret[p] > min)) 6 then ind p 7 min méret[p] 8 p köv[p] 9 if ind NIL 10 then KETTÉVÁG-PARTÍCIÓ(helyigény[ f ], eleje, köv, méret, ind, q) 11 part[ind] f 12 hely[f] ind 13 vár[f] HAMIS Ez az algoritmus bonyolultabb, mint az elozoek. Helyességének bizonyításához azt kell észrevennünk, hogy a belso ciklus egy módosított feltételes minimumkeresés. A feltétel elso része, azaz hogy part[p] = 0 és méret[p] helyigény[f], továbbra is azt mondja, hogy olyan partíciót keresünk, amely szabad, és belefér a folyamat. A második rész egy diszjunkció, azaz három esetben cseréljük ki a vizsgált elemet az eddig megtalálttal. Az egyik eset, amikor méret[p] < min és méret[p] KORLÁT(helyigény[ f ]), vagyis a vizsgált partíció mérete legalább akkora, mint az eloírt minimális, de kisebb, mint az eddig talált legkisebb. Ha nem lenne több feltétel, akkor ez a feltételes minimumkeresés lenne, ahol a feltételek közé bevettük azt is, hogy a partíció mérete egy bizonyos korlát fölött legyen. Azonban van még két lehetoség is, amikor kicseréljük az eddig megtalált elemet az éppen vizsgálttal. Ezek közül az elso, amikor ind = NIL, azaz az éppen vizsgált partíció az elso olyan, amely szabad, és
468 11. Memóriagazdálkodás (Balogh Ádám és Iványi Antal) elfér benne a folyamat. Erre azért van szükség, mert továbbra is megköveteljük, hogy ha van olyan szabad partíció, amelyben elfér a folyamat, akkor az algoritmus helyezze is azt el ezek valamelyikében. Végül a harmadik feltétel alapján akkor cseréljük ki az eddig megvizsgáltak közül legmegfelelobbnek talált elemet az aktuálissal, ha min < KORLÁT(helyigény[ f ]) és méret[p] > min, vagyis az eddigi minimum nem érte el az eloírt korlátot, és az aktuális nagyobb nála. Ez a feltétel kettos célt szolgál. Egyrészt, ha eddig olyan elemet találtunk csak, amely nem felel meg a kiegészíto feltételnek, az aktuális pedig igen, akkor kicseréljük vele, hiszen ekkor min < KORLÁT(helyigény[ f ]) méret[p], tehát az aktuális partíció mérete nyilvánvalóan nagyobb. Másrészt, ha sem az eddig megtalált, sem az aktuális partíció mérete nem éri el az eloírt korlátot, de az aktuálisan vizsgált jobban közelíti azt alulról, akkor min < méret[p] < KORLÁT(helyigény[ f ]) teljesül, tehát ebben az esetben is kicseréljük az eddig megtaláltat az aktuálisra. Így, ha van olyan partíció, amely legalább akkora, mint az eloírt korlát, akkor az algoritmus minden folyamatot ezek közül a legkisebbe fog helyezni, míg ha nincsenek ilyenek, akkor a legnagyobba, amelybe befér. Bizonyos feladatoknál elofordulhat, hogy kizárólag az a cél, hogy a megmaradó területek mérete minél nagyobb legyen. Ezt úgy érhetjük el, hogy minden folyamatot a legnagyobb szabad partícióban helyezünk el: WORST-FIT(f, fej, méret, helyigény, köv, part, vár) 1 max 0 2 ind NIL 3 p fej[p] 4 while p NIL 5 do if part[p] = 0 és méret[p] helyigény[f] és méret[p] > max 6 then ind p 7 max méret[p] 8 p köv[p] 9 if ind NIL 10 then KETTÉVÁG-PARTÍCIÓ(helyigény[ f ], eleje, köv, méret, ind, q) 11 part[ind] f 12 hely[f] ind 13 vár[f] HAMIS Az algoritmus helyességének bizonyítása hasonló a BEST-FIT algoritmuséhoz, a különbség csupán annyi, hogy feltételes minimumkeresés helyett feltételes maximumkeresést használunk. Az is nyilvánvaló ebbol, hogy a fennmaradó helyek mérete maximális lesz. A WORST-FIT algoritmus garantálja, hogy a legkisebb üresen maradó partíció mérete a leheto legnagyobb lesz, azaz kevés lesz az olyan partíció, amely a legtöbb folyamat számára már túl kicsi. Ezt azáltal éri el, hogy mindig a legnagyobb partícióból vág le. Ennek az a következménye, hogy a nagy helyigényu folyamatok számára sokszor már nem is jut megfelelo méretu partíció, hanem azok várakozásra kényszerülnek a háttértárban. Hogy ez ne így történjen, a BEST-FIT-hez hasonlóan itt is megfogalmazhatunk egy kiegészíto feltételt. Itt azonban nem alsó, hanem felso korlátot adunk. Az algoritmus igyekszik olyan partíciót kettévágni, amelynek mérete egy bizonyos korlát alatt van. A korlát itt is a folyamat helyigényének függvénye. (Például annak kétszerese.) Ha talál ilyen partíciókat, akkor ezek
11.1. Partícionálás 469 közül a legnagyobbat választja, hogy elkerülje a túl kis partíciók létrejöttét. Ha csak olyanokat talál, amelyek nagyobbak ennél a korlátnál, akkor viszont a minimálisat vágja ketté közülük, nehogy elvegye a helyet a nagy folyamatok elol. KORLÁTOS-WORST-FIT(f, fej, méret, helyigény, köv, part, vár) 1 max 0 2 ind NIL 3 p fej[p] 4 while p NIL 5 do if part[p] = 0 és méret[p] helyigény[ f ] és ((méret[p] > max és méret[p] KORLÁT(helyigény[ f ])) vagy ind = NIL vagy (max > KORLÁT(helyigény[ f ]) és méret[p] < max)) 6 then ind p 7 max méret[p] 8 p köv[p] 9 if ind NIL 10 then KETTÉVÁG-PARTÍCIÓ(helyigény[f], eleje, köv, méret, ind, q) 11 part[ind] f 12 hely[f] ind 13 vár[f] HAMIS Látható, hogy az algoritmus nagyon hasonlít a KORLÁTOS-BEST-FIT-hez, csupán a relációs jelek mutatnak az ellenkezo irányba. A különbség valóban nem nagy. Mindkét algoritmusban ugyanazt a két feltételt próbáljuk teljesíteni: ne keletkezzenek túl kis üres partíciók, és ne pazaroljuk el a nagy szabad partíciókat kis folyamatokra. Az egyetlen különbség, hogy e két feltétel közül melyiket vesszük gyelembe elsodlegesen, és melyiket másodlagosan. Ezt mindig az adott feladat határozza meg. Gyakorlatok 11.1-1. Adott egy rögzített partíciókat használó rendszer két darab 100 kb, egy 200 kb és egy 400 kb méretu partícióval. Kezdetben mindegyik üres, majd egy másodperc múlva érkezik egy 80 kb, egy 70 kb, egy 50 kb, egy 120 kb és egy 180 kb helyigényu folyamat, amelyek adatai ebben a sorrendben kerülnek tárolásra a megfelelo vektorokban. A 180 kb méretu a beérkezésétol számított ötödik másodpercben véget ér, ám ekkorra már egy 280 kb helyigényu folyamat is érkezett a memóriába. Mely partíciókban mely folyamatok lesznek a kezdeti folyamatok beérkezésétol számított hatodik másodpercben, ha feltételezzük, hogy más folyamatok nem érnek véget addig, és a LEGNAGYOBB-BEFÉRO algoritmust használjuk? Mi a helyzet, ha a LEGNAGYOBB-VAGY-RÉGÓTA-VÁRAKOZÓ-BEFÉRO, illetve ha a RÉGÓTA- VÁRAKOZÓ-VAGY-KISEBBE-NEM-FÉR algoritmust használjuk 4 küszöbértékkel? 11.1-2. Egy dinamikus partíciókat használó rendszerben a következo szabad partíciók találhatók meg a partíciók listájában: egy 20 kb méretu, amit egy 100 kb, egy 210 kb, egy 180 kb, egy 50 kb, egy 10 kb, egy 70 kb, egy 130 kb és egy 90 kb méretu követ, pontosan ebben a sorrendben. Legutoljára a 180 kb méretu szabad partíció elé helyeztünk el folyamatot. A rendszerbe érkezik egy 40 kb helyigényu folyamat. Melyik szabad partícióban fogja ezt elhelyezni a FIRST-FIT, a NEXT-FIT, a BEST-FIT, a KORLÁTOS-BEST-FIT, a WORST-FIT, illetve a
470 11. Memóriagazdálkodás (Balogh Ádám és Iványi Antal) KORLÁTOS-WORST-FIT algoritmus? 11.1-3. A WORST-FIT algoritmus egy hatékony megvalósítása, ha a partíciókat nem lineárisan láncolt listában, hanem bináris kupacban tároljuk. Mekkora lesz így az ELHELYEZ algoritmus muveletigénye? 11.2. Lapcserélési algoritmusok Mint már említettük, a mai számítógépek memóriája több szintbol áll. A felhasználóknak nincs szüksége arra, hogy ezt a többszintes szerkezetet részletesen ismerjék: az operációs rendszerek egységesnek látszó virtuális memóriává szervezik a szinteket. Ennek a virtuális memóriának a kezelésére a két legelterjedtebb módszer a lapozás és a szegmentálás: elobbi egységes méretu részekre, úgynevezett lapkeretekre osztja mindkét memóriaszintet (és ennek megfeleloen a programokat is), míg a szegmentálásnál a program szegmenseknek nevezett, változó méretu részeit mozgatjuk a memóriaszintek között. Eloször az egyszeru tárgyalás érdekében tegyük fel, hogy a vizsgált számítógép memóriája két szintbol áll: a kisebb és gyorsabb elérésu rész a zikai memória (röviden memória), a nagyobb méretu és nagyobb elérési ideju rész pedig a háttérmemória. Kezdetben a zikai memória üres, a háttérmemóriában pedig egyetlen program van, amely n részbol áll. Feltesszük, hogy a program futása során utasításokat kell végrehajtani, és minden utasítás végrehajtásához egy-egy programrészre van szükségünk. A hivatkozási sorozat feldolgozása során a következo részfeladatokat kell megoldani. 1. Hol helyezzük el a zikai memóriában (ha nincs ott) a következo utasítás végrehajtásához szükséges programrészt? 2. Mikor helyezzünk el programrészeket a zikai memóriában? 3. Hogyan szabadítsunk fel helyet a zikai memóriában az elhelyezendo programrészek számára? Az elso kérdésre az elhelyezési algoritmusok válaszolnak: a lapozásnál egyszeruen azt, hogy akárhol ugyanis a zikai memória lapkeretei azonos méretuek és hozzáférési idejuek. A szegmentálás során a zikai memóriában programszegmensek és lyukaknak nevezett üres memóriarészek váltakoznak és az elso kérdésre a szegmenselhelyezési algoritmusok válaszolnak. A második kérdésre az átviteli algoritmusok válaszolnak: a muködo rendszerek nagy többségében azt, hogy igény szerint, azaz akkor kezdodik meg a programrész beolvasása a háttértárból, amikor kiderül, hogy az adott programrészre szükség van. A másik lehetoség az elobetöltés lenne, a tapasztalatok szerint azonban ez sok felesleges munkával jár, ezért nem terjedt el. A harmadik kérdésre a cserélési algoritmusok válaszolnak: lapozásnál a lapcserélési algoritmusok, amelyeket ebben az alfejezetben mutatunk be. A szegmentálásnál alkalmazott szegmenscserélési algoritmusok lényegében a lapcserélési algoritmusok ötleteit hasznosítják azokat a szegmensek különbözo méretének megfeleloen kiegészítve. A lapozott számítógépekben mindkét szintet azonos méretu részekre úgynevezett lapkeretekre osztjuk. A zikai memória mérete m lapkeret, a háttérmemória mérete pedig n lapkeret. A paraméterek között természetes az 1 m n egyenlotlenség. A gyakorlatban
11.2. Lapcserélési algoritmusok 471 n rendszerint több nagyságrenddel nagyobb, mint m. Kezdetben a zikai memória üres, a háttérmemóriában pedig egyetlen program van. Feltesszük, hogy a program futása során p utasítást kell végrehajtani, és a t-edik utasítás végrehajtásához az r t lapkeretben lévo lapra van szükségünk, azaz a program futását az R = r 1, r 2,..., r p hivatkozási tömbbel modellezzük. A továbbiakban csak az igény szerinti lapozással, azon belül is csak a lapcserélési algoritmusokkal foglalkozunk. Ebben az egyszeru modellben azt tételezzük fel, hogy az utasítás végrehajtásához az r t programrészt beolvassuk, és az utasítás végrehajtásának eredményét is az r t programrészbe írjuk. Ahol szükség van arra, hogy az olvasást és írást megkülönböztessük, ott az R tömb mellett egy W = w 1, w 2,..., w p írási tömböt is megadunk, melynek w t eleme IGAZ, ha az r t lapra írunk, egyébként w t = HAMIS. Az igény szerinti lapcserélési algoritmusokat szokás statikus és dinamikus algoritmusokra osztani. A program futásának elején mindkét típus teletölti a zikai memória lapkereteit lapokkal, a statikus algoritmusok azonban ezután a futás végéig pontosan m lapkeretet tartanak lekötve, míg a dinamikus algoritmusok legfeljebb m lapkeretet foglalnak le. 11.2.1. Statikus lapcserélés A statikus lapcserélési algoritmusok bemeno adatai a zikai memória mérete lapkeretben (m), a program mérete lapban (n), a program futási ideje utasításban (p) és a hivatkozási sorozat (R), kimeno adata pedig a laphibák száma (laphiba). A statikus algoritmusok muködése a laptábla kezelésén alapul. A laptábla egy n 2 méretu tömb, melynek i-edik sora (i [0.. n 1]) az i-edik lapra vonatkozik. A sor elso eleme egy logikai változó (jelzobit), melynek értéke azt jelzi, hogy a lap az adott idopontban a zikai memóriában van-e: ha az i-edik lap a zikai memóriában van, akkor laptábla[i, 1] = IGAZ és laptábla[i, 2] = j, ahol a j [0.. m 1] azt adja meg, hogy a lap a zikai memória j-edik lapkeretében van. Ha az i-edik lap nincs benn a zikai memóriában, akkor laptábla[i, 1] = HAMIS és laptábla[i, 2] értéke deniálatlan. A foglalt munkaváltozó a zikai memória foglalt lapkereteinek számát tartalmazza. Ha a lapok mérete z, akkor a v virtuális címbol úgy számítjuk ki az f zikai címet, hogy j = v/z megadja a virtuális lap indexét, v z v/z pedig megadja a v virtuális címhez tartozó s eltolást. Ha az adott idopontban a j-edik lap a zikai memóriában van amit laptábla[ j, 1] = IGAZ jelez, akkor f = s + z laptábla[ j, 2]. Ha viszont a j-edik lap nincs a zikai memóriában, laphiba lép fel. Ekkor a lapcserélési algoritmus segítségével kiválasztjuk a zikai memória egyik lapkeretét, abba betöltjük a j-edik lapot, frissítjük a laptábla j-edik sorát és azután számítjuk ki f -et. Az igény szerinti statikus lapcserélési algoritmusok muködése leírható kezdoállapottal rendelkezo Mealy-automatával. Ezek az automaták (Q, q 0, X, Y, δ, λ) alakban adhatók meg, ahol Q a vezérlo állapotok halmaza, q 0 Q a kezdeti vezérlo állapot, X a bemeno jelek halmaza, Y a kimeno jelek halmaza, δ : Q X Q az állapot-átmenetfüggvény és λ : Q X Y a kimenetfüggvény. Itt nem foglalkozunk az automaták leállásának formalizálásával. A bemeno jelek R p = r 1, r 2,..., r p (vagy R = r 1, r 2,... ) sorozatát hivatkozási sorozatnak hívjuk. Egyszerusíti az algoritmusok deniálását az S t (t = 1, 2,...) memóriaállapotok beve-
472 11. Memóriagazdálkodás (Balogh Ádám és Iványi Antal) zetése: ez az állapot a t-edik bemeno jel feldolgozása után az automata memóriájában (a zikai memóriában) tárolt lapok halmaza. Az igény szerinti statikus lapcserélési algoritmusok esetén S 0 =. Ha az új memóriaállapot a régitol különbözik (azaz lapbevitelre volt szükség), akkor laphiba történt. Eszerint mind a lapnak üres lapkeretbe való bevitelét, mind pedig a lapcserét laphibának nevezzük. A lapcserélési algoritmusok esetén Denning javaslatára λ és δ helyett inkább a g : M Q X M Q Y átmenetfüggvényt használjuk, ahol M a lehetséges memóriaállapotok halmaza. Mivel a lapcserélési algoritmusokra X = {0, 1,..., n 1} és Y = X, ez a két elem a denícióból elhagyható és így a P lapcserélési algoritmus a (Q, q 0, g P ) hármassal adható meg. Elso példánk az egyik legegyszerubb lapcserélési algoritmus, a FIFO (First In First Out), amely a lapokat a betöltés sorrendjében cseréli. Deníciója a következo: q 0 = és (S, q, ɛ), ha x S, g FIFO (S, q, x) = (S {x}, q, ɛ), ha x S, S = k < m, (S \ {y 1 } {x}, q, y 1 ), ha x S s S = k = m, (11.1) ahol q = y 1, y 2,..., y k, q = y 1, y 2,..., y k, x és q = y 2, y 3,..., y m,x. A programok futtatását a következo *-FUTTAT algoritmus végzi. Ebben az alfejezetben az algoritmusok nevében a helyére mindig az alkalmazandó lapcserélési algoritmus neve kerül (FIFO, LRU OPT, LFU vagy NRU). A pszeudokódokban feltételezzük, hogy a meghívott eljárások ismerik a hívó eljárásban használt változók értékét, és a hívó eljárás hozzáfér az új értékekhez. *-FUTTAT(m, n, p, R, hibaszám, laptábla) 1 hibaszám 0 2 foglalt 0 3 for i 0 to n 1 A laptábla elokészítése. 4 do laptábla[i, 1] HAMIS 5 *-ELOKÉSZÍT(laptábla) 6 for i 1 to p A program futtatása. 7 do *-VÉGREHAJT(laptábla, i) 8 return hibaszám Az algoritmus következo megvalósítása egy Q sorban tartja nyilván a lapok betöltési sorrendjét. Az elokészíto algoritmus feladata az üres sor létrehozása, azaz a Q utasítás végrehajtása. A következo pszeudokódban kidob a cserélendo lap sorszáma, behoz a zikai memória azon lapjának sorszáma, melybe az új lapot behozzuk.
11.2. Lapcserélési algoritmusok 473 FIFO-VÉGREHAJT(laptábla, t), 1 if laptábla[r t, 1] = IGAZ A következo lap benn van. 2 then NIL 3 if laptábla[r t, 1] = HAMIS A következo lap nincs benn. 4 then laphiba laphiba + 1 5 if foglalt < m A zikai memória nincs tele. 6 then SORBA(Q, r t ) 7 behoz foglalt 8 foglalt foglalt + 1 9 if foglalt = m A zikai memória tele van. 10 then kidob SORBÓL(Q) 11 laptábla[kidob, 1] HAMIS 12 behoz laptábla[kidob, 2] 13 KIÍR(behoz, kidob) 14 BEOLVAS(r t, behoz) Beolvasás. 15 laptábla[r t, 1] IGAZ Adatok frissítése. 16 laptábla[r t, 2] behoz A KIÍR eljárás feladata, hogy a cserére kiválasztott lapot kiírja a háttértárba: elso paramétere a honnan (a memória melyik lapkeretébol), második paramétere a hová (a háttértár melyik lapkeretébe) kérdésre ad választ. A BEOLVAS eljárás feladata az, hogy a következo utasítás végrehajtásához szükséges lapot a háttértárból a zikai memória megfelelo lapkeretébe beolvassa: elso paramétere a honnan (a háttértár melyik lapkeretébol), második paramétere a hová (a memória melyik lapkeretébe) A két eljárás paramétereinek megadásánál kihasználjuk, hogy a lapkeretek mérete azonos, ezért a j-edik lapkeret kezdocíme mindkét memóriában a z lapméret j-szerese. A lapcserélési algoritmusok többségének az r t hivatkozás feldolgozásához nincs szüksége az R sorozat többi elemének ismeretére, ezért a helyigény elemzésekor a sorozat helyigényével nem kell számolnunk. Kivételt képez például az OPT algoritmus. A FIFO-FUTTAT algoritmus helyigényét a laptábla mérete határozza meg ez a helyigény Θ(m). A FIFO-FUTTAT algoritmus futási idejét a ciklusa határozza meg. Mivel a 6 7. sorokban meghívott eljárás csak konstans számú lépést végez (feltéve, hogy a sorkezelo muveleteket O(1) ido alatt elvégezzük), ezért FIFO-FUTTAT futási ideje Θ(p). Érdemes megjegyezni, hogy a lapok egy része a memóriában tartózkodás alatt nem változik meg, ezért ha a memóriában lévo lapokhoz használtsági bitet rendelünk, akkor az esetek egy részében a 12. sorban lévo kiírás megtakarítható. A következo példánk az egyik legnépszerubb lapcserélési algoritmus, az LRU (Least Recently Used), amely a legrégebben használt lapot cseréli. Ennek deníciója a következo: q 0 = és (S, q, ɛ), ha x S, g LRU (S, q, x) = (S {x}, q, ɛ), ha x S, S = k < m, (11.2) (S \ {y 1 } {x}, q, y 1 ), ha x S s S = k = m, ahol q = y 1, y 2,..., y k, q = y 1, y 2,..., y k, x, q = y 2, y 3,..., y m,x és ha x = y k, akkor q = y 1, y 2,..., y k 1,..., y k+1... y m, y k.
474 11. Memóriagazdálkodás (Balogh Ádám és Iványi Antal) Az LRU következo megvalósítása nem igényel elokészítést. Az utolsó-hiv[0.. n 1] tömbben tartjuk nyilván az egyes lapok utolsó használatának idopontját, és amikor cserélni kell, lineáris kereséssel határozzuk meg a legrégebben használt lapot. LRU-VÉGREHAJT(laptábla, t) 1 if laptábla[r t, 1] = IGAZ A következo lap benn van. 2 then utolsó-hiv[r t ] t 3 if laptábla[r t, 1] = HAMIS A következo lap nincs benn. 4 then laphiba laphiba + 1 5 if foglalt < m A zikai memória nincs tele. 6 then behoz foglalt 7 foglalt foglalt + 1 8 if foglalt = m A zikai memória tele van. 9 then kidob r t 1 10 for i 0 to n 1 11 do if laptábla[i, 1] = IGAZ és utolsó-hiv[i] < utolsó-hiv[kidob] 12 then kidob utolsó-hiv[i] 13 laptábla[kidob, 1] HAMIS 14 behoz laptábla[kidob, 2] 15 KIÍR(behoz, kidob) 16 BEOLVAS(r t, behoz) Beolvasás. 17 laptábla[r t, 1] IGAZ Adatok frissítése. 18 laptábla[r t, 2] behoz 19 utolsó-hiv[r t ] t Ha most n és p értékét is változónak tekintjük, akkor az LRU-VÉGREHAJT 10 11. sorában szereplo lineáris keresés miatt az LRU-FUTTAT algoritmus futási ideje Θ(np). A következo algoritmus optimális abban az értelemben, hogy az adott feltételek (azaz rögzített m és n) mellett minimális számú laphibát okoz. Ez az algoritmus a bennlévo lapok közül azt a lapot választja a cseréhez, amelyikre a legkésobb lesz újra szükség (ha több olyan lap is van, amelyre többet nincs szükség, akkor közülük a legkisebb memóriacímen lévo lapot választjuk). Elokészítésre ennek az algoritmusnak sincs szüksége. OPT-VÉGREHAJT(t, laptábla, R) 1 if laptábla[r t, 1] = IGAZ A következo lap benn van. 2 then NIL 3 if laptábla[r t, 1] = HAMIS A következo lap nincs benn. 4 then laphiba laphiba + 1