Linux: Mi az a memória összevonás?

Tux
Memória összevonás, vagy szaknyelven memory compaction, röviden nem más, mint az operatív tár töredezettségmentesítése. A hosszabb verzió lentebb olvasható.

Nem találtam magyar terminust erre a megoldásra, így kreáltam egyet. Bár lényegében a „memória tömörítés” szókapcsolat is kifejezte volna a módszer lényegét, hála a magyar nyelv szépségeinek. De akkor milyen címet adtam volna annak a cikknek, amelyik tényleg a memória tartalmának a tömörítéséről fog (majd valamikor) szólni? Így előremenekültem.

Probléma: memória töredezés

Sokak számára ismerős lehet a kifejezés, noha nem az operatív tárral kapcsolatban, hanem sokkal inkább a merevlemezek esetén találkozhattak vele. Az alap probléma itt is ugyanaz, mint a lemezeknél: a rendszer a memóriát fix (4 KiB) méretű, úgynevezett lapokra osztja fel. Amikor egy program memóriát kér magának, a kernel ezeket a lapokat osztja ki, mikor felszabadít a program (mert pl. befejezte a futását vagy csak nincs többé szüksége a lefoglalt területre), pedig ezeket szabadítja fel. Ahogy telik-múlik az idő, programok indulnak és záródnak be, a szabad és lefoglalt lapok szétszórva fognak elhelyezkedni a memóriában.

A memóriát nagyobb blokkokban célszerű és hatékony foglalni, ezért a Linux fejlesztői komoly munkaórákat öltek abba, hogy a lehető legkisebbre csökkentsék a memória töredezettségét, illetve elkerüljék a memórialapok nem folytonos elhelyezését. Ennek a munkának hála a legtöbb kernel funkciót nem érinti a memória töredezés problémája. Azonban még mindig vannak olyan helyzetek, ahol nem lehet egy memóriaterületet egyben lefoglalni. Ilyenkor magasabb szintű, nem folytonos memóriafoglalást kell végrehajtani. Ha ez bekövetkezik, akkor előfordulhat, hogy nem sikerül memóriát foglalni a rendszerben.

Ha ez nem lenne elég baj, a problémát tovább súlyosbítja, hogy a napjainkban kapható processzorok már nem csak 4 KiB méretű lapokkal tudnak dolgozni: egy alkalmazás a saját címterében sokkal nagyobb lapmérettel („óriás lapokkal” ~ Huge Pages) is dolgozhat. Ez valós teljesítményelőnyt jelent, ugyanis így csökken a processzor címtranszlációs gyorsítótárának a terhelése. Azonban az óriás lapok használatához a rendszernek képesnek kell lennie arra, hogy megtalálja a fizikailag folyamatos részeket a memóriában, amiknek nem csak, hogy egyben kell rendelkezésre állniuk, de még a helyüknek is megfelelőnek kell lenniük. A régóta futó rendszereken ezeknek a helyeknek a megtalálása kifejezetten nehéz lehet.

Megoldás: memória összevonás

Az évek alatt a kernelfejlesztők megpróbálták különböző módszerekkel megoldani a töredezés problémáját. Olyan technikák bevezetésével mint a ZONE_MOVABLE vagy a darabonkénti visszanyerés (lumpy reclaim). Még mindig van min dolgozni, főleg azon a területen, ami a memória nagyobb részén próbálja megszüntetni a töredezettséget. Némi szünet után, Mel Gorman beküldte a memória összevonás javítást a kernelbe.

Hogyan működik?

Tegyük fel, hogy a memória egy kis területe így néz ki:
Memory compaction 1

A fehér lapok a szabad, a pirosak a lefoglalt, használatban lévő memórialapokat mutatják. Jól látható, hogy ez a rész elég töredezett, nincs kettőnél több, egyben lefoglalható memória lap, szóval bármely foglalás, mellyel négy lapnyi területet foglalnánk, hibát fog eredményezni. Persze, a két lapos foglalás is hibát fog eredményezni, mivel nincs olyan pár, amely jól lenne pozicionálva.

Itt az idő a memória összevonó kód futtatására. Ez két, különböző algoritmussal dolgozik: az egyik a terület végén kezdi és összegyűjti azon lapok listáját, amelyek mozgathatóak.
Memory compaction 2

Míg a másik, a terület elején elkezdi összegyűjteni azokat a megfelelő, üres lapokat, amelyekre majd át lehet helyezni a kigyűjtött foglaltakat.
Memory compaction 3

A terület közepe körül, ahol találkozik a két algoritmus, meghívásra kerül a lapmozgató kód (ami már nem csak a NUMA rendszereken érhető el), amely megcseréli a használatban lévő és szabad lapokat, és ez a gyönyörű kép fogad minket:
Memory compaction 4

Immár van egy nyolc lapból álló, összefüggő memóriaterületünk, ami szükség esetén akár egy magasabb szintű memóriafoglalásra is használható.

Persze, ezek a képek durván leegyszerűsítik azt, hogy mi történik az igazi rendszerben. Kezdjük azzal, hogy a memória területek sokkal nagyobbak, ami nyilván azt jelenti, hogy többet kell rajta dolgozni, persze ezzel több szabad területet is nyerünk.

Azonban mindez csak akkor működik, ha a kérdéses lapok mozgathatóak. Nem lehet minden lapot tetszés szerint mozgatni. Az a lap mozgatható, amelyről vagy megmondtunk a rendszernek, hogy pakolgathatja, vagy az, amely nincs egyéb módon mozgathatatlannak jelölve. Azaz a legtöbb felhasználói program, amely a felhasználó virtuális címein keresztül érhető el, mozgatható. Azonban a legtöbb, kernel által közvetlenül használt rész nem, habár újrahasznosítható, azaz szükség esetén felszabadítható. Elég mindössze egy mozgathatatlan memórialap ahhoz, hogy a memória adott részén elrontsa a folytonosságot. A jó hír az, hogy a kernel elkülöníti a mozgatható és mozgathatatlan lapokat, így a valóságban a nem mozgatható lapok valószínűleg kisebb problémát jelentenek, mint azt gondolod.

Indítás!

Két módon érhetjük el, hogy elinduljon a memória összevonás: az egyik, hogy csomópont számot írunk a /proc/sys/vm/compact_node állományba, így elindul az összevonás az adott számú csomóponton. A másik, hogy a rendszernek nem sikerül több lapnyi memóriát lefoglalnia. Ilyenkor az összevonás, mint előnyben részesített, közvetlen lapfelszabadító eljárás indul el. Közvetlen indítás nélkül, az összevonó algoritmus nem csinál semmit, ugyanis a lapok mozgatása némi teljesítménybe kerül, ezért jobb ha csak akkor fut, amikor szükség van rá.

A cikk részben az LKML Memory Compaction cikkén alapul.