Seriál s: Malý úvod do assembleru III (4 b)

Zadáno v čísle 23.6.

Zadání

Nyní už umíme základní věci, které jsou k programování potřeba – máme základní aritmetické operace1, umíme manipulovat s jednotlivými bity pomocí logických operací a měnit chod programu pomocí větvení a skoků. Všechno tohle dohromady je sice hezké, ale i přes to jsou všechny výpočty našeho procesoru v tuto chvíli k ničemu – neumíme je z něj dostat ven. Také časem zjistíme, že osm registrů není mnoho – zkuste si psát program o nejvýše osmi proměnných. Na některé problémy to stačí, ale brzy to bude problém – ti z vás, kteří programují, běžně používají pole o stovkách i tisících prvků. Řádově nad tisícovky se sice nedostaneme – naše šestnáctibitová architektura zatím nemá možnost, jak adresovat paměť nad adresou hodnoty 0xFFFF2, ale nám to bude stačit. Takže nás bude zajímat i zápis do paměti. Minule jste měli za úkol si práci s pamětí zkusit navrhnout, dneska se na ni tedy podíváme blíže.

S pamětí už budeme mít ke vstupu a výstupu blíže – podíváme se na takzvaný vstup/výstup mapovaný do paměti (dále budu pro vstup/výstup používat zkratku I/O, z anglického Input/Output), což je jedna z technik, které se dají použít například pro vykreslování pixelů na nějaké zobrazovací zařízení.

Až budeme mít vstup a výstup hotový, zamyslíme se nad problematikou periodického čtení těchto údajů, s čímž velmi úzce souvisí mechanika přerušení (anglicky interrupt).

Nuže, směle do toho. Chceme navrhnout instrukce pro práci s pamětí – konkrétně čtení a zápis.

[labelwidth=3.0cm, leftmargin=2cm, align=right]
LOAD Rd, Rr

tato instrukce načte z paměti hodnotu na adrese Rr do registru Rd.

STORE Rd, Rr

tato instrukce uloží do paměti na adresu Rd obsah registru Rr.

V našem případě je tedy práce s pamětí docela jednoduchá – při čtení nejprve uložíme do registru adresu, ze které chceme číst, a po zavolání instrukce LOAD si v dalším registru vyzvedneme příslušnou hodnotu. Podobně při zápisu do  paměti – do jednoho registru uložíme adresu, do druhého hodnotu, kterou chceme zapsat, a instrukcí STORE provedeme zápis.

Na procvičení si zkuste sepsat instrukce, kterými na adresy 0xF00xFA uložíte čísla od jedné do deseti. A zkuste to napsat kratším kódem než:

    LDI A, 0xF0
    LDI B, 0x1
    STORE  A, B
    INC A
    INC B
    STORE A, B
    INC A
    INC B
    STORE A,B
    ; a pokračujeme ještě 7x INC A INC B STORE A, B

To je nějaké podezřele jednoduché, nemyslíte? Teď se zamyslete nad odpovědí na otázku z minula: Máme oddělenou programovou paměť od paměti datové? Je to potřeba? Co když ji nemáme oddělenou? Nemůže se něco stát? Tyto otázky jsou natolik zásadní, že se u nich na chvíli zdržíme. Vezmeme to popořadě:

Máme oddělenou programovou a datovou paměť?
V našem případě ano3. A protože tím se nám docela podstatně mění situace, tak provedu trochu obsáhlejší paměťové shrnutí.

Do druhého dílu včetně byla situace jednoduchá. Používali jsme interní paměť procesoru, zvanou registry, a externí paměť, označovanou prostě jako paměť. Registry používáme k ukládání vnitřního stavu procesoru a na naše výpočty, z externí paměti jsme načítali instrukce, které procesor vykonával. K určení místa, ze kterého se čte instrukce k vykonání, slouží speciální registr, zvaný programový čítač.

Toto vše zůstane zachováno. Jen přejmenujeme naši externí paměť na „programovou paměť“ a zavedeme druhou externí paměť, kterou nazveme „datová paměť“ (viz Obrázek 1).

Je to potřeba?
Není, jen nám to velmi zjednoduší život a zabrání udělat spoustu chyb. Výhodou tohoto přístupu je jednoduchost a bezpečnost. Tato architektura se nazývá architekturou harvardskou. Používá se zejména u malých, často jednoúčelových čipů. Její nevýhodou je pak nemožnost měnit program za chodu4 – co jste si do programové paměti nahráli, to máte. Občas se z ekonomických nebo bezpečnostních důvodů používá paměť pouze pro čtení – program do ní ve výrobě natvrdo „vypálíte“ a potom jej již není možno měnit. Ani když zjistíte, že vašich deset tisíc kávovarů při stisknutí tlačítka pro presso zároveň s tlačítkem pro horkou čokoládu opaří klienta proudem klokotající vanilky.

