Příkladem práce s porty může být např. čtení informací z paměti CMOS. Provádí se to tak, že na port 70h vyšleme číslo čtené slabiky v CMOS. Někdy může být potřeba na CMOS paměť chvilku počkat, a potom z portu 71h přečteme hodnotu žádané slabiky.
V dnešních OS bývají instrukce pro práci s porty privilegované, takže je v nich nemůžeme vyzkoušet. Pro toto opatření je celkem rozumný důvod, protože bychom mohli ovlivnit hardware, který se ovládá přes porty (např. síťové karty) a OS by tak nad ním mohl ztratit kontrolu.
Při nepřímém adresování si můžeme vypočítanou adresu uložit do registru pomocí instrukce LEA.
LEA se dá použít i pro jednoduché násobení. Takové násobení je nejrychlejší možné (na 486 až 1 takt), např. LEA eax, [eax+8*eax] znamená eax = 9*eax.
Otázka: Jaké násobky lze uskutečnit? Je možné takto vynásobit např. 5-ti nebo 6-ti?
Vzhledem k tomu, že 16-bitový obvod 8086 je schopen práce s pamětí o velikosti 1 MB, pro jejíž adresaci je potřeba 20 bitů (2^20 = 1 MB), a obsahuje jen šestnáctibitové registry, je nutný přístup k této paměti po logických blocích velikosti maximálně 64 kB (2^16). Intel pro to vytvořil schéma tzv. segmentace paměti. Bloku říkáme segment a jeho adresa (adresa začátku segmentu) v paměti je násobkem šestnácti. Tyto logické bloky (segmenty) se mohou překrývat a ve skutečnosti se překrývají téměř vždy. Umístění jednotlivých slabik v segmentu určuje číslo offsetu, zkráceně offset. Logická adresa slabiky v paměti se potom skládá ze dvou částí: segmentové a offsetové, které se při zápisu adresy oddělují dvojtečkou a do hranatých závorek se píše jen offset. Obě tyto části jsou vyjádřeny šestnáctibitovým číslem. Segmentová část adresy je adresa segmentu vydělená šestnácti a offsetová část je rovna offsetu. (Lineární) adresa slabiky v paměti se z těchto dvou částí spočítá následovně: segmentová část je vynásobena šestnácti (v binárním vyjádření jsou přidány čtyři bity s hodnotou nula) a k tomuto dvacetibitovému číslu je potom přičten offset. Pro zmatení uživatelů/programátorů se pro segmetovou část adresy také často nesprávně používá označení ,,adresa segmentu'' nebo i ,,segment'' (i když už víte, že skutečná adresa segmentu je šestnáctinásobek a segment je blok paměti s touto adresou). Pro výrazné zjednodušení výkladu budu i já používat místo sousloví ,,segmentová část adresy bloku paměti'' jen ,,segment'', zda je myšlena část adresy nebo blok paměti vyplyne z kontextu. Např. (lineární) adresa místa v paměti na segmentu 0xAB1E a offsetu 0x1111 je 0xAB1E0 + 0x1111 = 0x0xAC2F1.
Důsledky segmentace:
Operační systém vyčlení pro běh programu při jeho spuštění segmenty (logické bloky paměti) pro jeho strojový kód (instrukce), data a zásobník a program je (samozřejmě) nemůže měnit. Segmenty se výrazně (nebo i úplně) překrývají a segment pro data a zásobník bývá většinou společný. Pro adresaci každého tohoto segmentu jsou určeny speciální registry.
Segmentové registry - 16-bitové, určené pro uložení segmentové části adresy (segmentu, viz výkladové ujednání):
Dnes v době 32-bitových operačních systémů pracujeme a programujeme (téměř) výhradně v 32-bitovém režimu procesoru. Z důvodu zpětné kompatibility ale dokáží i dnešní procesory pracovat v 16-bitovém režimu procesoru 8086 (přímo v tzv. reálném režimu nebo nepřímo v tzv. virtuálním režimu). V tomto režimu máme oproti 32-bitovému tato omezení:
Kromě instrukce LEA lze v 16-bitovém režimu použít i instrukce LDS, LES, LFS, LGS a LSS, které fungují stejně jako LEA, navíc však nastaví příslušný segmentový registr. V 32-bitovém režimu (obecně v tzv. chráněném režimu) jsou to ale privilegované instrukce, tudíž je zde použít nelze.
Příklad (v 16-bitovém režimu):unsigned long val1, val2; _asm { lfs si, val1 les di, val2 mov ax, fs:[si+2] cmp ax, es:[di+2] jb mensi ja vetsi mov ax, fs:[si] cmp ax, es:[di] jb mensi ja vetsi jmp konec mensi: push fs mov fs, es pop es mov ax, si mov si, di mov di, ax vetsi: mov ax, fs:[si+2] mov es:[di+2], ax mov ax, fs:[si] mov es:[di], ax konec: }Ekvivalentní příklad v 32-bitovém režimu:
unsigned long val1, val2; _asm { lea esi, val1 lea edi, val2 mov ax, [esi+2] cmp ax, [edi+2] jb mensi ja vetsi mov ax, [esi] cmp ax, [edi] jb mensi ja vetsi jmp konec mensi: mov eax, esi mov esi, edi mov edi, eax vetsi: mov ax, [esi+2] mov [edi+2], ax mov ax, [esi] mov [edi], ax konec: }
Pokud v zápisu adresy neuvedeme segment, defaultně se použije registr DS. Použití jiného segmentového registru lze zadat i jinak než jeho uvedením spolu s dvojtečkou - pomocí instrukčních prefixů SEGDS, SEGES, SEGCS a SEGSS. Tedy např. MOV AX, ES:[BX] je to samé jako SEGES MOV AX, [BX].
Segmentace se ale netýká jen 16-bitového režimu, existuje i na dnešních 32-bitových procesorech a operačních systémech (M$ Windows, Linux a další). Na programové úrovni ji ale bylo nutné používat jen v programech pracujících v 16-bitovém režimu, v dobách 16-bitových OS (např. M$ DOS) nebo dnes v emulaci 16-bitového operačního prostředí (např. M$ DOS v M$ Windows). V tomto režimu mají segmenty velikost typicky 64 kB. Jelikož dnešní 32-bitové operační systémy pracují v (chráněném) 32-bitovém režimu, nemusíme se v programu při práci s pamětí segmentací vůbec zabývat, protože s 32-bitovými registry jsme schopni adresovat celých 4 GB paměti jen pomocí offsetové části adresy a segmentaci transparentně (pro program) řeší OS, tzn. automaticky nastavuje segmentové registry podle přístupu do paměti a program to nemůže ovlivnit. Segmenty jsou potom velké i několik GB (překrývají se). Segmentace nás tedy zajímá, jen pokud programujeme v 16-bitovém režimu procesoru (procesor po resetu pracuje v reálném režimu a 32-bitové OS provádějí nějaký kód ještě před přepnutínm do chráněného 32-bitového režimu, nebo např. tzv. bootloadery, tj. zavaděče OS).
Assembler má velmi silný nástroj v řetězcových instrukcích. Za řetězec je v Assembleru považován libovolný blok dat v paměti o libovolné délce, avšak v těchto instrukcích se řetězcem myslí posloupnost znaků, slov nebo dvojslov. Řetězcové instrukce nemají operandy, pracují s hodnotami na adresách určených vždy těmito registry (registrovými páry):
Př. Zjednodušte předchozí příklad použitím některých z těchto instrukcí.
Příklad:void *memcpy(void *dest, void *src, unsigned long n) { _asm { mov esi, src mov edi, dest mov ecx, n cykl: movsb loop cykl mov eax, edi } }
Prefix opakování se velmi často používá před řetězcovými instrukcemi a umožňuje tak jejich podmíněné i nepodmíněné opakování. Jejich společným použitím se program zrychlí a hlavně zjednoduší. Nepodmíněným prefixem je:
Instrukční prefix REP rozšiřuje použití předchozích řetězcových instrukcí na řetězce jako posloupnosti jednotek dat. Jestliže máme nastavený registr ECX na počet slabik řetězce a adresy zdrojového a cílového řetězce, zajistí REP např. jejich zkopírování na jednom řádku programu (dalo by se říci jedinou instrukcí), REP MOVSB.
Př. Zjednodušte předchozí příklad použitím prefixu opakování. Použijte řetězcovou instrukci pracující s dvojslovem (končící D).
Příklad, jak nejrychleji vynulovat blok paměti:XOR EAX, EAX MOV ECX, čtvrtina_velikosti_bloku REP STOSD
Řetězcové instrukce porovnávání nastavují mimo jiné i vlajku ZF. Proto Assembler obsahuje navíc prefixy podmíněného opakování:
Opakování je tedy přerušeno nejen při nulovém ECX, ale i při nastavení ZF do log. 1 nebo 0.
K prohození dvou hodnot je potřeba jednu z nich uložit na pomocné místo a navíc to nejde méně než třemi instrukcemi. Instrukční sada procesoru však obsahuje i jedinou instrukci na prohození obsahu registrů nebo registru a paměti (která samozřejmě nepotřebuje pomocné místo, v paměti):
Př. Přepište bloky Assembleru provádějící funkce strcpy, strcmp a strchr jazyka C z jednoho z minulých cvičení tak, aby řetězce a znak byly parametry funkcí obsahujících blok Assembleru. Jednodušeji řečeno, napište tyto funkce celé v Assembleru, tzn. tělo funkcí bude obsahovat jen blok _asm. K parametrům přistupujte nepřímou adresací pomocí EBP a výsledek vracejte v EAX. Navíc v Assembleru nahraďte cykly řetězcovými instrukcemi s prefixy opakování REP*.