Assembly programozás szerkesztette: Iványi Péter September 27, 2010
2
Tartalomjegyzék 1 Bevezetés 11 1.1 RISC és CISC processzor architektúrák......................... 11 1.2 Assembly elsőre...................................... 12 1.3 Miért tanuljunk assembly nyelvet?............................ 12 1.4 Mikor ne használjunk assembly nyelvet?......................... 13 1.4.1 A magas szintű programozási nyelvek előnye.................. 14 1.4.2 Az assembly hátrányai.............................. 14 1.5 Mielőtt elkezdenénk assembly-ben programozni...................... 14 1.6 Szintakszis........................................ 15 1.7 Assemblerek....................................... 15 1.7.1 MASM...................................... 15 1.7.2 GAS....................................... 15 1.7.3 TASM....................................... 15 1.7.4 NASM...................................... 15 1.7.5 Melyik assembler?................................ 15 1.8 Összefoglalás....................................... 16 1.9 Ellenőrző kérdések.................................... 16 2 A számítógép felépítése 17 2.1 A processzor....................................... 17 2.1.1 Végrehajtási ciklus................................ 18 2.1.2 A rendszer óra.................................. 19 2.2 Címzési architektúra................................... 19 2.2.1 Három címes architektúra............................ 19 2.2.2 Két címes architektúra.............................. 20 2.2.3 Egy címes architektúra.............................. 20 2.2.4 Zéró cím architektúra............................... 20 2.2.5 Load/Store architektúra.............................. 20 2.3 Regiszterek........................................ 21 2.4 Végrehajtási sorrend................................... 21 2.4.1 Branching..................................... 21 2.5 Memória.......................................... 22 2.5.1 Memória műveletek............................... 22 3
2.5.2 Olvasási ciklus.................................. 22 2.5.3 Olvasási ciklus.................................. 23 2.5.4 Memória típusok................................. 23 2.5.5 Byte sorozatok tárolása.............................. 24 2.5.6 Adat alignment problema........................... 25 2.6 Input/Output........................................ 26 2.6.1 I/O eszközök elérése............................... 27 2.7 Összefoglalás....................................... 27 2.8 Ellenőrző kérdések.................................... 27 3 A processzor 29 3.1 Általános regiszterek................................... 30 3.2 Szegmentált címzés először................................ 30 3.3 Címzési módok...................................... 31 3.3.1 Direkt címzési mód................................ 33 3.3.2 Indirekt címzési mód............................... 34 3.4 Státusz regiszter...................................... 35 3.5 Ellenőrző kérdések.................................... 36 4 NASM assembler 37 4.1 Egy forrás file szerkezete................................. 37 4.2 Pszeudo utasítások.................................... 37 4.2.1 DB és társai.................................... 38 4.2.2 RESB és társai.................................. 38 4.2.3 Konstansok.................................... 39 4.2.4 TIMES pszeudo utasítás............................. 39 4.3 SEG kulcsszó....................................... 39 4.3.1 További hasznosítási területek.......................... 40 4.4 WRT kulcsszó....................................... 40 4.5 Parancssori opciók.................................... 40 4.6 Hibaüzenetek....................................... 40 5 DEBUG program 41 5.1 Jelölések.......................................... 41 5.2 A DEBUG indítása.................................... 41 5.3 A DEBUG parancsai................................... 41 5.4 Példák........................................... 44 5.4.1 1. Példa...................................... 44 5.4.2 2. Példa...................................... 44 5.4.3 3. Példa...................................... 45 6 Első programok 47 6.1 Első program....................................... 47 6.2 Egy karakter kinyomtatása................................ 48 4
6.3 Egy szöveg kinyomtatása................................. 49 6.4 Egy karakter beolvasása.................................. 51 7 Assembly nyelv utasításai 53 7.1 Adatmozgató utasítások.................................. 54 7.1.1 MOV....................................... 54 7.1.2 XCHG...................................... 54 7.1.3 XLAT....................................... 55 7.1.4 LDS........................................ 55 7.1.5 LES........................................ 55 7.1.6 LEA........................................ 56 7.1.7 PUSH....................................... 56 7.1.8 PUSHF...................................... 56 7.1.9 PUSHA...................................... 57 7.1.10 POP........................................ 57 7.1.11 POPF....................................... 58 7.1.12 POPA....................................... 58 7.1.13 LAHF....................................... 59 7.1.14 SAHF....................................... 59 7.2 Matematikai utasítások.................................. 60 7.2.1 INC........................................ 60 7.2.2 DEC....................................... 60 7.2.3 ADD....................................... 60 7.2.4 ADC....................................... 61 7.2.5 SUB........................................ 61 7.2.6 SBB........................................ 62 7.2.7 MUL....................................... 62 7.2.8 IMUL....................................... 63 7.2.9 DIV........................................ 63 7.2.10 IDIV....................................... 64 7.2.11 NEG....................................... 64 7.2.12 CBW....................................... 65 7.2.13 CWD....................................... 65 7.3 Bitforgató és bitléptető utasítások............................. 66 7.3.1 RCL........................................ 66 7.3.2 RCR....................................... 66 7.3.3 ROL........................................ 67 7.3.4 ROR....................................... 68 7.3.5 SAL, SHL.................................... 68 7.3.6 SAR........................................ 69 7.3.7 SHR........................................ 69 7.4 Logikai utasítások..................................... 71 7.4.1 AND....................................... 71 5
7.4.2 OR........................................ 71 7.4.3 XOR....................................... 71 7.4.4 NOT....................................... 71 7.4.5 TEST....................................... 71 7.4.6 CMP....................................... 71 7.5 Vezérlésátadó utasítások................................. 72 7.5.1 JMP........................................ 72 7.5.2 Feltételes utasítások............................... 72 7.5.3 JCXZ....................................... 72 7.5.4 LOOP....................................... 72 7.5.5 LOOPNZ..................................... 72 7.5.6 LOOPZ...................................... 73 7.5.7 CALL....................................... 73 7.5.8 RET........................................ 73 7.5.9 INT........................................ 73 7.6 String kezelő utasítások.................................. 74 7.6.1 MOVSB, MOVSW................................ 74 7.6.2 CMPSB, CMPSW................................ 75 7.6.3 LODSB, LODSW................................ 75 7.6.4 STOSB, STOSW................................. 75 7.6.5 SCASB, SCASW................................. 75 7.6.6 REP........................................ 76 7.6.7 REPZ....................................... 77 7.6.8 REPNZ...................................... 77 7.7 Processzor vezérlő utasítások............................... 78 7.7.1 CLC........................................ 78 7.7.2 STC........................................ 78 7.7.3 CMC....................................... 78 7.7.4 CLD....................................... 78 7.7.5 STD........................................ 78 7.7.6 CLI........................................ 78 7.7.7 STI........................................ 79 7.8 Egyéb utasítások..................................... 80 7.8.1 NOP....................................... 80 7.8.2 IN......................................... 80 7.8.3 OUT....................................... 80 7.9 Ellenőrző kérdések.................................... 81 8 Assembly programokról 83 8.1 Programozási módszer.................................. 83 8.2 Megszakítások...................................... 84 8.2.1 Hardware-es megszakítások........................... 84 8.2.2 Megszakítások 8086-os processzorokon..................... 85 6
8.2.3 INT 21h megszakítás............................... 86 8.2.4 Kivételek..................................... 86 8.3 Kitérő Linux-ra...................................... 87 8.4 COM programok..................................... 87 8.4.1 Program Segment Prefix............................. 88 8.5 EXE programok...................................... 88 8.6 XOR használata...................................... 88 8.7 Assembly integer aritmetika............................... 88 8.7.1 BCD aritmetika.................................. 88 8.8 Ellenőrző kérdések.................................... 89 9 Példa programok 91 9.1 Egy byte bináris kinyomtatása.............................. 91 9.2 Egy hexadecimális szám kinyomtatása.......................... 93 9.3 Egy byte hexadecimális kinyomtatása.......................... 95 9.4 Egy decimális számjegy ellenőrzött beolvasása és kinyomtatása............. 97 9.5 Egy karakter beolvasása és módosítása.......................... 98 9.6 Öt karakter bolvasása és kinyomtatása fordított sorrendben............... 100 9.7 Két egyjegyű szám összeadása.............................. 101 9.8 Egy karakter n-szeri kinyomtatása............................ 104 9.9 Téglalap kinyomtatása.................................. 105 9.10 Sakktábla nyomtatása................................... 108 9.11 ASCII tábla kinyomtatása................................. 111 9.12 Szám kiírása decimális formában............................. 112 9.13 Olvasás a memóriából................................... 114 9.14 Közvetlen videó memóriába írás............................. 115 9.15 Szöveg beolvasása.................................... 116 9.16 Beolvasott szövegben karakterek számlálása....................... 117 9.17 Beolvasott szöveg nagy betűsre konvertálása....................... 118 9.18 Feladatok......................................... 119 10 Függvények 121 10.1 A verem adatszerkezet.................................. 121 10.1.1 A verem implementációja............................ 121 10.1.2 Verem műveletek................................. 123 10.2 A verem használata.................................... 123 10.2.1 Értékek időleges tárolás............................. 123 10.3 Függvények definíciója.................................. 124 10.3.1 Egymásba ágyazott függvényhívások...................... 126 10.4 Paraméter átadás függvényeknek............................. 126 10.4.1 Paraméter átadás regiszteren keresztül...................... 128 10.4.2 Paraméter átadás memórián keresztül...................... 128 10.4.3 Paraméter átadás vermen keresztül........................ 130 10.4.4 Érték és cím szerinti paraméter átadás...................... 135 7
10.4.5 Változó számú paraméter átadása függvénynek................. 136 10.5 Lokális változók függvényekben............................. 141 10.5.1 ENTER és LEAVE utasítások.......................... 142 10.6 Rekurzív függvények................................... 143 10.7 Hatékonyság........................................ 144 10.8 Ellenőrző kérdések.................................... 144 10.9 Feladatok......................................... 148 11 Makrók 149 11.1 Egy soros makrók..................................... 149 11.2 Több soros makrók.................................... 151 11.2.1 Címkék makrókban................................ 152 11.2.2 Greedy makró paraméterek.......................... 153 11.3 Makrók és függvények még egyszer........................... 154 11.4 Makrók gyűjtemények.................................. 154 11.5 Ellenőrző kérdések.................................... 157 12 String műveletek 159 12.1 String utasítások...................................... 160 12.1.1 String másolás.................................. 160 12.1.2 Stringek összehasonlítása............................. 163 12.1.3 Keresés stringek-ben............................... 165 12.1.4 LODSB és STOSB utasítások használata.................... 166 12.2 String utasítások előnyei és hátrányai........................... 166 12.3 Ellenőrző kérdések.................................... 168 13 Példák függvényekre és szöveg kezelésre 169 13.1 Szöveg hosszának megállapítása............................. 169 14 C és assembly programok kapcsolata 171 14.1 Függvény hívási konvenciók............................... 171 14.1.1 16 bites mód................................... 171 14.1.2 32 bites mód................................... 171 15 Optimalizálás 173 15.1 Optimalizálás sebességre................................. 173 15.1.1 Sorrenden kívüli végrehajtás........................... 173 15.1.2 Utasítás betöltés és dekódolás.......................... 174 15.1.3 Utasítás késleltetés és átbocsátási képesség................... 174 15.1.4 Függőségi lánc megtörése............................ 174 15.1.5 Ugrások és függvény hívások.......................... 174 15.2 Optimalizálás méretre................................... 174 15.3 Memória hozzáférés optimalizálása........................... 174 15.4 Ciklusok optimalizálása.................................. 174 15.5 Vector programozás.................................... 174 8
15.6 Problémás utasítások................................... 174 16 Optimalizált példák 175 16.1 ASCII tábla nyomtatása rövidebben........................... 175 17 Megjegyzések 177 17.1 Szokásos hibák...................................... 177 A ASCII táblázat 179 B Felhasznált irodalom 181 Példa programok listája..................................... 182 Tárgymutató........................................... 183 9
10
1. Fejezet Bevezetés Ez a jegyzet egy összeállításnak, szerkesztett jegyzetnek indult inkább, mint egy önálló könyv. A jegyzet anyagát igyekeztem úgy összeszedni, hogy az egy egységes egészt alkosson. A jegyzet célja az assembly nyelv megismertetése a hallgatókkal. Ugyanakkor az assembly nyelv nagyon erősen kötődik a processzor architektúrához, így az assembly programozáshoz az architektúrával is meg kell ismerkedni egy kicsit. A jegyzet kifejezetten az Intel x86-os processzorának alacsony szintű programozásával foglalkozik, ezek közül is a 8086-os processzorral. Ez az egyik legkorábbi Intel processzor és furcsának tűnhet a választás, hogy 2010 környékén is erről a processzorról beszélünk. A jegyzet írása során fő célom az volt, hogy az alapokat minden hallgató meg tudja érteni és el tudja sajátítani. Ehhez egy viszonylag egyszerű processzorra volt szükség és főleg ezért választottam a 8086-os processzort alapul. A másik indok, hogy a szimulátorok és virtuális gépek biztos támogatják ezt a processzort és így bármilyen környezetben lehet az assembly programozást gyakorolni. Így a jegyzet főleg kezdőknek szól, de azt remélem, hogy a gyakorlottabb hallgatók is profitálnak a jegyzet elolvasásából. A jegyzet természetesen igyekszik foglalkozni az újabb utasításokkal és módszerekkel, amiket például a Pentium processzorokra fejlesztettek. 1.1 RISC és CISC processzor architektúrák A processzor architektúrák két nagy csoportba sorolhatók: CISC (Complex Instruction Set Computers) 1, RISC (Reduced Instruction Set Computers) 2. A domináns processzor architektúra a piacon a Pentium processzor, ami a CISC családhoz tartozik, de ugyanakkor a jelenlegi trendek szerint egyre inkább a RISC architektúra kerül előtérbe. A RISC processzorok közé tartoznak a MIPS, SPARC, PowerPC és ARM processzorok. A 64-bites Itanium is RISC alapú processzor. Mit jelent az, hogy komplex utasítás a CISC architektúra esetén? Két szám összeadása egyszerű műveletnek számít. Ugyanakkor, ha egy tömböt átmásolunk és közben a tömb mutatókat folyamatosan frissítjük, az már komplex utasításnak számít. 3 A RISC rendszerek csak egyszerű utasításokat használnak, és bizonyos feltételeket is szabnak. Például az utasítások argumentumainak a regiszterekben kell lenniük és nem a memóriában. 1 Szabad fordításban: Komplex utasításkészletű számítógép 2 Szabad fordításban: Egyszerűsített utasításkészletű számítógép 3 Létezik ilyen utasítás a CISC processzorokon, ez a MOVSB utasítás, lásd 7.6.1. bekezdés. 11
1.2 Assembly elsőre Az assembly nyelven írt programokat processzálni kell egy másik program, assembler, által ami gépi kódot generál. A gépi kódot fogja futtatni a processzor. Nézzünk néhány assembly utasítást: inc [result] mov [meret], 45 add [mask1], 256 Az első sorban megadott utasítás megnöveli a result változó értékét. A második sorban megadott utasítás a 45-ös értéket tölti a méret változóba, míg a harmadik utasítás 256-ot add a mask1 változóhoz. A fenti kódrészlet C programozási nyelven a következőképpen néz ki: result++; meret = 45; mask1 = mask1+ 256; A példa alapján a következőket lehet megállapítani az assembly nyelvről: Az assembly nyelv utasításai kriptikusak. Az assembly nyelv műveleteit mnemonikok írják le, például add vagy mov. Az assembly nyelv utasításai nagyon alacsony szintűek. Például a következőt már nem írhatjuk le 4 : mov [meret], [adat] A 1.1. táblázat néhány assembly utasítást és a neki megfelelő gépi kódot mutatja. A táblázatnál az első észrevétel, hogy a RISC processzorokon az utasítások hossza fix. (Ezzel is csökkentve a komplexitást.) A másik fontos észrevétel, hogy a gép kód megértése nagyon nehéz az emberek számára, hiszen több ezer szám kombinációt kellene megjegyezni. Ugyanakkor közvetlen, egy az egyes megfeleltetés van az assembly utasítás és a gépi kód között ezért ha az utasítást írjuk le az pontosan megfelel a szándék szerinti gépi kódnak és így csak mazochisták programoznának gépi kódban. Mindenki inkább az emberek számára jobban értelmezhető assembly parancsokat, mnemonikokat használja. Persze a digitális forradalom elején néhány programot még gépi kódban írtak. 1.3 Miért tanuljunk assembly nyelvet? Az assembly programozás nem annyira népszerű mint néhány éve volt. Ugyanakkor még mindig több oka van annak, hogy megtanuljunk assembly-ben programozni: Tanulás: Fontos tudni hogyan működnek a processzorok és fordítók az utasítás szinten. Ezen ismeretek segítségével meg lehet állapítani mely programozási módok a leghatékonyabbak, illetve, hogy a magasabb szintű programozási szerkezetek hogyan működnek. Debuggolás: Több szempontból is hasznos lehet ha a fordítók által generált kódot meg tudjuk érteni illetve meg tudjuk állapítani, hogy mennyire jó, optimalizált kódot generál egy fordító. Fordítók: Az assembly kód megértése elengedhetetlen ahhoz, hogy fordítót, debugger-t vagy egyéb fejlesztő eszközöket fejlesszünk. Beágyazott rendszerek: A beágyazott rendszereknek nincs annyi erőforrása mint egy hagyományos PC-nek és ezért szükség lehet az assembly nyelvre, hogy ilyen rendszerekre gyors és hatékony kódot írjunk. 4 A magyarázat a 3. fejezetben található. 12
Pentium processzor Assembly Művelet Gépi kód (hex) nop Üres művelet 90 inc result Növelés FF060A00 mov result, 45 Másolás C7060C002D00 and mask, 128 Maszkolás 80260E0080 MIPS processzor Assembly Művelet Gépi kód (hex) nop Üres művelet 00000000 mov $t2, $t15 Másolás 000A2021 and $t2, $t1, 15 Logikai ÉS 312A000F addu $t3, $t1, $t2 Összeadás 012A5821 1.1. tábla: Assembly parancsok és a megfelelő gépi kód Hardware eszközök: A magas szintű programozási nyelvek korlátozott (absztrakt) hozzáférést engednek a hardware elemekhez, így a hardware eszközök használatát és elérését biztosító eszközvezérlőt írni magas szintű nyelven nehéz vagy lehetetlen. Ilyen esetben is jól jöhet az assembly nyelv ismerete. Olyan utasításokat is használhatunk assembly-ben aminek a magasabb szintű nyelvekben nincs megfelelője. Méretre való optimalizálás: A méretre való optimalizálás azt jelenti, hogy Program A kevesebb helyet foglal mint Program B de ugyanazt a feladatot látja el. A memória ma már olyan olcsó, hogy tulajdonképpen nem éri meg assembly-ben kódot írni a program méretének csökkentése miatt. Ugyanakkor a cache még midig kis méretű és drága, így az erre optimalizált kód esetén még mindig fontos az assembly nyelv használata. Sebességre való optimalizálás: A sebességre optimalizált program a lehető legrövidebb idő alatt végzi el a feladatot. Habár a modern fordítók viszonylag jól optimalizálják a generált kódot, bizonyos esetekben a kézzel optimalizált assembly program részlet drámaian fel tudja gyorsítani a programot. Az utolsó két szempontból az utóbbi a fontosabb. Egyrészt a hely megtakarítás csak a program kódra vonatkozik és az adatra nem, másrészt a memória méretének növekedése miatt. Assemblyben azért lehet hatékony kódot írni, mivel a nyelv sajátossága, hogy a generált kód csak azt tartalmazza amit beleírtunk, vagyis ami a feladat megoldásához kell. Semmi más, extra információt nem fordít bele az assembler. A sebességre optimalizált alkalmazások két kategóriába sorolhatók: idő hatékony alkalmazások: ezeknél a programoknál a gyorsabb futás jelent előnyt, de nincs különösebb probléma ha a sebesség lassabb; idő kritikus alkalmazások: ebben az esetben a feladatot adott idő alatt kell elvégezni. Általában ezek a valós idejű alkalmazások (real-time systems), például: repülőgép navigációs eszközei, robot kontroll rendszerek, kommunikációs szoftverek. 1.4 Mikor ne használjunk assembly nyelvet? Olyan sok hátránya van az assembly nyelven való programozásnak, hogy mielőtt elkezdenénk programozni assembly-ben más alternatívákat is vegyünk figyelembe. 13
1.4.1 A magas szintű programozási nyelvek előnye A magas szintű programozási nyelvek viszonylag kényelmes absztrakciót tesznek lehetővé, hogy az adott problémát megoldjuk. A magas szintű programozási nyelvek előnyei: A programfejlesztés gyorsabb: A magas szintű programozási nyelvekben sokféle programozási konstrukció áll rendelkezésre. Általában rövidebbek is a programok. A programokat könnyebb karbantartani: A magas szintű programozási nyelven írt programokat egyszerűbb megérteni és ezért könnyebb mások által írt programot áttekinteni és megérteni. A programok hordozhatóak: A program nem tartalmaz processzor specifikus részleteket és ezért bármilyen rendszeren használhatóak. 5 1.4.2 Az assembly hátrányai Az assembly-ben való programozás ellen szóló legfontosabb érvek: 1. Fejlesztési idő: Az assembly-ben való programozás szinte mindig több időt igényel mint a magasabb szintű programozási nyelv használata. 2. Megbízhatóság és biztonság: Assembly nyelven könnyű hibát véteni. Az assembler csak szintaktikai ellenőrzéseket végez. 3. Debuggolás és ellenőrzés: Az assembly nyelven írt programokban nehezebb hibát keresni, illetve nehezebb ellenőrizni a kódot, hogy az előírt feladatot oldja meg. 4. Karbantartás: Az assembly nyelven írt programokat nehezebb módosítani és karbantartani. A nyelv megengedi a spagetti kód írási technikát és egyéb trükkök is megengedettek a nyelvben, melyeket más nyelven nem lehet megvalósítani. 5. Hordozhatóság: Az assembly kód a hardware platformhoz kapcsolódik, csak az adott processzoron, architektúrán lehet lefuttatni. 6. Modern fordítók: A modern fordítók sokat fejlődtek az elmúlt években és már nagyon jó kódot tudnak generálni és gyakran nehezebb jobb assembly kódot generálni. 1.5 Mielőtt elkezdenénk assembly-ben programozni... Van néhány szempont amit figyelembe kell venni mielőtt egy komplex alkalmazást elkezdenénk assemblyben programozni: Ha az a célunk, hogy egy program sebességét optimalizáljuk, akkor először azonosítsuk, hogy a program mely része fut a legtöbbet a processzoron. Ellenőrizzük, hogy mivel tölti a legtöbb időt a program, például a memória eléréssel, CPU utasítások végrehajtásával, file-ok elérésével vagy valami mással. Döntsük el, hogy a fejlesztett program újrahasznosítható vagy csak egy egyedi alkalmazás. Ha a kódot újra fel akarjuk használni, akkor érdemes több időt tölteni az optimalizálással. El kell dönteni, melyik assemblert használjuk, mivel a különböző assemblerek más-más szintakszist használhatnak. A jelentős mértékben optimalizált kódot nehéz lehet olvasni, így a karbantartás miatt érdemes kisebb egységekbe szervezni a programot melyeknek jól definiált interface-e van és megfelelően van dokumentálva. 5 Itt főleg a forráskódról beszélünk, nem a futtatható, már lefordított gépi kódú programról. 14
1.6 Szintakszis Kétféle jelentősebb szintakszis alakult ki az évek során, amiket az assembly programok írásánál használhatunk: AT&T szintakszis Intel szintakszis 1.7 Assemblerek Több assembler is létezik az Intel processzorokra, melyek az x86-os utasítás készletet használják, vagyis a mnemonikokból Intel gépi kódot hoznak létre. Az alábbiakban csak néhányat mutatunk be. 1.7.1 MASM Ez a Microsoft Assembler, mely a mai napig része a Microsoft fejlesztő környezetének, a Visual Studionak. A program neve: ml.exe. A MASM sokáig a de-facto ipari szabvány volt és több magasabb szintű programozási konstrukciót is tudott kezelni. A formátuma nem teljesen tiszta, vannak inkonzisztens részek benne. Microsoft továbbra is fejleszti, de igazából minimális módon. 1.7.2 GAS GAS rövidités megfelelője a GNU Assembler, mely a GNU binutils csomag része is. A GNU fordítók olyan formátumot generálnak, melyet ez az assembler képes lefordítani. GAS az úgynevezett AT&T szintakszist használja, bár ma már az Intel szintakszisnak megfelelő kódot is el tud fogadni. Ez az assembler használható Linux, Mac OS X és Windows alatt is. 1.7.3 TASM Az egyik legnépszerűbb fejlesztői eszközöket a Borland cég készítette. Az általuk készített programfejlesztő családba tartozik a Turbo Assembler is. Sajnos ma már nem fejlesztik, az újabb utasítások nem kerülnek bele, de még mindig elérhető az Interneten. Az assembler által használt szintakszis nagyon hasonló a MASM assembler szintakszisához. 1.7.4 NASM NASM megfelel a Netwide Assembler névnek és egy szabad forráskodú assembler, mely többféle objektum formátumot képes generálni és így több operációs rendszert támogat (Linux, Windows, Mac OS X, FreeBSD, stb). A szintakszisa tisztább mint a MASM assembler-é, de kevesebb magas szintű programozási konstrukciót képes kezelni. 1.7.5 Melyik assembler? Ez a jegyzet a NASM assemblert használja két fő ok miatt: Az egyszerű szintakszis nagyon logikus és konzisztens. Windows és Linux rendszeren is használható, melyek manapság a legjobban elterjedt operációs rendszerek. 15
1.8 Összefoglalás Az assembly nyelv tanulása mind gyakorlati és pedagógia célokat szolgálhat. Még ha nem is szándékozunk assembly-ben programozni, akkor is érdemes megtanulni, mivel egy nagyon jó alapot ad ahhoz hogy megértsük, hogyan működnek a számítógépek. Amikor magas szintű programozási nyelvet használunk, akkor a rendszert egy fekete dobozként kezeljük. Ezzel szemben assembly programozás esetén a rendszert részleteit is ismerni kell, például a regisztereket. 1.9 Ellenőrző kérdések 1. Soroljon fel különböző processzorokat! 2. Mit jelent a CISC kifejezés és mi jellemző az ilyen processzorokra? 3. Soroljon fel indokokat miért érdemes assembly nyelvet tanulni? 4. Soroljon fel indokokat mikor kell assembly nyelvet tanulni? 5. Soroljon fel indokokat mikor ne használjunk assembly nyelvet? 6. Mi az assembly nyelv és a gépi kód kapcsolata? 7. Magas szintű programozási nyelvben miért nem tudjuk teljes mértékben kontrollálni a hardwaret? 8. Miért hívjuk az assembly programozási nyelvet alacsony szintű nyelvnek és a C programozási nyelvet magas szintűnek? 9. Soroljon fel néhány különbséget a CISC és RISC processzorok között? 10. Hasonlítsa össze a két féle assembly szintakszist! 11. Soroljon fel assemblereket! 12. Miért lehet szükség az assembly használatára idő kritikus alkalmazások esetén? 13. Soroljon fel idő kritikus alkalmazásokat! 16
2. Fejezet A számítógép felépítése A számítógépnek alapvetően három fő komponense van: a központi egység vagy processzor (CPU), a memória, és az Input/Output eszközök. Lásd a 2.1. ábra. A részek közötti kapcsolatot a rendszer busz biztosítja. A memória tárolja a programokat és az adatokat is egyszerre. Az Input/Output eszközök lehetnek a billentyűzet, a képernyő és így tovább. A 2.2. ábra egy részletesebb nézetét adja a számítógépnek, ahol jól látható hogy a rendszer busz három részből áll: cím busz, adat busz és kontroll busz. A cím busz szélessége határozza meg az elérhető memória kapacitást, illetve az adat busz adja meg, hogy milyen méretű adatok mozoghatnak a CPU, a memória és az I/O eszközök között. Például a 8086-os processzornak 20 bites a cím busza és 16 bites az adat busza. Ezek alapján a processzor 2 20 byte-ot tud megcímezni, vagyis 1 MByte-ot és minden alkalommal 16 bit mozog az egységek között. A Pentium processzoroknak 32 cím vonaluk van a cím buszban és 64 adat vonala. Így a Pentium 4 GByte memóriát tud megcímezni. A 2.2. ábrán az is fontos, hogy a buszok milyen irányba képesek adatot küldeni. Látható, hogy a CPU a kontroll buszon keresztül ad utasításokat a memóriának és az I/O alrendszernek, adatot viszont az adat buszon keresztül fogad. A kontroll buszon kiadható jelek: memória olvasás, memória írás, I/O olvasás, I/O írás, megszakítás és így tovább. 2.1 A processzor A processzor kontrollálja a legtöbb tevékenységet a rendszerben. Úgy érdemes rá gondolni, hogy a következő ciklust hajtja végre: 1. Egy utasítás betöltése a memóriából (fetch), 2. Az utasítás dekódolása, azonosítása (decode), CPU Memória Kapcsolat Input/Output 2.1. ábra: Absztrakt értelmezése a számítógépnek 17
CPU Memória Cím busz Adat busz Kontroll busz I/O alrendszer 2.2. ábra: Egy számítógép egyszerűsített diagramja 3. Az utasítás végrehajtása (execute). Ez a végrehajtási ciklus, vagy fetch-decode-execute ciklus. 2.1.1 Végrehajtási ciklus Fetch Decode Execute A processzor a betöltendő utasítás címét felrakja a cím buszra. A processzor a kontroll buszon keresztül memória olvasásra ad utasítást a memória egységnek. A processzor addig vár amíg az utasítás meg nem jelenik az adat buszon. A memória egységnek idő kell míg hozzáfér a szükséges részhez. Ez a hozzáférési idő. A memória a beolvasott utasítást az adat buszra helyezi. A processzor beolvassa az adat buszról az utasítást Azonosítani kell a beolvasott utasítást. Ezt segítendő az utasítások bizonyos kódolási mintát követnek, melyet majd a 7. fejezetben tárgyalunk. A végrehajtáshoz két egységre van szükség: egy kontrol és egy aritmetikai (ALU) egységre. A kontroll egység segít az időzítésben, míg az ALU egység a matematikai számításokat végzi el. Megjegyezzük, hogy az adatok és az utasítások nem mindig közvetlenül a memóriából jönnek, hanem az úgynevezett cache-ből. A cache memóriához való hozzáférés gyorsabb. A Pentium processzoron 16 KB-os cache van a chipen, melynek fele adat és fele utasítás cache. Szerencsére a cache használata hardware-ben van megoldva és teljesen transzparens a programozó számára. 18
2.1.2 A rendszer óra A rendszer óra a system clock. A rendszer óra teszi lehetővé, hogy a műveleteket összeszinkronizáljuk. Az órajel 1-eseket és 0-kat ad ki sorozatban, szekvenciában. Az óra frekvencia értékét a másodpercenkénti ciklusok száma adja meg és a mértékegysége Hertz (Hz). A MHz és GHz 10 6 és 10 9 ciklust jelent másodpercenként. 1 óra frekvencia = (2.1) óra ciklus hossza A rendszer óra adja meg a számítógép sebességét. Minden processzor művelet végrehajtása több órajel ciklust igényel. Például egy 1 GHz-es Pentium processzoron egy adat továbbítása a memóriából a processzorra három órajel ciklust igényel. Egy óra ciklus hossza: és így az adattovábbításhoz 3 ns-ra van szükség. 1 = 1ns (2.2) 1 109 A számítógépek sebességét növelhetjük úgy, hogy nagyobb órajel frekvenciát használunk. Például egy 2 GHz-es processzoron az adat továbbítás már csak 1.5 ns-ig fog tartani. 2.2 Címzési architektúra Érdekes módon az egyik legfontosabb tulajdonsága egy architektúrának hogy hány címet használunk az utasításokban. A legtöbb művelethez egy vagy két argumentumra van szükség. Ezek alapján szokták a műveleteket binary és unary műveleteknek nevezni, ahol a a bi- kettőt, az un- egyet jelent. Unary művelet például a tagadás (NOT) művelet, míg binary művelet az összeadás és a kivonás. Ezek a műveletek egy eredményt adnak. Természetesen vannak kivételek, például az osztás. Az osztásnál két argumentumra van szükség, az osztandóra és az osztóra, viszont két eredmény is keletkezik: az eredmény és a maradék. Mivel a binary műveletek a leggyakoribbak és ebben az esetben két input argumentumra és egy eredmény, output argumentumra van szükség ezért ezért általában három címre van szükség egy utasításnál. Ebben a részben azt nézzük meg, hogyan lehet három, kettő, egy és zérus címet használni az utasításokkal. 2.2.1 Három címes architektúra A három címet használó utasításkészlettel rendelkező processzoroknál a két input argumentumot és az egyetlen output argumentumot tudjuk megadni. A legtöbb modern processzor ilyen utasításkészletet használ. Nézzünk egy példát: A = B + C * D - E + F + A mely pszeudo assembly-ben a következőképpen néz ki: mult T,C,D ; T = C * D add T,T,B ; T = B + C * D sub T,T,E ; T = B + C * D - E add T,T,F ; T = B + C * D - E + F add A,A,T ; A = B + C * D - E + F + A A példában az látható, hogy matematikai műveletre egy utasítást kell megadni. Ami szintén szembetűnő, hogy az első utasítást kivéve az első két argumentum azonos. Mivel az esetek jelentős részében így van, ezért a sok duplikáció elkerülése végett két-címes utasításkészleteket is szoktak implementálni processzorokban. 19
2.2.2 Két címes architektúra Ebben az esetben az utasításoknak csak cím argumentuma van és az egyik cím inputként és outputként is szolgál. Az Intel processzorok, például a Pentium is ilyen utasításokat használ. Nézzük az előző példát újra: A = B + C * D - E + F + A mely pszeudo assembly-ben a következőképpen néz ki: load T,C ; T = C mult T,D ; T = C * D add T,B ; T = B + C * D sub T,E ; T = B + C * D - E add T,F ; T = B + C * D - E + F add A,T ; A = B + C * D - E + F + A Mivel csak két argumentum áll rendelkezésre ezért az első utasítással betöltjük az adatot T-be. Ebben az esetben az a feltűnő, hogy az első 6 utasításban a T argumentum közös. Ha ez lesz az alap eset, akkor már csak egy cím, argumentum kell az utasításokhoz. 2.2.3 Egy címes architektúra Ha a memória drága vagy lassú akkor egy speciális regisztert használ a processzor. Ez a regiszter szolgáltatja az input és az output argumentumot egy utasításnak. Ezt a regisztert akkumulátor regiszternek is szokták nevezni, mivel benne gyűlik össze, akkumulálódik, az eredmény. A legtöbb arhitektúra esetén csak egy akkumulátor regiszter van. Ezt a regisztert nem kell megadni az utasításnak csak a másik argumentumot. 2.2.4 Zéró cím architektúra Arra is van lehetőség, hogy mindkét argumentum speciális helyen tárolódik és így nem kell megadni őket az utasításoknál. Ezek a processzorok egy vermet használnak. Az argumentumok a verem tetején vannak amiket az utasítás levesz onnan, majd az eredményt is a verem tetejére teszi vissza. 2.2.5 Load/Store architektúra Ebben az esetben a műveleteket a processzor belső regiszterein végezhetjük el és külön utasítással kell beolvasni az adatokat a memóriából a regiszterekbe, illetve a regiszterekből kiírni a memóriába. A fenti példa a következőképpen módosul: A = B + C * D - E + F + A mely pszeudo assembly-ben a következőképpen néz ki: load R1,B load R2,C load R3,D load R4,E load R5,F load R6,A mult R2,R2,R3 ; R2 = C * D add R2,R2,R1 ; R2 = B + C * D 20
sub R2,R2,R4 ; R2 = B + C * D - E add R2,R2,R5 ; R2 = B + C * D - E + F add R2,R2,R6 ; R2 = B + C * D - E + F + A store A,R2 A fenti példában hat regisztert is használunk. Bár nincs ennyire szükség, de ez általában jellemző ezekre az architektúrákra, hogy sok regiszterük van. A RISC processzoroknak több regiszterük van mint a CISC processzoroknak. A MIPS processzornak 32 regisztere van, az Intel Itanium processzornak 128 regisztere és az Intel Pentium processzornak csak 10 regisztere van. 2.3 Regiszterek Minden processzorban vannak regiszterek, melyeket két fő csoportba sorolhatunk: általános célú regiszterek, speciális célú regiszterek. A speciális célú regisztereket további két csoportba oszthatjuk: felhasználó által elérhető regiszterek és csak a rendszer által elérhető regiszterek. A Pentium regisztereit a 3. fejezetben tárgyaljuk. 2.4 Végrehajtási sorrend A program végrehajtása általában szekvenciálisan történik, az utasításokat egymás után hajtjuk végre. Az egyik regiszter, a Program Counter (PC) vagy Instructon Pointer (IP) regiszter, fontos szerept játszik a végrehajtási sorrend kezelésében. A processzor mindig azt az utasítást tölti be (fetch) amire a PC regiszter mutat. A betöltés után a PC regiszter értékét megnöveljük, hogy a következő utasításra mutasson. Ez a megnövelés lehet fix méretű, például a RISC processzoroknál, vagy változó méretű a CISC processzoroknál, ahogy ez látható a 1.1. A CISC processzorok esetén minden utasításnál külön meg kell állapítani, hogy mennyivel növeljük meg a PC regiszter értékét. A magasabb szintű programozási nyelvekben ugyanakkor vannak feltételes végrehajtási és ciklikus programozási konstrukciók, melyek a végrehajtási sorrendet változtatják meg valamilyen futás közbeni feltételtől függően. Ezek megvalósítása a processzorokban speciális módon történik. 2.4.1 Branching A branching szó fordítása talán az elágazás lehet. Arról van szó, hogy az eredeti szekvenciát megszakítva, máshol folytatódik a program végrehajtása. Két változata van: a feltétel nélküli és feltételes ugró utasítás. Ezeknek az utasításoknak egy argumentuma van, mely explicit módon megadja az új utasítás címét. Ez azt jelenti, hogy amikor máshol kell folytatni a végrehajtást, akkor a PC regiszterbe az új címet töltjük be és így a következő fetch -nél már ezt a címet fogja használni a processzor. A végrehajtási sorrend a feltétel nélküli ugró utasítás esetén a 2.3. ábrán látható. Feltételes ugrás A feltételes ugrás esetén az új cím csak akkor töltődik be a PC regiszterbe, ha valamilyen feltétel teljesül. Kétféle módon szokták ezt megadni a különböző processzorokban: Set-Then-Jump : Az ilyen architektúrájú processzorokban a vizsgálat és az ugrás szét van választva. A két, különálló rész közötti kapcsolatot egy regiszter biztosítja. A vizsgálat beállítja a regiszter értékét, majd az ugró utasítás ezt a regisztert vizsgálja meg hogy bekövetkezzen-e az ugrás vagy sem. A Pentium processzorok ezt a technikát használják. 21
utasítás jump utasítás a cím b utasítás utasítás c d 2.3. ábra: Ugró utasítás Test-And-Jump : A legtöbb processzor összekombinálja a két részt, például a MIPS processzorok. Például: beq Rsrc1, Rsrc2, célcím összehasonlítja az Rsrc1 és Rsrc2 regiszterek tartalmát és ha egyenlőek, akkor a célcím-nél folytatódik a végrehajtás. 2.5 Memória A számítógép memóriáját úgy érdemes elképzelni mint sok elektronikus kapcsoló összessége. Ezek a kapcsolók két állapotban lehetnek: nyitott vagy zárt állapotban. Ugyanakkor ezeket az állapotokat érdemesebb 1 és 0 állapottal jellemezni. Így minden kapcsolót reprezentálni lehet egy bináris számmal vagy bittel. A memória millió szám tartalmaz biteket. A jobb kezelhetőség miatt a memória a biteket csoportokba szervezik. 8 bit csoportja egy byte. Így a memória mint egy byte sorozat képzelhető el. Minden byte-ra egy index számmal lehet hivatkozni. Az első index értéke 0. Az utolsó index értéke 2 n 1, ahol az n az adatbusz szélessége (hány bites). A memória sematikus képe a 2.4. ábrán látható. 2.5.1 Memória műveletek Két alapvető művelet van: adat olvasás a memóriából és adat írás a memóriába. Mindkét esetben szükség van egy memória címre ahonnan olvashatunk, vagy ahova írhatunk. Ezenkívűl az írási művelet még egy adatot is igényel. 2.5.2 Olvasási ciklus 1. A processor elhelyezi az olvasandó adat címét a cím buszon. 2. A kontroll buszon a processzor kiadja a memória olvasási jelet. 3. A processzor várakozik amíg az olvasás megtörténik és az adat megjelenik az adat buszon. 4. A processzor beolvassa az adatot az adat buszról. 22
32 2-1 FFFF FFFF FFFF FFFE 1 0 0000 0001 0000 0000 2.4. ábra: A memória sematikus képe 5. A kontroll buszon jelzi a processzor, hogy véget ért az olvasás. Egy Pentium processzor olvasási ciklusa három órajel ciklusnak felel meg. Az első órajel ciklus alatt az 1. és 2. lépés hajtódik végre. A második órajel ciklus alatt a processzor várakozik. A harmadik órajel ciklus alatt az utolsó két lépés fut le. Ha a memóriának mégsem sikerül az olvasás, akkor ezt jelzi a processzornak ami egy újabb órajel ciklusig vár. 2.5.3 Olvasási ciklus 1. A processzor elhelyezi az írandó adat címét a cím buszon. 2. A processzor elhelyezi az adatot az adat buszra. 3. A kontroll buszon a processzor kiadja a memória írási jelet. 4. A processzor várakozik amíg az írás megtörténik. 5. A kontroll buszon jelezzük az írás végét. A Pentium processzor írási cíklusa is három órajel ciklust igényel. Az 1. és 3. lépés az első órajel alatt következik be. A 2. lépés csak a második órajel ciklus alatt történik. A második órajel ciklus végén jelzi az írás végét. 2.5.4 Memória típusok A memóriákat különböző kategóriákba lehet csoportosítani. Az egyik legfontosabb tulajdonsága a memóriáknak, hogy csak olvashatók vagy írhatók-olvashatók. Szintén fontos tulajdonság, hogy a memória minden részének elérése azonos időben lehetséges (random-access) vagy csak szekvenciálisan. A szekvenciális elérés magyarázatához a legjobb példa egy kazetta, amikor is addig kell olvasni a kazettát, amíg el nem értük a keresett adatot. Végül vannak a volatile memóriák, melyeknél amíg feszültség alatt van az egység csak addig őrzi meg a tartalmat. A nonvolatile memória akkor is megőrzi a tartalmát ha nincs feszültség alatt az egység. Csak olvasható memóriák A csak olvasható memóriák (Read Only Memory vagy ROM) csak olvasási műveletet enged. Ebbe a memóriába nem tudunk írni. A fő előnyük, hogy egyben nonvolatile memóriák is. A ROM memóriák 23
tartalmát a gyárban égetik bele. Ezeket a memóriákat olcsó gyártani. A régebbi számítógépekben a BIOS általában ROM. Vannak úgynevezett programozható ROM-ok is (PROM), illetve törölhető (erasbale) PROM-ok (EPROM). Írható-olvasható memóriák Az írható-olvasható memóriákat általában RAM-nak (random access memory-nak) is szokták nevezni, habár a ROM-ok esetén is igaz az, hogy minden része azonos időben érhető el. Ezeket a memóriákat két csoportba lehet sorolni: statikus és dinamikus. A statikus RAM memóriák (SRAM) megőrzi az adatot a beírás után, minden további menipuláció nélkül, amíg a rendszer feszültség alatt van. Ilyen memória a cache vagy a regiszterek. Ezzel szemben a fő memória dinamikus (DRAM). A DRAM egy komplex eszköz, mely kondenzátorok segítségével tárol egy bitet. A feltöltött kondenzátor jelöli az 1-es értéket. Mivel a kondenzátorok idővel vesztenek a töltésükből ezért időközönként frissíteni kell. Tipikusan 64 ms a frissítési periódus. Az olvasás során azt teszteljük, hogy a kondenzátor fel van-e töltve. Ugyanakkor ez a tesztelés tönkre is teszi a töltést. Ebben az értelemben a DRAM egy speciális memória, mivel az olvasás is destruktív, nem csak az írás. A legtöbb memória esetén csak az írás destruktív. A destruktív olvasás következménye, hogy az olvasás után egy helyreállítási ciklus szükséges. Ennek az a következménye, hogy az olvasás kétszer olyan sokáig tart mint más memóriák esetén. Modern memória típusok: FPM DRAM: Fast page-mode DRAM EDO DRAM: Extended Data Output DRAM SDRAM: synchronous DRAM DDR SDRAM RDRAM: Rambus DRAM 2.5.5 Byte sorozatok tárolása Természetesen általában nem csak egy byte-ot kell tárolni, hanem több byte-ot is. Például egy egész számot a C programozási nyelvben általában 4 byte-on tárolunk. Felmerülhet a kérdés, hogy hogyan tároljuk ezt a 4 byte-ot a memóriában? A 2.5. ábra két megoldást is mutat. Az ábrán az MSB jelölés a Most Significant Byte -nek felel meg, míg az LSB a Least Significant Byte. Mindkét megoldás esetén a 100-as címet adtuk meg, és ehhez képest történik a tárolás. A Little-endian tárolási módban a legkisebb helyiértékű byte (LSB) tárolódik először. Ezzel szemben a Big-endian tárolási módban a legnagyobb helyiértékű byte (MSB) tárolódik legelőször. Nézzünk egy másik példát a Little-endian tárolási módra. Az 1234h hexadecimális szám esetén először a 34h, majd a 12h byte-ot tárolja a rendszer. Az 12345678h szám esetén a tárolási sorrend: 78h, 56h, 34h, 12h. Melyik tárolási módszer a jobb? Mindkettő ugyanolyan jó. Csak a processzor tervező döntése, hogy melyiket használja. A Pentium processzorok a Little-endian tárolási módot használják. A MIPS és PowerPC processzorokon a Big-endian tárolási mód az alapértelmezett, de át lehet konfigurálni őket Little-endian tárolási módra is. Általában a különböző tárolási mód nem okoz problémát, ha mindig csak egyféle processzort használunk. Az igazi problémák akkor jelennek meg, ha különböző tárolási módszert használó processzorok között akarunk egy programot hordozni. Ebben az esetben az adatokat konvertálni kell! 24
MSB LSB 11110100 10011000 10110111 00001111 cím cím 11110100 10011000 10110111 00001111 103 103 102 102 101 101 100 100 00001111 10110111 10011000 11110100 Little-endian Big-endian 2.5. ábra: Little-endian és Big-endian tárolási mód 2.5.6 Adat alignment problema Egy program végrehajtási sebességét több tényező is befolyásolja. A tényezők közül néhány a programozó befolyása alatt van, míg másokat nem tudnak befolyásolni. Ebben a fejezetben az egyik fontos tényezőt vizsgáljuk meg. Tegyük fel, hogy egy 32 bites adatot szeretnénk olvasni a memóriából. Azt is tegyük fel, hogy az adat busz szintén 32 bites. Ha az olvasni kívánt adat címe néggyel osztható, akkor a memóriában pont úgy van elhelyezve, hogy egy sorba esik. Ez látható a 2.6. ábrán és ezt szoktuk illesztett, aligned adatnak nevezni. Mivel az adatbusz 32 bites, ezért egyszerre 4 byte-ot, egy sort lehet beolvasni a memóriából. Ez azt jelenti, hogy ha a cím nem osztható néggyel, akkor az adat két sorba kerül és kétszer kell olvasni a memóriából, majd ezekből fogja a processzor összeállítani a szükséges 32 bites adatot. A kétszeri olvasásnak hatása van a program futására, mivel a nem illesztett adatok miatt lassabban fog futni! n+3 k+2 24-31 16-23 n+2 k+1 8-15 n+1 k 0-7 n+0 k+3 CPU adatbusz 32 bit memória adat1 adat2 2.6. ábra: Adat illesztés - data alignment Az adat illesztés problémája teljesen transzparans módon, vagyis nem jelenik meg a felhasználó számára, kivéve, hogy a program lassabban fut. A 16 bites adatokat 2 byte-ra kell illeszteni. Ez azt 25
Cím busz Adat busz Kontroll busz Adat Státusz Parancs I/O kontroller I/O eszköz 2.7. ábra: Input/Output eszköz sematikus ábrája jelenti, hogy a cím legkisebb helyiértékű bite zérus, vagyis a cím páros. A 32 bites adatokat 4 byte-ra kell illeszteni vagyis a cím két legkisebb helyiértékű bite zérus. És így tovább. A Pentium processzorok megengedik az illesztett ( aligned ) és nem illesztett adattárolást is. Bizonyos processzorok az előbb leírt hatékonysági probléma miatt nem engedik meg, hogy az adat ne legyen illeszteve. 2.6 Input/Output Az Input/Output eszközök teszik lehetővé, hogy a számítógépek kommunikáljanak a külvilággal. Input/Output eszköz lehet hogy csak adatszolgáltatásra alkalmas, input-ra, például az egér, vagy csak output-ra képes, például a monitor, vagy input-ra és output-ra is képes. Így lényegében az I/O eszközöknek két fő célja van, a külvilággal kommunikálni és adatot tárolni. Mindegyik kommunikáció a rendszer buszon keresztül történik, bár az I/O eszközök nem közvetlenül kapcsolódnak a buszhoz, hanem van egy I/O kontroller az eszköz és a rendszer busz között, ahogy ez 2.7. ábrán látható. Két fontos ok miatt van szükség ezekre az I/O kontrollerekre: 1. A különböző I/O eszközöket különböző módon kell kezelni. Ez azt jelenti, hogy a különböző eszközökkel különböző módon kell kommunikálni, néha várni kell az adat megérkezésére vagy vezérlő jeleket kell adni. Ha a processzornak kellen mind ezt a feladatot ellátni, akkor több időt töltene ezzel, mint a felhasználó kiszolgálásával, vagyis a programok futtatásával. Az I/O eszköz kontroller elvégzi a processzor helyett ezeket a feladatokat. 2. A másik ok, hogy a rendszer buszon keresztül küldött elektromos jel igen alacsony, ami azt is jelenti, hogy a rendszer busz nem lehet túl hosszú. Emiatt az I/O eszköz kontrollerek közel vannak a processzorhoz, például a számítógép házban, és majd a kontroller tud külön, erősebb jelet küldeni az eszköznek. Az 2.7. ábra azt is mutatja, hogy az I/O eszköz kontrollerekben általában három regiszter is van. Például egy nyomtató esetén a Státusz regiszter jelzi, hogy az eszköz készen áll-e, az Adat regiszterbe kell tenni a nyomtatandó karaktert és a Parancs regiszterben kell utasítást adni az eszköznek, hogy nyomtassa ki a karaktert. A processzor I/O portokon keresztül éri el ezekete a regisztereket. Az I/O port nem más mint az I/O eszközön levő regiszter címe. Az I/O portok lehetnek a memóriára illesztettek, memory-mapped I/O. Ilyen rendszer például a MIPS processzorban van. A Pentium processzorok egy I/O cím tartományt használnak. Ez a cím tartomány különbözik a memória cím tartományától. Ebben az esetben külön I/O utasításokat kell használni. Ugyanakkor ez az utóbbi technika a memóriára illesztett I/O-t is lehető teszi. Később látni fogjuk, hogy például a képernyő a memóriára illeszthető és úgy is írhatunk a képernyőre, hogy egy speciális memória területre írunk. Ezzel szemben a billentyűzettel lehet I/O utasításokkal is kommunikálni. 26
2.6.1 I/O eszközök elérése Amikor assembly-ben programozunk közvetlenül vezérelhetjük az I/O eszközöket. Bár erre lehetőségünk van, de leírások és segítség nélkül gyakran nagyon bonyolult lehet, illetve minden esetben saját input és output függvényeket kellene kifejlesztenünk. Ezenkívül, ha mindenkinek teljesen szabad hozzáférése van az I/O eszközökhöz, akkor rosszindulatú emberek ezt ki is használhatják. Ezért van az, hogy általában az operációs rendszer kontrollálja az eszközökhöz való hozzáférést, illetve biztosítja a rutinokat is amiket használhatunk. A rutinok általában valamilyen megszakítást használnak. A megszakításokat a 8.2. bekezdésben tárgyaljuk. 2.7 Összefoglalás Ebben a fejezetben a számítógép alapvető elemeit ismertük meg olyan mélységben amire szükségünk lehet az assembly programozás során. Ezek az ismeretek lehetnek újak, illetve bizonyos fogalmak előfordulhattak más tárgyak keretében. 2.8 Ellenőrző kérdések 1. Milyen részekből áll egy sematikus számítógép? 2. Mi a fetch-decode-execute ciklus? Melyik lépésben, mi történik? 3. A rendszer busz milyen fő részekből áll? Melyiknek mi a szerepe? 4. Ha a processzornak 64 cím vonala van, mekkora lehet maximálisan a memória mérete? Mi az utolsó byte címe? 5. Mi határozza meg, hogy a memória és a processzor között adatok mérete mekkora? 6. Egy 2GH-es processzorban mekkora egy óra jel ciklus? 7. Miben különbözik a load/store architektúra a többi architektúrától? 8. Adjon magyarázatot arra hogy a RISC processzorokon az utasítások egymás utáni végrehajtása miért lehet gyorsabb mint a CISC processzorokon! 9. Mit jelent a három címes architektúra? 10. Miben különbözik a három és két címes architektúra? 11. Hogyan lehet olyan architektúrát megvalósítani, amelyikben az utasításoknak nem kell argumentumot megadni? 12. Adja meg a következő matematikai műveleteket pszeudo assembly-ben két címes architektúra esetén: E = A * B + C - D 13. A RISC vagy a CISC processzorokra jellemző a nagy számú regiszter? Melyik címzési architektúra esetén van szükség erre a nagyszámú regiszterre? 14. A Pentium processzor a RISC vagy CISC processzorok családjába tartozik? 15. A MIPS processzor a RISC vagy CISC processzorok családjába tartozik? 16. Hogyan lehet megvalósítani a feltételes ugró utasítást különböző architektúrákon? 17. Mi a PC regiszter szerepe a feltételes ugró utasítás végrehajtása során? 27