Co když ji nemáme oddělenou – může se něco stát?
Ve chvíli, kdy nemáme oddělenou programovou a datovou paměť (tzv. von Neumannova architektura), tak se dostáváme do zajímavé situace – procesor najednou nerozlišuje mezi daty a instrukcemi. Nemá jak, protože instrukce i data jsou pořád jenom nuly a jedničky. A jediný, kdo má šanci vědět, jestli je tahle hodnota 0xDEAD instrukcí nebo uloženou hodnotou, je programátor. Tuto architekturu používají prakticky všechny moderní počítače – operační systém je krásný příklad samomodifikujícího se kódu. Kód, který se mění za běhu, kód, který píše jiný kód – tedy takzvané metaprogramování. Kompilátory, linkery, loadery a koneckonců i sám assembler bez nutnosti psát ručně jedničky a nuly. Ach ta krása. Ach ta hrůza. Proč hrůza? Ve chvíli, kdy nemáme oddělenou programovou a datovou paměť, vzniká jedna obrovská bezpečnostní díra, se kterou doteď bojují tisíce programátorů, systémových administrátorů, návrhářů procesorů a mnozí jiní. Bez dalších opatření totiž není pro útočníka nic jednoduššího, než do paměti nahrát někam svůj kód a příští vykonávanou instrukci přepsat svou instrukcí skoku, kterou namíří na počátek tohoto kódu. A najednou je po zábavě, party skončila, jdeme domů. Způsobů, jak se tato situace s větším či menším úspěchem řeší, je nespočet, stejně tak jako je nespočet způsobů, jak tyto obrany obcházet. Je to nekonečná bitva, která s časem spíše nabírá na intenzitě. Ale to je zase na jiné povídání.

Rozhodli jsme se tedy prozatím oddělit programovou a datovou paměť a zavedli jsme instrukce LOAD a STORE. Tyto instrukce mají jednu drobnou nevýhodu – před každým čtením či zápisem musíme do registru nahrát adresu, ke které chceme přistupovat. To nás za prvé stojí čas a za druhé registr.

Obrázek 1: Schéma našeho modelu počítače. Procesor má krom registrů k dispozici programovou a datovou paměť. Z programové paměti procesor čte a následně vykonává instrukce, na které ukazuje Program Counter (PC). To, kam PC ukazuje, můžeme ovlivnit pomocí skoků. S datovou pamětí můžeme manipulovat buď přímo pomocí instrukcí LOAD a STORE, nebo nepřímo pomocí instrukcí PUSH a POP, které pracují se zásobníkem. K práci se zásobníkem slouží Stack Pointer (SP). Zásobník roste od vyšších adres směrem k nižším (tady zprava doleva) a SP ukazuje na první buňku paměti, která na zásobníku není (tedy nejvrchnější buňka zásobníku obsahuje hodnotu 0xBEEF, ne 0xDEAD).

Nedalo by se s tím něco dělat? Zavedeme si registr, který nazveme Stack Pointer (zásobníkový ukazatel, značit jej budeme zkratkou SP) a s jeho pomocí si vytvoříme zásobník. Ha, zásobník. Programátoři zastříhají ušima, zásobník jim je důvěrně známý. Náš zásobník bude fungovat na stejném principu jako ten, který nejspíš znáte, ale bude podstatně jednodušší. Vy ostatní nezoufejte, není to složité. Tak tedy – do registru SP nahrajeme nějakou adresu5. Použijeme adresu 0xFFFF, respektive maximální adresa paměťového rozsahu – náš zásobník totiž poroste směrem dolů. Je možno použít libovolnou jinou, ale chce to trochu opatrnosti. Tedy, SP ukazuje na hodnotu 0xFFFF a toto políčko paměti se tímto stává první pozicí našeho zásobníku. Ten je zatím prázdný. Zavedu nyní dvě instrukce, můžete je vidět na Obrázku 1:

[labelwidth=1.8cm, leftmargin=2cm, align=right]
PUSH Rr

tato instrukce uloží hodnotu registru Rr do paměti na místo určené registrem SP a sníží hodnotu registru SP o jedna

POP Rd

