Szerző Téma: (Leírás) Az AMX és az AMX-memóriatérkép  (Megtekintve 1239 alkalommal)

Nem elérhető krisk

  • 2380
    • Profil megtekintése
(Leírás) Az AMX és az AMX-memóriatérkép
« Dátum: 2013. január 12. - 10:09:51 »
+1 Show voters
KEZDJÜK A RIZSÁVAL
Igen, tudom, hogy van egy teljesen ugyanilyen leírás a hivatalos fórumon, és azt is, hogy ugyanazt fogom gyakorlatilag elmondani kétszer. Viszont fontosnak érzem, hogy azt az alapot kicsit felturbózzam, és megérthetõvé tegyem azok számára is, akik esetleg nem értik elsõre, hogy mi az a LIFO, heap, stack, és a többi. Épp ezért, és az emészthetõség miatt nem állt szándékomban a formális szaknyelv használata.
Ez után pedig térjünk rá, hogy ez a cikk lényegében kinek jó: akit érdekel. Ettõl a módod gyorsabb nem lesz, kevesebb bug biztos nem lesz benne, nem is lesz stabilabb, tehát tényleg csak azoknak ajánlom, akiket érdekel, hogy valójában hogy is fut le a kódjuk, amit írnak.
AMX
Mint ahogy az a deAMX topicban is elhangzott, az AMX rövidítés jelentése Abstract Machine Executor, vagyis absztrakt gép végrehajtó, értve ez alatt azt, hogy ez az a kód, amivel az absztrakt gép végrehajtja az utasításokat.
AZ ABSZTRAKT GÉPEK
Mi is egy absztrakt gép? Mint azt bizonyára mindenki tudja, a legfontosabb elméleti gép, automata az ún. Turing-gép. Gyakorlatilag egy olyan automata, amely végtelen sok memóriával rendelkezik. De mi is egy automata?
Elõször is szögezzük le, hogy a Turing-gép nem létezik, csak egy elméleti modell. Ezért használunk rá nagyon egyszerû, leíró nyelvezetet. A Turing-gép egy olyan gép, amelyhez egy végtelen hosszú papírszalag tartozik. Minden papírszalagon \"betûk\"/\"szimbólumok\" (ez a hivatalos megfogalmazás) találhatók. Egyszerûség miatt tekintsük ezeket nullának és egynek. A gépbe be van programozva egy adott utasítássorozat, amit végrehajt a szalagon, ezt a logikai vezérlõegységbe programozták be.
Például tegyük fel, hogy a szalagunkra ez van írva: 0[0]11. Az olvasófej a második nullán áll. Tegyük fel, hogy az az utasítássorozat van beprogramozva, hogy
 

