ÚÄÄÄ ÚÄÄÄ
ÚÄ ÚÄ
ÚÄ ÚÄ ÚÄ
ÚÄ ÚÄÚÄ ÚÄÚÄ
ÚÄ ÚÄ ÚÄ
ÚÄ ÚÄÄ
ÚÄ
ÚÄÄ ÚÄÄ ÚÄÄ
ÚÄ Ú ÚÄ
ASSEMBLY RULEZ
ÚÄÄÄÄÄÄÄÄÄ ÚÄ
ÚÄ ÚÄ
ÚÄÄ ÚÄÄ ÚÄ
ÚÄ ÚÄ ÚÄ
II.rész
ÚÄÄ ÚÄÄ ÚÄÄÄÄ
ÚÄ ÚÄ
Szegmentálás |
Most pedig következik a végzet, a szegmentálás elmagyarázása. Ha néhol kissé érthetetlen lennék, azért elnézést kérek, de ezt nehéz így írásban elmagyarázni. Akinek bármi problémája lenne az írjon nekem, a címem benne van az újságban. Mielõtt bárki feladná olvassa végig ezt a részt, mert lehet, hogy csak a végén közlöm, ami nem világos számodra.
A 8086 procit 20 darab címvezetékkel tervezték, amivel 2^20=1048576 (1MB) byte memória érhetõ el. Egy memóriarekesz eléréséhez 20 bitre van szükség, mert ezek adják a szükséges lehetõségeket az eléréshez. A probléma az volt, hogy a regiszterek 16 bitesek voltak és nem lett volna elõnyös egyes regisztereket 20 bitesre gyártani, mert a proci 16 bitekben gondolkodott és szörnyen lassította volna a 20 bittel dolgozás. Persze megoldás lett volna, ha az egész proci 20 bites, de ez nem lett volna kompatibilis semmivel, ezért kitalálták a szegmentálást.
A szegmentálás lényege, hogy a memóriát felosztják 64 KB-os részekre. Egy ilyen rendszerben egy byte eléréséhez két dolog kell: a szegmens kezdõcíme és a szegmens kezdetéhez viszonyított mutató (offset). Most úgy tûnhet hogy semmit se csináltunk, csak bonyolítottuk a helyzetet, hisz a szegmens kezdetének tárolásához szintén 20 bit kell. Itt jött a nagy ötlet és azt mondták, hogy szegmens CSAK és KIZÁRÓLAG 16-tal osztható címen kezdõdhet. Ennek nagy elõnye hogy az 1 MB-os tárban így csak 1048576/16= 65536 szegmens lehet. Igenám, de a 65536 a pontosan 2^16 és ez már elfér 16 biten. Ezek után a szegmenscím valójában nem egy memóriacímet fog jelenteni, hanem egy sorszámot amit 16-tal szorozva kapjuk a pontos memóriacímet.
Most mindezt átültetjük a gyakorlatba. A 20 bites cím hexadecimálisan 5 számjeggyel írható le pl.: 3EC5Ah. Egy szegmens csak 16-tal osztható címen kezdõdhet, ezért a hexa alakja MINDIG 0-ra végzõdik (ugyanis, minden hexa helyiérték 16-szorost ér. Az elsõ helyre 0-t írva biztos, hogy 16-tal osztható lesz a szám, mert minden jegyét 16 többszörösével szoroztuk. Az elsõ hely szorzótényezõje 1, de ha egy tizenhattal osztható számhoz 16-tal nem oszthatót adunk, akkor az eredmény sem lesz osztható, remélem világos?!). De ha ez így van akkor ez a 0 simán elhagyható, elég ha majd a proci odaképzeli. Nekünk pedig marad 4 hexa számjegy, ami 16 bit.
A szegmens elejéhez viszonyított mutató (offset) is 16 bites, mert így a 64 KB hosszú szegmens minden elemét elérhetjük. A két elembõl áll össze a pontos cím. Ez a következõképp jelöljük:
szegmenscím:offset példul 43DCh:06BF5h.
Most már
csak néhány példa hiányzik a sikerhez. Tegyük
fel, hogy érdekel melyik byteot jelenti a következõ cím:
37C4h:3D6Ah. Ehhez elõször a szegmenscímet
kell 16-tal szorozni, ami hexadecimálisan nem más, mint egy
0-t utána írni, így a szegmens kezdõcíme
37C40h. Ehhez kell hozzáadni az offsetet. Ezt a legegyszerûbb számológéppel megtenni.
Akinek nem lenne ilyen gépe az elvégezheti papíron is (persze vannak
számológép programok amik tudnak ilyet).
Papíron ugyanúgy kell csinálni,
mint tizes számrendszerben, csak a számjegyekbõl több
van:
37C40h
+
3D6Ah
3B9AAh
Megvan a keresett érték, ezzel utána elvégezhetjük amit akarunk.
Kissé bonyolultabb, mikor egy pontos címet akarunk szegmentált formába írni. Ennek egyszerû z oka. A szegmensek 16 byteonként követik egymást és egy-egy szegmens 65536 byte hosszú, ezért egy címet sokféleképpen meg lehet adni. Egy lehetséges módszert mutatok be, van még sok más is.
Alakítsuk
vissza az elõbb kapott értéket, lássuk mi lesz belõle. Elõször
is vágjuk le az abszolút cím - az elõbbi példában
ez 3B9AAh lett - utolsó számjegyét. Ez a 0Ah számjegy.
írjunk a helyére egy 0-t. Ha most jól megnézzük
az eredményt (3B9A0h), akkor láthatjuk, hogy pont olyan a szám
mint a nullával bõvített szegmenscím. Akkor a 0-t
nyugodtan el is hagyhatjuk és a maradékot elnevezzük a szegmenscímnek.
Az 0Ah szám pedig az offszet lesz. így a 3B9AAh szegmens:offszet
alakban így is felírható:
3B9Ah:000Ah. Ez persze még csak nem is hasonlít
az elõzõ példa kezdõ értékére
(37C4h:3D6Ah). Lehet viszont olyan helyzet, ahol egy abszolút címet
egy meghatározott szegmensre vonatkoztatva kell felbontani (nem túl
gyakran, de akadhat). legyen ez a szegmens a 37C4h. Ekkor az elõbb leírt
módon felbontjuk elõször úgy, hogy az offszet érték
0Fh-nál kisebb legyen (az elõzõ módszerrel ez mindig
így lesz). Ezután le kell vonni a kívánt szegmenscímet
(37C4h) az elõzõ módszerrel kapott szegmenscímbõl
(3B9Ah). Az így kapott szám - jelen esetben 3D6h - végéhez
hozzátesszük ez elsõ lépésben kapott számot.
ezzel megkapjuk a kívánt szegmensben az offszet címet,
ami itt 3D6Ah. Ezt összehasonlítva a kiinduló példa
értékeivel örömmel mondhatod, hogy visszakaptuk azt
(37C4h:3D6Ah).
Egyébként mikor egy hexa szám végérõl elveszünk egy számjegyet, az a 16-tal való osztás eredménye lesz, az elvett szám pedig a maradék. Amikor hozzáillesztünk egy számjegyet, akkor elõször 16-tal szorzunk, majd hozzáadjuk a számot. Ezzel gondolom nem mondtam túl nagy újdonságot.
VEREM |
Ez egy igazán fontos része az assembly programozásnak. Azért írom ezt, mert magas szintû nyelvben ritkán találkozunk a verem használatával. Legfeljebb beállítjuk a méretét oszt kalap. Az assembly ebben is más. Amikor különbözõ rutinokat írunk, akkor gyakran kellhet, hogy a regiszterek értékét ne változtassuk meg, mert a hívó programrésznek esetleg még szüksége van rájuk. Még ennél is fontosabb a verem, mikor rezidens programot írunk, hiszen ekkor egy megszakításon ülünk rajta, és esetleg a legváratlanabb pillanatban aktiválódhatunk (az éppen futó program számára váratlanul) és ekkor a futó proggy nem is tudná elmenteni a regisztereket, míg ez egyszerû rutinoknál lehetséges. Ezért nekünk kell gondoskodni a tárolásról. Az nem jó megoldás, hogy minden elmentendõ regiszterhez egy memóriarészt jelölünk ki, mert ez egyrészt pocsékolja a memóriát, másrészt - és ez a fontosabb, hiszen maga a verem is memóriát használ - a sok címke csak áttekinthetetlenné teszi a programot, mert ha nem definiálunk minden egyes mentéshez - és ilyen mentésre nem csak, rutinok elején lehet szükség, hanem akkor is, ha ideiglenesen másra kell az adott regiszter és más regiszterbe nem tudjuk átmásolni, mert az is kell - külön címkét akkor még hamarabb elkavarodunk, míg a veremnél könnyen meg lehet találni a betevés-kivétel párokat. Huhh, de szép barokkos mondat volt, szép hosszú. Már én se értem.
Na akkor mi is az a verem? A verem az verem. Vagyis amit beleteszünk, azt addig megõrzi amíg ki nem vesszük. Amit utoljára beletettünk, azt vehetjük ki belõle elõször, mint egy igazi veremnél. Ezt nevezik LIFO (Last In/First Out) típusú veremnek. A fõ eltérés az igazi vermektõl az, hogy ez lefele bõvül. A legelõször beletett adat a tetején "lebeg", a többi pedig ez alá kerül szépen sorjában. A veremben lévõ helyek sorszámozása a verem aljától kezdõdik. A veremmutató (SP - Stack Pointer) mindig az aktuális elsõ szabad helyre mutat a verem tetejétõl számítva. A következõ kis ábra mindent megmutat:
xxxx
<- ez a verem teteje, ennél feljebb tilos menni
yyyy <- egy verembe tétel
után ide fog mutatni az SP
....
....
xxxx <- ez a verem alja,
ennél lejjebb nem szabad menni, mert akkor más részeket
írunk át, esetleg a programot, az adatokat, vagy éppen
a DOS-t
A verem kezelésére két utasítás van a PUSH (beletenni) és a POP (kivenni). Ezentúl minden új utasításnál leírom a használatát is.
PUSH utasítás (PUSH word onto stack):
Használat: PUSH regiszter16/memória16
Az adat lehet bármilyen 16 bites regiszter, vagy bármilyen címzéssel meghatározott memóriatartalom. A procit nem érdekli, hogy te esetleg csak egy byteot akartál lenyomni a verembe, mindig egy egész szót fog letenni. Erre figyelni kell, nehogy a kivételkor a szó második tagja is visszaállítódjon, mert esetleg az már mást tartalmaz. Miután letette a verembe, 2-vel csökkenti SP-t, hogy jelezze a verem növekedését. A PUSH utasítás speciális változata a PUSHF, ami a FLAG regiszter tartalmát helyezi a lyukba. Ez nagyon hasznos, mikor pl egy összehasonlítás eredményét meg kell õrizni.
POP utasítás (POP a word from the stack):
Használat: POP regiszter16/memória16
Az utoljára betett szót berakja a 16 bites regiszterbe, vagy a memória egy szó részére. Ezután az SP-t 2-vel növeli, hogy ennek a szónak a helye felszabaduljon. A CS regiszterbe nem lehet POPolni!! A POPnak is van FLAG verziója, a POPF. Ez az SP által címzett szót a verembõl a FLAG-be teszi át.
És most egy
bónusz a késõbbi programokhoz (ha kezdõ vagy átugorhatod):
A 8086 máshogy hajtja végre a PUSH SP utasítást,
mint a fejlettebb procik. A 8086 elõször csökkenti SP-t 2-vel,
utána leteszi a értékét a verembe, míg a
80286+ procik elõször leteszik SP-t a gödörbe, majd csak
ezután csökkentik az értékét. Ezzel a kis különbséggel
pillanatok alatt el lehet dönteni, milyen proci van a gépben. Aki
PUSH SP-t akarja használni, az a következõ módon teheti,
hogy minden procin ugyanazt az eredményt kapja:
PUSH BP ;
BP lemegy a gödörbe
MOV BP,SP ;
SP átmegy BP-be
XCHG BP,[BP] ; BP-t kicseréljük
az SS:BP által címzett bytetal, így SP lenn van a
vermen és BP értéke is megmaradt
No jöjjenek a megszakítások.
Lucifer of ZeroBit