tato instrukce nejprve zvýší hodnotu registru SP o jedna a následně načte do registru Rd hodnotu z místa paměti určeného registrem SP.

Dalo by se říci, že PUSH A je ekvivalentní k zavolání STORE SP, A následovaným voláním DEC SP a stejně tak POP Rd můžeme nahradit sekvencí INC SP, LOAD A, SP. Proč tedy tyto instrukce zavádíme? Máme dva důvody:

  1. Pohodlí – u zásobníku nemusíme řešit, odkud načítáme ani kam zapisujeme. Tyto starosti za nás řeší Stack Pointer. Zásobník se taky hodí na odkládání proměnných, pokud je za chvíli budeme znovu potřebovat – operace na zásobníku totiž mohou mít automatické zvýšení/snížení hodnoty zadrátováno přímo v čipu – takže ušetříme instrukci.

  2. Síla zvyku – zásobník se běžně používá například při obsluze přerušení, takže se nám hodí se na něj podívat, abychom později nebyli překvapení.

  3. Zásobník nám umožňuje mít lokální úložiště nezávislé na kontextu, ze kterého se kód zavolal, a nerozbije se ani pokud se funkce zavolá rekurzivně. Jedná se o přirozenou dynamicky alokovanou paměť.

U zásobníku musíme mít na paměti, že se jedná o strukturu typu LIFO, z anglického Last In, First Out. Prvek, který uložíme jako první, si vyzvedáváme jako poslední6. A ještě jedno varování – ve chvíli, kdy zavoláte instrukci POP, aniž by v zásobníku bylo něco uloženo, tak bude veselo. Procesor totiž instrukci ochotně vykoná, přičte do SP jedničku a přečte si, co vlastně?

V našem případě je u prázdného zásobníku SP nastaven na hodnotu 0xFFFF – přičtením jedničky tato hodnota přeteče – na hodnotu 0x00 a vesele začneme číst paměť od začátku. Což je sice zajímavé, ale velmi, velmi nežádoucí7. Takže na vás mám jednu velkou prosbu – nenechávejte zásobník podtéci. I kdyby to nakrásně fungovalo, situace se dá řešit i jinak a podstatně to znepřehlední kód. Kapacita zásobníku je sice teoreticky vzato omezená pouze velikostí paměti, ale musíme pamatovat i na naši statickou paměť. Pokud bychom totiž do zásobníku dat uložili příliš mnoho, začneme si s ním přepisovat data, která jsou v paměťovém prostoru pod ním.

Instrukce PUSH a POP se občas zobecňují na instrukce použitelné pro libovolný registr, ne jen pro Stack Pointer. Zkuste si rozmyslet, jak by takové instrukce vypadaly. Kdo chce, může si je zavést a používat je – je to pohodlná alternativa například pro práci s poli. Ale nám bude současný arzenál stačit.

Je na čase se konečně podívat na vstupně-výstupní operace. Ještě než začnu, musím se přiznat – už práce s pamětí se dá považovat za takovou operaci považovat. Paměť ale bývá natolik těsně spjatá s procesorem8, že se protokoly pro práci s pamětí nebudeme zabývat – to už bychom se nořili příliš hluboko. Přijměte prosím ono zjednodušení, že procesor pracuje s pamětí a že to umí.

No a když už jednu I/O operaci umíme, tak nejjednodušší je zneužít ji a s její pomocí udělat i ty další. Jak? V paměti si vyhradíme některé umístění – třeba hodnotu 0xAA00 – a s některým zařízením se dohodneme, že své hodnoty nalezne tam. Pro tyto účely si pořídíme abstraktní displej9. Náš displej v tuto chvíli oplývá úžasnou schopností zobrazit dva znaky tabulky ASCII – první z nich odpovídá vyšším osmi bitům hodnoty paměti na místě 0xAA00, druhý si bere těch zbylých osm.

Takže když provedeme následující operace:

    LDI A,  0xAA00   ;adresa displeje
    LDI B, 0x4130    ;hodnota, kterou chceme zobrazit
    STORE A, B       ;příslušný zápis do paměti

tak si displej přečte hodnotu 0x4130, prvních osm bitů je 0x41 – dle ASCII se jedná o písmeno A. Druhých osm bitů je 0x30 – dle ASCII číslice 0. Displej tedy zobrazí A0.


1) Mimo násobení a dělení, ale ty zatím nepotřebujeme. A zájemci si je můžou dodefinovat analogicky ke sčítání.

2) Což pořád může být docela dost paměti. Ukážeme si za chvíli.