1: HA 0 AKKOR 1
2: HA 1 AKKOR 0
3: EGYEL ELÕRE
4: UGRÁS 1-RE

 
A következõ utasítás a szalagon lévõ karaktereket 0100-ra változtatja, és nem áll meg. Amennyiben nem ugranánk vissza egyre a következõ változó értéke már nem lenne módosítva.
MIÉRT ELÕNYÖS AZ AMX?
Az absztrakt gép egy számítógép virtuálisan. Ennek több elõnye van az x86 assemblyvel (tehát a \"tényleges\" natív assemblyvel) szemben:
 
  • Több platformon, több architektúrán elfut: a p-kód miatt minden operációs rendszeren és a 8-bitestõl a 64-bites processzorig átportolták.

  • A p-kódnak több elõnye is van: a modern operációs rendszerek nem engednek a CODE szegmensbe írni és a DATA szegmenst lefuttatni.

  • Sokkal egyszerûbb a programot a saját \"mini-számítógépen\" (sandbox) futtatni: egy natív assemblyben írt program rekurzív utasítása könnyen kifagyaszthatja az alkalmazást, és megrongálhatja a rendszer STACK-jét.


A REGISZTEREK
Az AMX regisztereket használó absztrakt gép. A regiszterek a processzorok elidegeníthetetlen részei a Neumman-elvek óta. Ennek elõnye az, hogy az absztrakt gép utasítása jobban \"rásimul\" a natív kódra, tehát a futásidõ csökken, valamint az utasítások száma csökkenthetõ.
Az AMX egy kétregiszteres struktúra, két számításra alkalmas regiszter található meg: a PRI és az ALT.
Az alábbi kódot:
 
a = bö 2;

 
Az AMX assemblyjében így kaphatjuk meg:
 

load.pri b ;betöltjük a b változót az elsõdleges (PRI) regiszterbe
const.alt 2 ;betöltjük a \"2\" konstanst az alternatív (ALT) regiszterbe
add ;az add utasítás a következõt csinálja: PRI = PRI + ALT
stor.pri a ;az a változóba eltároljuk a PRI értékét

 
A REGISZTEREK LISTÁJA
Regiszter rövidítéseRegiszter teljes neveHasználata
PRIPrimary RegisterALU, logikai és számolási mûveletek
ALTAlternative registerMásodlagos regiszter a bonyorultabb mûveletekhez
FRMStack frame pointerEgy függvény lefutása elõtti stack állapotot tárolja
CIPCode instruction pointerA végrehajtandó utasítás (az AMX-ben szinte mindig a main()-ra mutató pointer)
DATDATA pointerA DATA szegmens kezdetére mutató pointer
CODCODE pointerA CODE szegmens kezdete
STPSTACK topA STACK teteje / legalacsonyabb memóriacíme
STKSTACK indexA STACK jelenlegi helyzete
HEAHEAP pointerA HEAP teteje
MEMÓRIAKÉP
Az AMX fájl memóriaképét, vagyis a memóriában elfoglalt helyét mutatja be az alábbi táblázat. A HEAP és a STACK alapvetõen nincs a bináris fájlban, elõállítását az AMX végzi a PREFIX adatok alapján.
Ahhoz, hogy a lenti memóriaképnek megfelelõ bináris kódot kapj, használd a #pragma compress 0 direktívát a scriptedben.
LEGALACSONYABB MEM.-CÍM
PREFIX
CODE
DATA
HEAP ⇊
STACK ⇈
LEGMAGASABB MEM.-CÍM


A prefix
A prefix rész a kód legelején terül el, és elsõdleges feladata általános metaadatok, pointerek, és a függvények listáját tárolni. A memóriatérképe a következõ (az X-el jelölt dinamikusan változik az adat mennyiségétõl függõen):
(a teljes táblázat megtalálható a pawn-imp guide-ben, én ezt a verziót Y_Lesstõl szereztem, köszönöm)
MÉRETMemóriaterületLeírás
4MéretA fájl mérete
2Ismeretlen
1Fájl verzióAz AMX verziója
1Minimum VM verzió
2FlagekA fordítónak fordítás közben megadott paraméterek
2Defsize
4COD offsetA CODE szegmens kezdetének helyzete
4DAT offsetA DATA szegmens kezdetének helyzete
4HEA offsetA HEAP szegmens kezdetének helyzete
4STP offsetA STACK szegmens kezdetének helyzete
4CIPA main() függvvény memóriacíme (vagy -1)
4Publikus függvények listájára mutató pointer
4Natívák listájára mutató pointer
4Libek listájára mutató pointer
4Publikus változók listájára mutató pointer
4Public tagek listájára mutató pointer
4Names táblára mutató pointer
XPublic lista
XNative lista
XLib lista
XPubvar lista
XTag lista
XName tábla
A listák egy eleme a következõképp épül fel:
MÉRETMemóriaterületLeírás
4Függvény/változó pointerA függvény kezdetére/vált. helyére mutató pointer
4Név pointer
A Names tábla a stringeket tartalmazza: ömlesztve, tömörítve ábrázojla.
A stringek NULL-delmitáltak (tehát a minden string végén szereplõ \\0 karakter jelzi a string végét), és tömörítettek. A konstansok (különösen a stringkonstansok) a DAT szegmensben tárolódnak, tehát irrelevánsak jelen esetben.


A code szegmens
A TEXT vagy CODE szegmens egy olyan szegmens egy bináris fájlban, vagy a memóriában, amely a futtatható utasításokat tartalmazza. A CODE szegmensnek a memóriában általában csak 1 példányra van szüksége, mivel megosztható. A CODE szegmens szinte mindig csak olvasható, hogy  megakadályozható legyen az, hogy a program véletlen a saját utasításait módosítsa.


A data szegmens
A DATA szegmens a program virtuális memóriájának egy része, az egész program számára elérhetõ (ti. globális) változókat tárolja.


A heap
A heap, vagy \"halom\" a memória dinamikusan használható része. A \"halom\" igen deskriptív: hiszen gyakorlatilag a halomra akármikor hozzáadhatunk vagy elvehetünk valamit anélkül, hogy az egész összedõljön. Ha egy új dinamikus változót hozunk létre, lényegében a heap-bõl vesszük el a területet.
Mivel a létrehozott változónak lefoglalt memóriaterület sosem ismeretes elõre, a new statement mindig egy pointerrel tér vissza a heapban tárolt pozícióval.
A PAWN nyelv nem használ dinamikus változókat, minden változót vagy a DATA (glob), vagy a STACK (lok) szegmensek tárolnak, ezért például a memory leak-ek nem lehetségesek. Azért ide megemlítem, ha valakinek van kedve a memory leakekhez, hogy Y_Less malloc függvénykönyvtárával lehetõséged van \"dinamikus\" memóriát kiosztani (azért idézõjelbe, mert ez sem igazi dinamikus memória, és ez sem a heapben van, csak egyfajta workaround).


A stack
A STACK, hivatalos magyar nevén \"verem\", deskriptívabb nevén \"rakás\" egy memóriakezelési szerkezet. Mielõtt jobban megvizsgáljuk, nézzük meg az alábbi példát:
Tegyük fel, hogy van egy rakás téglád egymáson. Mivel a téglák nehezek és rakásban vannak (tehát mindegyik tégla a másik tetején), ezért lényegében három dolgot tehetsz a téglákkal anélkül, hogy összedõljenek:
 
  • Ránézel a legfelsõ téglára

  • Leveszed a legfelsõ téglát

  • Felraksz egy újabb téglát


A stack egy olyan memóriakezelési struktúra, ami változókat tárol, hasonlóan a tömbökhöz. Viszont míg a tömbökben tetszés szerinti elemet elérhetsz, addig a stack ennél limitáltabb. Lényegében a fenti három dolgot teheted meg a stackkel:
 
  • Megnézed, mi a felsõ érték, vagy top

  • Leveszed a legfelsõ értéket, vagy pop

  • Felraksz egy újabb értéket, vagy push


Ahogy a téglás hasonlat is mondja: ha valaki egy téglát rárak a rakásra, akárki, aki levesz egy téglát a legfelsõt fogja levenni. Ez a LIFO-elv: last in-first out. A legelsõ felrakott elem lesz a legelsõ, amit levesznek.
Ez, bár szép analógia, lehetne egy jobbat is létrehozni. Vegyünk egy adott számú postaládát: mindegyik egymás fölött, mindegyik csak egy dolgot tarthat magában, mindegyik üresként kezdi, és persze mindegyik az alatta lévõvel össze van ragasztva, így a számuk nem változik. A kérdés: ha a postaládák száma nem változtatható, hogyan kapunk egy stacket? Jelöljük meg egy matricával a legmagasabban lévõ üres postaládát. Minden a matrica alatt lévõ postaláda a stack tagja, minden a matrica fölött lévõ postaláda nem a stack tagja.
Ez szinte azonos azzal, hogy mûködik a stack. A stack egy elõre meghatározott mennyiségû memória: a postaládák memóriacímek, és a levelek a memóriacím alatt lévõ adatok. A \"matrica\" a fentebb említett STK regiszter: Stack Index/Stack Pointer, ami mindig a legnagyobb elérhetõ stack címre mutat. Az egyetlen különbség a postaládás hasonlat és a valós stack közt az, hogy ha az adatot el akarjuk tüntetni, nem kell \"kiüríteni\" a postaládát: elég a matricát egyel lejjebb helyezni és az a memóriarész onnantól nem a stack tagja, amíg a következõ memóriarész felül nem írja.
Miket rakunk a stackbe? Változókat, paramétereket, és függvényhívásokat.
Mivel a globális változók a DATA szegmensbe kerülnek, ezért nekünk lényegében csak azzal kell foglalkoznunk, mi történik a lokális változókkal: tehát a függvényeken belül meghívottakkal.
Példaképp tekintsük meg az alábbi kódot:
 

main() { new a=4; foo(a,2); }
foo(a,b) { }

 
Ez, a nem mûködõ, viszont példaként tökéletesen használható kódrészlet tökéletes példa a stack bemutatására. Mikoris a main() függvény meghívja a foo függvényt, az alábbi játszódik le (ezt szaknyelven a Standard Entry Sequence-nek  hívjuk):
Az egyszerûség kedvéért a main()-t fogjuk hívni a meghívó függvvénynek míg a foo()-t a meghívott függvvénynek.
 
  • A main() ráhelyezi a foo() függvény visszatérési pointerét, vagyis azt, hogy a függvény lefutása után a foo() függvény a main()-en belül hova ugorjon vissza.

  • Ezután a main() függvény betölti foo() függvény paramétereit a stackbe fordított irányban (ez az ún. C-declaration order vagy cdecl).

  • Ezután következik a main() függvény stack frame pointerének helyzete, szintén rákerül a stackre.

  • A fenti adat memóriacíme tárolódik el a Stack Frame Pointerben (FRM regiszter). A pointernek több célja is van: egy részrõl meghatározza, hol kezdõdik az a stack szegmens, ahová a függvényünk (foo()) a helyi változóit fogja tárolni.
    Más részrõl be kell lássuk, hogy az STK (a stack tetejére / legalacsonyabb memóriacímére mutató pointer) nem megbízhatóan adja vissza a változók helyét, hiszen új változók felvétele esetén a stack is feljebb kerül az STK-val együtt. Ez a FRM elsõdleges használati területe: nevezetesen, hogy a függvényekben használt lokális változók létrehozásához a pontatlan STK helyett a függvény lefutása során kötött FRM-t használjuk.
    Mivel a stack memóriacímei csökkennek (lásd a memóriaképet), egy lokális változó (ami a mostani pointer fölött fog lenni) memóriacíme mindig a pointerhez képest negatív, míg a paraméterek értéke mindig pozitív lesz.
    Harmadrészrõl az FRM alatt lévõ memóriacím (tehát a main() függvény ugyanilyen framejére mutató pointer) megadni azt a területet, ahova a függvény lefutásakor vissza kell léptetni a STK-t (érvénytelenítve ezzel az összes lokális változót).

  • Ezután természetesen a függvény lefut, és minden lokális változó rákerül a stackre.


Minden stackre helyezett elem esetén természetesen frissítõdik a STK regiszter helye (ami a stack legfelsõ elemét mutatja).
Ha a függvény megáll, lefut a fenti ellentéte, a Standard Exit Sequence
 
  • A STK (stack tetejére / legalacsonyabb memóriacímére mutató pointer) értéke frissül a FRM értékére, megszûntetve ezzel a lokális változók elérésnek lehetõségét.

  • A FRM a mostani helyzete alatt található értékére frissül (ami pont a meghívó függvény ugyanolyan FRM értéke).

  • A main() függvény visszatérési pointerét leszedi a stackrõl és odaugrik, és folytatódik a kód végrehajtása.


Zárásnak még annyit: hogy azért is futnak a több-threades programok gyorsabban, mert mindegyiknek külön stackje van.


A heap és a stack, stack overflow
Lehetõség van arra, hogy a stack számára elõre lefoglalt memóriát túllépjük, és beleírjunk a heap-be: ez általában nagy mennyiségû változó esetén következik be, ezt nevezzük stack overflownak.
Ez azért lehetséges, mert a heap és a stack ugyanazt a memóriaterületet kapja, de ellentétes irányban kezdõdnek: a heap egyre növekvõ, míg a stack egyre csökkenõ memóriacímeket kap.
A stack overflow tényérõl a fordító tájékoztatást ad: mégpedig kiírja, hogy az egyes szegmensek mennyi bájtot foglalnak el, és hogy a HEAP/STACK szegmensnek mekkora a maximális mérete, és mekkora az a méret, amit igényelne ahhoz, hogy minden változó elférjen benne. Ez azért lehetséges, mivel a stack mérete a fordítás során már ismert. Jogosan felmerülhet a kérdés, hogy akkor miért nem kap az AMX automatikusan több memóriát a programhoz: ez a mennyiség limitált és alapjában véve nem túl nagy, tehát megeshet, hogy egy normálisabb módhoz is növelni kell. Ezt a #pragma dynamic direktívával lehet megtenni.


(és ennyi, frissítések várhatóak)
« Utoljára szerkesztve: 2013. május 22. - 16:47:18 írta krisk »

Nem elérhető Rupert

  • 2301
    • Profil megtekintése
(Leírás) Az AMX és az AMX-memóriatérkép
« Válasz #1 Dátum: 2013. január 12. - 12:19:40 »
0 Show voters
Köszönjük a leírást, nagyon szép! Áthelyezve.


Nem elérhető krisk

  • 2380
    • Profil megtekintése
(Leírás) Az AMX és az AMX-memóriatérkép
« Válasz #2 Dátum: 2013. január 12. - 12:34:54 »
0 Show voters
Idézetet írta: Rupert date=1357989580\" data-ipsquote-contentapp=\"forums\" data-ipsquote-contenttype=\"forums\" data-ipsquote-contentid=\"32288\" data-ipsquote-contentclass=\"forums_Topic
Köszönjük a leírást, nagyon szép! Áthelyezve.


 
Köszönöm az áthelyezést.

(Leírás) Az AMX és az AMX-memóriatérkép
« Válasz #3 Dátum: 2013. január 12. - 15:24:55 »
+1 Show voters
Ez a pillanat amikor azt mondom:
\"Szabi (Infó tanárom) visszaadhatja a tanári diplomáját, ha egy végzettség nélküli \"névtelen\" fórumozó is érthetõbben magyaráz, mint õ!\"

Nem elérhető Vic15

  • 500
    • Profil megtekintése
(Leírás) Az AMX és az AMX-memóriatérkép
« Válasz #4 Dátum: 2013. január 16. - 17:15:52 »
0 Show voters
Szép leírás krisk :)

 

SimplePortal 2.3.7 © 2008-2024, SimplePortal