3) Ti z vás, kteří se těšili na samomodifikující se kódy mají prozatím smůlu. Ale je to technika pro naše účely příliš pokročilá a ještě nebezpečnější.

4) Berte s rezervou. Instrukce pro modifikaci programové paměti v některých případech existují, ale my se jimi zabývat nebudeme – to už je příliš velké sousto.

5) Pro tuto chvíli budeme předpokládat, že se tak stane automaticky po spuštění procesoru. Inicializace zásobníku ale při běžném programování v assembleru bývá jedna z prvních instrukcí, které se provádí.

6) Pokročilejší z vás se nepochybně budou schopní dostat i k jiným prvkům zásobníku – použijete techniku používanou například v jazyce C – nahrajete si SP do jiného registru, hodnotu upravíte a dále s ní pracujete pomocí instrukcí LOAD a STORE, jako s kterýmkoliv jiným místem v paměti.

7) Což neznamená, že někde neexistuje nějaký programátor, který s přetečením zásobníku nevytvořil něco úžasného.

8) Některé malé čipy nemají registry – používají pouze externí paměť. Cože, programování bez registrů? Nikoliv, tyto čipy mají některé adresy externí paměti natvrdo zadrátované do instrukcí – a používají je tak jako náhražku registrů.

9) Protože každý konkrétní displej má svůj vlastní způsob, jak si s ním procesor má povídat. A operace s displejem už navíc docela trvají – oproti procesoru i o několik řádů déle, museli bychom se tedy naučit rozumně čekat a začít více řešit například rychlost, se kterou procesor vykonává instrukce.

6.5 Vstup a výstup (4b)

Podúloha 6.5.1 – Hello world (2b)

Každý úvod do programovacího jazyka začíná programem Hello world. Nám to sice trvalo tři kapitoly, ale lepší pozdě, než nikdy. Tedy: napište program, který vypíše: „Hello, World!“.

Ale my umíme vypsat jen dva znaky? Nevadí, rozšíříme si displej na celý blok adres – jedna adresa, dvě po sobě následující písmena. Pokud budete chtít více řádků, stačí se rozhodnout, že třeba po deseti adresách (20 písmenech) ukončíte řádek a začnete nový.

Malý bonus: když budu chtít kreslit po pixelech, jak na to?

Teď už umíme výstup – a stejným způsobem můžeme data i přijímat. Domluvíme se, že naše vstupní zařízení – třeba abstraktní klávesnice – uloží hodnotu právě stisknuté klávesy na předem dohodnuté místo. A my budeme mít možnost si onu pozici v paměti přečíst instrukcí LOAD.

Tam, kde je výstup poměrně jasný, tam u vstupu narazíme na zajímavý problém. Konkrétně: kdy máme vstupní paměť číst?

Nejjednodušší je číst paměť opakovaně – řekněme každou milisekundu. Co v ní najdeme, to použijeme, pokud jsme změnu nestihli, asi nebyla důležitá. Toto řešení má úskalí právě v onom opakování. Aby nám totiž naše vstupní zařízení k něčemu bylo, musíme se k němu opakovaně vracet. Tento přístup má své výhody i nevýhody: Je jednoduchý na pochopení – prostě cyklicky kontrolujeme políčko paměti a když se změní, tak se podle toho zařídíme. Také můžeme přesně vědět, kdy čtení provádíme (třeba každou milisekundu), což může být v některých případech důležité. Na druhou stranu nám to může zpomalit odezvu programu, zejména pokud v něm probíhají nějaké delší výpočty při kterých na kontrolu periferií nezbývá čas. Sice možná ušetříme nějakou režii spojenou s přepínáním kontextu, ale pro uživatele to nemusí být rozhodující.

Co dělat, když se nám tento přístup nehodí? Budeme se muset naučit vstup a výstup řešit asynchronně – a k tomu budeme potřebovat mechaniku tzv. interruptů neboli přerušení.

K tomu si zavedeme ještě dva další registry: Interrupt Cause (IC) – ve kterém najdeme příčinu přerušení a Volatile, který budeme používat k následné obsluze – je možné jej používat i jako normální registr, ale jeho obsah není bezpečný pokud jsou povolena přerušení.

Dále bude potřeba si zavést nový bit registru Flags – konkrétně bit Interrupt Flag (I) – v našem případě se jedná o třetí bit zprava. Pokud je v bitu Interrupt Flag hodnota 0 – stav po startu procesoru, jsou přerušení zakázána. Důvod je prostý – nechceme být rušeni při inicializaci zásobníku a případných dalších operacích. Doteď jsme přerušení nepoužívali a díky tomuto bitu jsme se jím nemuseli zabývat.

My ale chceme přerušení povolit, k tomu nám bude stačit tato instrukce:

ORI Flags, 0b100
Registr Flags zůstane zachován, pouze přenastavíme hodnotu bitu I. Od této chvíle jsou přerušení povolena.

Co jsme ale povolili? Umožnili jsme procesoru reagovat na asynchronní vnější podněty. Asynchronní, tedy nezávislé na instrukcích, které procesor zrovna vykonává. Procesor obdrží přerušení a začne s jeho obsluhou, pokud bude v bitu I registru Flags hodnota 1 (tedy pokud jsou povolená přerušení) a zároveň bude libovolný bit registru IC nastaven na 1.

Obsluha přerušení vypadá následovně:

  1. Procesor dokončí vykonávanou instrukci.

  2. Zakáže přerušení (podobně jako bychom to mohli udělat my operací
    ANDI FL, 0xFE). Rozmyslete si, proč je tento krok nutný.

  3. Provede PUSH PC.

  4. Skočí na předem dohodnutou adresu. V našem případě to bude adresa 0x0 – na které jsme doteď měli svůj kód – takže máme problém.

  5. A pokračuje dále v běžné činnosti – tedy ve vykonávání instrukcí. Zbytek je na programátorovi.

Programátor tedy musí napsat kus kódu, který si přečte obsah registru IC, a podle jeho hodnoty obslouží příslušná přerušení.

Teď si rozšíříme naši klávesnici – ta při stisku klávesy provede následující:

  1. Uloží ASCII hodnotu stisknuté klávesy na nějakou adresu. Použijeme třeba adresu 0xBB00.

  2. Nastaví bit registru Interrupt Cause – jako by provedla ORI IC, 0b10.

Pokud jsou přerušení povolena, procesor začne provádět před chvílí zmíněné operace. Je na čase dohodnout se, kam skočí. A protože restart procesoru je taky přerušení, budeme skákat na začátek paměti. Na adresu 0x00. Což nás bude stát nějaké úsilí – doteď tam totiž sídlil náš veškerý kód. Za chvíli si ukážeme příklad možné úvodní sekvence instrukcí. Ještě před tím nás však čeká trocha papírování – najednou pro nás začalo být velmi důležité, na kterých adresách je náš kód uložen1.

Zavedeme následující konvenci zápisu: Pokud chceme, aby blok kódu byl na konkrétní adrese, napíšeme ji před první instrukci na řádek. Další instrukce navazují, dokud nenarazíme na jinou adresu:

0x00  LDI A, 0x10  ;začínáme na adrese nula
      INC A        ;adresa 0x1, je to navazující adresa - nemusím 
                   ;ji vypisovat
      JMP A        ;adresa 0x2, skok na adresu 0x11
                   ;tady je mezera mezi poli 0x0 a 0x11 - doporučuji 
                   ;v případě mezery v paměti vynechat alespoň jeden 
                   ;řádek.
0x11  JMPR zkratka ;návěstí stále fungují - píšeme je před řádek, 
                   ;na který chceme skočit. JMPR zkratka tedy skočí 
                   ;na adresu 0x20
zkratka:
0x20  STOP

A ještě jedna věc, pro přehlednost zavedeme pojmenované konstanty:

#define Jmeno Hodnota

Tedy například #define RESTART_CAUSE 0b0001 značí, že když se v instrukci vyskytne RESTART_CAUSE, tak místo ní můžeme dosadit hodnotu 0b0001. Jedná se jenom o pomůcky pro nás na úkor překladače. Protože překladač nemáme, tak to pro nás znamená přehlednost za cenu trochy práce navíc.

Ale teď už ta slíbená úvodní sekvence.

#define RESTART_CAUSE  0b0001
#define KEYBOARD_CAUSE 0b0010   ;pro tento příklad bude přerušení 
                                ;způsobené restartem (a tedy i 
                                ;startem) značeno bitem 1 a 
                                ;přerušení vyvolané klávesnicí
				;bitem 2 registru IC
#define INTERRUPT_FLAG 0x0100   ; bit I registru Flags,

0x00 MOV V, IC   ;používáme registr V - abychom náhodou nepřepsali
                 ;data našemu programu. Přerušení jsou zakázána,
                 ;takže naše práce s ním je v pořádku - nikdo nám ho
                 ;už znova nezmění.
     ANDI V, RESTART_CAUSE   ;vynulujeme všechny bity kromě posledního
     SUBI V, RESTART_CAUSE   ;pro názornost. V případě rovnosti 
                             ;nastaví bit Z registru Flags - znamení 
                             ;toho, že přerušení byl restart.
     BREQ restart            ;skok na obsluhu restartu.
     MOV   V, IC                
     ANDI  V, KEYBOARD_CAUSE  ;analogicky pokud byla příčinou 
                              ;přerušení klávesnice
     SUBI  V, RESTART_CAUSE
     BREQ  klavesnice:
                              ;v paměti cíleně nechám mezeru
                              ;na případnou obsluhu dalších 
                              ;přerušení.
restart:
0xF0 LDI    SP, 0xFF
     SHL    SP, 8
     ORI    SP, 0xFF          ;inicializace stack pointeru.
     SUBI   A, RESTART_CAUSE  ;vytvoříme si bitovou masku
     AND   IC, A              ;zaznačíme, že přerušení bylo
                              ;obslouženo - smažeme příslušný 
                              ;bit registru Interrupt Cause
     ORI    FL, INTERRUPT_FLAG    ;a povolíme přerušení
     JMPR main

main:
0xFF NOP          ;tělo programu
     NOP          ;já ho psáti nebudu, radši se dám na vojnu
     NOP          ;tady by byl hlavní výpočet.
     JMPR main    

    
klavesnice:
0xF00 NOP    ;uděláme se vstupem z klávesnice to, co jsme 
      NOP    ;měli v plánu, respektive uděláte
      LDI V, 0xFF
      SHL V, 8
      ORI V, 0xFF
      SUBI V, KEYBOARD_CAUSE 
0xF06 ANDI IC, V   ;zaznačím, že přerušení bylo obslouženo.
                   ;sice bych ho zatím mohl rovnou vynulovat, 
                   ;ale budu poctivý.
      POP V        ;popneme ze zásobníku adresu uloženou
                   ;při zahájení obsluhy přerušení
0xF08 ORI FL, INTERRUPT_FLAG   ;povolím přerušení
      JMP V   ;a vrátím se, odkud jsem přišel.

Pozor. Velký vykřičník. Máme v programu chybu, která je natolik závažná, že kvůli ní musíme zavést novou instrukci – jinak by se totiž téměř nedala řešit. Ne žádným rozumným způsobem.

Představte si totiž, že jste v sekci na obsluhu klávesnice, na adrese 0xF08, dokončíte instrukci – ale někdo pořád drží klávesu stisknutou. Procesor tedy provedl bod 1 obsluhy přerušení – dokončil instrukci. A protože přerušení jsou povolená, tak pokračujeme bodem 2 – přerušení opět zakážeme. A v bodu tři se stane strašná věc – v následné obsluze přepíšeme hodnotu V, na kterou jsme se chtěli vrátit, hodnotou 0xF08 na které teď jsme. Jsme namydlení jako Jeníček s Mařenkou, kterým ptáci sezobali chlebové značky – nevíme, kam se vrátit.

Kudy z toho ven? Instrukcí RETIreturn from interrupt. Tato instrukce provede všechny potřebné operace – tedy povolení přerušení, vyzvednutí návratové adresy ze zásobníku a skok na návratovou adresu – naráz.

Podúloha 6.5.2 – Hrejme si (2b)

Zkuste si pohrát s klávesnicí, displejem a přerušeními a něco hezkého vytvořte. I kdyby to mělo být jenom asynchronní vypisování zapsaných znaků. Kdo zvládnete něco drastického – třeba hada v ASCII artu?

Tímto dílem malý úvod do assembleru uzavírám. Pevně věřím, že jste si odnesli alespoň základní vhled do této problematiky. Zájemcům o další studium mohu doporučit nádhernou knihu o architektuře počítačů od A. Tannenbauma [1]. Kniha se assemblerem zabývá jen okrajově, ale skvěle a dopodrobna rozebírá architekturu počítačů jako takovou. Je to nádherné a vypiplané dílo – jenom varuji, že poměrně náročné. Ale ze všech knih na toto téma, které jsem zatím potkal (no, zas tolik jich nebylo), mi přišla nejzajímavější.

Tomáš Bartoněk

  1. Tanenbaum, A. S. (1976). Structured computer organization. Prentice-Hall, Pearson. ISBN 978-0132916523.


1) Už jsme to trochu řešili, ale zatím nijak podrobně.