limbaj de asamblare-ivan

395
399 Ion IVAN Paul POCATILU Doru CAZAN Coordonatori PRACTICA DEZVOLTĂRII SOFTWARE ÎN LIMBAJE DE ASAMBLARE Editura Economică Bucureşti 2002

Upload: buddy72

Post on 10-Jun-2015

3.574 views

Category:

Documents


54 download

TRANSCRIPT

Page 1: limbaj de asamblare-ivan

399

Ion IVAN Paul POCATILU Doru CAZAN Coordonatori

PRACTICA DEZVOLTĂRII SOFTWARE

ÎN LIMBAJE DE ASAMBLARE

Editura Economică Bucureşti 2002

Page 2: limbaj de asamblare-ivan

400

Colectivul de autori

Ilie ADRIAN

Laurenţiu ARICIU Doru CAZAN

Cristian CODREANU Valentin DRAGOMIR

Ion IVAN Laur IVAN

Alexandru LEAU Adrian LICURICEANU

Zsolt MARK Teodor MIHAI

Paul POCATILU Mihai POPESCU Gabriel ŞUTAC

Sebastian TCACIUC Daniel VERNIŞ

Page 3: limbaj de asamblare-ivan

401

CUPRINS

1 Introducere 11 2 Caracteristicile limbajelor de asamblare 13 2.1 Prelucrări elementare 13 2.2 Caracterul neimplicit al definirilor si prelucrărilor 14 2.3 Secvenţialitatea instrucţiunilor 16 2.4 Absenţa priorităţilor 17 2.5 Contextul dinamic 19 2.6 Libera poziţionare 20 2.7 Adresarea generalizată 21 2.8 Necompactitatea construcţiilor 23 2.9 Incluziunea 24 2.10 Concluzii 25

3 Reprezentarea informaţiei 27 3.1 Codificarea informaţiei 27 3.2 Organizarea datelor 28 3.3 Reprezentarea informaţiei numerice 30 3.4 Reprezentări ale şirurilor de caractere 36

4 Moduri de adresare 39 4.1 Calculul adresei unui operand 39 4.2 Modul de adresare imediată 41 4.3 Modul de adresare registru 43 4.4 Adresarea directă 44 4.5 Adresarea indexată 45 4.6 Adresarea bazată 47 4.7 Adresarea bazată şi indexată 48

Page 4: limbaj de asamblare-ivan

402

4.8 Adresarea indirectă 49 5 Indicatorii de condiţie 53 5.1 Registrul FLAGS 53 5.2 Operaţii cu indicatorii de condiţie 55 5.3 Poziţionarea indicatorilor de condiţie la execuţie 56 5.4 Interacţiunea indicatori de condiţie - instrucţiuni 56

6 Instrucţiuni 61 6.1 Clasificarea instrucţiunilor 61 6.2 Descrierea instrucţiunilor 63 6.3 Forma externă a instrucţiunilor 65 6.4 Forma internă a instrucţiunilor 71 6.5 Comentariile 77 6.6 Efectele execuţiei instrucţiunilor 78

7 Definirea structurilor de date 81 7.1 Date elementare 81 7.2 Masive unidimensionale 82 7.3 Masive bidimensionale 82 7.4 Articolul 84 7.5 Variabile pointer 88 7.6 Variabile enumerative 89 7.7 Tabele de date 90 7.8 Obiecte 91

8 Implementarea structurilor fundamentale 95 8.1 Programarea structurată 95 8.2 Structura liniară 95 8.3 Structura alternativă 96 8.4 Structura repetitivă standard 99 8.5 Structura repetitivă condiţionată posterior 102 8.6 Structura alternativă multiplă 103

9 Aritmetici binare 107 9.1 Aritmetica binară pe 8 biţi 107 9.2 Aritmetica binară pe 16 biţi 109 9.3 Aritmetica binară pe 32 biţi 112

10 Aritmetici zecimale 117 10.1 Ajustări 117 10.2 Adunarea şi scăderea 119 10.3 Înmulţirea şi împărţirea 120 10.4 Proceduri de calcul 122 10.5 Determinarea lungimii rezultatului evaluări unei expresii aritmetice 129

11 Întreruperi 131 11.1 Întreruperi interne şi externe 131 11.2 Modul de funcţionare al întreruperilor 131 11.3 Tipuri de întreruperi 134 11.4 Exemplu de folosire al întreruperilor 138 11.5 Concluzii 145

12 Macrodefiniţii 147 12.1 Structura macrodefiniţiei 147

Page 5: limbaj de asamblare-ivan

403

12.2 Macroapelul şi macroexpandarea 149 12.3 Etichete locale 151 12.4 Variabile locale 153 12.5 Macrodefiniţii derivate 155 12.6 Macrodefiniţii recursive 156 12.7 Redefinirea macrodefiniţiilor 157 12.8 Macrodefiniţii uzuale 157 12.9 Concluzii 163

13 Proceduri 165 13.1 Reutilizabilitatea 165 13.2 Structura unei proceduri 166 13.3 Apelarea procedurilor 168 13.4 Locul procedurilor 172 13.5 Instrucţiuni specifice lucrului cu proceduri 173

14 Prelucrări în virgulă mobilă 179 14.1 Scurtă istorie a prelucrărilor în virgulă mobilă 179 14.2 Resurse 179 14.3 Setul de instrucţiuni 182 14.4 Sintaxa instrucţiunilor 187 14.5 Forma externă a instrucţiunilor 189 14.6 Exemple 190 14.7 Concluzii 193

15 Lucrul cu şiruri de caractere 195 15.1 Caracteristicile instrucţiunilor de lucru cu şiruri de caractere 195 15.2 Setul de instrucţiuni pentru lucrul cu şiruri de caractere 196 15.3 Lucrul cu şiruri de caractere fără a folosi instrucţiuni specializate 198 15.4 Prefixul REP 199 15.5 Compararea a două şiruri 201 15.6 Poziţia unui subşir intr-un şir 203 15.7 Eliminarea spatiilor 204 15.8 Copierea unui şir sursă în şir destinaţie 205 15.9 Stabilirea lungimii unui şir 205 15.10 Conversie de la întreg binar la hexazecimal ca şir de caractere 206 15.11 Înlocuirea apariţiilor unui caracter 207 15.12 Concatenarea a două şiruri 207 15.13 Iniţializarea unui şir 209 15.14 Copierea unui şir dintr-o zonă de memorie într-o altă zonă de memorie 210 15.15 Numărarea caracterelor identice cu un caracter specificat 211

16 Fişiere 213 16.1 Fişiere, articole, identificatori 213 16.2 Operaţii cu fişiere 215 16.3 Utilizarea de fişiere pentru calculul fondului de salarii 221 16.4 Operaţii cu directori si subdirectori 231

17 Programarea mixtă C - limbaj de asamblare 235 17.1 Programul principal este scris în limbaj de asamblare,

procedurile apelate sunt scrise în limbajul C 235

Page 6: limbaj de asamblare-ivan

404

17.2 Programul principal este scris în limbajul C, procedurile apelate sunt scrise în limbaj de asamblare

242

17.3 Concluzii 251 18 Dezvoltarea de aplicaţii orientate obiect in limbaj de asamblare 253

18.1 Concepte folosite în dezvoltarea programelor orientate obiect 253 18.2 Definirea obiectelor prin structuri 257 18.3 Definirea obiectelor prin macrodefiniţii 260 18.4 Folosirea specificaţiilor proprii limbajului de asamblare 276 18.5 Analiza comparativă a variantelor de implementare a obiectelor 282

19 Structuri de programe 289 19.1 Programul ca singură secvenţă (0, 0, 1) 289 19.2 Programul din secvenţe distincte grupate într-un segment (1, 1, 1) 296 19.3 Proceduri incluse în segmentul programului principal (1, 1, 1) 300 19.4 Definiri distincte pentru toate componentele (m, k, n) 304 19.5 Structuri liniare de program 306 19.6 Structura arborescentă 307 19.7 Structuri de tip reţea 316 19.8 Concluzii 318

20 Optimizarea programelor 319 20.1 Criterii de optim 319 20.2 Cicluri maşină 320 20.3 Volumul de operaţii 323 20.4 Secvenţe echivalente 327 20.5 Alegerea tipului de dată 329 20.6 Eliminarea subexpresiilor comune 330 20.7 Gestionarea corectă a invarianţilor 331 20.8 Regruparea ciclurilor 332 20.9 Eliminarea secvenţelor inconsistente 334 20.10 Eliminarea secvenţelor inactive 334 20.11 Reacoperirea segmentelor 335 20.12 Alocarea optimă a regiştrilor 336 20.13 Concluzii 338

21 Designul limbajelor de asamblare 339 21.1 Cerinţe ale designului 339 21.2 Structura instrucţiunii limbajului de asamblare 340 21.3 Designul corectiv pentru limbajele de asamblare 342 21.4 Stabilirea numărului de registre 344 21.5 Concluzii 345

22 Elemente de grafica 347 22.1 Istoric al adaptoarelor grafice 347 22.2 Rutine BIOS şi moduri grafice 349 22.3 Lucrul în mod grafic folosind un adaptor VGA 351 22.4 Lucrul în mod grafic folosind un adaptor SVGA 359 22.5 Concluzii 364

23 Programe rezidente 365 23.1 Caracteristicile programelor rezidente 365 23.2 Întreruperi pentru programe rezidente 366

Page 7: limbaj de asamblare-ivan

405

23.3 Sistemul de operare MS-DOS 369 23.4 Resurse ale programelor rezidente 370 23.5 Controlul proceselor 373 23.6 Probleme specifice în realizarea programelor rezidente 378 23.7 Activarea programelor rezidente 385 23.8 Program rezident pentru afişarea ceasului 388 23.9 Concluzii 398

24 Programarea în modul protejat 399 24.1 Moduri de operare ale procesorului 80x86 399 24.2 Regiştrii procesoarelor i386/i486 400 24.3 Moduri de gestiune a memoriei 407 24.4 Comutarea in modul protejat 409 24.5 Implementarea modului de lucru multitasking 423 24.6 Concluzii 426

25 Programarea aplicaţiilor Windows în limbaj de asamblare 427 25.1 Interfaţa de programare a aplicaţiilor Windows 427 25.2 Organizarea memoriei in Windows 9x 428 25.3 Încărcarea programelor Win32 430 25.4 Structura unui program în limbaj de asamblare 430 25.5 Programarea sub Windows 432 25.6 Asamblarea si editarea de legături 441 25.7 Exemplu de program Win32 441 Bibliografie 447 Anexe A I Arhitectura procesoarelor din familia Intel 80x86 A-1 A II Mnemonicele instrucţiunilor procesorului 80x86 A-5 A III Setul de instrucţiuni ale procesoarelor Intel 80x86 A-11 A IV Regiştrii coprocesorului 80x87 A-69 A V Mnemonicele instrucţiunilor coprocesorului 80x87 A-73 A VI Primul program în limbaj de asamblare A-77 A VII Analiza comparată a utilizării diferitelor tipuri de reprezentări A-89 A VIII Calcul matriceal - studiu de caz A-101

Page 8: limbaj de asamblare-ivan

406

2

CARACTERISTICI ALE LIMBAJELOR DE ASAMBLARE

2.1 Prelucrări elementare Limbajele algoritmice de programare (FORTRAN, COBOL, PL/I, ALGOL) şi limbajele evoluate de programare (C++, PASCAL) conţin construcţii care realizează prelucrări complexe. Compilatoarele lor dezvoltă secvenţe cu multe instrucţiuni în module obiect. În schimb, limbajele de asamblare conţin prelucrări elementare. Instrucţiunile lor sunt puse în corespondenţă cu operaţii simple. Instrucţiunea

add O1, O2 realizează adunarea operandului O2 la operandul O1. Rezultatul este dat de operandul O1. Instrucţiunea

mov O1, O2 efectuează copierea operandului O2. După executarea ei, operandul O1 are acelaşi conţinut cu cel al operandului O2.

Instrucţiunea

shl O1,x realizează multiplicarea operandului O1 cu 2x sau deplasarea spre stânga a biţilor ce alcătuiesc conţinutul operandului O1 cu x poziţii.

Instrucţiunea

xchg O1, O2 realizează interschimbul dintre operanzii O1 şi O2. Dacă înainte de execuţie operandul O1 este egal cu 7 iar operandul O2 este egal cu 13, după execuţia instrucţiunii xchg, operandul O1 va fi 13, iar operandul O2 va fi 7.

Page 9: limbaj de asamblare-ivan

407

Se identifică o multitudine de prelucrări elementare. Fiecărei prelucrări elementare i se asociază o instrucţiune. Proiectarea limbajului de asamblare ia în considerare rezolvarea oricărei probleme prin construirea de algoritmi la nivelul paşilor elementari. Înseamnă că mai jos de acest nivel de detaliere nu se mai poate coborî.

Este foarte important de observat că diferenţele dintre limbajele de asamblare sunt nesemnificative dacă sunt analizate prelucrările elementare.

Dacă se studiază listele de instrucţiuni ale limbajelor de asamblare ASSEMBLER, ASSIRIS, MACRO-11, corespunzătoare unor generaţii mai vechi, cu cele ale limbajelor de asamblare definite pentru microprocesoarele de azi se observă existenţa aceluiaşi nivel de detaliere în cazul aritmeticii binare şi în aria instrucţiunilor destinate controlului execuţiei.

Tabelul 2.1.

Limbajul Limbaj asamblare microprocesor

Instrucţiunea

ASSEMBLER ASSIRIS MACRO-11 486 PENTIUM II

Adunare add ad4, add add add add Scădere sub sb4, sbd sub sub sub Înmulţire mul mp4, mpd mul mul mul Împărţire div dv4, dvd div div div Comparare cmp cp4, cp1 cmp cmp cmp Salt necondiţionat

jmp bru br jmp jmp

Salt condiţionat jz, je, jne,... bcf, bz, ... beq, bne,... jz, je, jne,... jz, je, jne,... 2.2 Caracterul neimplicit al definirilor şi prelucrărilor Limbajele de programare evoluate posedă implementări la nivel de compilatoare care realizează:

alocări automate de memorie pentru operanzii utilizaţi dar nedefiniţi în program;

iniţializări la definire a operanzilor; semnalarea unor neconcordanţe între natura operanzilor sau

incompatibilităţi, cu efectuarea corecţiilor celor mai probabile sau prin eliminarea instrucţiunii din secvenţă;

conversiile necesare pentru a asigura gradul de omogenitate necesar al prelucrărilor.

Limbajele de asamblare presupun existente la dispoziţia programatorului a tuturor resurselor sistemului de calcul. Înseamnă deci, că programatorul este obligat să:

definească în program toţi operanzii; iniţializeze toate zonele de memorie;

Page 10: limbaj de asamblare-ivan

408

asigure omogenitatea operanzilor în vederea efectuării corecte a operaţiilor de calcul.

În programele scrise în limbaj de asamblare se execută exact operaţiile pentru care există instrucţiuni.

Dacă un operand nu este definit dar este utilizat, asamblorul semnalează acest eveniment ca fiind major şi nu se va proceda la trecerea la etapele ulterioare asamblării.

Dacă un operand nu a fost iniţializat se va lucra cu conţinutul existent al zonei de memorie. Simplitatea programului asamblor nu permite analiza corelaţiilor dintre instrucţiuni aşa fel încât să poată semnala că se folosesc variabile neiniţializate.

În cazul în care un operand A este iniţializat cu un întreg binar, iar operandul B este iniţializat cu un număr zecimal împachetat, dacă se scrie instrucţiunea:

add a, b

cei doi operanzi sunt trataţi ca întregi binari. Dacă programatorul nu a inclus în program procedura de realizare a conversiei operandului B această operaţie nu este implicită, deci nu se efectuează. Într-un limbaj algoritmic, dacă operandul de tip întreg A este iniţializat cu 27, iar operandul B de tip zecimal împachetat este iniţializat cu 13, instrucţiunea: C = A + B se efectuează prin parcurgerea paşilor următori:

se converteşte operandul B de la zecimal împachetat la întreg binar

00 13 00 0D B Temp B

Figura 2.1 – Rezultatul conversiei de la zecimal împachetat la binar

se adună operanzii A şi Temp B şi rezultatul este memorat în variabila

C

00 1B 00 0D A + Temp B

00 28

C

Page 11: limbaj de asamblare-ivan

409

Figura 2.2 – Rezultatul adunării

Dacă în programul scris în limbaj de asamblare sunt definiţi operanzii A şi

B în secvenţa:

A dw 27 B dw 0013h C dw ? executarea adunării fără ca în prealabil să se apeleze de către programator procedura de conversie de la reprezentarea zecimal împachetat la reprezentarea binară, mov ax, A add ax, B mov C, ax conduce la obţinerea în operandul C a şirului 002Eh ce corespunde valorii întregi 46. 2.3 Secvenţialitatea instrucţiunilor În programele scrise în limbaj de asamblare se află instrucţiuni dispuse una după cealaltă, care formează secvenţe lineare. Apariţiile instrucţiunilor de salt întrerup caracterul liniar al programelor. Chiar dacă nu sunt dispuse unele după altele, instrucţiunile tot secvenţial se execută, figura 2.3.

I1 I2 JMP A B: I3 I4 A: I5 I6 I7 JZ B I8

Figura 2.3 – Secvenţă de instrucţiuni

Instrucţiunile se execută în ordinea: I1 I2 I5 I6 I7 I3 I4 I5 I6 I7 I3 … I7 I8. În cazul utilizării instrucţiunilor de apel pentru proceduri, secvenţialitatea este construită dinamic.

Programului din figura 2.4 îi corespunde secvenţa:

Page 12: limbaj de asamblare-ivan

410

I1 I2 A1 A2 A3 I3 I4 B1 B2 I5 I6.

I1 I2 call A I3 I4 call B I5 I6 mov ax,4C00h int 21h A proc A1 A2 A3 Ret endp B proc B1 B2 ret endp

Figura 2.4 – Apel de proceduri

Programatorul cunoaşte exact că secvenţa liniarizată conţine instrucţiuni de

salt necondiţionat, tocmai elementele care disting liniarizarea aşa cum e definită în programarea structurată. Programului din figura 2.4 îi corespunde în realitate secvenţa:

I1 I2 J A1 A2 A3 J I3 I4 J B1 B2 J I5 I6

unde prin J s-au notat instrucţiunile de salt necondiţionat ce corespund apelului de proceduri, respectiv, reîntoarcerii în programul apelator. 2.4 Absenţa priorităţilor Limbajele evoluate de programare au definite liste de operatori cu priorităţile din matematică sau priorităţi definite special. La construirea de expresii, evaluarea se efectuează în raport cu priorităţile. Astfel, expresia: a--*++b-c

Page 13: limbaj de asamblare-ivan

411

se evaluează astfel: se incrementează b; se înmulţeşte a cu b; se scade c; se decrementează a. Absenţa priorităţilor operatorilor în limbajele de asamblare determină ca

ordinea de efectuare a operaţiilor să fie cea indicată de succesiunea instrucţiunilor în program. Astfel, dacă se consideră expresia: a*b+c*d şi se construieşte secvenţa: mov ax,a mul b add c mul d se obţine de fapt evaluarea expresiei: ((a*b)+c)*d Pentru evaluarea corectă, cu luarea în considerare a ordinei de efectuare a operaţiilor aritmetice se scrie secvenţa: mov ax,a mul b mov e,ax mov ax,c mul d add e,ax ceea ce corespunde evaluării: e=a*b e=e+c*d sau: (e=(e=(a*b))+(c*d)) Limbajul de asamblare dă posibilitatea programatorului să recompună expresii complexe prin traversarea secvenţială a instrucţiunilor elementare.

Page 14: limbaj de asamblare-ivan

412

2.5 Contextul dinamic Limbajele de programare sunt structurate în definiri de tipuri de operanzi şi referiri de operanzi în vederea prelucrării. Contextul de prelucrare începe cu definirea operanzilor. Între modul de definire şi modul de utilizare există restricţii pe care programatorul trebuie să le respecte. În definirea limbajelor evoluate sau la implementările unor compilatoare sunt specificate restricţiile şi libertăţile care sunt în legătură cu o serie de operaţii. Sunt numeroase tabelele în care apar tipuri de operanzi şi se indică prin Y (Yes) sau N (No) dacă o anumită operaţie sau conversie este posibilă (implementată) sau nu.

Astfel, pentru instrucţiunea MOVE op1 to op2 din limbajul COBOL se consideră tipurile de date şi permitivităţile din tabelul

Tabelul 2.2. Op2

Op1

Întreg Zecimal împachetat

Zecimal despachetat Display Şir

caractere

Întreg Y Y Y Y Y Zecimal

împachetat Y Y Y Y Y

Zecimal despachetat Y Y Y Y Y

Display Y Y Y Y Y Şir

caractere N N N N Z

Limbajele de asamblare definesc tipuri de zone de memorie ca lungime (DB

– define byte, DW – define word, DD – define double etc. ) şi ca aliniere, fără a le pune în corespondenţă cu tipurile constantelor (integer, float, char, pointer, boolean, complex, decimal, display, computational, real, double, string, etc.).

Sensul unui operand este dat de locul pe care îl are în lista de operanzi şi de instrucţiuni care îl foloseşte. Se consideră definirea: a dw 80 În instrucţiunea add a, 2

Page 15: limbaj de asamblare-ivan

413

operandul a este termen al unei adunări şi rezultat al acesteia. În instrucţiunea: add ax,[a] operandul a este o variabilă pointer care indică adresa unde se află operandul care participă ca termen la efectuarea adunării. În instrucţiunea cmp a+1,’c’ se preia unul dintre baiţi şi este interpretat caracter pentru a fi comparat cu caracterul ‘c’. Semnificaţiile operandului a sunt date de contextul creat de programator prin utilizarea unei anumite instrucţiuni şi prin poziţionarea anumită a respectivului operand în expresia de adresare. Cunoaşterea foarte exactă a tipurilor de expresii de referire şi a instrucţiunilor permit găsirea de soluţii elegante de a obţine prelucrări foarte diferite şi de a da sensuri noi limbajului însuşi. 2.6 Libera poziţionare Orice program foloseşte operanzi şi operatori. Limbajul de asamblare permite structurarea distinctă pe segmente a datelor, a stivei şi a codului executabil. Mai mult, există posibilitatea de a defini o variabilă oriunde în segmente şi de a o referi cu semnificaţia pe care contextul local (cod instrucţiune, poziţie în lista de operanzi) îl dă. De exemplu, pentru evaluarea expresiei z=x+y variabilele x,y,z se definesc în segmentul de date: .data x dw 3 y dw 5 z dw ? .stack dw 1000 dup (?) .code ... mov ax,x add ax,y mov z,dx Aceeaşi soluţie se obţine şi dacă variabilele se definesc pe stivă:

.data

.stack

Page 16: limbaj de asamblare-ivan

414

x dw 3 y dw 5 z dw ?

.code ... mov bx, bp mov ax, [bx] add ax, [bx+2] mov [bx+4], ax

Dacă variabilele x, y, z se definesc în segmentul de cod al programului se

obţine construcţia:

.code mov ax,@code mov ds,ax jmp alfa x dw 3 y dw 5 z dw ? alfa: mov ax,a add ax,y mov z,ax Mai mult, poziţionarea operanzilor se efectuează fie cu luarea în considerare a cerinţelor de aliniere, fie fără a se ţine seama de cerinţele de aliniere se procedează astfel:

se definesc mai întâi variabilele de tip dd; se definesc mai apoi variabilele de tip dw, dt; ultimele se definesc variabilele de tip db sau de tip struc. În cazul în care nu este respectată această regulă şi variabilele de tip dw, dd,

dt se află la adrese impare, pentru referirea operanzilor sunt necesare cicluri maşină în plus la execuţie.

2.7. Adresarea generalizată În limbajele de asamblare sunt definite modalităţi variate de referire a operanzilor (adresare directă, adresare indirectă, adresare indexată, adresare bazată, etc.). Baiţii sunt puşi în corespondenţă cu identificatori şi cu etichete. Adresarea generalizată se referă la faptul că în program sunt adresabili toţi baiţii, indiferent de locul pe care îl au în segmentul de stivă, în segmentul de date sau în segmentul de cod. Semnificaţia zonei de memorie referite este dată de context (codul instrucţiunii şi poziţia în lista de operanzi). Adresarea generalizată include şi posibilitatea de a referi oricare bait din orice punct al segmentului din care baitul face parte.

Page 17: limbaj de asamblare-ivan

415

În definirea: .data a dw 300 dup (?)

b dw 100 dup (?) ... mov ax,a+200 mov bx,b-400 se referă acelaşi element din masivul unidimensional, A[100] ( figura 2.5.).

A[0] A[1] A[100] A[299] B[0] B[1]

a a+2 a+200 a+598 b b+2 b-400

mov ax, a+200 mov bx, b-400

Figura 2.5 – Referiri element în raport cu bazele a şi b

Tratarea diferenţiată a identificatorilor şi a etichetelor din programele scrise în limbaje algoritmice exclud posibilitatea modificării textului executabil în timpul execuţiei programului. Dacă într-un program C++, alfa este eticheta, instrucţiunea alfa++ este o construcţie incorectă, semnalată în faza de compilare.

Într-un program scris în limbaj de asamblare, considerând secvenţa: mov ax,5 b: mov ax,5 a: inc ax atunci instrucţiunile: inc b

sau

add b, 01h

în cazul în care ar fi acceptate de asamblor, ar putea fi folosite în transformarea dinamică a codului operaţiei.

Page 18: limbaj de asamblare-ivan

416

Se observă absenţa restricţiilor în referirea de baiţi şi modificarea acestora. Instrucţiunea mov ax,7 se structurează astfel: b b+1 b+2 00 07

cod operaţie Figura 2.6 – Structura instrucţiunii mov ax,7

deşi la asamblare pe baitul al treilea al instrucţiunii cu eticheta b se află numărul 7 instrucţiunea: mov b+2,15 modifică valoarea conţinută de acest bait, iar rezultatul adunării este 25 (10+15) şi nu 17 (10+7). 2.8 Necompactitatea construcţiilor Întrucât o instrucţiune efectuează o operaţie simplă şi instrucţiunile se organizează în secvenţe programul scris în limbaj de asamblare este văzut ca secvenţe şi nu ca expresii. Mai mult, limitele de poziţionare a instrucţiunilor în secvenţe reduc gradul de compactare a programelor. Dacă în limbajul C++ expresia de atribuire multiplă sau expresia virgulă permit creşterea gradului de agregare cum o efectuează şi operatorul condiţional, în programele scrise în limbaj de asamblare acest grad de agregare nu este atins datorită simplităţii operaţiilor cu care sunt asociate instrucţiunile.

Evaluarea expresiei:

m = min(a,b,c) se realizează prin secvenţele de program scris în limbajul C. m=a if (mb) m=b; if (mc) m=c; Compactitatea secvenţei creşte prin utilizarea operatorului condiţional:

Page 19: limbaj de asamblare-ivan

417

(ab) ? (bc) ? m=c: m=b: (ac) ? m=c: m=a; se evaluează o singură expresie cu grad de complexitate ridicat. În programul scris în limbaj de asamblare evaluarea expresiei revine la utilizarea secvenţei. ... mov ax,a cmp ax,b jle e1 mov ax,b e1: cmp ax,c jle e2 mov ax,c e2 mov m,ax

... 2.9 Incluziunea Până la apariţia limbajelor de programare din anii ’70, limbajelor de asamblare li se atribuiau o serie de caracteristici, dintre care se enumeră:

accesul la toate resursele sistemelor de calcul (registre, funcţii de bază, memorie la nivel de bit);

posibilitatea de a efectua prelucrări imposibil de efectuat în limbaje precum COBOL, FORTRAN din cauza modurilor de adresare şi a expresiilor de referire;

definirea de mecanisme specifice, particulare de referire a operanzilor şi operatorilor.

Odată cu apariţia limbajelor C++, Pascal aceste bariere au dispărut. Toate prelucrările specifice limbajelor de asamblare sunt incluse în limbajele C++ şi Pascal.

Instrucţiunilor inc şi dec le corespund operatorii ++, respectiv --. Mai mult, în limbajul C++ apar nuanţările de preincrementare, postincrementare, predecrementare, postdecrementare.

Accesul la registre se efectuează cu structura:

struct REGPACK {

unsigned r_ax, r_bx, r_cx, r_dx; unsigned r_bp, r_si, r_di; unsigned r_ds, r_es, r_flags;

};

Figura 2.7 – Structura folosită pentru accesul la regiştri

Page 20: limbaj de asamblare-ivan

418

Accesul la funcţiile sistemului de operare se efectuează direct, folosind

funcţii cu acelaşi cod pe care îl au întreruperile sistemului de operare. De exemplu secvenţa de schimbare a directorului curent cu ajutorul funcţiilor sistemului de operare:

int main(void) {

char directory[80]; struct REGPACK reg;

printf("Introduceţi numele directorul de schimbat:"); gets(directory); reg.r_ax = 0x3B << 8; /*incarcă AH cu 3Bh*/ reg.r_dx = FP_OFF(directory); reg.r_ds = FP_SEG(directory); intr(0x21, &reg); if (reg.r_flags & CF)

printf("Schimbare director esşuată\n"); getcwd(directory, 80); printf("Directorul curent este: %s\n", directory); return 0;

}

Mai mult, în programele C++ se scriu secvenţe de instrucţiuni scrise în limbaj de asamblare care dau posibilitatea programatorului să controleze mai bine utilizarea resurselor fizice din programul său.

#include <stdio.h> void main() { unsigned char a=10, b=11, c; asm {

mov al,a add al,b mov c,al }

printf("\nc=%d\n",c); }

Figura 2.8 – Folosirea limbajului de asamblare într-un program scris în C++

2.10 Concluzii Aceste caracteristici au rolul de a diferenţia limbajele de asamblare de celelalte limbaje. Va fi mai clar momentul în care programatorul trebuie să

Page 21: limbaj de asamblare-ivan

419

dezvolte secvenţe sau proceduri în limbaj de asamblare pentru a obţine performanţa în aplicaţiile sale complexe.

3

REPREZENTAREA INFORMAŢIEI Chiar dacă un computer stochează diverse tipuri de informaţie (texte, imagini, sunete), această informaţie este reprezentată intern ca informaţie numerică. Aceasta face ca o importanţă deosebită să fie acordată reprezentării şi prelucrării acestui tip de informaţie. 3.1 Codificarea informaţiei

Sistemele de calcul folosesc pentru prelucrarea şi transmiterea informaţiei două nivele de tensiune (de obicei 0V pentru 0 logic si +3.3V până la +5V pentru 1 logic). Cu aceste două nivele de tensiune se reprezintă cele două valori diferite, notate prin convenţie cu ‘0’ şi ‘1’, şi care corespund cifrelor sistemului de numeraţie binar.

Tabelul 3.1. Binar Octal Hexazecimal Zecimal 0000 0 0 0 0001 1 1 1 0010 2 2 2 0011 3 3 3 0100 4 4 4 0101 5 5 5 0110 6 6 6 0111 7 7 7 1000 10 8 8 1001 11 9 9 1010 12 A 10 1011 13 B 11 1100 14 C 12 1101 15 D 13

Page 22: limbaj de asamblare-ivan

420

1110 16 E 14 1111 17 F 15

Pe lângă sistemul de numeraţie binar care utilizează baza de numeraţie 2, în sistemele de calcul sunt folosite şi sistemele de numeraţie octal, hexazecimal şi bineînţeles zecimal, care folosesc bazele de numeraţie 8, 16 respectiv 10. În sistemul de numeraţie hexazecimal, numerele de la 10 la 15 sunt codificate cu ajutorul literelor: ‘A’, ‘B’, ‘C’, ‘D’, ‘E’, ‘F’. În tabelul 3.1 este prezentată corespondenţa pentru cele patru sisteme de numeraţie. Toate aceste sisteme de numeraţie sunt numite şi sisteme de numeraţie poziţionale, aceasta însemnând că ‘valoarea’ fiecărei cifre depinde, la formarea numerelor, de poziţia acesteia în cadrul numărului. Pentru a exemplifica această afirmaţie considerăm numărul 42349(10) scris în baza 10 (vom folosi în continuare notaţia ‘(b)‘ pentru a indica baza ‘b‘ în care a fost scris numărul). Valoarea acestui număr se calculează astfel:

410.000 + 21.000 + 3100 + 410 + 91 = 42349(10) Se observă că cifra 4 participă la formarea numărului de două ori, prima dată având ‘valoarea’ 40.000, iar a doua oară având ‘valoarea’ 40. O altă observaţie foarte importantă este aceea că ‘valorile’ cifrelor se obţin înmulţindu-le cu puteri ale bazei (1=100, 10=101, 100=102, 1.000=103, etc.). Aceasta afirmaţie este valabilă şi în cazul altor sisteme de numeraţie poziţionale, printre care şi sistemele binar, octal şi hexazecimal. Numărul 10010(2) reprezintă:

124 + 023 + 022 + 121 + 020 = 18(10). 3.2 Organizarea datelor O singură cifră binară (‘0’ sau ‘1’) este numită ‘bit’ (prescurtare de la BInary digiT) atunci când este implementată hardware într-un computer. Deoarece în memoria computerului informaţia este reprezentată ca o înşiruire de biţi, (‘0’ şi ‘1’), se pune problema localizării unei anumite părţi a informaţiei (de exemplu un număr care participă la o operaţie de adunare). Pentru a facilita accesul la informaţie, biţii au fost grupaţi prin convenţie. Modalităţile uzuale de grupare folosite sunt: un singur bit, câte patru biţi (numiţi ‘nibble’), câte opt biţi (numiţi ‘bait’ sau 'octet'), câte 16 biţi (numiţi ‘cuvânt’), câte 32 biţi (numiţi ‘dublu-cuvânt’).

3.2.1 Bit

Page 23: limbaj de asamblare-ivan

421

Deoarece un singur bit este capabil să reprezinte doar două valori diferite, există impresia că foarte puţină informaţie poate fi reprezentată cu ajutorul unui bit. Dar nu este chiar aşa! Se poate reprezenta cu ajutorul unui bit informaţie privitoare la valori de adevăr (Adevărat sau Fals), la stările unui comutator (Închis sau Deschis), la sexul unei persoane (Bărbătesc sau Femeiesc), etc.

3.2.2 Nibble Construcţia nibble este un grup de patru biţi. El prezintă interes numai din punctul de vedere al codificării numerelor în format BCD (Binary-Coded Decimal) şi al reprezentării numerelor hexazecimale. Cu ajutorul unui nibble se reprezintă cel mult 16 valori distincte. Structura internă a unui nibble este:

b3 b2 b1 b0

Figura 3.1 – Structura internă a unui nibble

unde cu b0,b1,b2,b3 au fost notaţi cei patru biţi care îl compun.

3.2.3 Bait Baitul este cea mai importantă structură de date folosită într-un computer. Are o lungime de opt biţi, notaţi b0, b1, b2, b3, b4, b5, b6, b7, cel mai semnificativ fiind bitul b7, iar cel mai puţin semnificativ b0.

b7 b6 b5 b4 b3 b2 b1 b0

Figura 3.2 – Structura internă a unui bait Un bait conţine exact doi nibble, ceea ce înseamnă că se reprezintă cu ajutorul unui bait două cifre hexazecimale. Numărul de valori distincte ce pot fi reprezentate pe un byte, este de 256 (28). În computer un bait este folosit pentru a reprezenta:

- numere fără semn de la 0 la 255; - numere cu semn de la -128 la +127; - codurile caracterelor ASCII; - alte tipuri de date pentru care 256 simboluri distincte sunt suficiente.

3.2.4 Cuvânt

Page 24: limbaj de asamblare-ivan

422

Limitele pentru cuvânt au fost stabilite prin convenţie la 16 biţi sau la dimensiunea magistralei de date a procesorului. Aceasta înseamnă că dimensiunea unui cuvânt este variabilă, depinzând de procesorul instalat pe sistem. În continuare vom folosi însă cuvântul definit doi baiţi (16 biţi). Structura unui cuvânt este:

b15 b14 b13 b12 b11 b10 b9 b8 b7 b6 b5 b4 b3 b2 b1 b0

Figura 3.3 – Structura internă a unui cuvânt Un cuvânt este împărţit în doi baiţi sau patru nibble. Cei doi baiţi din componenţa unui cuvânt poartă numele de baitul cel mai semnificativ (MSB – most significant byte) şi baitul cel mai puţin semnificativ (LSB – less significant byte) format din biţii b7-b0. Cu ajutorul unui cuvânt sunt reprezentate 216=65536 valori distincte. Cele mai importante utilizări ale unui cuvânt sunt pentru reprezentarea:

întregilor pe 16 biţi; adreselor pe 16 biţi; numerelor ce au nevoie de cel mult 16 biţi; instrucţiunilor cu operanzi impliciţi. 3.2.5 Dublu-cuvânt

Aşa cum arată şi numele un dublu cuvânt este format prin concatenarea a două cuvinte. Lungimea unui dublu-cuvânt este de 32 biţi şi stochează 4,294,967,296 valori distincte. 3.3 Reprezentarea informaţiei numerice Informaţia numerică este organizată într-un computer în diferite moduri, în funcţie de scopul memorării acesteia şi de modurile de prelucrare. Din punct de vedere al existenţei părţii zecimale, numerele sunt întregi sau reale. 3.3.1 Reprezentarea numerelor întregi în formatul binar Numerele întregi se reprezintă pe unul sau mai mulţi baiţi. Se notează acest număr cu n. De obicei numărul n are valoarea 1, 2, sau 4. Există două cazuri distincte de reprezentări:

- pentru numere întregi fără semn; - pentru numere cu semn.

Page 25: limbaj de asamblare-ivan

423

Pentru numerele întregi fără semn, reprezentarea acestora într-un sistem de calcul se face identic cu reprezentarea acestora în sistemul de numeraţie binar. Pentru aceste numere, valoarea maximă reprezentabilă pe numărul dat de baiţi, este 2n8-1 unde 8 este numărul de biţi ai unui bait (‘-1’ apare datorită necesitaţii de a reprezenta numărul ‘0’). În tabelul următor sunt prezentate câteva numere şi reprezentările lor ca numere întregi fără semn pe doi baiţi.

Tabelul 3.2. Valoare Reprezentare (n=2)

0 0000 0000 0000 0000 1 0000 0000 0000 0001 2 0000 0000 0000 0010 3 0000 0000 0000 0011 4 0000 0000 0000 0100

64 0000 0000 0100 0000 128 0000 0000 1000 0000 256 0000 0001 0000 0000

1024 0000 0100 0000 0000 Reprezentarea numerelor întregi cu semn se face cu ajutorul unui sistem de reprezentare numit complement faţă de doi, primul bit (cel mai semnificativ) fiind considerat prin convenţie bit de semn. Dacă acest bit are valoarea ‘1’, atunci numărul are o valoare negativă, iar dacă are valoarea ‘0’, numărul este pozitiv. Valoarea absolută a numărului se calculează diferit după cum valoarea numărului numărul este pozitivă sau negativă:

dacă valoarea numărului este pozitivă (primul bit are valoarea ‘0’), atunci restul de n8-1 biţi reprezintă chiar valoarea numărului;

dacă valoarea numărului este negativă (primul bit are valoarea ‘1’), atunci valoarea absolută a numărului se obţine prin complement faţă de 2.

Pentru a obţine complementul faţă de 2 al unui număr se inversează toţi biţii numărului (‘0’ trece în ‘1’ şi invers) şi apoi se adună la rezultat valoarea ‘1’. Acest procedeu se aplică şi în cazul transformării inverse (din număr pozitiv în număr negativ). De exemplu pentru a găsi reprezentarea numărului –5 pe un bait, se va proceda astfel:

se scrie numărul în sistemul binar: 0000 0101 se inversează toate cifrele numărului: 1111 1010 se adună valoarea ‘1’ la rezultat: 1111 1011

Page 26: limbaj de asamblare-ivan

424

În continuare este prezentată transformarea inversă, pentru a obţine numărul reprezentat prin configuraţia de biţi 1111 1011 (se cunoaşte că numărul este reprezentat cu semn, pe un bait):

primul bit este ‘1’ deci numărul este negativ; se aplică procedeul de complementare faţă de 2 pentru a obţine valoarea absolută a numărului;

biţii _111 1011 se inversează: _000 0100 (nu s-a ţinut cont de bitul de semn);

se adună ‘1’ la rezultat: _000 0100 + 1 = _000 0101, adică reprezentarea numărului 5 în sistemul binar.

Numerele ce se reprezintă pe n baiţi în acest format au domeniul de valori cuprins între –2n8-1…2n8-1-1. În tabelul următor se găsesc câteva numere întregi negative reprezentate pe doi baiţi:

Tabelul 3.3. Valoare Reprezentare (n=2)

0 0000 0000 0000 0000 1 0000 0000 0000 0001 2 0000 0000 0000 0010 3 0000 0000 0000 0011

-1 1111 1111 1111 1111 -2 1111 1111 1111 1110 -3 1111 1111 1111 1101 -4 1111 1111 1111 1100 -5 1111 1111 1111 1011

3.3.2 Reprezentarea numerelor întregi în formatul BCD Formatul BCD(binary-coded decimal) este folosit pentru a reprezenta numere zecimale întregi, fiecare cifră fiind reprezentată pe patru biţi (un nibble). De exemplu, numărul 375 va fi reprezentat astfel:

0011 0111 0101

Un avantaj al acestei reprezentări faţă de reprezentarea binară este acela că nu există o limită maximă a dimensiunii numărului reprezentat. Pentru a mai adăuga o cifră numărului se adaugă încă patru biţi, în timp ce numerele reprezentate în format binar sunt limitate de 8, 16, 32 sau 64 de biţi. În computer, pentru stocarea acestor numere sunt folosite două reprezentări diferite:

reprezentarea zecimală împachetată (packed BCD); reprezentarea zecimală despachetată (unpacked BCD).

Page 27: limbaj de asamblare-ivan

425

3.3.2.1 Reprezentarea zecimala despachetată se concretizează prin ocuparea de către fiecare cifră a numărului a câte unui bait. Astfel, numărul 75431 se va reprezenta în format zecimal despachetat pe 5 baiţi, astfel:

00 07 00 05 00 04 00 03 00 01

un bait Figura 3.4 – Reprezentarea zecimală despachetată

Dacă numerele se introduc de la tastatură ca şir de caractere ASCII, cifrele numărului reprezentat zecimal despachetat se obţin prin scăderea numărului 30h din codurile caracterelor.

Tabelul 3.4.

Caracter Codul hexazecimal ASCII 0 30 1 31 2 32 3 33 4 34 5 35 6 36 7 37 8 38 9 39

3.3.2.1 Reprezentarea zecimala împachetată presupune ocuparea fiecărui bait de câte două cifre zecimale. Astfel numărul 75431 se va reprezenta în format zecimal împachetat astfel:

00 07 05 04 03 01

un bait Figura 3.5 – Reprezentarea zecimală împachetată

3.3.3 Reprezentarea numerelor reale Reprezentarea numerelor reale se face în două moduri distincte, şi anume reprezentare în virgulă fixă şi reprezentare în virgulă mobilă, în funcţie de modul de reprezentare a părţii întregi şi a părţii fracţionare a numărului.

Page 28: limbaj de asamblare-ivan

426

3.3.3.1 Reprezentarea numerelor reale în virgulă fixă presupune că se alocă pentru reprezentarea părţii întregi şi a părţii fracţionare a numărului, un număr de cifre constant. Dacă se consideră, de exemplu, că un număr în baza 10 reprezentat va avea o precizie de 0.001 şi că acest număr nu poate fi mai mare decât 9999, atunci numărul de cifre pentru partea întreagă este 4, iar cel pentru partea fracţionară este 3. Dacă numărul are şi valori negative, atunci se impune folosirea şi a unui câmp separat pentru reprezentarea semnului, care prin convenţie va avea valoarea 0 dacă numărul este pozitiv sau valoarea 1 dacă numărul este negativ. Deoarece sunt cunoscute numărul de cifre al părţii întregi şi numărul de cifre al părţii fracţionare, necesitatea reprezentării virgulei dispare. Fiecare cifră se va reprezenta fie pe un octet (din care vor fi ocupaţi doar patru biţi), fie câte două pe un octet. Câteva numere astfel reprezentate sunt:

Tabelul 3.5. Reprezentare Număr semn mii sute zeci unităţi zecimi Sutimi Miimi

4823.493 0 4 8 2 3 4 9 3 -52.3 1 0 0 5 2 3 0 0 2.445 0 0 0 0 2 4 4 5

3.3.3.2 Reprezentarea numerelor reale în virgulă mobilă (floating point) se foloseşte în reprezentarea numerelor implicate în calcule ştiinţifice. Forma generală a unui număr reprezentat în virgulă mobilă este:

(-1)SMBE unde:

S este un bit folosit la reprezentarea semnului numărului; M este un număr pozitiv subunitar, numit mantisă; B este baza de numeraţie în care se face reprezentarea numărului; E este un număr întreg cu semn, numit exponent.

Pentru a elimina problema reprezentării semnului exponentului, se adună la valoarea acestuia o constantă C pentru a deplasa astfel intervalul în care exponentul poate lua valori. Dacă exponentul se reprezintă pe 8 biţi, iar un bit va fi folosit pentru semn, exponentul va lua valori în intervalul [–128, 127]. Pentru a deplasa acest interval, se adună la valoarea exponentului constanta C = 128, intervalul în care va putea lua valori exponentul după această transformare devenind [0, 255]. Deoarece cel mai mic număr din interval este acum pozitiv, semnul nu se va mai reprezenta, cei 8 biţi fiind suficienţi în continuare pentru reprezentarea exponentului deplasat. La reprezentarea numerelor în virgulă mobilă se aplică o transformare şi asupra mantisei, numită normalizare, în urma căreia, prima cifră de după virgulă va fi diferită de 0 (ceea ce impune şi modificarea exponentului). Dacă

Page 29: limbaj de asamblare-ivan

427

reprezentarea numărului se face în baza 2, atunci prima cifră de după virgulă va fi întotdeauna 1 şi nu va fi reprezentată nici aceasta. Trei standarde privind reprezentările numerelor în virgulă mobilă sunt folosite în funcţie de precizia dorită. Cele trei standarde diferă din punctul de vedere al numărului de cifre disponibile reprezentărilor părţii întregi şi a părţii fracţionare. Aceste standarde sunt prezentate în următoarele figuri: B7 B6 B5 B4 B3 B2 B1 B0 B15 B14 B13 B12 B11 B10 B9 B8 E0 B22 B21 B20 B19 B18 B17 B16 S E7 E6 E5 E4 E3 E2 E1

Figura 3.6 – Reprezentarea în virgulă mobilă, simplă precizie

B7 B6 B5 B4 B3 B2 B1 B0 B15 B14 B13 B12 B11 B10 B9 B8 B23 B22 B21 B20 B19 B18 B17 B16 B31 B30 B29 B28 B27 B26 B25 B24 B39 B38 B37 B36 B35 B34 B33 B32 B47 B46 B45 B44 B43 B42 B41 B40 E3 E2 E1 E0 B15 B50 B49 B48 S E10 E9 E8 E7 E6 E5 E4

Figura 3.7 – Reprezentarea în virgulă mobilă, dublă precizie

B7 B6 B5 B4 B3 B2 B1 B0 B15 B14 B13 B12 B11 B10 B9 B8 B23 B22 B21 B20 B19 B18 B17 B16 B31 B30 B29 B28 B27 B26 B25 B24 B39 B38 B37 B36 B35 B34 B33 B32 B47 B46 B45 B44 B43 B42 B41 B40 B55 B54 B53 B52 B51 B50 B49 B48 1 B62 B61 B60 B59 B58 B57 B56 E7 E6 E5 E4 E3 E2 E1 E0 S E14 E13 E12 E11 E10 E9 E8

Figura 3.8 – Reprezentarea în virgulă mobilă, precizie extinsă

S-au notat cu Bi biţii mantisei normalizate, cu Ei biţii exponentului deplasat

şi cu S semnul mantisei. Se observă aşezarea baiţilor în memorie, primul fiind baitul cel mai puţin semnificativ al mantisei.

Page 30: limbaj de asamblare-ivan

428

3.4 Reprezentări ale şirurilor de caractere

3.4.1 Definire şir de caractere

Conţinutul unei zone de memorie este interpretat în funcţie de context. Cei opt biţi care alcătuiesc un bait sunt puşi în corespondenţă într-un context dat cu un element al alfabetului ASCII . Şirurile de constante se definesc şi se iniţializează prin construcţii de forma: sir1 db ‘Ionescu Gheorghe’ sir2 db ‘c’, ‘i’, ‘m’, ‘e’, ‘n’, ‘t’ sir3 db 30 dup (‘=’)

Utilizarea şirurilor în programe pentru operaţii de intrare / ieşire presupune luarea în considerare a unor caractere de control:

delimitatorul de sfârşit de şir; în limbajul C++ delimitatorul de sfârşit de şir este ‘\0’ iar funcţiile sistemului de operare impun ca delimitator caracterul ‘$’.

caracterul de control pentru trecerea la pagină nouă sau rând nou; pentru sfârşit de rând (trecere pe rând nou) sunt folosite împreună două caractere: ‘\13’, ‘\10’. Primul (‘\13’) numit CR (carriage return) este folosit pentru întoarcerea la începutul rândului iar al doilea (‘\10’) numit LF (line feed) este folosit pentru avansul la rândul următor.

deplasarea cu o poziţie înapoi însoţită de ştergerea caracterului de pe acea poziţie se face cu ajutorul caracterului ‘\8’ (backspace).

deplasarea înainte cu opt poziţii se realizează cu ajutorul caracterului TAB (‘\9’).

3.4.2 Constante de tip şir de caractere Constantele şir de caractere sunt definite cu ajutorul apostrofurilor. Astfel:

‘abcdEFG’ ‘12345678’ ‘+-.a()’ sunt şiruri de caractere.

Page 31: limbaj de asamblare-ivan

429

Lungimea unui şir de caractere este dată de numărul simbolurilor cuprinse între apostrofuri. Astfel, şirul ‘a+b+c+-d’ are lungimea 8 (caractere), iar şirul ‘x’ are lungimea 1.

Cu ajutorul şirurilor de caractere se iniţializează numeroase câmpuri ale structurilor de date care descriu nume de persoane, denumiri de materiale, adrese de instituţii, numere de telefon (‘Ionescu Gheorghe’, ‘ciment’, ‘+40.1.15.15.00’ sau ‘(40)115.15.15’).

3.4.3 Variabile de tip şir de caractere

Sunt limbaje de programare care lucrează cu tipul de date şir de caractere (string). Alte limbaje însă lucrează cu zone de memorie date prin adresă de început (sau de sfârşit) şi prin lungime. Conţinutul baiţilor este interpretat ca reprezentând simbolurile codului ASCII . Şi limbajul de asamblare defineşte zona de memorie ca şir de baiţi pe care o iniţializează. Astfel, în secvenţa: sir1 db 15 dup(?) sir2 db ‘abcdefghijk’ sir3 db 100 dup(‘a’), ‘b’ sau alocat trei zone de memorie după cum urmează:

prima zonă de memorie are 15 baiţi, nu este iniţializată, iar primul bait este pus în corespondenţă cu identificatorul sir1;

a doua zonă de memorie are atâţia baiţi câte simboluri se află între apostrofurile ce delimitează constanta şir de caractere (11 caractere); primul bait este pus în corespondenţă cu identificatorul sir2;

a treia zonă a lungimea de 101 baiţi, din care primii 100 baiţi au fost iniţializaţi cu caracterul ‘a’, iar ultimul bait a fost iniţializat cu caracterul b; primul bait al zonei este pus în corespondenţă cu identificatorul sir3. Expresiile de referire sir2+5, sir3+100 sau sir3-5 referă baiţii ce conţin caracterele 'f', 'b', respectiv 'g'.

Page 32: limbaj de asamblare-ivan

430

4

MODURI DE ADRESARE 4.1 Calculul adresei unui operand Adresa fizică a unui bait este un număr natural. În funcţie de dimensiunea memoriei care se accesează sunt proiectate dimensiunile registrelor care stochează informaţia de adresă. Există calculatoare care au în structură registre pe patru baiţi numite ‘registre de bază’ care stochează adresa de început a segmentului program, de exemplu. Adunând la această adresă deplasamentul operandului se obţine adresa lui fizică. Numărul reprezentat pe 32 biţi este suficient de mare, şi nu mai necesită artificii de combinare adresă segment cu deplasament. Există tabele care concentrează corelaţia dintre lungimea expresiei de adresă în biţi şi lungimea maximă a zonei ce poate fi accesată. Astfel, cu adrese definite pe 20 biţi se pot accesa baiţii ce formează o memorie de 1 MB. Algoritmul pentru calculul de adresă este format din operaţii de deplasare spre stânga şi din adunări. Valoarea adresei apare ca o agregare a mai multor elemente. Sunt rare cazurile când este impusă proprietatea de dezagregare unică a adresei, în componentele iniţiale. De cele mai multe ori, dezagregarea vizează determinarea deplasării unui operand având ca elemente de calcul adresa fizică şi conţinutul registrului segment, adică adresa de început a segmentului. Procesorul 286 de exemplu, ia în considerare pentru calculul de adresă:

conţinutul registrului segment (16 biţi); deplasarea operandului a cărui adresă se calculează (16 biţi). Prin deplasarea cu patru poziţii spre stânga a conţinutului registrului

segment şi prin adăugarea la noua valoare obţinută a deplasării operandului, se obţine adresa fizică a operandului.

De exemplu, registru segment DS are conţinutul 00F0h, iar deplasarea operandului este 000Ah. Deplasarea spre stânga cu 4 biţi conduce la

00F00h + 000Ah=00F0Ah, acest rezultat reprezintă adresa fizică a operandului considerat.

Page 33: limbaj de asamblare-ivan

431

Expresia de calcul a adresei fizice a unui operand va apare de fiecare dată, indiferent de sistemul de calcul cu care se operează sub forma unei expresii de forma:

EA=x1*y1 + x2*y2 + … + xn*yn unde:

xi – număr întreg, deplasare absolută sau relativă; yi – coeficient de forma 2k, k=0,1,2,…,m; n – numărul termenilor care intervin în calcul; m – are valoarea 15 dacă se lucrează pe 16 biţi respectiv, 31 dacă se lucrează pe 32 biţi. Se consideră memoria unui calculator ca fiind formată din baiţii dispuşi unul după altul, având adresele fizice 0000, 0001, …., FFFFh. Memoria este împărţită în segmente de lungime fixă, lungime care se transformă în raţia unei progresii aritmetice. Termenii progresiei sunt de fapt adresele de început ale segmentelor. Segmentul Si va avea adresa de început a0 + (i-1)*L unde a0 reprezintă adresa primului bait al memoriei, iar L este lungimea segmentului. În cadrul unui segment se consideră un punct fix, a cărui poziţie se stabileşte faţă de începutul segmentului. Se consideră în continuare un alt punct a cărui poziţie este stabilită faţă de cel considerat anterior. Poziţiile astfel obţinute sunt poziţii relative. Aşa cum rezultă din figura 4.1. adresa baitului x se calculează luând în considerare deplasările relative d1, d2, d3, d4 şi deplasarea absolută d0.

d0 Sk d1 d2 d3 d4

0 A B C D x

Figura 4.1 – Calculul adresei unui operand unde: di – distanţe relative, iar d0 este distanţa absolută; Sk – segmentul k din memoria calculatorului; A – adresa de început a segmentului; B, C, D, - baiţii consideraţi repere în segmentul Sk alese de programatori; x – bait oarecare ce se asociază unei variabile. În cazul procesorului 286 adresa se calculează după formula:

EA=c0a0*22 + c1*x1 + c2*x2 + c3*x3

Page 34: limbaj de asamblare-ivan

432

unde: a0 – adresa de început a segmentului, conţinută într-un registru (CS, DS, ES);

ci – coeficientul cu valoare unu dacă tipul de deplasare relativă i este definit (c1 corespunde deplasării ca bază, c2 corespunde deplasării variabile, c3 corespunde deplasării baitului asociat unei variabile); dacă c0=c1=c2=c3=0 adresarea este imediată; xi – deplasarea de tip i; x1 este stocată într-un registru de bază, x2 este stocată într-un registru index, iar deplasarea x3 este pusă în corespondenţă cu numele unei variabile. Expresiile de calcul ale adreselor prin valorile diferite ale coeficienţilor ci determină modurile de adresare. Modul de adresare este definit ca algoritm de calcul al adresei absolute pe bază de distanţe ale operanzilor. Modul de adresare oferă posibilitatea implementării atât a structurilor fundamentale de date (capacitatea de a referi elemente din matrice, linii din matrice sau chiar matricea în ansamblul ei) cât şi a structurilor de programare (structura repetitivă este posibilă numai utilizând registrul de index, iar includerea de structuri repetitive presupune şi utilizarea unui registru de bază). 4.2 Modul de adresare imediată Acest mod de adresare corespunde situaţiei în care programatorul scrie instrucţiuni de forma R-I, M-I, ca de exemplu: mov ax, 1ah add ax, 2 xor bx, 0fa00h mov alfa, 500 Programul asamblor generează codul asociat fiecărei instrucţiuni incluzând lângă baitul (baiţii) de descriere ai codului operaţiei şi ai modului de adresare codul asociat constantei definite în al doilea operand. Astfel, secvenţa:

mov cx, 4 ;b9 0004 sub si, 15 ;83 ee 0f add ax, 100 ;05 0064 mov byte ptr[bx+1], 120 ;c6 47 01 78 mov dx, 16 ;ba 0010

pune în evidenţă faptul că asamblorul generează chiar în corpul codului asociat fiecărei instrucţiuni constanta (al doilea operand) cu care se operează.

Din analiza numărului de cicluri maşină rezultă că adresarea imediată necesită un număr de cicluri mai mare. Constantele se utilizează când trebuie

Page 35: limbaj de asamblare-ivan

433

incrementat sau decrementat un registru cu o raţie mai mare ca 2, când se lucrează cu măşti în instrucţiuni logice şi când se efectuează comparări pentru a defini limite de intervale. Astfel, instrucţiuni ca: mov ax,0 ;iniţializare registru cmp bx,100 ;comparare jl alfa ;salt dacă este la stânga intervalului cmp bx,400 ;comparare jg beta ;salt dacă este la dreapta intervalului and bx,0ff0h ;’şi’ pe biţi registru bx cu masca 0ff0h add si,4 ;incrementare registru si sub di,2 ;decrementare registru di demonstrează utilitatea modului de adresare imediată pentru a nu face exces de definire de variabile în memorie şi de folosire a instrucţiunilor de tip R-M.

Instrucţiunea: mov ax,200 ;iniţializare registru – 4 cicluri poate fi înlocuită prin instrucţiunea: mov ax,val_200 ;14 cicluri unde variabila val_200 a fost definită prin: val_200 dw 200 şi este folosită o singură dată. De regulă nu se definesc zone de memorie pentru a fi folosite o singură dată şi mai ales pentru iniţializări. Este necesară definirea corectă a constantelor, aşa fel încât lungimea operandului destinaţie să fie mai mare sau egală cu lungimea operandului sursă la definire, conversia de lungime este efectuată de programul asamblor. În secvenţa: mov al, 2000 mov ax, 100 mov bx, 4000 lungimea registrului al este de un bait în timp ce constanta 2000 se reprezintă pe 2 baiţi, deci construcţia este eronată. A doua instrucţiune are operandul destinaţie cu lungime de 2 baiţi, iar operandul sursă poate fi generat pe un bait. Programul asamblor va face conversia, generând constanta 0064h. În a treia instrucţiune, lungimile operanzilor coincid. Adresarea imediată se dovedeşte ineficientă atunci când constantele definite ca

Page 36: limbaj de asamblare-ivan

434

operanzi trebuie modificate la dezvoltarea programelor în timp. Acest impediment se elimină prin definirea de constante simbolice. Instrucţiunea: mov ax,100 este echivalentă cu instrucţiunea: mov ax,c_100 unde în prealabil s-a făcut definirea: c_100 equ 100 4.3 Modul de adresare registru Se utilizează atunci când operanzii se află în registre, fiind specific tipurilor de instrucţiuni R-R. La definirea mnemonicelor şi a codurilor de operaţii, când se lucrează în modul R-R instrucţiunile au asociate coduri distincte. Viteza de calcul este superioară, prin numărul de cicluri, de regulă mai mic necesar pentru acest mod de adresare.

Secvenţa: inc ax 40h push ax 50h pop ax 58h xchg cx,ax 91h pune în evidenţă faptul că registrele prefixate sunt cele care influenţează structura codului fiecărei instrucţiuni. Modul de adresare registru conduce la dezvoltarea în procesul de asamblare a codurilor de instrucţiuni pe un bait (dacă registrele sunt prefixate) sau pe doi baiţi pentru combinaţii oarecare de registre. Unele dezvoltări de coduri sunt prezentate în secvenţa: mov cs,ax 8e c8 mov ds,ax 8e d8 mov ss,ax 8e d0 shr ah,cl d2 ec xor ax,ax 33 c0 sub si,cx 2b f1 Se observă că al doilea bait conţine constantele hexazecimale F, D, E, C fapt ce determină ca dezvoltarea biţilor 7, 6 ce corespund codului mm din baitul MODRM (vezi capitolul INSTRUCŢIUNI) să fie 11b, adică biţii 2, 1, 0 vor fi

Page 37: limbaj de asamblare-ivan

435

interpretaţi cod registru. Modul de adresare registru este obligatoriu la iniţializarea registrelor segment (mov reg-seg, AX). Când se efectuează calcule şi rezultate intermediare sunt stocate în alte registre, în final se va obţine un rezultat care va fi în registrul AX. Există operaţii care impun lucrul cu registre, precum XCHG (Exchange), PUSH, POP, INC, însă pot lucra şi cu operanzi definiţi în memorie, dar specificul informaţiilor pe care le vehiculează oferă frecvenţă mai mare structurilor de tip R-R a naturii operanzilor. Deci, în programe, acestea vor apare frecvent având operanzi registre. 4.4 Adresarea directă Este modul de adresare cel mai frecvent întâlnit în programe. Corespunde tipurilor de instrucţiuni R-M, M-R. Un operand este registru, iar al doilea operand ocupă o zonă de memorie, pusă în corespondenţă cu un nume. Adresarea directă presupune instrucţiuni de forma: cod_operaţie operand_1, nume_variabilă sau cod_operaţie nume_variabilă,operand_2. Figura următoare arată cum funcţionează o instrucţiune mov ax, x în care se foloseşte acest mod de adresare: mov ax, x AX 4F 13

1 2

13 4F x Figura 4.2 – La primul pas se face calculul adresei de memorie iar la al doilea pas

conţinutul zonei de memorie este mutat în registrul AX Secvenţa:

mov ax, alfa mov al, byte ptr beta

mov gama, dx add ax, sum sub cx, ratia mov ah, sir+2 add sir-5, al

Page 38: limbaj de asamblare-ivan

436

utilizează operanzii alfa, beta, gama, sum, ratia, şir, direct prin numele lor, ştiind că în procesul de asamblare acestor nume li se asociază deplasări faţă de adresa de început a segmentului unde sunt definite. Dacă s-au definit şi s-au alocat zone pentru operanzii din secvenţa: 00a0 a dw 10

00a2 b dw 20 00a4 c dw 30, 40, 10, 20 00ac e dw ?

referirea lor folosind modul de adresare directă din secvenţa: mov ax, a

add ax, b add ax, c

add ax, c+6 mov e, ax

şi generarea la asamblare arată modul de folosire a deplasărilor 00A0, 00A2, 00A4, 00AC, care deşi puteau fi reprezentate pe un bait sunt expandate pe doi baiţi, lucrul cu deplasări impune operanzi pe 16 biţi. În modelul de calcul al expresiei de adresă c1=c2=0 şi c0=c3=1. Distanţa apare dintr-o singură ‘bucată’. Expresia de adresă este constantă, includerea în structuri repetitive echivalează cu folosirea de invarianţi, dacă se justifică. Secvenţa: mov ax, alfa+10*3-4

ciclu: add ax, beta

loop ciclu exemplifică introducerea unui invariant (beta) în structura repetitivă, justificabil dacă se urmăreşte o înmulţire cu un număr dat a conţinutului zonei beta. 4.5 Adresarea indexată Procesorul este înzestrat cu registrele index SI şi DI. Conţinutul unui registru index este un număr, termen al unei progresii aritmetice, cu semnificaţie de deplasare relativă. Adresarea indexată permite traversarea de structuri de date omogene şi implementarea structurilor repetitive. Registrul index trebuie mai întâi iniţializat, după care conţinutul său se modifică. Dacă traversarea structurii omogene se efectuează de la primul element spre ultimul, iniţializarea registrului index se face cu valoarea zero, iar în timpul traversării i se va adăuga o raţie. Când traversarea se efectuează de la ultimul spre

Page 39: limbaj de asamblare-ivan

437

primul element, conţinutul registrului index va conţine o deplasare care în expresia de adresă, va accesa ultimul element, după care din registrul index se scade o raţie. Registrul index poate fi utilizat pentru controlul numărului de repetări. Forma expresiei de adresare cu registrului de index este: cod_operaţie reg, deplasare [registru_index] cod_operaţie reg, deplasare+registru_index cod_operaţie registru_index deplasare reg. Următoarea secvenţă evidenţiază modul de calcul al adresei pentru adresarea indexată: mov cx,20 xor si,si xor ax,ax ciclu:

add ax, x[si] inc si inc si loop ciclu În această secvenţa de program este pusă în evidenţă iniţializarea registrului SI, utilizarea sa pentru referirea unui element al masivului unidimensional x şi adăugarea unei raţii.

Secvenţa: xor si,si xor ax,ax ciclu:

add ax,a[si] inc si inc si cmp si,2*(20-1) jle ciclu arată cum registrul index este folosit ca registru de control pentru gestionarea ciclurilor. În expresie poate lipsi deplasarea, caz în care registrul index trebuie pregătit ca să refere corect elementele, încărcând adresa primului element ce se accesează. Secvenţa: mov cx,20 lea si,x xor ax,ax ciclu:

add ax,[si] add si, 2 loop ciclu

Page 40: limbaj de asamblare-ivan

438

utilizează o expresie de referire a elementului unui masiv unidimensional fără termenul deplasare, acesta fiind inclus deja în registrul si de index. 4.6 Adresarea bazată În cazul structurilor complexe referirea părţilor şi a elementelor este realizată diferenţiat. O variabilă de tip articol corespunde unei structuri cu două nivele. La primul nivel se află întregul, variabila în totalitatea ei, iar la nivelul al doilea sunt membrii ei. În cazul masivelor bidimensionale, reprezentarea arborescentă conduce la o structură cu trei nivele. Pe primul nivel se află matricea ca un întreg, cu toate componentele sale şi se referă ca zonă de memorie nedivizată. La nivelul al doilea se află liniile matricei, iar pe al treilea nivel se află elementele acesteia. Baitul care delimitează începutul zonei de memorie a unei structuri compuse este considerat bait de bază. Referirile membrilor se efectuează în raport cu poziţia acestuia. Registrele BX şi BP se utilizează pentru a memora adresele baiţilor de bază. Aceste registre se numesc din această cauză registre de bază. Dacă registrul de bază utilizat este BX, registrul de segment pentru evaluarea expresiei de adresă va fi DS. Prin utilizarea registrului de bază BP, registrul de segment este SS, deci se va lucra pe stivă. Ca şi în cazul adresării indexate, registrul de bază ce se iniţializează, este utilizat pentru referire şi este incrementat sau decrementat sau se utilizează deplasări în mod adecvat, care la evaluarea expresiei de adresă vor referi corect membrul cerut din structura compusă. mov AX, [BX]+2 1 BX 00 04 AX FC 4A 2 4 3 4A FC 01 02 03 04 05 06 07 08 09 0A 0B 0C

Figura 4.3 – Calculul adresei în cazul adresării bazate

Secvenţa:

mov bp, offset alfa mov ax, bp + 2 add ax, bp + 8 add ax, bp + 144

Page 41: limbaj de asamblare-ivan

439

exemplifică referire de membri cu poziţii neregulate în structură. Dacă structura compusă nu are un grad de omogenitate scăzut, registrul de bază, împreună cu deplasarea referă elementele ca în expresii de forma: mov ax, beta[bp + 52] mov bx, gama + bp mov cx, delta + bx + 54 4.7 Adresarea bazată şi indexată Structurile de date definite pe mai multe nivele necesită expresii complexe pentru referirea elementelor. Astfel, pentru referirea unui element dintr-o matrice este necesară poziţionarea pe linia matricei şi apoi în cadrul liniei, pe element. Formula de referire a elementului aij al unei matrice cu m linii şi n coloane este: adresa(aij) = adresa(a00) + (i*n + j)*lungime_element.

Adresa primului element este reper fix, iar adresa de început a liniei i-1 este adresă de bază pentru referirea unui element. În formulă apar două progresii aritmetice, una referă linia, a doua referă elementul în cadrul liniei. Expresia de adresă creşte în complexitate. Mai mult, în timp ce un registru operand în expresia de adresă are conţinut crescător, celălalt poate avea conţinut crescător sau descrescător. Traversarea structurilor complexe se va efectua în direcţii compuse, ceea ce permite realizarea unor algoritmi de referire cu grad de generalitate ridicat.

Expresiile de adresă au una din structurile: bp + di bx + si bp [di] bx [si] reg_bază + reg_index + deplasare deplasare reg_bază + reg_index deplasare [reg_bază] [reg_index].

Secvenţa: xor bx, bx xor si, si xor ax, ax mov cx, nr_linii ciclul_1: push cx mov cx, nr_coloane ciclul_2 add ax, x [bx] [si] add si, 2

Page 42: limbaj de asamblare-ivan

440

loop ciclul_2 xor si, si pop cx mov dx, nr_coloane shl dx,2 add bx, dx loop ciclul_1 exemplifică accesarea elementelor unei structuri cu trei nivele şi utilizează registrul de bază BX, pentru accesarea liniilor (elementele de pe al doilea nivel) şi registrul index SI pentru accesarea membrilor de pe ultimul nivel. 4.8 Adresarea indirectă Utilizarea variabilelor pointer presupune implementarea modului de adresare indirectă. Adresarea indirectă presupune ca un registru sau o zonă de memorie să conţină adresa (deplasamentul) unei variabile. De asemenea, adresarea indirectă presupune existenţa unui operator care să realizeze extragerea conţinutului din acea zonă de memorie şi să-l interpreteze ca deplasament. mov AX, [BX]

1

BX 00 02 AX E5 26 2 3 26 E5 00 01 02 03 04

Figura 4.4 – Calculul adresei în cazul adresării indirecte

Dacă se consideră secvenţa: mov si, offset x xor bx, bx xor ax, ax add ax, [bx + si] aceasta conţine evaluarea de adresă BX + SI care înseamnă: conţinutul lui BX se adună cu conţinutul registrului SI şi rezultă adresa operandului al cărui conţinut este adunat la registrul acumulator AX, deci registrul SI conţine un deplasament care localizează operandului care e folosit la adunare. Sunt limbaje de asamblare în care modul de adresare indirect este implementat, în acest scop utilizându-se un operator. Se presupune că este definit

Page 43: limbaj de asamblare-ivan

441

limbajul de asamblare XX, iar operatorul de indirectare este *. În această situaţie se impune restructurarea formei interne a instrucţiunii ca să accepte descrierea distinctă pentru adresarea directă, şi respectiv, pentru adresarea indirectă (un bit cu valoare zero – adresare directă, unu – adresare indirectă). Operatorul * este utilizat şi pentru construirea de secvenţe ce corespund indirectării pe mai multe nivele. Astfel secvenţa: mov ax, offset a mov adr_a, ax mov ax, offset adr_a mov adr_adr_a, ax mov ax, offset adr_adr_a mov adr_adr_a, ax mov bx, *** adr_adr_adr_a pregăteşte mai întâi variabilele pointer. Variabila pointer adr_a va conţine adresa operandului a. Variabila adr_adr_a va conţine adresa variabilei pointer adr_a. Variabila adr_adr_adr_a conţine adresa variabilei pointer spre pointer spre variabila a. Ultima instrucţiune din secvenţă are ca efect încărcarea în registrul BX a conţinutului zonei de memorie (a) a cărei adresă se află într-o zonă de memorie (adr_a), a cărei adresă la rândul ei se află într-o zonă de memorie (adr_adr_a), a cărei adresă se află în zona de memorie adr_adr_adr_a. Cele trei asteriscuri pun în evidenţă că este vorba de o adresare indirectă cu trei nivele de indirectare. Deci în structura instrucţiunii, dacă este adresare indirectă, al localizarea operandului se va specifica dacă acesta este sau nu pointer, pentru a continua sau a întrerupe procesul de referire. Adresarea indirectă poate fi simplă, adresare indirectă indexată şi adresare indirectă bazată. Toate aceste mecanisme de indirectare pot fi implementate în limbajele evoluate şi cu setul de facilităţi de evaluare a expresiilor de adresă existent acum la microprocesorul 80x86. Expresiile de evaluare a adreselor pot fi dezvoltate mult în sensul specificării momentului în care intervin registrele index sau de bază. Astfel, se poate proiecta un limbaj de asamblare în care expresia: ** alfa bp si să fie interpretată astfel:

alfa – variabilă pointer spre pointer, conţine adresa unei zone ce conţine la rândul ei adresa unei zone de memorie;

BP – registrul de bază care conţine adresa unei zone de memorie, zonă ce conţine o deplasare relativă;

SI – registrul de index, ce conţine o deplasare faţă de deplasarea relativă identificată în zona de memorie localizată prin registrul BP.

Page 44: limbaj de asamblare-ivan

442

Mai întâi folosind variabila alfa (acum alfa nu este o deplasare) se găseşte variabila ce conţine o adresă. La această adresă se adaugă conţinutul zonei de memorie a cărei adresă s-a aflat în registrul BP, obţinându-se o nouă adresă. Este adresa unei zone de memorie ce conţine o adresă (al doilea nivel de indirectare). La această adresă se adaugă conţinutul registrului index şi rezultă adresa operandului cu care se va lucra de fapt. Fiecare mod de adresare este însoţit de un număr de cicluri maşină. Alegerea unui anumit mod este dictată de structurile de date cu care se operează. Expresia de adresă, ca sumă de un număr de termeni fixat se particularizează funcţie de codul specificat pe biţii 2, 1, 0 ai baitului MODRM (v. capitolul Instrucţiuni). Modurile de adresare trebuie privite ca algoritmi de calcul ai expresiilor de adresă. Operanzii sunt registre sau numere, în final se obţin adrese ale unor zone de memorie al căror conţinut este întrebuinţat fie pentru a iniţializa, fie pentru a modifica (prin adunare, scădere, deplasare, interschimb, conversie) un alt operand, registru. Indiferent care este procesorul cu care se lucrează, programatorul în limbajul de asamblare va cunoaşte restricţiile de utilizare registre şi structurile posibile ale expresiilor de adresă. Folosirea adecvată a unui mod de adresare se pune în vedere prin lungimea textului scris şi prin mulţimea de instrucţiuni ce pot fi înlocuite dacă s-ar folosi un alt mod de adresare, mod care s-ar dovedi mai eficient.

Page 45: limbaj de asamblare-ivan

443

5

INDICATORII DE CONDIŢIE 5.1 Registrul FLAGS

Procesoarele din familia x86 dispun de o serie de indicatori de condiţie. Aceştia reprezintă informaţii legate de starea procesorului şi efectul execuţiei instrucţiunilor. Fiecare indicator de condiţie ocupă 1 bit în cadrul unui registru special, registrul F. Pentru microprocesorul 8086, structura acestui registru este cea din figura 5.1.

15 14 13 12 11 10 09 08 07 06 05 04 03 02 01 00 -- -- -- -- OF DF IF TF SF ZF -- AF -- PF -- CF

Figura 5.1 – Registrul de indicatori de condiţie al procesorului 8086.

Semnificaţia indicatorilor de condiţie este următoarea: CF (Carry Flag, indicator de transport) semnifică un transport sau un

împrumut în, respectiv din bitul cel mai semnificativ al rezultatului. PF (Parity Flag, indicator de paritate) este poziţionat după cum biţii din

cel mai puţin semnificativ octet al rezultatului sunt în număr par (poziţionare pe 1) sau impar (poziţionare pe 0).

AF (Adjust Flag, indicator de ajustare) este folosit în aritmetica zecimală şi semnifică un transport sau un împrumut în, respectiv din bitul 4 (cel mai semnificativ bit al jumătăţii celei mai puţin semnificative) al rezultatului.

ZF (Zero Flag, indicator de zero) indică dacă rezultatul unei operaţii a fost sau nu zero.

SF (Sign Flag, indicator de semn) are aceeaşi valoare ca bitul cel mai semnificativ al rezultatului (bitul de semn): 0 - pozitiv, 1 - negativ.

TF (Trap Flag, indicator de urmărire a execuţiei) este folosit la depanarea programelor prin execuţia lor pas cu pas - dacă este setat, procesorul forţează automat o excepţie după execuţia fiecărei instrucţiuni.

Page 46: limbaj de asamblare-ivan

444

IF (Interrupt Flag, indicator de întreruperi) precizează dacă procesorul ia în considerare sau nu întreruperile externe.

DF (Direction Flag, indicator de direcţie) precizează sensul (0 - crescător sau 1 - descrescător) în care este modificat contorul de adrese la operaţiile cu şiruri.

OF (Overflow Flag, indicator de depăşire) semnifică depăşirea domeniului admisibil la reprezentarea rezultatului unei operaţii aritmetice cu sau fără semn. Practic, este poziţionat pe 1 dacă apare un transport înspre bitul cel mai semnificativ al rezultatului din bitul vecin, dar nu şi din bitul cel mai semnificativ spre CF sau invers, dinspre bitul cel mai semnificativ spre CF, dar nu şi spre bitul cel mai semnificativ din bitul vecin. Similar, la împrumut, este poziţionat pe 1 dacă apare un transport de la bitul cel mai semnificativ la bitul vecin, dar nu şi înspre bitul cel mai semnificativ dinspre CF sau invers, dinspre CF spre b.c.m.s., dar nu şi dinspre bitul cel mai semnificativ spre bitul vecin.

Indicatorii CF, PF, AF, SF, ZF, TF şi OF se mai numesc indicatori de stare;

DF este indicator de control, iar IF indicator de sistem. Odată cu evoluţia procesoarelor din gama x86, au fost introduşi noi

indicatori. Astfel, la procesoarele 80386, registrul FLAGS este extins la 32 de biţi, formând noul registru EFLAGS, a cărui structură este reprezentată în figura 5.2.

<- Registrul FLAGS pe 16 biţi -> 31-18 17 16 15 14 13 12 11 10 09 08 07 06 05 04 03 02 01 00

0 VM RF 0 NT IO PL OF DF IF TF SF ZF 0 AF 0 PF 1 CF

Figura 5.2 – Registrul de indicatori de condiţie al procesorului 80386

Noii indicatori au următoarele semnificaţii: IOPL (Input-Output Privilege Level, nivel de prioritate al operaţiei de

intrare-ieşire) indică nivelul de prioritate al operaţiei de intrare-ieşire. NT (Nested Task, procese imbricate) este folosit pentru controlul

înlănţuirii proceselor imbricate, influenţând operaţiile executate de instrucţiunea IRET.

RF (Resume Flag, indicator de reluare) este folosit pentru a inhiba temporar excepţiile de depanare, permiţând reluarea execuţiei unei instrucţiuni fără a genera imediat o nouă excepţie.

VM (Virtual 8086 Mode, mod virtual 8086) indică execuţia unui proces în modul virtual 8086.

Toţi indicatorii adăugaţi sunt indicatori de sistem. Biţii pe care apar valorile 0 sau 1 sunt rezervaţi de către producător şi nu trebuiesc folosiţi.

Page 47: limbaj de asamblare-ivan

445

Pentru relaţia cu coprocesorul matematic există de asemenea o altă serie de indicatori, grupaţi în registrul de control CR0. Structura acestuia este reprezentată în figura 5.3.

Semnificaţia indicatorilor este următoarea: PE (Protection Enabled, protecţie activată) forţează execuţia

instrucţiunilor în mod real (0) sau protejat (1) de adresare. MP (Math Present, coprocesor prezent) controlează execuţia

instrucţiunii WAIT, folosită pentru coordonarea coprocesorului matematic.

EM (Emulation, emulare) indică dacă trebuie sau nu emulat coprocesorul matematic.

TS (Task Switch, schimbare de proces) este setat de procesor la fiecare schimbare de proces şi testat la interpretarea instrucţiunilor pentru coprocesor.

ET (Extension Type, tip coprocesor) indică tipul de coprocesor prezent în sistem (80287 sau 80387).

PG (PaGing, paginare) indică faptul că procesorul foloseşte tabele de pagini pentru translatarea adreselor liniare în adrese fizice.

31 30-05 04 03 02 01 00 PG -- ET TS EM MP PE

Figura 5.3 – Registrul de control CR0.

Acest registru este accesibil pentru citire şi modificare doar prin intermediul

instrucţiunii MOV, deci prin transferul conţinutului său într-un sau dintr-un registru de uz general. 5.2 Operaţii cu indicatorii de condiţie

Programatorii folosesc mai multe categorii de operaţii cu indicatorii de condiţie:

operaţii de setare / resetare a unor indicatori înaintea unor instrucţiuni cu scopul de a afecta modul de execuţie a acestora.

operaţii de setare / resetare a indicatorului CF înaintea unor instrucţiuni de rotaţie, adunare cu transport sau scădere cu împrumut, pentru a obţine anumite rezultate.

operaţii de evaluare a stării unor indicatori pentru a trage concluzii asupra rezultatelor instrucţiunilor anterioare.

operaţii de salvare / restaurare a registrului de indicatori, cu scopul conservării / modificării stării reflectate de indicatori.

Page 48: limbaj de asamblare-ivan

446

Instrucţiunile de control al indicatorilor sunt următoarele: STC (SeT Carry flag, setează indicatorul de transport) are ca efect CF=1. CLC (CLear Carry flag, resetează indicatorul de transport) are ca efect

CF=0. CMC (CoMplement Carry flag, complementează indicatorul de transport)

are ca efect CF= NOT CF. STD (SeT Direction flag, setează indicatorul de direcţie) are ca efect DF=1

(contorul de adrese va fi decrementat în operaţiile cu şiruri). CLD (CLear Direction flag, resetează indicatorul de direcţie) are ca efect

DF=0 (contorul de adrese va fi incrementat în operaţiile cu şiruri). STI (SeT Interrupt flag, setează indicatorul de întreruperi) are ca efect

IF=1. CLI (CLear Interrupt flag, resetează indicatorul de întreruperi) are ca efect

IF=0. LAHF (Load AH from Flags, încarcă AH din registrul de indicatori) are ca

efect transferul indicatorilor SF, ZF, AF, PF, CF în biţii 7,6,4,2, respectiv 0 ai registrului AH.

SAHF (Store AH into Flags, încarcă registrul de indicatori din AH) are ca efect transferul biţilor 7,6,4,2 şi 0 ai registrului AH în indicatorii SF, ZF, AF, PF, respectiv CF.

PUSHF şi POPF stochează, respectiv refac registrul de indicatori în, respectiv din vârful stivei. Aceste două operaţii sunt folosite atât pentru conservarea stării procesorului prin salvare, cât şi pentru examinarea acesteia. Variantele PUSHFD şi POPFD lucrează pe 32 de biţi, dar nu afectează indicatorii RF şi VM, aflaţi în cuvântul mai semnificativ al registrului extins de indicatori.

5.3 Poziţionarea indicatorilor de condiţie la execuţie

Pentru lucrul individual cu ceilalţi indicatori nu există instrucţiuni specifice. Rezultatele dorite se pot obţine însă prin executarea unor secvenţe de operaţii.

De exemplu, dacă se doreşte setarea indicatorului ZF, se execută o operaţie care să producă un rezultat nul şi să nu afecteze regiştrii cu care se lucrează în acel moment, ca de exemplu XOR CL,CL.

5.4 Interacţiunea indicatori de condiţie - instrucţiuni

Pentru o programare corectă şi o folosire eficientă a efectelor interacţiunii indicatori de condiţie - instrucţiuni este necesară cunoaşterea efectelor pe care le au unele asupra celorlalte.

Page 49: limbaj de asamblare-ivan

447

Astfel, indicatorii de sistem au efect asupra modului de execuţie a instrucţiunilor, iar indicatorii de stare influenţează operaţiile matematice şi salturile şi apelurile condiţionate.

Interacţiunea indicatori de stare - instrucţiuni este prezentată în tabelul 5.1.

Tabelul 5.1 Indicatorul

Instrucţiunea OF SF ZF AF PF

CF AAA -- -- -- TM -- M AAS -- -- -- TM -- M AAD -- M M -- M -- AAM -- M M -- M -- DAA -- M M TM M TM DAS -- M M TM M TM ADC M M M M M TM ADD M M M M M M SBB M M M M M TM SUB M M M M M M CMP M M M M M M CMPS M M M M M M SCAS M M M M M M NEG M M M M M M DEC M M M M M INC M M M M M IMUL M -- -- -- -- M MUL M -- -- -- -- M RCL/RCR 1 M TM RCL/RCR cont -- TM ROL/ROR 1 M M ROL/ROR cont -- M SAL/SAR/SHL/SH M M M -- M M SAL/SAR/SHL/SH -- M M -- M M SHLD/SHRD -- M M -- M M BSF/BSR -- -- M -- -- -- BT/BTS/BTR/BTC -- -- -- -- -- M AND 0 M M -- M 0 OR 0 M M -- M 0 TEST 0 M M -- M 0 XOR 0 M M -- M 0

Semnificaţia simbolurilor este următoarea: M - instrucţiunea modifică indicatorul.

Page 50: limbaj de asamblare-ivan

448

T - instrucţiunea testează indicatorul. -- - efectul instrucţiunii asupra indicatorului este nedefinit. 0 - instrucţiunea resetează indicatorul. Spaţiu - instrucţiunea nu afectează indicatorul. Pentru o listă completă, care include atât indicatorii de sistem, cât şi alte

instrucţiuni decât cele aritmetice, de rotire şi salt, se poate consulta tabelul 5.2.

Tabelul 5.2 Indicatorul Instrucţiunea OF SF ZF AF PF CF TF IF DF NT RF

AAA -- -- -- TM -- M AAD -- M M -- M -- AAM -- M M -- M -- AAS -- -- -- TM -- M ADC M M M M M TM ADD M M M M M M AND 0 M M -- M 0 ARPL M BOUND BSF/BSR -- -- M -- -- -- BT/BTS/BTR/BTC

-- -- -- -- -- M CALL CBW CLC 0 CLD 0 CLI 0 CLTS CMC M CMP M M M M M M CMPS M M M M M M T CWD DAA -- M M TM M TM DAS -- M M TM M TM DEC M M M M M DIV -- -- -- -- -- -- ENTER ESC HLT IDIV -- -- -- -- -- -- IMUL M -- -- -- -- M IN INC M M M M M

Page 51: limbaj de asamblare-ivan

449

INS T INT 0 0 INTO T 0 0 Indicatorul Instrucţiunea OF SF ZF AF PF CF TF IF DF NT RF

IRET R R R R R R R R R T Jcond T T T T T JCXZ JMP LAHF LAR M LDS/LES/LSS/LFS/LGS

LEA LEAVE LGDT/LIDT/LLDT/LMSW

LOCK LODS T LOOP LOOPE/LOOPNE

T LSL M LTR MOV MOV control, debug

-- -- -- -- -- -- MOVS T MOVSX/MOVZX

MUL M -- -- -- -- M NEG M M M M M M NOP NOT OR 0 M M -- M 0 OUT OUTS T POP/POPA POPF R R R R R R R R R R PUSH/PUSHA/PUSHF

RCL/RCR 1 M TM RCL/RCR count

-- TM REP/REPE/REPNE

RET ROL/ROR 1 M M ROL/ROR count

-- M SAHF R R R R R

Page 52: limbaj de asamblare-ivan

450

SAL/SAR/SHL/SHR 1

M M M -- M M SAL/SAR/SHL/SHR count

-- M M -- M M SBB M M M M M TM SCAS M M M M M M T Indicatorul Instrucţiunea OF SF ZF AF PF CF TF IF DF NT RF

SET cond T T T T T SGDT/SIDT/SLDT/SMSW

SHLD/SHRD -- M M -- M M STC 1 STD 1 STI 1 STOS T STR SUB M M M M M M TEST 0 M M -- M 0 VERR/VERRW

M WAIT XCHG XLAT XOR 0 M M -- M 0

Semnificaţia simbolurilor este următoarea: M - instrucţiunea modifică indicatorul. T - instrucţiunea testează indicatorul. R - instrucţiunea reface valoarea anterioară a indicatorului. -- - efectul instrucţiunii asupra indicatorului este nedefinit. 0 - instrucţiunea resetează indicatorul. 1 - instrucţiunea setează indicatorul. Spaţiu - instrucţiunea nu afectează indicatorul. La execuţia instrucţiunilor de salt condiţionat se evaluează expresii

relaţionale logice ce conţin indicatorii ca operanzi. Dacă rezultatul evaluării este 0 nu se execută saltul, iar dacă este 1 se execută saltul.

Page 53: limbaj de asamblare-ivan

451

6

INSTRUCŢIUNI 6.1 Clasificarea instrucţiunilor Există numeroase criterii de clasificare a instrucţiunilor, care nu se exclud. Aceeaşi instrucţiune este identificată ca aparţinând unei grupe cel puţin din fiecare împărţire a oricărui criteriu.

După numărul operanzilor instrucţiunile limbajului de asamblare se grupează în:

instrucţiuni fără operanzi; instrucţiuni cu un operand; instrucţiuni cu doi operanzi; instrucţiuni cu mai mulţi operanzi. În mod uzual, instrucţiunile sunt proiectate cu un număr redus de operanzi,

întrucât creşterea numărului de operanzi atrage mărirea gradului de complexitate al instrucţiunii. În limbajul de asamblare o instrucţiune execută o operaţie elementară, cu nivel de complexitate redus.

Natura operanzilor grupează instrucţiunile în: instrucţiuni de tip R-R, operanzii sunt registre; instrucţiuni de tip R-M, un operand este registru, destinaţia, iar

operandul sursă, este în memorie; instrucţiuni de tip M-R, operandul destinaţie se află în memorie,

operandul sursă este un registru; instrucţiuni de tip R-I, operandul sursă este o constantă imediată, iar

operandul destinaţie este registru; instrucţiuni de tip M-I, sursa este constantă imediată, iar destinaţia este

zonă de memorie; instrucţiuni de tip M-M, operanzii se află în memorie. Limbajele de asamblare se proiectează cu structuri de instrucţiuni care să

gestioneze numai o zonă de memorie, deci instrucţiunile de tip M-M sunt rar întâlnite sau excluse.

Lungimea zonei de memorie ocupată clasifică instrucţiunile astfel: instrucţiuni pe un bait, în care operanzii au poziţie fixată, efectul este

unic; instrucţiuni definite pe doi baiţi, de regulă au ca operanzi registre;

Page 54: limbaj de asamblare-ivan

452

instrucţiuni cu trei baiţi, în care un operand este un registru, iar celălalt poate fi definit ca valoare sau ca deplasare pe un bait;

instrucţiuni pe patru baiţi, un operand este un registru, iar celălalt este definit ca valoare sau deplasare pe doi baiţi.

Se pot defini instrucţiuni şi pe un număr superior de baiţi, de fiecare dată alocarea fiind determinată de natura operanzilor din expresiile de evaluare a adreselor sursei / destinaţiei.

În raport cu modul în care sunt prelucrate de către asamblor, instrucţiunile sunt:

instrucţiuni executabile, cărora asamblorul le dezvoltă un cod pe unul, doi, sau mai mulţi baiţi, la execuţia programului efectuează transformări de conţinut asupra registrelor sau zonelor de memorie, selectează alte instrucţiuni sau testează indicatorii de condiţie;

instrucţiuni neexecutabile sau directive destinate alocării de memorie, definirii de constante, gestionării contorului de locaţii, delimitării de segmente, de proceduri, de macrodefiniţii, punerii în corespondenţa a constantelor cu nume simbolice, a registrelor cu numele de segment, stabileşte tipul unei etichete, alinieri pentru adrese.

Instrucţiunile executabile sunt foarte diverse, puterea limbajului de asamblare fiind asigurată de ortogonalitatea lor. Instrucţiunile neexecutabile contribuie la implementarea tuturor structurilor de date, la realizarea tuturor combinaţiilor de structuri de programe şi la gestionarea riguroasă a resurselor (registre şi memorie).

Instrucţiunile executabile se clasifică după tipul operaţiei în: instrucţiuni de transfer de date sau adrese numite surse, spre registre sau

zone de memorie, numite destinaţie; instrucţiuni aritmetice, pentru adunări, înmulţiri, scăderi, împărţiri şi

pentru ajustări; instrucţiuni de manipulare la nivel de biţi, efectuează deplasări, rotiri,

operaţii logice pe biţi; instrucţiuni de manipulare a şirurilor de caractere efectuează copierea a

două zone de memorie, compararea de şiruri, încărcare şi memorare de caractere sau cuvinte cu sau fără testare;

instrucţiuni de control destinate apelului şi revenirii din procedură, salturi condiţionate şi salt necondiţionat, repetări secvenţă sub controlul unui registru;

instrucţiuni de poziţionare a indicatorilor de condiţie. După numărul ciclurilor maşină instrucţiunile se grupează în: instrucţiuni cu număr fix de cicluri maşină din care fac parte

instrucţiunile în care operanzii fie lipsesc, fie sunt numai registre;

Page 55: limbaj de asamblare-ivan

453

instrucţiuni cu număr variabil de cicluri maşină; pentru fiecare instrucţiune este dat numărul de cicluri specific la care se adaugă ciclurile necesare evaluării şi localizării adresei operandului.

6.2 Descrierea instrucţiunilor

Programatorul trebuie să cunoască exact semnificaţia instrucţiunilor pe care le utilizează şi mai ales efectele secundare, concretizate prin poziţionarea indicatorilor de condiţie. Prezentarea instrucţiunilor prin intermediul unui limbaj de programare evoluat are avantajul ordonării clare a operaţiilor elementare în totalitatea lor.

De exemplu, instrucţiunea SAL (Shift Arithmetic Left) este descrisă cu instrucţiunile limbajului C astfel: temp=CONTOR; while(temp){ CF=High_Bit(Operand); Operand*=2; temp--; if(CONTOR= =1) if(High_Bit(Operand)!=CF)

OF=1; else OF=0; else OF=CF; } unde:

temp - variabilă de lucru; CONTOR - indică numărul de poziţii cu care se face deplasarea spre

stânga; pentru valori diferite de 1, stocarea se va efectua în registrul CL;

CF, OF - indicatori de condiţie; High_Bit - funcţie care returnează valoarea bitului cel mai

semnificativ al operandului. Această instrucţiune are efectul înmulţirii operandului cu 2 la puterea CONTOR. Reprezentarea sub formă grafică a mecanismului unei instrucţiuni este sugestiv, având însă pierderi de informaţii de detaliu asupra modului în care decurge realizarea operaţiei. Pentru instrucţiunea shl operand, contor în care Operand este registrul AL, reprezentarea grafică este dată în figura 6.1.

Page 56: limbaj de asamblare-ivan

454

7 0

C 0 CF AL

Figura 6.1. Deplasare spre stânga

Dacă registrul CL asociat variabilei CONTOR conform restricţiilor de

implementare ale instrucţiunii conţine valoarea 3, după efectuarea deplasării spre stânga, registrul AL va conţine 01001000. Reprezentarea tabelară a instrucţiunilor ia în considerare variantele ce pot fi utilizate de programator şi numărul de cicluri necesar. O prezentare generalizată ţine seama şi de tipul de microprocesor utilizat, pentru că diferenţele pentru fiecare se impun a fi specificate. Astfel, pentru instrucţiunea SHL se va alcătui tabelul 6.1.

Tabelul 6.1. Număr cicluri maşină Mod adresare Exemplu 286 386 486

Reg,1 SHL bx,1 2 3 3 Mem,1 SHL alfa,1 7 7 4 Reg,CL SHL ax,cl 5+n 3 3 Mem,CL SHL alfa,cl 8+n 7 4 Reg,imediat8 SHL bx,5 5+n 3 2 Mem,imediat8 SHL alfa,5 8+n 7 4

În tabelul 6.1. s-au utilizat notaţii cu semnificaţiile: Reg - registru de lucru; Mem - variabilă definită în memorie; CL - registrul CL; N - numărul de poziţii cu care se face deplasarea. Pentru descrierea instrucţiunilor se construiesc limbaje care să aibă capacitatea de a reda exact semnificaţia operaţiei asociate fiecărui mnemonic. Un registru este format din biţii b0, b1, b2, …, b15. Instrucţiunea de deplasare shl bx,1 este descrisă folosind ecuaţii recurente astfel: CF=b15 bi+1=bi b0=0 pentru i=0, 1, 2, …, 14. Dacă apar unele condiţionări şi neregularităţi de comportament pe mulţimea biţilor sau se lucrează cu anumite grupuri de biţi, prezentarea devine mai dificil de urmărit. În plus, pentru a marca deosebirea biţilor dintr-un registru de biţii dintr-o

Page 57: limbaj de asamblare-ivan

455

zonă de memorie se vor face notaţii adecvate. Restricţiile noului limbaj, de cele mai multe ori nu se regăsesc în descrierile altor limbaje de asamblare pentru a uşura învăţarea sau compararea lor. Se va putea vorbi de o descriere bună a instrucţiunilor dacă:

este prezentată mnemonica şi semnificaţia fiecărei litere; printr-un text se specifică operaţia pe care o execută fiecare instrucţiune; se detaliază fiecare formă de adresare a operanzilor admisă şi se

exemplifică efectele pe care execuţia instrucţiunilor le are asupra operanzilor;

se specifică numărul de cicluri maşină necesare execuţiei fiecărei variante de definire a expresiei de adresare a operanzilor;

se dau reprezentările la nivel de bit ale dispunerii informaţiilor din linia sursă, după asamblare;

se precizează poziţionarea indicatorilor de condiţie. Manualele care descriu limbaje de asamblare îmbină toate modurile de prezentare pentru a asigura atât rigurozitate, cât şi de a face limbajul de asamblare mai atractiv, mai uşor de învăţat şi de a utiliza cât mai potrivit instrucţiunile. 6.3 Forma externă a instrucţiunilor Programul scris în limbaj de asamblare este format dintr-o succesiune de instrucţiuni. Ceea ce scrie programatorul sunt forme concrete de prezentare a instrucţiunilor sau forme externe.

O linie sursă conţine: eticheta instrucţiunii (opţional); codul operaţiei; lista de operanzi; comentariu (opţional).

Regulile de construire a etichetelor sunt comune şi pentru identificatori. Lungimea cuvântului asociat etichetei este strict legată de dorinţa programatorului de a da lizibilitate programului. Dacă se construiesc etichete cu o destinaţie specială, reutilizate într-un context anume, este folosit caracterul blanc subliniat (underscore). Astfel, construcţiile __alfa__ , _beta, gama _ sunt etichete din categoria menţionată. În programe se întâlnesc variabile globale definite în fişierele unor produse program de largă circulaţie, în expresii de adresă. În mod curent se definesc etichete precum: ciclu, start, final, adună, alege. Pentru a mări lizibilitatea se fac concatenări de nume şi delimitarea este obţinută prin utilizarea de litere mari. Astfel, etichetele AlegeMaxim, PretUnitar, ExtrageBait, SirCifric, utilizate corespunzător împreună cu comentariile de pe fiecare linie ajută programatorul la depanarea sau la dezvoltarea programului.

Page 58: limbaj de asamblare-ivan

456

Codul operaţiei este de fapt o mnemonică. Programatorul care a lucrat într-un limbaj de asamblare va observa că există diferenţe minore la alcătuirea mnemonicelor. De aceea, cunoscând un vocabular al operaţiilor fundamentale, generarea de mnemonice devine un proces natural.

În tabelul 6.2 sunt prezentate câteva din mnemonicele utilizate în timp pentru operaţiile cele mai frecvente din programe.

Tabelul 6.2. Mnemonicele limbajelor

Operaţia ASSEMBLER-IBM ASSIRIS MACRO11 ASM

Addition A,AR,AD AD4,AD8 ADD ADD Move MVC,MVI MVZ MVSR,MVSL MOV MOV Jump B BRU BR JMP Compare C,CD,CDR,CE CP4,CP2 CMP CMP Shift SLA,SRA SLAx,SRAx ASL,ASR SAL,SAR Increment IC2,IC4 INC INC

Construirea mnemonicelor fără a include lungimi ale zonelor de memorie deplasează informaţiile spre cei doi operanzi; operandul cu lungimea cea mai mică dacă este receptor impune lungimea sursei. Dacă operandul sursă are o lungime mai mică se va produce o preluare din zone învecinate, cu alterarea rezultatelor. În cazul limbajelor cu o diversitate restrânsă de tipuri de operanzi mnemonicele nu conţin date despre lungimile operanzilor. Lista de operanzi este de fapt o listă de expresii cu care se referă operanzii. Faptul că registrele generale au asociate numele AX, BX, CX, DX sau AH, AL, BH, BL, CH, CL înseamnă că adrese anumite sunt puse în corespondenţă cu aceste nume. Şi în cazul referirii registrelor este vorba de a evalua o expresie - expresia cu un singur termen, numele asociat registrului. Complexitatea expresiilor de evaluat pentru a obţine adresa unui operand diferă în funcţie de multitudinea şi diversitatea operatorilor care pot fi utilizaţi. Indiferent care este nivelul de complexitate al expresiilor, condiţia impusă este ca evaluarea să se efectueze în procesul de asamblare. Toţi operanzii trebuie să fie complet definiţi ca valori atunci când intervin într-o expresie din lista de operanzi. Şi generaţiile mai vechi de limbaje de asamblare permiteau construirea de expresii în care apăreau operatorii aritmetici +, -, *, /, MOD (pentru obţinerea restului împărţirii a doi termeni). Astfel, dacă se defineşte secvenţa: a equ 15 b equ a + 4*6 c equ a + b se va instrucţiunea:

Page 59: limbaj de asamblare-ivan

457

mov ax, 2*b+c+a mod 4 care este echivalentă cu: mov ax, 135 pentru că lui B i se asociază 15+24=39, lui C i se asociază 54, iar A MOD 4 înseamnă rest 3; 2*39+54+3=135. Dacă în expresii apar operatori care lucrează cu şiruri de biţi, precum OR, AND, XOR, NOT (OR – sau logic, AND – şi logic, XOR – sau exclusiv, NOT – complement faţă de unu) expresiile cresc în complexitate. Astfel instrucţiunea: add ax, not 10011b and 10101b or 11000b este echivalentă cu instrucţiunea: add ax, 11100b pentru că NOT 10011b este egal cu 01100b, 01100b AND 10101b este egal cu 00100b, iar 00100b OR 11000b este egal cu 11100b. La introducerea operatorilor relaţionali este important să se cunoască valorile numerice cu care sunt puse în corespondenţă valoarea logică “adevărat” şi, respectiv, valoarea logică “fals”. În cazul asamblorului pentru limbaj ASM adevărat înseamnă (-1), iar fals înseamnă (0). Operatorii relaţionali sunt: GT (mai mare ca), GE (mai mare sau egal), EQ (egal cu), NE (neegal), LT (mai mic decât), LE (mai mic sau egal). Instrucţiunea: mov bx,(7 gt 5)and(8 le 3) are ca efect încărcarea în registrul BX a valorii zero pentru că la evaluare, 7 GT 5 înseamnă “adevărat”, adică (-1), 8 LE 3 înseamnă “fals”, adică 0. Operatorul logic AND pe biţi când un termen este cu toţi biţii zero conduce la obţinerea unui şir de şaisprezece zerouri în registrul BX. Operatorul TYPE aplicat unei variabile returnează un număr care indică lungimea ca număr de baiţi ai unui element care alcătuieşte variabila respectivă. Dacă se consideră definiţiile:

a db ? b dw l0 dup (0) str struc x dw ? y db 8 dup (?)

Page 60: limbaj de asamblare-ivan

458

str ends alfa str 20 dup (<>)

aplicând operatorul TYPE unora dintre ele în secvenţa: mov al, type a ; al =1 (db este 1 bait) mov bl, type b ; bl =2 (dw are 2 baiţi) mov cl, type alfa ; cl =10 (x are 2 baiţi, y are 8)

Operatorul SIZE returnează lungimea zonei de memorie ocupată de o variabilă în totalitate.

Dacă se consideră definirea:

st struc xx db ? ; lungime 1 yy dw ? ; lungime 2 zz dt ? ; lungime 10 st ends mat st 10 dup (5 dup (<>))

aplicând operatorii TYPE, SIZE şi LENGTH (acest operator returnează numărul de componente care alcătuiesc masivul), în secvenţa:

mov ax, type mat ; ax=1+2+10 mov bx, length mat ; bx=50 (10*5 componente) mov cx, size mat ; cx=50*13 baiţi rezervaţi.

Aceşti operatori permit o creştere a capacităţii de mentenanţă a textului la modificările de structură ale datelor, ştiut fiind faptul că în multe situaţii trebuie contorizate elementele şi trebuie traversate zone de memorie ale căror lungimi rezultă din calcul. Dacă se cunoaşte adresa de început şi lungimea zonei poate fi calculată adresa ultimului bait, pentru a face traversarea de la acesta către primul bait, mod de lucru des întrebuinţat. Operatorii HIGH şi LOW returnează baitul cel mai semnificativ, respectiv, baitul cel mai puţin semnificativ al unei date cu un tip specificat. Din secvenţa: număr dd 12345678h …… mov ax, word high număr ; ax=1234 mov bx, word low număr ; bx=5678 mov al, byte high word low număr ; al=56 se observă capacitatea de a selecta părţi dintr-un întreg, specificate cu atributele BYTE şi WORD, ce corespund tipurilor fundamentale de date ale limbajului de asamblare, alături de DWORD, QWORD, TBYTE. Când se defineşte o variabilă se

Page 61: limbaj de asamblare-ivan

459

specifică numele, tipul şi eventual valoarea cu care este iniţializată zona de memorie. În secvenţa: a dw 7 b db ‘abcd’ c struc d db ? x dd 11 dup (?) c ends w c 10 dup () a este o variabilă de tip cuvânt, b este variabilă de tip bait, w este variabilă de tip c. Utilizarea numelui unei variabile înseamnă asocierea cu întreaga zonă de memorie alocată, 2 baiţi pentru variabila a, 1*4 baiţi pentru variabila b şi 450 baiţi pentru variabila w. Totuşi, orice variabilă chiar dacă are un tip, este privită ca zonă de memorie. Există posibilitatea de a face conversii de tip aşa fel încât variabila a definită iniţial ca un cuvânt să poată fi tratată ca un şir de 2 baiţi aşa cum este variabila b. Tot astfel se pune problema de a face o conversie ca variabila w să fie privită fie ca un şir de cuvinte, fie ca un şir de baiţi. Conversia de tip presupune utilizarea operatorului PTR într-o construcţie de forma: expresie_1 ptr expresie_2 care realizează conversia de la tipul dat după evaluarea expresiei-2 la tipul cerut de expresia-1. Dacă se consideră definirea:

aa dd 12345678h

secvenţa:

mov al, byte ptr aa ;al:=78h and byte ptr [si], 0fah ;operaţie pe 8 biţi and byte ptr aa, 0aah ;operaţie 8 biţi, deplasare 16 biţi

pune în evidenţă că deşi variabila aa este definită dublu cuvânt se explorează din ea numai un bait, presupunând că registrul SI conţine deplasamentul lui aa. Operatorii SEG şi OFFSET returnează adresa de început a segmentului unde se află operandul, respectiv, deplasarea pe care o are operandul faţă de începutul segmentului. Dacă o variabilă xx este definită în segmentul de date SEGDAT, iniţializarea registrului de segment DS se efectuează prin:

Page 62: limbaj de asamblare-ivan

460

mov ax, seg xx mov ds, ax

În cadrul expresiilor apar operatorii (, ), [, ] şi operatorul punct, utilizat pentru referirea membrilor unor structuri de date de tip articol. Secvenţa:

add ax, (5+3)*(7-2) ;parantezele modifică priorităţile ;ca în algebră

mov bx, [bx+si+4*5] ;[, ] se folosesc pentru evaluare ;şi extragere conţinut

mov cx, xxx.alfa ;alfa e membru în structura xxx exemplifică utilizarea acestor operatori.

În tabelul 6.3. este prezentată mulţimea operatorilor, grupaţi pe priorităţi.

Tabelul 6.3. Priorităţi Operatori

1 () [] LENGTH, MASK, WIDTH 2 . (operatorul de referire membri din structuri) 3 HIGH, LOW 4 +,- (operatori unari) 5 : (specificare segment:offset) 6 OFFSET, PTR, SEG, THIS, TYPE 7 *, /, MOD, SHL, SHR 8 +,- (operatori binari) 9 EQ, NE, GT, GE, LT, LE

10 NOT 11 AND 12 OR, XOR 13 LARGE, SHORT, SMALL

Unii dintre operatori sunt prezentaţi în capitolele următoare pe măsură ce sunt definite tipuri de date şi directive care impun evaluări de expresii cu includerea lor. Se observă că asamblorul este un program extrem de complex din moment ce permite evaluarea unor expresii de complexitate ridicată, mulţimea operatorilor fiind comparabilă cu operatorii din limbajul C. Comentariile, ultimul element al liniei sursă, sunt opţionale. Scrierea de comentarii va uşura munca de dezvoltare a programului, oferind programatorului explicaţii la sensul operaţiilor, o formulă sau o valoare care are o semnificaţie particulară pentru program.

Page 63: limbaj de asamblare-ivan

461

6.4 Forma internă a instrucţiunilor Asamblorul evaluează expresiile în măsura în care acest lucru este posibil, identifică operandul registru sau operandul imediat, stabileşte codul operaţiei. În final toate elementele formei externe a instrucţiunii se vor codifica. Rezultă un număr de baiţi ocupaţi cu configuraţii care la execuţia programului se interpretează şi determină prelucrările dorite de programator. Codul operaţiei şi unele informaţii în continuare vor indica numărul de baiţi din care este alcătuită codificarea instrucţiunii. Structura internă a instrucţiunii conţine informaţii privind:

codul operaţiei; modul de adresare; registrele utilizate şi semnificaţia lor; lungimea în baiţi a descrierii operandului memorie; rolul pe care îl are registrul (sursă sau destinaţie); lungimile operanzilor; sensul de efectuare a traversării.

În cazul unor limbaje de asamblare în care mnemonica defineşte tipul operanzilor, lungimea, deci numărul de mnemonice este ridicat, codul asociat mnemonicelor ocupă un bait. Limbajele de asamblare de acest tip vehiculează cel mult 256 de instrucţiuni diferite. Se creează în acest fel premisa structurii fixe a poziţiei câmpurilor din instrucţiune, ceea ce înseamnă pe de o parte un mod simplu de codificare şi pe de altă parte risipă de memorie. Se va prezenta în continuare concepţia limbajului de asamblare în care dispunerea informaţiilor se face din aproape în aproape şi câmpurile se intercondiţionează. Grupele de instrucţiuni se diferenţiază după primele patru poziţii ale codului operaţiei. Dacă se construieşte un tabel al frecvenţelor de utilizare pentru instrucţiuni, este posibilă atribuirea unor coduri care sunt mai deosebite pentru instrucţiunile cu frecvenţele cele mai mari. Este posibilă construirea unui tabel cu 16 linii şi 16 coloane ce corespund combinaţiilor biţilor cei mai semnificativi din baitul codului de operaţie, respectiv, biţilor mai puţin semnificativi. Dispunând pe liniile matricei în continuare, codurile instrucţiunilor în ordinea descrescătoare a frecvenţelor de utilizare mnemonice, se va obţine o structură avantajoasă şi pentru constructorul de asambloare eficiente.

Tabelul 6.4. prezintă o dispunere a mnemonicelor, aşa cum rezultă din codurile deja atribuite de designerul limbajului de asamblare, perfectibilă după analiza statistică a unui lot reprezentativ de programe.

Page 64: limbaj de asamblare-ivan

462

Tabelul 6.4.

Instrucţiune Numărul de apariţii mov 567 pop 152 cmp 89 push 77 sub 32 ret 28 add 20 jmp 14 call 6 lea 6 xor 4

Structura internă a instrucţiunilor limbajului de asamblare variază funcţie de modul de adresare, de numărul de operanzi şi de natura operanzilor.

Instrucţiunile care se reprezintă pe un bait şi nu au operanzi sunt destinate poziţionării pe zero sau pe unu a indicatorilor de condiţie (CLC, STC, CLI, STI, CLD, STD, CMC), controlează execuţia programului (HLT, NOP, WAIT). Cei 8 biţi pe care este reprezentată o astfel de instrucţiune sunt interpretaţi cod operaţie, astfel:

bit 7 6 5 4 3 2 1 0 c c c c c c c c

Figura 6.2 – Forma internă a instrucţiunilor pe un bait fără operanzi

unde c reprezintă o cifră binară 0 sau 1 care intră în componenţa codului instrucţiunii. Dacă instrucţiunea admite o construcţie de forma:

mnemonica registru

ca de exemplu, instrucţiunile din secvenţa:

push ax dec cx inc dx fiind vorba, deci de instrucţiuni cu un singur operand şi acesta fiind

registru, dispunerea informaţiei pe un bait va conduce la structura internă de instrucţiune din figura 6.3, unde c reprezintă cifre binare asociate codului operaţiei, iar r reprezintă cifre binare asociate codurilor registrelor generale. Codurile asociate registrelor generale sunt date în tabelul 6.5.

Page 65: limbaj de asamblare-ivan

463

bit 7 6 5 4 3 2 1 0 c c c c c r r r

Figura 6.3 – Forma internă a instrucţiunilor pe un bait cu un operand

Tabelul 6.5. Registrul Cod Operaţie pe cuvânt Operaţie pe bait

000 AX AL 001 CX CL 010 DX DL 011 BX BL 100 SP AH 101 BP CH 110 SI DH 111 DI BH

Pentru incrementare de exemplu, codul instrucţiunii INC (operaţie pe cuvânt) este 01000 dacă operandul va fi de tip registru. Instrucţiunea: inc si se va codifica astfel:

bit 7 6 5 4 3 2 1 0 0 1 0 0 0 1 1 0 c C c c c r r r

Figura 6.4 – Forma internă a instrucţiunii inc si

Codificarea afişată de asamblor va fi 46h. Există unele limitări la folosirea operanzilor, în sensul fixării acestora. De exemplu, instrucţiunea de conversie de la un bait la cuvânt, CBW, presupune ca baitul să fie memorat în registrul AL, iar rezultatul conversiei se va găsi în registrul AX. Instrucţiunile PUSHA, PUSHF, POPA, POPF se prezintă pe un bait pentru că operanzii lor sunt prefixaţi, toate registrele (PUSHA, POPA), respectiv, biţii indicatorilor de condiţie (PUSHF, POPF). Instrucţiunile de lucru cu şiruri de caractere se reprezintă de asemenea, pe un bait pentru că adresele zonelor de memorie sunt stocate în registrele DS, SI (zona de memorie destinaţie) şi registrul CX va conţine lungimea zonei sursă. Deşi

Page 66: limbaj de asamblare-ivan

464

execută operaţii complexe, instrucţiuni ca LODSB, LODSW, STOSB, STOSW, SCASB, SCASW, CMPSB, CMPSW, MOVSB, MOVSW, pentru că lucrează cu şiruri de caractere, având registre prefixate pentru adrese, operanzi, asamblorul le codifică pe un bait. Instrucţiunile de tip R-R, R-I, R-M, M-R, M-I presupun informaţii privind lucrul la nivel de bait sau la nivel de cuvânt. Bitul 0 din primul bait al instrucţiunii codificat w, dacă are valoarea 0 înseamnă că operanzii sunt pe un bait, iar dacă are valoarea 1, operanzii sunt pe cuvânt. Registrul operand poate fi sursă (d=0) sau destinaţie (d=1), unde cu d a fost notat bitul unu din primul bait al instrucţiunii. Celelalte informaţii necesită încă un bait, numit MODRM de programatori datorită conţinutului său. Dacă operanzii sunt de tip imediat sau sunt în memorie şi se definesc cu deplasare, urmează la codificarea instrucţiunii al treilea bait (d=0) sau al treilea şi al patrulea (d=1). Baitul codului de instrucţiune se structurează pentru instrucţiunile cu doi operanzi astfel:

bit 7 6 5 4 3 2 1 0 c c c c c c d w

Figura 6.5 – Forma internă a primului bait al instrucţiunilor pe doi baiţi

bit 7 6 5 4 3 2 1 0 m m R R R r/m

Figura 6.6 – Forma internă a baitului al doilea al instrucţiunilor pe doi baiţi

Al doilea bait asociat unei instrucţiuni după asamblare are structura dată în figura 6.6, unde:

m – reprezintă biţii 7,6 asociaţi modului în care sunt interpretaţi biţii 2, 1, 0 şi cum este definită deplasarea operandului faţă de începutul segmentului (pe un bait sau pe doi baiţi), tabelul 6.6;

R – ocupă biţii 5 ,4 , 3 şi indică registrul operand; r/m – stabileşte formula de calcul a adresei operandului aflat în memorie.

Page 67: limbaj de asamblare-ivan

465

Tabelul 6.6. Biţii 7, 6

Mm Deplasarea

00 Lipseşte din instrucţiune 01 Este definită pe un bait 10 Este definită pe doi baiţi 11 Biţii 2, 1, 0 se interpretează drept cod registru

Tabelul 6.7. Biţii 2, 1, 0 Formula de calcul a adresei 000 BX + SI + deplasare 001 BX + DI + deplasare 010 BP + SI + deplasare 011 BP + DI + deplasare 100 SI + deplasare 101 DI + deplasare 110 BP + deplasare 111 BX + deplasare

Deplasarea intră în calcule pe 16 biţi. Dacă biţii mm au valoarea 01 are loc extensia pe 16 biţi prin propagarea bitului de semn. Dacă biţii mm sunt 00 şi biţii r/m sunt 110 deplasarea este pe 16 biţi, deci nu lipseşte. Instrucţiunea: mov dl, bx[si] se codifică prin: ba10h Cei doi baiţi se interpretează astfel:

7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 c c c c c c d w m m R R R r/m 1 0 1 1 1 0 1 0 0 0 0 1 0 0 0 0

unde:

biţii notaţi cccccc (101110) reprezintă codul operaţiei MOV registru, memorie (R - M);

bitul de direcţie (d=1) arată că registrul este destinaţie; bitul tipului de operand (w=0) arată că operandul este un bait;

Page 68: limbaj de asamblare-ivan

466

biţii mm prin codul 00 arată că deplasarea lipseşte din instrucţiune; biţii RRR prin valoarea 010 arată că operandul destinaţie registru este

DL (tabelul 6.5.) ; biţii r/m prin valoarea 000 pun în evidenţă (tabelul 6.7.) că expresia de

adresă se calculează după formula BX + SI + 0. Dacă se ia în considerare multitudinea de combinaţii de expresii de adresare, pentru o decodificare corectă a instrucţiunilor se identifică mai întâi apartenenţa la unul din tipurile:

T1 - cod operaţie; T2 - cod operaţie (operanzii sunt prefixaţi); T3 - cod operaţie registru; T4 - cod operaţie constantă imediată; T5 - cod operaţie registru, registru; T6 - cod operaţie registru, constantă imediată; T7 - cod operaţie registru, expresie de adresă; T8 - cod operaţie expresie de adresă; T9 - cod operaţie expresie de adresă, registru; T10 - cod operaţie expresie de adresă, constantă imediată.

Numai după aceea se va face distincţia între registrele generale şi registrele de segmente care au coduri asociate conform tabelului 6.8.

Tabelul 6.8. Cod Registru segment 00 ES 01 CS 10 SS 11 DS

Apariţia codului registru segment determină ca o zonă de trei biţi să fie completată 0ss, unde ss reprezintă codul registrului segment. Dacă se impune, codul segment poate ocupa şi poziţiile 4, 3 din baitul codului operaţie. Se observă că structura internă a instrucţiunii reprezintă un mod convenţional de punere în corespondenţă a multitudinii de tipuri de instrucţiuni cu coduri. Se urmăreşte realizarea de combinaţii unice, neambigue, iar asamblorul şi dezasamblorul să fie aplicaţii, una transformată a celeilalte. Trecerea la microprocesorul 386 a condus la noi performanţe. Limbajul de asamblare a rămas în mare parte nemodificat, iar programatorul poate realiza construcţii noi, putând integra complet pe cele vechi. În dinamicile hardware, structura internă a instrucţiunilor este afectată cel mai frecvent. În cazul microprocesorului 386 se specifică următoarele aspecte:

Page 69: limbaj de asamblare-ivan

467

codul operaţiei ocupă doi baiţi, permiţând definirea unor noi variante de prezentare dar şi deschizând posibilitatea definirii de noi instrucţiuni ca operaţii sau noi tipuri de instrucţiuni, ca structură de listă de operanzi;

lungimile operanzilor sunt un bait, doi baiţi şi patru baiţi, lucru ce se reflectă prin informaţiile descriptorilor d şi w;

registrele se codifică pentru trei situaţi: registrele de un bait, registrele pe 16 biţi şi registrele EAX, ECX, EDX, EBX, ESP. EBP, ESI, EDI pe 32 de biţi, cărora li se asociază coduri tot de trei biţi, pe poziţiile din baitul MODRM.

biţii r/m iau în considerare toate structurile de expresii de adresă în varianta lucrului pe 16 biţi, iar separat în varianta lucrului pe 32 de biţi;

factorul de scală pentru adresarea indexată (biţii 7, 6) din al treilea bait de descriere a instrucţiunii;

registrul index (biţii 5, 4, 3) din al treilea bait; registrul de bază, următorii biţi, 2, 1, 0; deplasarea sau constanta imediată se reprezintă în continuare pe zero

(dacă lipseşte) baiţi, pe un bait, pe doi baiţi sau pe patru baiţi; în cazul instrucţiunilor de salt condiţia de testare este înglobată pe patru

biţi în instrucţiune, primii trei biţi definesc combinaţia, iar bitul al patrulea când este unu determină complementarea faţă de unu a primilor trei biţi.

Trecerea de la un procesor la altul devine pentru programatorul în limbaj de asamblare un exerciţiu de codificare cu amplasarea impusă a câmpurilor într-o succesiune dată de biţi. Mai apare în plus o completare a listei de instrucţiuni ca mnemonice, cu descrieri riguroase şi eventual noi resurse (registre) sau noi caracteristici ale resurselor (lucrul pe 32 de biţi). Prin studierea comparată a structurilor interne ale instrucţiunilor limbajelor de asamblare, a frecvenţei de utilizare a instrucţiunilor şi a diversităţii instrucţiunilor şi expresiilor de adresare, este posibilă proiectare unei codificări optime, care minimizează lungimea textului generat după asamblare, ca număr de baiţi. 6.5 Comentariile Programele sunt scrise pentru a rezolva probleme dar şi pentru a fi dezvoltate în aşa fel încât să continue rezolvarea problemelor, după un număr de ani, după ce au intervenit modificări în algoritmi, prin efectuarea unor modificări în definirea operanzilor şi în secvenţele de prelucrare. Comentariile sunt mai necesare în programele scrise în limbajul de asamblare pentru caracterul ermetic, codificat al mnemonicelor şi al registrelor. Este preferabil să se introducă numeroase comentarii ca linii sursă distincte dar şi în continuarea instrucţiunilor.

Page 70: limbaj de asamblare-ivan

468

Astfel, secvenţa: ; programul calculează suma ; a două numere a şi b ; rezultatul se va afla în c mov ax,a ;ax=a add ax, b ;ax=ax+b mov c,ax ;c=ax va putea fi uşor modificată de un alt programator după câţiva ani de la scrierea programului. Secvenţa:

xor bx, 1fach add bx, x[si] + 0cch and di – 1dh, 0ff00h

în absenţa explicării semnificaţiei constantelor 1FACh, 0CCh, 1Dh, 0FF00h, va fi dificil pentru programator să interpreteze şi mai ales să actualizeze aceste constante.

Deşi este un efort suplimentar să se includă într-un text comentarii, introducerea obligativităţii de a se autodocumenta programele va avea efecte pozitive asupra procesului de mentenanţă a acestora. 6.6 Efectele execuţiei instrucţiunilor Instrucţiunile sunt simple sau complexe fie în raport cu modul în care sunt scrise pe o linie program, fie în raport cu ceea ce efectuează. Este interesant pentru programator să cunoască complexitatea în raport cu efectele de prelucrare şi să folosească eficient tot ceea ce oferă instrucţiunile. Este cunoscut faptul că după efectuarea de calcule are loc poziţionarea indicatorilor de condiţie. Neglijarea acestui aspect va conduce la scrierea în mod inutil a unei instrucţiuni de comparare.

Secvenţa:

mov ax, a sub ax, b cmp ax, 0 jle alfa

va fi scrisă, cunoscând cum se poziţionează indicatorii de condiţie, astfel:

mov ax, a sub ax, b jle alfa

Page 71: limbaj de asamblare-ivan

469

În cazul utilizării instrucţiunilor LOOP are loc modificarea conţinutului registrului contor CX prin decrementare, pentru a gestiona repetările secvenţelor. Pentru construirea de secvenţe repetitive imbricate (incluse) este necesară salvarea acestui registru înainte de a intra în secvenţa repetitivă inclusă şi restabilirea după ieşirea din aceasta. Instrucţiunile de lucru cu şiruri de caractere sunt utilizate alături de instrucţiunea de repetare REP. Traversarea zonelor de memorie presupune incrementarea automată a registrelor index SI şi DI. La terminarea execuţiei, aceste registre permit accesarea primului bait din afara fiecărei zone. Lucrul cu zone de memorie adiacente în aceste condiţii nu mai necesită pregătirea registrelor, prin a încărca deplasamentul zonelor sursă, respectiv, destinaţie. Se va lucra folosind efectul secundar al instrucţiunii REP. Multe din instrucţiunile care presupun memorare în stivă (CALL, RET, DIV, INT, IRET, INTO, PUSH, POP) modifică mai întâi conţinutul registrului SP şi stochează anumite informaţii. Prin incrementarea sau decrementarea registrului SP programatorul va avea acces la informaţii stocate automat ca parte din execuţia unei instrucţiuni. De asemenea, programatorul are posibilitatea să definească noi combinaţii în care să se efectueze salturile, testând în mod propriu prin expresiile sale când şi cum să se selecteze o anumită secvenţă. Alegerea între a lucra pe un bait, pe doi baiţi sau pe mai mulţi baiţi depinde de natura operanzilor şi de magnitudinea rezultatului. Pentru a efectua corect operaţiile trebuie cunoscut ce se modifică şi ce rămâne nemodificat la execuţia unei instrucţiuni. Dacă un operand are o lungime L1 şi celălalt operand are lungimea L2, când se efectuează operaţiile, rezultatul are o lungime dependentă de acestea. În cazul adunării totalul va avea max (L1, L2) +1 cifre, la înmulţire produsul va fi de L1 + L2 cifre, iar la împărţire câtul va fi de L1-L2 cifre. Aceste calcule sunt făcute şi rezervările de zone de memorie vor acoperi acest tip de cerinţe. Studierea instrucţiunilor are rolul de a da mobilitate programatorului şi de a-i creşte capacitatea de adaptare la toate cerinţele realizării de programe performante, chiar dacă lucrul în limbaj de asamblare e impus şi nu dorit.

Page 72: limbaj de asamblare-ivan

470

7

DEFINIREA STRUCTURILOR DE DATE 7.1 Date elementare Datele elementare sunt asociate unor zone de memorie de lungime fixată care opţional sunt iniţializate cu constante care impun atât natura operanzilor, cât şi setul de instrucţiuni cu care se va lucra. Directivele pentru descrierea datelor elementare determină modificarea contorului cu o unitate dacă tipul este DB (Define Byte), cu două unităţi dacă tipul este DW (DefineWord), cu patru unităţi pentru tipul DD (Define Double), cu opt unităţi pentru tipul DQ (Define Quad Word) şi cu zece unităţi pentru tipul DT (Define Ten).

Secvenţa:

a db 13 : a ocupă un bait iniţializat cu 13 b dw 5 ; b ocupă 2 baiţi iniţializaţi cu 5

c dd 0fah ; c ocupă 4 baiţi iniţializaţi cu 0fah d dt 0 ; d ocupă 10 baiţi iniţializaţi cu 0 e dq 7 ; e ocupă 8 baiţi iniţializaţi cu 7 f dw ? ; f ocupă 2 baiţi neiniţializaţi g db ? ; g ocupă un bait neiniţializat exemplifică definiri de variabile elementare cu sau fără iniţializare. Alegerea numelui de dată elementară va fi sugestiv şi se va căuta să nu fie întrebuinţată aceeaşi variabilă pentru mai multe categorii de rezultate în acelaşi program. Alegerea tipului DB este utilă dacă datele au valori cuprinse între 0 şi 255. Descriptorul DW este preferat când variabila are valori cuprinse între 0 şi 65535. Tipul de dată DD este utilizat când datele sunt cuprinse între 0 şi 232-1, iar tipul de date DQ, atunci când plaja de valori este cuprinsă între 0 şi 264-1. Organizarea datelor cu semn determină identificarea corectă a intervalelor. Astfel, se va defini o variabilă de tip DB dacă plaja de valori este cuprinsă între –128 şi 127. Se utilizează tipul DW pentru plaja de valori –32768 şi 32767. Pentru descriptorul DD plaja de valori este cuprinsă între –232 şi 2 31 –1, iar pentru tipul DQ plaja este definită prin -263 şi 22

63 –1. Când se alege un descriptor de tip, pentru omogenitate se va avea în vedere

ca rezultatele operaţiilor să aparţină aceluiaşi tip pentru a evita operaţiile de conversie.

Page 73: limbaj de asamblare-ivan

471

7.2 Masive unidimensionale Masivele unidimensionale se definesc prin: nume tip n dup (val_1, val_2,…,val_m) unde: nume - identificator construit după regulile limbajului;

tip - DB, DD, DQ, DT sau un tip definit de utilizator; n - numărul de componente ale masivului; val_j - valoarea obţinută după evaluarea unei expresii cu poziţia j din lista

dacă sunt iniţializate componentele masivului.

În secvenţa: x db 10 dup (‘a’) y dw 20 dup (0) z dd 40 dup (?) w db 5 dup (3, 4, 5) u dw 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 se alocă memorie pentru:

masivul unidimensional x având elemente de un bait, în număr de zece, fiecare fiind iniţializat cu constanta caracter ‘a’;

masivul de 20 componente, numit y; fiecare componentă este iniţializată cu zero; zona de memorie alocată are o lungime de 40 baiţi;

z, masiv cu 40 componente, care ocupă 160 baiţi, fără iniţializare; masivul w, care are 5×3 componente, iniţializate cu w0 = 3, w1 = 4, w2 =

5, w3 = 3, w4 = 5, w5 = 5,…, w12 = 3, w13 = 4, w14 = 5; masivul w ocupă 15 baiţi;

masivul numit u, având 10 componente, iniţializate una după alta; componentele sunt definite ca variabile elementare distincte, cu acelaşi descriptor de tip, DW.

7.3 Masive bidimensionale

Se definesc prin compunerea vectorilor de vectori astfel: nume tip n dup (m dup (val1-1, val1-2,…,val-k)) unde: nume - identificator cu care se referă matricea; tip - descriptor de tip DB, DW, DD, DQ, DT; n - numărul de linii ale matricei;

Page 74: limbaj de asamblare-ivan

472

m - numărul de coloane ale matricei; val-j - valoarea cu care se evaluează o anumită componentă.

Operatorul DUP este inclus în mod repetat, permiţând generalizarea ce conduce la definirea de masive de masive multidimensionale. Secvenţa: a dw 10 dup (10 dup (0)) b db 3 dup (3 dup (8), (19)) c dd 2 dup (7, 4 dup (3)) d dw 4 dup (1, 2, 3, 4) e dd 4 dup (5, 2 dup (0), 9) defineşte matricele următoare:

a – matrice cu 10 linii şi l0 coloane, având toate elementele zero; b – matrice cu 3 linii şi 4 coloane iniţializată astfel:

8 8 8 19 8 8 8 19 8 8 8 19

c – matrice cu două linii şi 5 coloane iniţializată astfel:

3333733337

d – matrice cu 4 linii identic iniţializate conducând la:

4321432143214321

e – matrice cu 4 linii şi 4 coloane, iniţializată astfel:

9005900590059005

Matricele fiind masive care presupun elemente de acelaşi tip, lungimile

zonelor de memorie ocupate se obţin înmulţind numărul de componente cu lungimea zonei asociată fiecărui tip (elementul de tip DB are lungimea un bait, elementul de tip DW are lungimea doi baiţi, tipul DD presupune alocare 4 baiţi,

Page 75: limbaj de asamblare-ivan

473

tipul DT-10). De exemplu, masivul bidimensional ee, având tipul DD, cu 5 linii şi 5 coloane, va ocupa o zonă de memorie de 100 baiţi (4 × 5 × 5). 7.4 Articolul Articolul este un conglomerat de membri, care sunt fie date elementare, fie masive, fie alte articole. Tipurile membrilor sunt diferite. Un articol se defineşte prin: nume struc membrul_1 membrul_2 …… membrul_n nume ends Masivele sunt cazuri particulare, în care membrii sunt de acelaşi tip. Definirea unui articol nu presupune rezervare de zonă de memorie. Pentru a rezerva zonă de memorie este necesară definirea de variabile de tip articol cu numele articolului definit.

În secvenţa: persoana struc nume db 30 dup (?) vârsta dw ? salariu dd ? nr_copii db ? reţineri dw ? persoana ends este definit tipul de date persoana, care este un articol care are în componenţă şase membri având tipurile DB, DW, DD, DB, respectiv, DW. Lungimea zonei de memorie ocupată de o variabilă de tip STRUC este dată de lungimea membrilor care intră în alcătuirea ei. Astfel, o variabilă de tip STRUC persoana va ocupa 30+2+4+1+2=39 baiţi.

În secvenţa: pers persoana (?) mat_pers persoana 10 dup (10 dup (?)) vect_pr persoana 20 dup (?) se definesc:

o variabilă cu numele de tip STRUC persoana, ce ocupă 39 baiţi, şi ai cărei membri se referă cu operatorul . (punct) ca de exemplu, pers. nume, pers.vârsta, pers.salariu;

Page 76: limbaj de asamblare-ivan

474

un masiv bidimensional, având 10 linii şi 10 coloane, cu elemente de tip persoana (matrice de articole);

un vector de articole cu 20 de componente. În toate cazurile construcţia <?> indică neiniţializarea zonelor de memorie asociate elementelor de tip persoană.

Pentru iniţializarea valorilor pentru fiecare membru la definire sunt dispuse valori prefixate sau sunt cuprinse într-o listă de valori respectând concordanţa dintre tipul membrului şi tipul constantei. În cazul absenţei unei valori, caracterul poziţional al membrilor impune păstrarea ordinii prin menţinerea separatorilor (virgula), în lista de valori iniţiale putând apare separatori consecutivi.

În secvenţa: maşina struc tip db 20 dup (?) culoare db ‘roşie’ vechime dw ? maşina ends . . . . . . . . opel maşina (‘berlina’, ,15) dacia maşina 2 dup (‘break’, , 0) diverse maşina 100 dup ( , , 20 ) se defineşte tipul de dată STRUC maşina având un câmp cu valoare prefixată (care poate fi modificată printr-o iniţializare într-o variabilă dacă respectiva variabilă impune). Se defineşte variabila OPEL de tip STRUC maşina ale cărei câmpuri sunt iniţializate după cum urmează: OPEL.nume = ‘berlina’, OPEL.culoare = ‘ROŞIE’, OPEL.vechime OPEL.vechime=15. DACIA este un masiv unidimensional cu două componente de tip STRUC maşina. Prima componentă are membri iniţializaţi astfel: DACIA.nume = ‘brek’, DACIA.culoare = ‘ROŞIE’, DACIA. vechime=0. A doua componentă (lungimea unui articol este 20+5+2=27 baiţi) are membri referiţi prin DACIA+27 cu valorile iniţializate următoare: DACIA+27.nume=’brek’, DACIA+27.culoare= ‘ROŞIE’, DACIA+27.vechime=0. Masivul unidimensional numit DIVERSE are 100 componente de tip STRUC maşina în care membri culoare şi vechime se iniţializează cu ‘ROŞIE’, respectiv, valoarea 20.

Articole cu câmpuri definite la nivel de biţi Există variabile care au un număr de valori restrânse şi definirea cu tip DB înseamnă deja risipă de memorie. În evidenţa populaţiei, mediul din care provine persoana este rural sau urban (1 sau 0), sexul individului este masculin sau feminin (1 sau 0), persoana poate avea sau nu copii (1 sau 0), poate fi necăsătorit (00),

Page 77: limbaj de asamblare-ivan

475

căsătorit (01), divorţat (10) sau văduv (11). Pregătirea profesională poate fi: absolvent şcoală generală (001), analfabet (000), absolvent liceu (010), absolvent şcoală profesională (011), absolvent postliceal (100 ), licenţiat (101), absolvent colegiu (102), doctor în ştiinţe (110), master in science (111). Persoana poate avea sau nu carnet de conducere (1 sau 0) poate avea sau nu cazier (1 sau 0). Lucrând la nivel de câmpuri de biţi cu lungime unu, doi sau trei, se va obţine o importantă economie de memorie la utilizarea de fişiere pentru colectivităţi foarte mari. Utilizarea de fişiere inverse vine să mărească viteza de regăsire după combinaţii de caracteristici date sub forma câmpurilor de biţi. Articolele definite cu câmpuri de biţi utilizează descriptorul RECORD astfel: nume record nume-1:n1, nume-2:n2,…, nume-k:nk unde: nume reprezintă numele tipului de dată RECORD ce se defineşte; nume-j - numele câmpului j din structura de câmpuri de biţi; nj - numărul de biţi care formează câmpul j.

În cazul în care la definire apar construcţii de forma: nume-j:nj=expresie-j expresie_j se evaluează şi este corectă în raport cu lungimea câmpului, reprezintând valoarea ce se atribuie respectivului câmp la alocarea de memorie. Astfel, definirea: a record al:2, a2:4=11b, a3:2 aa a 3 dup (?) construieşte tipul de dată având câmpuri pe biţi repartizaţi după urmează: a1 ocupă 2 biţi, a2 ocupă 4 biţi, iar a3 ocupă 2 biţi. Se creează vectorul aa cu trei componente, fiecare componentă fiind pe un bait structurat conform tipului de dată RECORD a. Cele trei elemente ale masivului unidimensional au iniţializaţi biţii ce corespund câmpului a2 cu valoarea 0011b. Dacă se lucrează la nivel de cuvânt este importantă poziţionarea câmpurilor întrucât baiţii rămân unităţi de bază şi câmpurile nu pot fi dispersate în doi baiţi adiacenţi. Fiecare asamblor poate impune restricţii la lungimea în baiţi a tipului de dată RECORD. De cele mai multe ori, lungimea câmpurilor n1+n2+…+nk nu poate depăşi 16 biţi. Când se lucrează cu astfel de structuri se utilizează operatorul WIDTH care returnează lungimea ca număr de biţi a unui câmp căruia i-a fost aplicat. Dacă se construieşte secvenţa: aaa record b:5, c:1, d:2, e:4, f:2, g:2 bbb aaa (?) . . . . . . . .

Page 78: limbaj de asamblare-ivan

476

mov a1, width bbbb + width bbb.e instrucţiunea mov are ca efect încărcarea în registrul AL a numărului 9 întrucât operatorul WIDTH aplicat succesiv câmpurilor b şi e, returnează 5, respectiv, 4, acestea fiind lungimile cu care au fost definite. Pentru a lucra cu biţii câmpului trebuie “izolaţi” folosind operatorul MASK. În definirea de mai sus a tipului RECORD aaa, pentru a putea testa valoarea câmpului c acesta va fi încărcat după ce a fost “izolat” de restul elementelor care formează primul bait, prin instrucţiunea: mov h a1, mask bbb.c ; a1:=000000x00 cmp a1,0

Bitul notat x reprezintă câmpul izolat, care face obiectul testării. Câmpurile “neinteresante” ale variabilei RECORD bbb din primul bait sunt

puse pe zero. Cea mai mică unitate de memorie adresabilă este baitul. Programatorul nu poate avea acces în mod direct la şiruri de biţi. Operatorii WIDTH şi MASK permit menţinerea nemodificată a secvenţelor de prelucrare atunci când se modifică structura de definire a variabilelor RECORD.

Reuniunea de date Se consideră tipuri de date diferite care trebuie să ocupe o aceeaşi zonă de memorie. Pentru aceasta se utilizează descriptorul UNION astfel: nume union membrul-1 membrul-2 membrul-3 . . . . . membrul-n nume ends unde: nume - reprezintă un identificator care se asociază tipului de dată UNION; membrul-j - definire tipul de date j;

Lj - lungimea în baiţi a variabilei de tipul j. O variabilă de tip UNION nume are o lungime egală cu maximul dintre

lungimile L1, L2, . . ., Ln. Secvenţa: salariu union

sala dw 5 dup (0) salb dw ? nume db 20 dup ?

salariu ends

Page 79: limbaj de asamblare-ivan

477

ss salariu 7 dup ( ? ) defineşte o variabilă UNION salariu a cărei lungime L=max (10,2,20)=20 şi

masivul unidimensional ss cu 7 componente neiniţializate. Modul de realizare a suprapunerii baiţilor este dat în figura 7.1.

baitul 0 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 . . 19 sala(i) 0 0 1 1 2 2 3 3 4 4 salb ? ? nume(i)0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 … 19

Figura 7.1 – Suprapunerea în structură de tip UNION

Cifrele din dreptul poziţiilor baiţilor corespund indicilor masivelor. Astfel, cu poziţia 9 aparţine şi elementul sala (4) din masivul unidimensional sala, dar şi elementul nume (9). Elementul salb ocupă numai primii doi baiţi. 7.5 Variabile pointer

Spre deosebire de limbajul C în limbajul de asamblare, nu se fac declarări

speciale pentru variabilele pointer. În limbajul de asamblare toate tipurile de variabile sunt reduse la zonele de memorie pe care le ocupă. Variabilele pointer memorează deplasamentele unor variabile sau ale unor etichete definite pentru instrucţiuni executabile. Secvenţa: a db ? sir db ‘abcdefgh’ c dw ? e dd 111 addr_a dw offset a addr_sir dw offset sir addr_c dw offset c addr_e dw offset e addr_x dw ? . . . . . . . . . . . . . x: mov addr_x, offset x defineşte variabilele a, şir, c, e de tipuri diferite şi variabilele addr a, addr_sir, addr_c, addr_e care sunt iniţializate la definire cu deplasamentele variabilelor, toate având definirea cu descriptorul DW. Variabila addr x tot de tip DW va fi iniţializată la asamblarea programului, ea conţine deplasamentul unei instrucţiuni (deplasarea instrucţiunii faţă de punctul de început al segmentului de cod program).

În programe se definesc vectori de pointeri prin: vector_ptr dw offset adună

Page 80: limbaj de asamblare-ivan

478

dw offset scade dw offset produs dw offset rest dw offset ordine unde: adună, scade, produs, rest şi ordine sunt nume de proceduri ce pot fi apelate prin instrucţiunea call vector_ptr + bx într-o structură repetitivă, incrementând registrul BX. Faţă de limbajele evoluate, vectorul de pointeri poate conţine deplasamente atât de proceduri cât şi de variabile sau de instrucţiuni. 7.6 Variabile enumerative Dacă o variabilă parcurge un set de valori stabilit, aceste valori se pun în corespondenţă cu coduri numere întregi în ordine crescătoare. Programatorul utilizează valorile, (uneori nenumerice) dar în program sunt vehiculate codurile. Definiţia: nume enum val_1, val_2, val_3, . . . ., val_n unde: nume - tipul de date enum definit de programator; val_j - valoarea j pe care o poate avea o variabilă de acest tip, care se pune în corespondenţă cu j –1;

De exemplu, în secvenţa: culoare enum roşu, alb, verde, galben zile enum luni, marţi, miercuri, joi, vineri, sâmbăta calificat enum satisfăcător, bine, excelent boolean enum fals, adevărat unitate enum bucată, litri, metru, tona a culoare ( ? ) b zile ( ? ) c calificat 5 dup (bine) d boolean (fals) e unitate litri, bucăţi, tona s-au definit tipurile de date culoare enum, zile enum, calificat enum, boolean şi unitate tot enum. Variabila a este de tip culoare enum, c este un masiv unidimensional cu 5 componente de tip califcat enum, variabilele d şi e sunt şi iniţializate.

Lungimea zonei de memorie asociată depinde de lungimea listei val-j şi poate fi un bait, doi baiţi, patru baiţi. De cele mai multe ori codurile sunt 0, 1, 2, . . .

Page 81: limbaj de asamblare-ivan

479

. , 255, pentru că nivelele variabilelor sunt restrânse şi se alocă pentru variabilele enum un bait. Dacă se doreşte alocarea altor coduri, acest lucru este posibil utilizând construcţii de forma val_j = expresie.

Definirea: preţ enum preţ_mic=1000, preţ_bun=2000, preţ mare=5000 realizează o altă punere în corespondenţă. Dacă nu se mai continuă iniţializare, valorile sunt puse în corespondenţă cu coduri generate în continuare. De exemplu: termin enum start=100, continuare, avansare, final ccc termin ? pune în evidenţă că variabila ccc de tip termin ENUM poate lua numai valorile 100, 101 şi 102 şi 103. 7.7 Tabele de date

Ca în cazul lucrului cu şiruri de biţi există posibilitatea de a defini articole cu câmpuri formate din baiţi, cuvinte, cuvinte duble, 8 sau 10 baiţi folosind descriptorii BYTE, WORD, DWORD, QWORD?. TBYTE SAU PWORD / FWORD (câmpuri de 6 baiţi). Construcţia: nume TABLE num_1: tip_1, num_2: tip_2, ....,num_k: tip_k defineşte tipul de date tabelă unde: nume - numele tipului de dată tabelă definit de programator; num_j - numele membrului j al tabelei;

tip_j - tipul membrului j al tabelei, în cazul în care tipul este nespecificat se consideră WORD (DWORD dacă se lucrează pe 32 biţi).

În cazul în care se utilizează construcţia:

num_j: tip_j: nr_j se va asocia membrului num_j un masiv unidimensional cu nr_j componente de tip_j.

Secvenţa: articol TABLE nume:BYTE:30, salariu:WORD,reţineri:WORD:4 aaa articol

Page 82: limbaj de asamblare-ivan

480

defineşte tipul de dată articol TABLE şi variabila de tip articol, numită aaa, care ocupă 50 baiţi. Tipul de date articol TABLE conţine masivele unidimensionale nume şi reţineri. 7.8 Obiecte Structura de date obiect este implementată şi în limbajele de asamblare. Aici se văd mai bine implementările mecanismelor şi proprietăţilor specifice lucrului cu obiecte. Un obiect este un conglomerat de membri mult mai neomogen ca structura de tip articol întrucât acceptă ca membri proceduri sau pointeri spre proceduri. Înainte de a da regula generală de definire a unei structuri de dată obiect, se va considera exemplul următor de definire obiect. Pentru rezolvarea unei probleme sunt necesare procedurile:

suma – procedură NEAR destinată însumării elementelor unui vector; alege – procedura FAR destinată alegerii elementului minim dintr-un

masiv bidimensional; calcul – procedură FAR pentru calculul expresiei A/B unde A este suma

returnată de prima procedură, iar B este minimul returnat de procedura alege.

Datele cu care se vehiculează sunt:

vector dw 100 dup ( ? ) matrice dd 10 dup ( 20 ) total dw 0 minim dd ? raport dd ?

Definirea obiectului de tip calcul_min trebuie să includă într-o tabelă

metodele (procedurile asociate) şi separat variabilele cu care se va lucra. Pointerii spre procedurile NEAR se definesc WORD, iar pointerii

procedurilor FAR se definesc DWORD. Tabela metodelor conţine pentru fiecare metodă numele variabilei pointer

asociată procedurii, tipul şi numele procedurii. Obiectul calcul_min se defineşte astfel:

calcul_min struc global method ptr_suma:word=suma ptr_alege:dword=alege ptr_calcul:dword=calcul vector dw 100 dup (?) matrice dd 10 dup (20 dup (?)) total dw 0 minim dd ?

raport dd ?

Page 83: limbaj de asamblare-ivan

481

calcul_min ends În general, pentru a defini complet un obiect în lista metodelor se vor

include procedurile constructor şi destructor care returnează tipul FAR, necesitând descriptorul DWORD.

Pentru metode se utilizează atributele GLOBAL (pentru a da caracter global tabelei de metode) virtuale NEAR şi FAR (pentru a determina conversiile specifice lucrului cu 16, respectiv, 32 biţi). Aceste atribute vor fi numite modificatori.

În programarea orientată obiect moştenirea este o proprietate importantă, care presupune un obiect de bază sau obiect părinte. Legătura între obiectul de bază şi obiectul descendent se efectuează specificând la definirea acestuia din urmă, numele părintelui.

Toţi operanzii cu care se vehiculează într-un obiect se definesc sub forma unor membri dintr-un articol.

Pentru definirea unui obiect se foloseşte definirea: nume struc modificator obiect_părinte method ptr-1: tip-1=nume-proc-1 ptr-2: tip-2=nume_proc-2 . . . . . . ptr-n:tip-n=nume_proc-n membrul-1 membrul-2 . . . . . . . . membrul-k

nume ends

Definirea unei variabile obiect se efectuează ca şi în cazul articolelor, însă prin utilizarea constructorilor se face alocarea şi iniţializarea acestor structuri complexe, care preiau adresele de început ale instrucţiunilor executabile din procedurile membre. Apelarea metodelor definite static sau virtual necesită o serie de informaţii care să permită localizarea segmentului unde se află variabila obiect din care metoda este membru, numele pointerului care referă metoda precum şi cuvintele cheie CALL şi METHOD. Bibliotecile standard conţin definiri de obiecte pentru lucrul cu liste, cozi, stive şi arbori binari. Metodele vizează operaţiile uzuale cu structurile de date dinamice, precum: creare, inserare, traversare şi ştergere. Diversitatea tipurilor de structuri de date este dată de modul în care este construit asamblorul. De aceea, este foarte important ca înainte de al lucra cu un asamblor să se vadă care sunt tipurile de date şi ce structuri de date pot fi utilizate şi cum. Nu toate asambloarele operează cu obiecte, cu tipuri de date sinonime (typedef) cu tabele sau cu date de tip ENUM. În toate cazurile este necesar să se urmărească modul de definire a tipului, definirea variabilelor de tipul respectiv,

Page 84: limbaj de asamblare-ivan

482

iniţializarea lor la definire şi cum se referă în program variabilele sau componente ale acestora.

8

IMPLEMENTAREA STRUCTURILOR FUNDAMENTALE

8.1 Programarea structurată Programarea structurată trebuie privită ca o modalitate de disciplinare a programatorilor şi de introducere a unui stil unic în scrierea de texte sursă. Eliminarea instrucţiunii GO TO din programe este numai aparentă deoarece apelurile de proceduri, instrucţiunile PERFORM, conţin în mod mascat instrucţiuni de salt necondiţionat în modulele obiect rezultate la compilare. Limbajele de asamblare conţin dezvoltări care implementează aproape perfect cerinţele programării structurate. La nivel de bază implementarea structurilor fundamentale este o traducere cu posibilităţile specifice ale schemelor asociate acestor structuri. Obiectivul implementării structurilor folosind construcţii tipizate este de a obţine programe lizibile, cu un nivel de liniaritate mai ridicat şi mai simple, chiar dacă lungimea lor ca număr de instrucţiuni este mare. 8.2 Structura liniară

Structura liniară apare ca o secvenţă de instrucţiuni ce nu conţine salturi condiţionate sau necondiţionate, instrucţiuni de revenire în procedura apelatoare. Deşi instrucţiunea CALL este însoţită şi de un salt necondiţionat, ca factor de liniarizare a programelor se include în mulţimea instrucţiunilor care contribuie la realizarea de structuri liniare. Structura liniară conţine instrucţiuni care se execută una după cealaltă, exact în ordinea în care apar în secvenţă. Secvenţa: mov ax,0 add ax, b ; ax:=ax+b add ax, c ; ax:=ax+c sub ax, d ; ax:=ax-d mul ax, e ; dx:ax:=ax × e mov prod, ax ; prod:=ax

Page 85: limbaj de asamblare-ivan

483

mov prod+2,dx ; prod+2:=dx reprezintă un exemplu tipic de structură liniară.

Secvenţa:

cld mov cx, OFFSET VAR call prel mov x, dx mov y, c1 xor cx, cx call prel mov ah, 9 mov dx, OFSET text2 int 21h int 11h

corespunde de asemeni unei structuri liniare, instrucţiunile CALL şi INT însemnând mai întâi integrarea unor secvenţe de instrucţiuni din proceduri, instrucţiunile de salt spre ele fiind numai un mijloc.

În plus, ele se traduc prin execută procedura prel, respectiv, execută întreruperea 21h sau 11h, ca operaţii mai complexe, tot aşa cum se execută un transfer prin instrucţiunea mov sau o poziţionare a indicatorului de condiţie DF prin instrucţiunea CLD.

8.3 Structură alternativă

În limbajele evoluate, structura alternativă este asociată instrucţiunii IF – THEN – ELSE.

Prin convenţie, implementarea structurii alternative presupune evaluarea unei expresii, poziţionarea indicatorilor de condiţie sau efectuarea unei comparări cu poziţionarea de indicatori de condiţie.

Dacă este adevărată condiţia se execută secvenţa etichetată “adevărat”, iar în caz contrar se execută secvenţa etichetată “fals”.

Compararea se realizează prin instrucţiunile CMP, CMPSB, CMPSW. Poziţionarea indicatorilor se efectuează şi prin operaţii de scădere pentru a compara sau prin alte operaţii, excepţia făcând instrucţiunile MOV, CBW, CWD, LEA, CALL, ESC, HLT, JXX, LDS, LOCK, LOOP, LES, NOP, CUT, POP, PUSH, RET, STOS, WAIT, XCHG, XALT.

Secvenţa: mov ax, a cmp ax, b jg adevărat fals:

mov maxim, b

Page 86: limbaj de asamblare-ivan

484

jmp final adevărat : mov maxim, ax final: nop

pune în evidenţă faptul că secvenţa corespunzătoare condiţiei verificate se află după secvenţa etichetată “fals”. Pentru a aduce secvenţa etichetată “adevărat” cât mai aproape de instrucţiunea care poziţionează indicatorii de condiţie ca în limbajele evoluate pentru IF, se schimbă modul de efectuare a testelor.

Secvenţa: mov ax, a cmp ax, b jle fals

adevărat: mov maxim, ax

jmp final fals:

mov maxim, b final:

nop

este apropiată de instrucţiunea: IF(a>b)

maxim:=a ELSE maxim:=b;

În cazul în care în program se cere implementarea structurii pseudo-alternative IF – THEN, este preferabil să se modifice de la început testul indicatorilor de condiţie pentru a obţine o construcţie naturală, fără instrucţiuni de salt necondiţionat introduse în plus.

Secvenţa: cmp ax,0 jge final mov ah, 9 mov dx, OFFSET text int 21h ;afişare text final:

mov ah, 4ch int 21h este preferată secvenţei:

Page 87: limbaj de asamblare-ivan

485

cmp ax, 0 jl negativ jmp final negativ:

mov ah, 9 mov dx, OFFSET text int 21h final:

mov ah, 4ch int 21h

care are două instrucţiuni de salt consecutive, greu de acceptat în programe scrise în limbaj de asamblare eficiente.

Compunerea structurilor alternative impune realizarea unei liniarizări a secvenţelor fără a introduce multe instrucţiuni de salt.

Secvenţei: IF (condiţie_1) THEN IF (condiţie_2) THEN secvenţa_1 ELSE secvenţa_2 ELSE IF (condiţie_3) THEN secvenţa_3 ELSE secvenţa_4;

scrisă într-un limbaj evoluat, îi va corespunde liniarizarea:

condiţie_1 salt fals_1 adevărat_1: condiţie_2 salt fals_2 secvenţa_1 salt final_1 fals_2: secvenţa_2 salt final_2 fals_1: condiţie_3 salt fals_3 adevărat_3: secvenţa_3 salt final_3 fals_3: secvenţa_4 final_1:

Page 88: limbaj de asamblare-ivan

486

nop final_2:

nop final_3:

nop

Dacă se doreşte o apropiere de limbaje evoluate se introduc comentarii în care apar cuvintele IF, THEN, ELSE pe câte o linie a textului sursă din programul assembler. Unele dintre etichete pot lipsi, cum secvenţele de instrucţiuni consecutive NOP pot fi reduse la o singură apariţie. Se va urmări ca regulă poziţionarea secvenţelor aşa fel încât instrucţiunile de salt să refere instrucţiuni ce vor fi scrise în continuare.

8.4 Structura repetitivă standard Se consideră o variabilă de control care ia valorile n, n-1, n-2,…, 3, 2, 1, 0 şi o secvenţă care se repetă atât timp cât variabila de control este diferită de zero. Testul asupra variabilei de control urmează decrementării acesteia, iar secvenţă de repetat precede decrementarea variabilei de control (figura .8.1)

nu da

Figura 8.1- Structură repetitivă condiţionată posterior

cx:=n

secvenţa de repetat

cx:=cx-1

cx=0

Page 89: limbaj de asamblare-ivan

487

Într-un program se implementează această structură standard, condiţionată posterior cu instrucţiunea loop. Instrucţiunea loop decrementează registrul CX, îl testează şi efectuează un salt necondiţionat la instrucţiunea a cărei etichetă este indicată pe aceeaşi linie cu ea.

Secvenţa:

xor si, si xor di, di

xor ax, ax mov cx, 15 ciclu:

add ax, x [si] add si, 2 loop ciclu mov suma, ax exemplifică utilizarea structurii repetitive standard pentru însumarea primelor cincisprezece elemente ale unui masiv unidimensional.

Dacă apare problema însumării elementelor fiecărei linii dintr-un masiv bidimensional, separat, este necesară includerea unei structuri repetitive standard într-o structură repetitivă standard, ca în secvenţa:

xor bx, bx mov cx, NR_LINII ciclul1: xor ax, ax xor si, si push cx mov ax, NR_COL ciclul2:

add ax, mat [bx][si] add si,2 loop ciclul2 mov sum [di], ax add di, 2 add bx, NR_COL * 2 pop cx loop ciclul1

Registrul BX este folosit ca bază, registrul CX are dublu rol (contorizează numărul de elemente de pe linia matricei şi, respectiv, numărul de linii al matricei). Trecerea de la o linie la alta se asigură prin modificarea registrului BX. Registrele index sunt SI (pentru accesarea elementelor de pe coloanele matricei) şi DI (pentru accesarea elementelor vectorului de sume care sunt calculate.

S-au definit constantele simbolice NR_LIN şi NR_COL pentru a specifica câte linii şi câte coloane au matricea.

Page 90: limbaj de asamblare-ivan

488

Structura repetitivă condiţionată anterior este definită de secvenţa de schemă logică dată în figura 8.2.

Figura 8.2 – Structura repetitivă condiţionată anterior În limbajul de asamblare lipseşte instrucţiunea care implementează direct

această structură. Vor exista două instrucţiuni de salt, una pentru întreruperea repetărilor şi alta, la sfârşitul secvenţei de repetat care va relua evaluarea expresiei condiţie şi testarea ei. Implementarea este necesară în cazul în care nu se poate obţine o transformare a structurii repetitive într-o structură standard. De exemplu, pentru însumarea elementelor dintr-un vector până când suma depăşeşte 1000 sau sunt epuizate elementele vectorului, în secvenţa:

mov ax, 0 mov si, 0 mov cx, 0

modificare operanzi din

expresia condiţie

iniţializări

expresia condiţie

secvenţa de repetat

Page 91: limbaj de asamblare-ivan

489

ciclu: cmp ax, 1000

jg final cmp cx, n je final add ax, x[si] add si, 2 inc cx jmp ciclu final:

mov suma, ax mov număr, cx

cele două teste conduc spre instrucţiunea cu eticheta final, iar asigurarea repetării este dată de instrucţiunea jmp ciclu. Controlul numărului de repetări se asigură atât prin contorul CX cât şi prin valoarea registrului AX. Dacă testul este efectuat asupra registrului CX şi în secvenţa repetitivă nu se modifică în mod corespunzător (dacă se porneşte de la zero are loc incrementarea, iar dacă se porneşte de la limita superioară a intervalului are loc decrementarea) efectul este ciclarea infinită.

În toate cazurile se urmăreşte stabilirea corectă a numărului de repetări. Testarea incorectă a limitei intervalului va conduce fie la neincluderea în calcule a unor elemente (primul element dacă se va începe cu limita interioară unu), fie la includerea unui element din afara intervalului (dacă se începe cu elementul zero şi se numără n elemente în loc de n-1).

8.5 Structura repetitivă condiţionată posterior

Forma nestandard este utilizată când numărul de repetări e dependent de valoarea unei expresii sau de semnul acesteia sau când variabila de control generează termenii unei progresii, alta decât progresia aritmetică cu primul termen n, al doilea termen n-1, iar ultimii termeni nu sunt 3,2,1,0.

Se utilizează condiţionarea posterioară pentru a nu mai repeta unele instrucţiuni. De exemplu, pentru afişarea unor texte introduse de la terminal se procedează astfel:

se afişează mesajul: “INTRODUCEŢI TEXTUL:”; se introduce textul de către utilizator; se afişează mesajul: “DORIŢI SĂ CONTINUAŢI? (d/n)”; se tastează “d” sau “n” şi dialogul continuă sau e terminat. Dacă se utilizează structura repetitivă condiţionată anterior este necesar ca

înainte de testul de continuare, în mod artificial să se iniţializeze variabila RĂSPUNS (definită RĂSPUNS EQU al) cu constanta “n”. Structura repetitivă condiţionată posterior evită acest impediment, aşa cum se poate vedea în secvenţa:

ciclu:

mov ah,9

Page 92: limbaj de asamblare-ivan

490

mov dx, OFFSET textl ;text invitaţie int 21h ;text şir caractere mov ah, 0ah mov dx, OFFSET text2 ;text introdus int 21h mov ah,9 mov dx, OFFSET text3 ;text de continuare int 21h mov ah,1 ;funcţia de int 21h ;introducere caractere: cmp a1,”d” jz ciclu cmp a1, “D” jz ciclu mov ah, 4Ch int 21h S-a optat pe posibilitatea de a considera corectă tastarea pentru continuare a

caracterului “d” sau a lui “D”. Variabila textl este iniţializată cu şirul de caractere “INTRODUCEŢI TEXTUL:”, variabila text3 este iniţializată cu şirul “DORIŢI SĂ CONTINUAŢI? (d/n)”, iar variabila text2 este definită prin DB 100 DUP (?) ceea ce înseamnă că textele introduse în cadrul dialogului nu pot depăşi 100 de caractere. Se are în vedere posibilitatea de a defini şi caracterele de control care să asigure finalul şirurilor şi returul de car.

8.6 Structura alternativă multiplă

Are corespondent în limbajul C instrucţiunea switch şi în limbajul PASCAL instrucţiunea case. În limbajele FORTRAN şi COBOL se utilizează forme generale ale instrucţiunii GO TO cu mai multe etichete şi cu o variabilă de indicare a etichetei selectate, prin poziţie. Se consideră o variabilă de control care poate lua una din valorile VAL1,….,VALn, pentru fiecare din valori se ia câte o secvenţă de instrucţiuni, respectiv, secvenţa_1, secvenţa_2,…,secvenţa_n.

Dacă variabila de control are o valoare diferită de cele specificate se va executa secvenţa-0. Fiecare secvenţă are o etichetă de început şi se termină cu o instrucţiune de salt necondiţionat spre un punct comun de continuare a programului. Eticheta de început a secvenţei I este etich_i, iar eticheta punctului de continuare este etich_m. Variabila de control are numele var. Structura alternativă multiplă va fi implementată prin construcţia:

cmp var, VALI je etich_1 cmp var, VAL2 je etich_2 … cmp var, VAL_n

Page 93: limbaj de asamblare-ivan

491

jz etich_n etich_0: secvenţa_0 jmp etich_m etich_1 secvenţa_1 jmp etich_m … etich_n: etiche secvenţa_n etich_m:

nop Structura alternativă multiplă este caracteristică meniurilor în care

utilizatorul poate tasta un caracter îi permite să selecteze o anumită opţiune. Dacă, de exemplu se cere evaluarea expresiei:

cazuricelelalteinxdacaaxdacabxdacabaxdacabaxdacaba

e

051413*21

se definesc şi se iniţializează variabilele a şi b şi se defineşte variabila e. Diversitatea expresiilor conduce la imposibilitatea includerii unei structuri repetitive în care x să fie variabilă de control.

Secvenţa: cmp x,1 je aduna cmp x,2 je scade cmp x, 3 je produs cmp x, 4 je decrement cmp x,5 je increment zero:

mov e, 0 jmp final aduna:

Page 94: limbaj de asamblare-ivan

492

mov ax, a add ax, b mov e, ax jmp final scade: mov ax, a sub ax, b mov e, ax

jmp final produs: mov ax, a mov bx, b mul bx mov e, ax jmp final decrement: mov ax, a dec ax mov e, ax final:

nop

implementează structura alternativă multiplă. Se poate vedea că s-a obţinut o construcţie uşor de interpretat, în care variabilele a şi b nu sunt modificate. Printr-o altă concepţie, instrucţiunea mov e, ax poate fi integrată secvenţei etichetate final.

O altă formă de implementare a structurii alternative multiple este legată de includerea secvenţelor în vecinătatea instrucţiunilor de test, selecţiile efectuându-se din aproape în aproape. Evaluarea expresiei e se realizează prin secvenţa:

cmp x, 1 jne salt_1 adună:

mov ax, a add ax, b

jmp final salt_1:

cmp ax, 2 jne salt_2 scade:

mov ax, a sub ax, b jmp final salt_2:

cmp x, 3 jne salt_3 produs:

mov ax, a mul b1 jmp final salt_3:

Page 95: limbaj de asamblare-ivan

493

cmp x, 4 jne salt_4 decrement: mov ax, b dec ax jmp final salt_4:

cmp x ,5 jne salt_5 increment:

mov ax, a inc ax jmp final salt_5:

mov ax, 0 final:

mov e, ax Toate structurile descrise pot fi îmbunătăţite sau ignorate dacă se urmăreşte

construirea de secvenţe cât mai concentrate. Totuşi, pentru realizarea unui stil de programe specific lucrului în echipă sunt preferate convenţii de tipul celor prezentate ca implementări ale structurilor fundamentale de programe. Designul limbajului de programare ia în considerare şi acurateţea limbajului de a reflecta proprietăţile structurate.

9

ARITMETICI BINARE 9.1 Aritmetica binară pe 8 biţi

Aritmetica pe 8 biţi presupune definirea operanzilor sursă ai expresiilor cu descriptorul DB.

Pentru efectuarea adunării se utilizează instrucţiunea add. De exemplu, pentru evaluarea expresiei:

e = a + b

se utilizează secvenţa:

Page 96: limbaj de asamblare-ivan

494

a db 20 b db 13 e db ?

mov al, a add al, b mov e, al

Regiştrii care participă la aritmetica pe 8 biţi sunt AH, AL, BH, BL, CH, CL, DH

şi DL.

Iniţializarea cu zero a unui registru se efectuează prin instrucţiuni precum:

mov ah, 0 sub bl, bl xor cl, cl mov ch, cl

Scăderea pe 8 biţi se efectuează cu instrucţiunea sub. Secvenţa:

x db 25 y db 7 z db ? mov ch, x mov dl, y sub ch, dl mov z, ch

evaluează expresia:

z = x - y

Înmulţirea pe 8 biţi utilizează instrucţiunea mul. Deînmulţitul se memorează în registrul AL, iar rezultatul se va găsi în registrul AH : AL. Secvenţa:

a db 7 b db 5 c db 8 e dw ? f dw ? g dw ? mov al, a mov bl, b mul bl

Page 97: limbaj de asamblare-ivan

495

mov e, ax mov al, a mul c mov f, ax mov al, a mul 3 mov g, ax

evaluează expresiile :

e = a * b f = a * c g = 3 * a

Instrucţiunea mul realizează operaţia de înmulţire considerând operanzii ca

numere fără semn (foloseşte bitul de semn ca făcând parte din numărul respectiv şi nu ca semnul acestuia). Atunci când operatorii sunt consideraţi ca având semn (din 8 biţi, cel mai semnificativ arată semnul) trebuie să se folosească instrucţiunea imul, aceasta folosind bitul de semn în mod corespunzător. Rezultatul este considerat număr cu semn. Modul de folosire al acesteia este identic cu cel al instrucţiunii mul.

Operaţia de împărţire utilizează instrucţiunea div. Secvenţa:

a db 32 b db 4 cat db ? rest db ? mov al, a cbw div b mov cat, ah mov rest, al

evaluează expresia:

cat = [a/b] rest = a – cat * b

unde parantezele [ ] reprezintă operatorul de extragere a părţii întregi. Pentru efectuarea împărţirii este obligatoriu ca deîmpărţitul să se afle în

registrul AX, după efectuarea conversiei de la dată de tip DB la dată de tip DW. Câtul împărţirii se află în registrul AH. Restul împărţirii se află în registrul AL.

Împărţitorul se referă fie ca nume de variabilă: div b

Page 98: limbaj de asamblare-ivan

496

fie ca registru:

div cl sau

div dh sau

div bl

fie sub formă de operand imediat: div 7

sau div 2

În mod asemănător înmulţirii, împărţirea cu semn nu se mai realizează cu instrucţiunea div, ci cu idiv. Aceasta recunoaşte bitul de semn şi îl foloseşte corespunzător, într-o sintaxă identică cu cea a instrucţiunii div. 9.2 Aritmetica binară pe 16 biţi

Aritmetica pe 16 biţi presupune definirea operanzilor sursă ai expresiilor cu descriptorul DW.

Pentru efectuarea adunării se utilizează instrucţiunea add. De exemplu, pentru evaluarea expresiei:

e = a + b se utilizează secvenţa:

a dw 20 b dw 13 e dw ?

mov ax, a add ax, b mov e, ax

Regiştrii care participă la aritmetica pe 16 biţi sunt AX, BX, CX ŞI DX

Iniţializarea cu zero a unui registru se realizează prin instrucţiuni precum:

mov ax, 0 sub bx, bx

Page 99: limbaj de asamblare-ivan

497

xor cx, cx

Scăderea pe 16 biţi se efectuează cu instrucţiunea sub. Secvenţa:

x dw 25 y dw 7 z dw ? mov cx, x mov dx, y sub cx, dx mov z, cx

evaluează expresia:

z = x-y

Înmulţirea pe 16 biţi utilizează instrucţiunea mul. In timp ce deînmulţitul se memorează în registrul AX, rezultatul se va găsi în perechea de regiştrii DX:AX. Secvenţa următoare:

a dw 7 b dw 5 c dw 8 e dd ? f dd ? g dd ? mov ax, a mov bx, b mul bx mov e, ax mov e+2, dx mov ax, a mul c mov f, ax mov f+2, dx mov ax, a mul 3 mov g, ax mov g+2, dx

evaluează expresiile :

e = a * b f = a * c g = 3 * a

Page 100: limbaj de asamblare-ivan

498

Pentru o înmulţire în care operanzii sunt consideraţi ca având semn, se va

folosi, la fel ca şi pe 8 biţi, instrucţiunea imul. Operaţia de împărţire se realizează prin instrucţiunea div.

Secvenţa:

a dw 32 b dw 4 cat dw ? rest dw ? mov ax, a cwd div b mov cat, ax mov rest, dx

evaluează expresia:

cat = [a/b] rest = a – cat * b

unde parantezele [ ] reprezintă operatorul de extragere a părţii întregi. Pentru efectuarea împărţirii deîmpărţitul se află în registrul AX, iar câtul

împărţirii se află în registrul AX. Restul împărţirii se găseşte în registrul DX. Împărţitorul se referă fie ca nume de variabilă:

div b

fie ca registru: div cx

sau div bx

fie sub formă de operand indexat: div 7

sau

div 2

În cazul în care operanzii sunt cu semn, situaţia se repetă: se va folosi instrucţiunea idiv.

Page 101: limbaj de asamblare-ivan

499

9.3 Aritmetica binară pe 32 biţi

Atunci când se lucrează cu un procesor cu regiştri de până la 16 biţi şi fără coprocesor, aritmetica binară pe 32 biţi se realizează prin folosirea concomitentă a doi regiştri pe 16 biţi.

Adunarea pe 32 biţi se face cu ajutorul instrucţiunii adc (adunare cu transport). Astfel, secvenţa:

dp1 dd A92Fh dp2 dd 49837h dpsum dd ? mov ax, dp1 add ax, dp2 mov dpsum, ax mov ax, dp1+2 adc ax, dp2+2 mov dpsum+2, ax

evaluează expresia:

dpsum = dp1 + dp2

0 0 0 6 A 9 2 F + 0 0 0 4 9 8 3 7 0 0 0 B 4 1 6 6

sau detaliat: carry 1+ 0 0 0 6+ A 9 2 F + 0 0 0 4 9 8 3 7 0 0 0 B 4 1 6 6

Scăderea pe 32 de biţi se face cu ajutorul instrucţiunii sbb (scăderea cu

împrumut). Secvenţa:

a dd 6A92Fh b dd 4B837h c dd ?

mov ax, b sub ax, a mov bx, b+2

Page 102: limbaj de asamblare-ivan

500

sbb bx, a+2 mov c, ax mov c+2, bx

evaluează expresia:

c = a - b

0 0 0 6 A 9 2 F - 0 0 0 4 B 8 3 7 0 0 0 1 F 0 F 8

sau detaliat:

0 0 0 6- A 9 2 F - 0 0 0 4- B 8 3 7 carry 1

0 0 0 1 F 0 F 8

În continuare se prezintă un exemplu de calcul ce foloseşte combinat

adunarea şi scăderea pe 32 de biţi. Astfel, dacă se doreşte calcularea unei expresii de forma: W = x + y +24 – z se foloseşte o secvenţa de cod de genul:

x dd <valoare oarecare> y dd <valoare oarecare> z dd <valoare oarecare> w dd ?

mov ax, x mov dx, x+2 add ax, y adc dx, y+2 add ax, 24 adc dx, 0 sbb ax, z sbb dx, z+2 mov w, ax mov w+2, dx

Înmulţirea pe 32 de biţi, atunci când doar registre pe 16 biţi sunt

disponibile, se face prin despărţirea fiecărui operand în două numere. Astfel, dacă

Page 103: limbaj de asamblare-ivan

501

avem de înmulţit două numere, A şi B, fiecare cu o valoarea ce depăşeşte capacitatea de memorare a 16 biţi, acestea se pot scrie şi sub forma:

A = a1 216 + a2 B = b1 216 + b2

Prin urmare, înmulţirea acestor două numere se descompune astfel:

a1 216 + a2 X b1 216 + b2

a1 b2 216 + a2 b2 + a1 b1 232 + a2 b1 216

a1 b1 232 + (a1 b2 + a2 b1 ) 216 + a2 b2

Acestei descompuneri îi corespunde următoarea secvenţă de cod: mov ax, a mul b mov rez, ax mov cx, dx mov ax, a + 2 mul b mov bx, dx add cx, ax adc bx, 0 mov ax, a mul b + 2 add cx, ax adc bx, dx mov rez + 2, cx mov cx, 0 adc cx, 0 mov ax, a + 2 mul b + 2 add ax, bx adc dx, cx mov rez + 4, ax mov rez + 6, dx

Pentru împărţirea pe 32 de biţi nu putem aplica aceeaşi metodă de

descompunere a numerelor pe 32 de biţi în câte două numere pe 16 biţi. Pentru a împărţi pe A la B, cu aflarea câtului C şi a restului R, recurgem la

o metodă simplă: adunarea repetată a lui B până la depăşirea numărului A. În pseudocod, această metodă arată astfel:

Page 104: limbaj de asamblare-ivan

502

S = B C = 0 cât timp ( S <= A ) { C = C + 1 S = S + B } R = A – ( S – B )

Această metodă este relativ rapidă, deoarece nu foloseşte decât operaţii de

adunare şi de scădere pe 32 de biţi. În limbaj de asamblare, transpunerea pseudocodului de mai sus arată astfel:

a dd <valoare> b dd <valoare> c dd ? r dd ? s dd ? . . . . . . . . . mov ax, offset s mov word ptr [ax], b mov word ptr [ax+2], b+2 mov ax, offset c mov word ptr [ax], 0 mov word ptr [ax+2], 1

iar: cmp32 s, a jg et add32 c, 1 add32 s, b jmp iar

et: sub32 s, b mov ax, offset r mov word ptr [ax], a mov word ptr [ax+2], a+2 sub32 r, s

unde cmp32, add32, sub32 sunt macrodefiniţii pentru compararea, adunarea şi scăderea numerelor pe 32 de biţi. Pentru construirea macrodefiniţiilor, add32 si sub32, s-au descris mai sus blocurile de instrucţiuni, în timp ce pentru cmp32, codul macrodefiniţiei este următorul:

cmp32 MACRO va1, val2

mov ax, val cmp ax, val2 jnz iesire

Page 105: limbaj de asamblare-ivan

503

mov ax, val + 2 cmp ax, val2 + 2

iesire: ENDM

Se compară mai întâi părţile mai semnificative ale celor două valori. Numai

dacă acestea sunt egale se trece la compararea parţilor mai puţin semnificative. În final indicatorii de condiţie vor fi setaţi corespunzător.

Aceste structuri de cod pot fi folosite atunci când nu se poate face uz de un coprocesor matematic, iar microprocesorul nu posedă regiştri pe mai mult de 16 biţi. Odată cu apariţia microprocesoarelor pe 32 de biţi, se pot folosi registrele generale EAX, EBX, ECX, EDX. Aceste metode încă prezintă importanţă pentru operaţii cu numere de peste 64 de biţi, ele putând fi extinse foarte uşor pe orice număr de biţi, atunci când se lucrează cu numere foarte mari.

10

ARITMETICI ZECIMALE 10.1 Ajustări

Aritmetica zecimală presupune efectuarea operaţiilor bait cu bait în cazul

reprezentării despachetate sau nibble cu nibble pentru reprezentarea împachetată. Pentru a menţine la nivelul fiecărui bait reprezentări ale cifrelor de la 0 la 9 se impune efectuarea de ajustări care se fac în mod diferit în funcţie de operaţiile efectuate asupra operanzilor. De exemplu, ajustarea pentru adunarea zecimal despachetată înseamnă corectarea fiecărui nibble mai puţin semnificativ a fiecărui octet în cazul unei depăşiri la adunare. Rezultatul adunării numerelor:

0 1 0 3 0 5 0 8 + 0 6 0 6 0 1 0 0 0 7 0 9 0 6 0 8

nu necesită ajustări pentru că toate cifrele corespund alfabetului pe care se defineşte reprezentarea zecimal despachetată. În schimb, rezultatul adunării numerelor:

0 7 0 5 0 6 +

Page 106: limbaj de asamblare-ivan

504

0 8 0 7 0 9 0 F 0 C 0 F

conţine elemente care nu sunt în alfabetul reprezentării zecimal despachetate (C, F). Aplicând instrucţiuni de ajustare aaa bait de bait de la dreapta la stânga se realizează corecţia şi rezultatul acestei adunări va fi: 01060305.

Efectul unei instrucţiuni de ajustare asupra unui bait stocat în AL se exprimă în pseudocod astfel: dacă ((AL) & 0Fh)>9 sau (AF)=1 atunci { (AL)←(AL)+6 (AH)←(AH)+1 (AF)←1 (CF)←(AF) (AL)←(AL) & 0Fh } Ajustarea pentru adunarea zecimal împachetat realizează corecţia fiecărui nibble a octetului unde a fost semnalată depăşirea. Astfel pentru adunarea numerelor:

0 3 7 8 5 4 + 0 7 8 5 8 7 0 A F D D B

aplicând ajustarea bait de bait se va obţine numărul: 11 64 41. Există următoarele instrucţiuni pentru efectuarea de ajustări: Pentru reprezentări ale numerelor în format zecimal despachetat:

AAA – (ASCII Adjust for Addition) care face o ajustare a registrului acumulator pentru adunare (descrisă mai sus);

AAD – (ASCII Adjust for Division): ajustare pentru împărţire. Această instrucţiune face o înmulţire a registrului AH cu 10, rezultatul este adunat cu registrul AL, iar rezultatul final este depus în AL, registrul AH fiind iniţializat cu 0.

AAM – (ASCII Adjust for Multiply): ajustare pentru înmulţire. Conţinutul registrului AH este înlocuit cu câtul împărţirii registrului AL la 10, apoi conţinutul registrului AL este înlocuit cu restul împărţirii.

AAS – ((ASCII Adjust for Subtraction): ajustare pentru scădere. Modificările asupra registrului acumulator efectuate de această instrucţiune sunt exemplificate cu ajutorul limbajului pseudocod:

dacă ((AL) & 0Fh)>9 sau (AF)=1 atunci

Page 107: limbaj de asamblare-ivan

505

{ (AL)←(AL)-6 (AH)←(AH)-1 (AF)←1 (CF)←(AF) (AL)←(AL) & 0Fh }

Pentru reprezentări ale numerelor în format zecimal împachetat: DAA – (Decimal Adjust for Addition): ajustare pentru adunare: dacă cel mai

puţin semnificativ nibble al registrului AL este mai mare decât 9 sau dacă AF este setat pe 1, atunci la registrul AL se adună 6 si AF este setat pe 1. Dacă AL este mai mare decât 9Fh sau dacă AF este setat pe 1, atunci la registrul AL se adună 60h şi AF este setat pe 1.

DAS – (Decimal Adjust for Subtraction): ajustare pentru scădere: dacă cel mai puţin semnificativ nibble al registrului AL este mai mare decât 9 sau dacă AF este setat pe 1, atunci din registrul AL se scade 6 si AF este setat pe 1. Dacă AL este mai mare decât 9Fh sau dacă AF este setat pe 1, atunci din registrul AL se scade 60h şi AF este setat pe 1.

10.2 Adunarea şi scăderea ADD - Arithmetic Addition (Adunare) Folosire: ADD dest, src Flag-uri modificate: AF CF OF PF SF ZF Adună operandul src la operandul dest şi memorează rezultatul în operandul dest. Ambii operanzi au reprezentare binară.

Timpi Lungime Operatori 808x 286 386 486 Bytes reg,reg 3 2 2 1 2 mem,reg 16+EA 7 7 3 2-4 (W88=24+EA) reg,mem 9+EA 7 6 2 2-4 (W88=13+EA) reg,immed 4 3 2 1 3-4 mem,immed 17+EA 7 7 3 3-6 (W88=23+EA) accum,immed 4 3 2 1 2-3

De exemplu, secvenţa: termen1 dw 11 termen2 dw 23

Page 108: limbaj de asamblare-ivan

506

total dw ? …………….. mov ax, termen1 add ax, termen2 mov total, ax ……………

realizează adunarea valorii variabilei termen2 la conţinutul registrului AX, în care fusese mutată valoarea variabilei termen1. Rezultatul adunării este depus în registrul AX, iar apoi este mutat în variabila total.

SUB – Subtract (scădere) Folosire: SUB dest, src Flag-uri modificate: AF CF OF PF SF ZF Sursa este scăzută din destinaţie şi rezultatul este copiat în destinaţie.

Timpi Lungime Operatori 808x 286 386 486 Bytes reg,reg 3 2 2 1 2 mem,reg 16+EA 7 6 3 2-4 (W88=24+EA) reg,mem 9+EA 7 7 2 2-4 (W88=13+EA) reg,immed 4 3 2 1 3-4 mem,immed 17+EA 7 7 3 3-6 (W88=25+EA) accum,immed 4 3 2 1 2-3 Astfel, secvenţa:

termen1 dw 7 termen2 dw 15 dif dw ?

…………… mov ax, termen2 sub ax, termen1 mov dif, ax ……………

realizează scăderea valorii variabilei termen1 din conţinutul registrului AX, în care fusese mutată valoarea variabilei termen2. Rezultatul scăderii este copiat în registrul AX, iar apoi este mutat în variabila dif.

10.3 Înmulţirea şi împărţirea MUL - Unsigned Multiply (înmulţire fără semn)

Page 109: limbaj de asamblare-ivan

507

Folosire: MUL src Flag-uri modificate: CF OF (AF,PF,SF,ZF nedefiniţi) Înmulţeşte fără semn acumulatorul cu operandul sursă. Dacă operandul sursă src este o valoare pe un octet, atunci AL este folosit ca deînmulţit şi rezultatul se memorează în AX. Dacă operandul src este un cuvânt, atunci AX este înmulţit cu sursa şi DX:AX primeşte rezultatul. Dacă sursa este un dublu-cuvânt atunci EAX este înmulţit cu sursa şi rezultatul este pus în EDX:EAX.

Timpi Lungime Operatori 808x 286 386 486 Bytes reg8 70-77 13 9-14 13-18 2 reg16 118-113 21 9-22 13-26 2 reg32 - - 9-38 13-42 2-4 mem8 (76-83)+EA 16 12-17 13-18 2-4 mem16 (124-139)+EA 24 12-25 13-26 2-4 mem32 - - 12-21 13-42 2-4 Astfel, secvenţa:

factor1 dw 41 factor2 dw 15 produs dd ?

…………….. mov ax, factor1 mul ax, factor2 mov produs, ax mov produs+2, dx ……………

realizează înmulţirea valorii variabilei factor2 cu conţinutul registrului AX, în care fusese mutată valoarea variabilei factor 1. Produsul se obţine în registrul dublu DX:AX. Rezultatul înmulţirii este apoi mutat în variabila produs definită pe dublu cuvânt.

Această operaţie presupune obligativitatea folosirii registrului AX pentru deînmulţit şi a registrului dublu DX:AX pentru rezultat. Înmulţitorul se găseşte într-un registru, într-o zonă de memorie sau este o constantă imediată.

DIV – Divide (împărţire) Folosire: DIV src Flag-uri modificate: (AF,CF,OF,PF,SF,ZF nedefiniţi)

Page 110: limbaj de asamblare-ivan

508

Acumulatorul este împărţit la sursă binar fără semn. Dacă operatorul sursă este un octet atunci AX este împărţit de sursă (DX trebuie să fie 0); câtul împărţirii este pus în AL, iar restul în AH. Dacă operatorul sursă este un cuvânt, atunci DX:AX este împărţit la sursă; câtul împărţirii este pus în AX, iar restul este pus în DX.

Timpi Lungime Operatori 808x 286 386 486 Bytes reg8 80-90 14 14 16 2 reg16 144-162 22 22 24 2 reg32 - - 38 40 2 mem8 (86-96)+EA 17 17 16 2-4 mem16 (150-168)+EA 25 25 24 2-4 (W88=158-

176+EA) mem32 - - 41 40 2-4

De exemplu, în secvenţa: deimpartit dw 41 impartitor dw 15 cat dw ? rest dw ?

…………….. mov ax, deimpartit cwd div impartitor mov cat, ax mov rest, dx ……………

se realizează împărţirea valorii variabilei deimpartit aflată în registrul DX:AX la conţinutul variabilei impartitor. Câtul se obţine în registrul AX, iar restul se obţine în registrul DX. Rezultatele împărţirii sunt apoi mutate în variabilele cat, respectiv rest.

Deîmpărţitul este obligatoriu să fie memorat în registrul dublu DX:AX, extensia semnului se obţine cu instrucţiunea cwd.

10.4 Proceduri de calcul Necesitatea aritmeticii zecimale este dată de manipularea numerelor foarte mari în condiţiile asigurării preciziei. Lucrul în aritmetica binară pe doi baiţi asigură precizie operanzilor care după evaluarea expresiei conduc la un rezultat cuprins între [-32768…32767]. În tabel sunt prezentate preciziile pentru rezultatele evaluărilor în cazul celorlaltor tipuri de date.

Page 111: limbaj de asamblare-ivan

509

Tabelul 10.1. 1 octet cu semn -128…127 1 octet fără semn 0…255 2 octeţi cu semn -32768…32767 2 octeţi fără semn 0…65535 4 octeţi cu semn -2^31…2^31-1 4 octeţi fără semn 0…2^32-1

Ideea aritmeticii zecimale constă în dezvoltarea unor proceduri de manipulare a şirurilor de caractere formate din elementele 00h, 01h, 02h, 03h, 04h, 05h, 06h, 07h, 08h, 09h în cazul aritmeticii zecimale despachetate sau din 0000b, 0001b, 0010b, 0011b, 0100b, 0101b, 0110b, 0111b, 1000b, 1001b în cazul aritmeticii zecimale împachetate. De la tastatură se iniţializează şirul x cu ‘7245893106’ care obţine imaginea memorie (hexazecimal):

37 32 34 35 38 39 33 31 30 36 $ x

Figura 10.1 – Imaginea zonei de memorie ocupată de şirul x

folosind codurile ASCII pentru simbolurile cifrice. Dacă se traversează masivul unidimensional x (de la stânga la dreapta) şi

din fiecare bait se scade constanta 30h (care corespunde caracterului ‘0’) se va obţine şirul x modificat a cărui imagine este:

07 02 04 05 08 09 03 01 00 06 $

Figura 10.2 – Imaginea zonei de memorie ocupată de şirul x după modificare

Aceasta este forma zecimal despachetată a numărului foarte mare 7245893106 introdus de la tastatură, şi cu care se doreşte găsirea unei modalităţi de a face calcule. Interesează faptul că se cunoaşte numele zonei de memorie (x) şi folosind o procedură de stabilire a lungimii unui şir a cărui delimitator de sfârşit este caracterul ‘$’ se va cunoaşte câte cifre are numărul. Restricţia lungimii numărului este dată de cât de mare este spaţiul contiguu de memorie cu care se poate opera într-un program scris în limbaj de asamblare. Se observă că dacă un bait conţine o cifră a numărului primii patru biţi (high nibble) conţin zerouri iar ceilalţi patru biţi (low nibble) conţin combinaţia corespunzătoare cifrei.

Page 112: limbaj de asamblare-ivan

510

Există posibilitatea de a elimina zerourile (din high nibble) şi de a aduce acolo combinaţia de biţi pentru o cifră. Deci pe un bait se vor reprezenta două cifre ale numărului. Această reprezentare se numeşte zecimal împachetată. Zona de memorie ce conţine numărul introdus de la tastatură este (în forma zecimal împachetată):

72 45 89 31 06 $ y

Figura 10.3 – Imaginea zonei de memorie ocupată de variabila y

Împachetarea se face de la dreapta la stânga şi dacă numărul de cifre este impar se completează baitul cel mai din stânga în partea mai semnificativă cu biţi de 0. Astfel numărul 72541 introdus de la tastatură va ocupa o zonă de memorie a cu conţinutul:

37 32 35 34 31 $ a

Figura 10.4 – Imaginea zonei de memorie ocupată de variabila a

Transformarea sa în număr zecimal despachetat în zona de memorie b este:

07 02 05 04 01 $ b

Figura 10.5 – Imaginea zonei de memorie ocupată de variabila b

Împachetarea numărului conţinut de zona de memorie b în zona de memorie c este:

07 25 41 $ c

Figura 10.6 – Imaginea zonei de memorie ocupată de variabila c

În continuare sunt prezentate exemple de proceduri pentru operaţii în aritmetica zecimală:

.data buf db 255,255 dup(?) null equ 0ffh

Page 113: limbaj de asamblare-ivan

511

.code ; procedura de citire de la tastatură a unui şir de caractere cifrice : ; adresa destinaţiei este es:[di] getnumber proc cld mov ah, 0ah mov dx, seg buf mov ds, dx

mov dx, offset buf mov [dx], 255 int 21h mov si, offset buf inc si xor cx, cx mov cl, byte ptr [si] inc si

@@01: lodsb cmp al, ’0’

jb @@02 cmp al, ’9’ ja @@02 stosb loop @@01

@@02: mov byte ptr [di], null ret getnumber endp ; procedura de aflare a numărului de octeţi ; adresa şirului în es:[di] şi rezultat în ax ndigit proc cld

mov bx, di mov al, null xor cx, cx not cx repne scasb mov ax, di sub ax, bx dec ax ret

ndigit endp ; procedura de conversie şir de cifre – zecimal despachetat ; sursa ds:[si], destinaţia es:[di] asctobcd proc

cld mov ah, null

@@03: lodsb

Page 114: limbaj de asamblare-ivan

512

cmp al, ah je @@04 sub al, ’0’ stosb jmp @@03

@@04: stosb

ret asctobcd endp ; procedura de conversie zecimal despachetat – şir de cifre ; sursa ds:[si], destinaţia es:[di] bcdtoasc proc

cld mov ah, null

@@05: lodsb

cmp al, ah je @@06 add al, ’0’ stosb jmp @@05

@@06: stosb

ret bcdtoasc endp ; procedura de conversie despachetat – împachetat ; sursa ds:[si], destinaţia es:[di] unpackedtopacked proc cld push es push di mov di, si mov ax, ds mov es, ax call ndigit

mov cx, ax pop di pop es shr cx, 1 jnc @@07 movsb

@@07: lodsw shl ah, 1 shl ah, 1 shl ah, 1 shl ah, 1 or al, ah stosb loop @@07

Page 115: limbaj de asamblare-ivan

513

movsb ret

unpackedtopacked endp ; procedura de conversie împachetat – despachetat ; sursa ds:[si], destinaţia es:[di] packedtounpacked proc cld @@07:

lodsb cmp ah, null je @@08 mov ah, al shr ah, 1 shr ah, 1 shr ah, 1 shr ah, 1 and al, 0fh stosw jmp @@07

@@08: stosb ret

packedtounpacked endp ; procedura de copiere a unui şir terminat cu caracterul special (null) ; sursa ds:[si], destinaţia es:[di] bcdcopy proc cld @@09:

mov ah, null lodsb cmp al, ah stosb je @@09

bcdcopy endp ; procedura de redimensionare a unui număr zecimal împachetat ; sursa ds:[si], destinaţia es:[di], dimensiune şir nou în ax bcdnorm proc cld

push ax push es push di mov ax, ds mov es, ds mov di, si mov al, null xor cx, cx neg cx

Page 116: limbaj de asamblare-ivan

514

repne scasb mov cx, di sub cx, si mov si, di dec si pop di pop es pop ax add di, ax sub ax, cx std rep movsb mov cx, ax mov al, 0 rep stosb ret

bcdnorm endp ; procedura de adunare zecimal împachetat ; sursa ds:[si], destinaţia es:[di] (dest=dest+sursa) ; considerăm numerele normalizate la un număr dat de octeţi dat ;în cx bcdadd proc

cld clc

@@10: lodsb adc al, byte ptr es:[di] daa stosb loop @@10 ret

bcdadd endp ; procedura de scădere zecimal împachetat ; sursa ds:[si], destinaţia es:[di] (dest=dest–sursa) ; considerăm numerele normalizate la un număr dat de octeţi dat în cx bcdsub proc

cld clc

@@11: lodsb abb al, byte ptr es:[di] das stosb loop @@11 ret

bcdsub endp ; exemplu de apelare proceduri .data

Page 117: limbaj de asamblare-ivan

515

s1 db ‘12632’, null ; ascii s11 db 11 dup(?) ; unpacked bcd s12 db 11 dup(?) ; packed bcd s13 db 11 dup(?) ; normalized packed bcd s2 db 0, 0, 0, 0, 0, 0, 0, 22, 31, 69 ; normalized packed bcd .code start:

mov ax, seg s1 mov es, ax mov ds, ax lea si, s1 lea di, s11 call asctobcd lea si, s11 lea di, s12 call unpackedtopacked lea si, s12 lea di, s13 mov ax, 10 call bcdnorm lea di, s13 lea si, s2 mov ax, 10 call bcdadd end

10.5 Determinarea lungimii rezultatului evaluări unei expresii aritmetice Se consideră expresia:

e = ( a * b + c ) / d – a Pentru evaluarea acestei expresii în aritmetica zecimală este necesară efectuarea unor calcule privind lungimea zonelor de memorie cu care se lucrează. Operanzii a, b, c , d, e ocupă zone de memorie, respectiv de lungimile la, lb, lc, ld, le. Rezultatul înmulţirii a*b necesită o zonă de memorie la = la + lb + 1. Rezultatul adunării necesită o zonă de memorie de lungime

ly = max { lx, lc } + 1

Rezultatul împărţirii necesită o zonă de memorie de lungime ly = ly - ld iar rezultatul scăderii necesită o zonă de memorie de lungime le = max { lz, la }.

Page 118: limbaj de asamblare-ivan

516

11

ÎNTRERUPERI 11.1 Întreruperi interne şi externe

O întrerupere este un eveniment care duce la întreruperea temporară a execuţiei unui program, executând o subrutina numită serviciul întreruperii (Interrupt Service Routine – ISR), după care reia programul oprit din acelaşi punct, ca şi când nu s-ar fi întâmplat nimic. Întreruperile au fost realizate ca alternativă la examinarea repetată a perifericelor pentru detectarea unei operaţii de intrare/ieşire (polling). Astfel, în locul acestei interogări repetate şi costisitoare, perifericele generează semnale de întrerupere, apelând serviciul corespunzător.

Deşi momentul de declanşare este oarecare, semnalele de întrerupere nu sunt recunoscute de către procesor decât între doua apeluri de instrucţiuni. Astfel, dacă se execută o instrucţiune ce necesită multe cicluri maşina (cum este, spre exemplu, o înmulţire, care poate necesita până la 139 de cicluri procesor), iar un apel de întrerupere are loc în timpul acesta, el este pus în aşteptare până la terminarea instrucţiunii. Excepţie de la această regula o fac instrucţiunile cu repetare, cum sunt instrucţiunile de lucru cu şiruri (de exemplu rep movsb), care sunt întrerupte.

În cadrul programării procesoarelor din familia 8086, întreruperile se împart în două clase:

întreruperi externe întreruperi interne Întreruperile externe au loc în momentul generării unui semnal de către

diferitele dispozitive spre procesor. Pe de altă parte, întreruperile interne sunt declanşate în două moduri: ca urmare a apelului unei instrucţiuni int sau ca urmare a unei excepţii generate de o condiţie de eroare (cum ar fi spre exemplu împărţirea la zero). Întreruperile interne generate prin apeluri int se mai numesc întreruperi software. 11.2 Modul de funcţionare a întreruperilor

Un serviciu oferit de o întrerupere nu este altceva decât un tip special de subprogram. Subprogramele implicite pot fi înlocuite cu rutine construite de către programator. Datorită faptului că aceste servicii nu sunt proceduri obişnuite, câteva măsuri trebuiesc luate în cadrul lor:

Page 119: limbaj de asamblare-ivan

517

salvarea tuturor regiştrilor la începutul rutinei apelul instrucţiunii sti pentru procesarea altor întreruperi în cadrul

serviciului restaurarea regiştrilor la sfârşitul rutinei apelul iret ca ultimă instrucţiune Salvarea şi restaurarea regiştrilor folosiţi este necesară datorită incertitudinii

în ceea ce priveşte momentul în care au loc întreruperile externe. Astfel, întreruperile de acest tip pot avea loc în orice moment, întrerupând execuţia unui bloc de instrucţiuni, iar modificarea fără restaurare a regiştrilor folosiţi în acest bloc duce la rezultate catastrofale pentru program. Pe de altă parte, întreruperile software au un control mai mare relativ la momentul lor de execuţie şi se pot sustrage de la această regulă.

Dacă se doreşte ca şi alte întreruperi să fie capabile de activare în timpul execuţie serviciului, întrerupând astfel execuţia acestuia, se realizează apelul instrucţiunii sti, care are ca efect setarea indicatorului de condiţie IF (interrupt-enable flag). Dacă nu se realizează acest apel, iar dacă if nu este setat, întreruperile ce au loc în timpul rezolvării întreruperii curente, nu vor fi recunoscute de către procesor. Pentru resetarea (punerea pe zero) a acestui flag se foloseşte instrucţiunea cli. De remarcat, că aceste două instrucţiuni, nu au nici un efect asupra întreruperilor interne, software sau generate de o condiţie de eroare, ci numai asupra acceptării sau nu a întreruperilor externe.

Familia de procesoare 8086 are doi pini de intrare de la dispozitivele generatoare de întreruperi:

pentru întreruperi mascabile (Maskable Interrupts) – INTR pentru întreruperi nemascabile (Nonmaskable Interrupts) – NMI Majoritatea dispozitivelor ce generează întreruperi folosesc linia INTR

pentru semnalarea evenimentelor. Instrucţiunile cli şi sti afectează întreruperile ce sosesc pe acest pin: cli realizează o “mascare” a întreruperilor, prevenind recunoaşterea lor de către procesor, în timp ce sti permite intervenţia acestora în codul executat de către procesor. Nici una dintre aceste instrucţiuni nu are efect asupra liniei NMI, pe care circulă semnale importante, ce nu pot fi mascate, cum ar fi cele ce declanşează execuţia codul în cazul căderii tensiunii. În cadrul arhitecturii IMB PC originale, linia NMI era folosită pentru erorile de paritate a memorie RAM, cauzate de defectarea unuia sau mai multor biţi. Arhitecturile actuale oferă multe alte servicii noi pentru situaţii critice, făcând programarea serviciilor NMI complicată.

Deşi linia NMI pare de neblocat în sarcina sa de a trimite semnale de întrerupere către procesor, ea poate fi totuşi blocată prin diferite trucuri ce diferă de la o arhitectură la alta. Astfel, pentru IBM XT, se poate masca şi debloca această linie prin scrierea valorii 00h (pentru mascare) şi a valorii 080h (pentru deblocare) pe portul 0A0h.

Page 120: limbaj de asamblare-ivan

518

Pentru întoarcerea în mod corect în programul întrerupt, se va folosi instrucţiunea iret în locul instrucţiunilor obişnuite de revenire în aplicaţie. Această instrucţiune, pe lângă sarcina de refacere a punctului de execuţie (refacerea din stivă a perechii CS:IP), realizează şi sarcini suplimentare specifice întreruperilor, cum ar fi refacerea flagurilor IF şi TF (trap flag). Pe lângă cele doua linii de transmitere a întreruperilor mai sus amintite, arhitectura IBM vine în ajutorul programatorilor cu un cip, numit Intel 8259 Programmable Interrupt Controller (PIC), care serveşte până la opt dispozitive ce generează întreruperi. Arhitecturile mai noi au în dotare şi alte cipuri de tip PIC pentru mai multe dispozitive. Pentru arhitecturile IBM AT cu două cipuri PIC lista întreruperilor externe se prezintă astfel:

Tabel 11.1. Nivelul PIC Numarul Intreruperi Dispozitiv 0 08h Timer (ceasul software) 1 09h Tastatura 2 0Ah Slave 8259 3 0Bh Portul serial secundar (COM2) 4 0Ch Protul serial primar (COM1) 5 0Dh Hard Disk (Fix) 6 0Eh Floppy Disk 7 0Fh Imprimanta pe port paralel 8 070h Ceasul hardware 9 071h Master 8259 Nivel 2 10 072h - 11 073h - 12 074h - 13 075h Coprocesorul numeric 14 076h Hard Disk (Fix) 15 077h - NMI 02h Paritate Memorie

*datorita faptului că întreruperile NMI sunt tot externe, aceasta linie a fost inclusa in tabel deşi nu este ataşată controller-ului 8259

Prima coloana a tabelului anterior, nivelul PIC, arată prioritatea serviciului:

dacă două întreruperi au loc simultan, cotroller-ul 8259 dă prioritate celei cu acest număr mai mic.

A doua coloană, numită numărul întreruperii (tipul sau nivelul întreruperii), identifică ISR-ul (serviciul) ce rulează la declanşarea acesteia. Acesta este un index folosit de procesor pentru determinarea adresei rutinei, în timp ce nivelul PIC arată pinul din cadrul cipului 8259. Acest număr se regăseşte şi în cadrul întreruperilor software, el fiind folosit în instrucţiunile int ca argument.

Page 121: limbaj de asamblare-ivan

519

Prin intermediul numărul instrucţiunii, procesorul află un pointer pe patru baiţi, numit vectorul întreruperii, care punctează rutina de tratare a întreruperii. În acest pointer, segmentul şi deplasamentul, sunt memorate la adresa mai mica, cu valori între 0000:0000 si 0000:30FF pentru arhitectura PC AT. În momentul în care un semnal de întrerupere este generat de unul de dispozitivele periferice, cipul 9259 activează linia INTR, aşteptând încuviinţarea procesorului pentru primirea întreruperii. La primirea încuviinţării, procesorul primeşte numărul întreruperii şi îl foloseşte pentru aflarea adresei rutinei de tratare pe care o apelează.

În mod asemănător au loc operaţiile pentru apelul rutinei de tratare a unei întreruperi interne ca urmare a unei instrucţiuni int sau ca urmare a unei condiţii de eroare. Atât în cazul întreruperilor externe, cât şi în cel al celor interne, procesorul realizează următoarele operaţii când primeşte un număr de întrerupere:

indicatorii de condiţie sunt salvaţi pe stivă indicatorii de condiţie IF si TF sunt setaţi pe zero regiştrii IP şi CS sunt salvaţi pe stivă vectorul întreruperii (adresa serviciului) este copiat în CS:IP Odată cu iniţializarea regiştrilor CS:IP cu valoarea vectorului întreruperii,

se trece la execuţia rutinei aflată la acea adresă. Programatorul poate modifica unul sau mai mulţi vectori de întreruperi şi, astfel, să îşi introducă propriile rutine de tratare. La terminarea unei rutine de tratare, se apelează instrucţiunea iret care realizează următoarele operaţii:

regiştrii CS şi IP sunt restauraţi din stivă indicatorii de condiţie sunt refăcuţi, de asemenea, din stivă Prima operaţie este operaţia obişnuită de refacere a adresei de revenire, pe

care şi o instrucţiune obişnuită de retur o realizează. După care urmează refacerea indicatorilor de condiţie. Se observă că indicatorii de condiţie IF şi TF sunt resetaţi, la intrarea în rutină, după ce au fost salvate împreună cu celelalte flaguri. Astfel, pe parcursul execuţiei serviciului ele vor avea valoarea zero, nepermiţând întreruperea rutinei de alte întreruperi (indicatorul de condiţie IF) şi nici suport de întrerupere a instrucţiunilor pentru debuggere (indicatorul de condiţie TF – trap flag). Pentru permiterea întreruperii temporare a serviciului (setarea indicatorului de condiţie IF) se apelează instrucţiunea sti. 11.3 Tipuri de întreruperi

O primă clasificare a întreruperilor este cea realizată în primul subcapitol: externe şi interne. Întreruperile se mai pot clasifica si după locul pe care îl ocupă rutinele de tratare implicite. Astfel, unele rutine de tratare se găsesc în BIOS, în timp ce altele sunt oferite de sistemul de operare.

În cazul sistemului de operare MS-DOS, pe lângă shell-ul, ce interpretează comenzile, se găsesc doua straturi numite BDOS (Basic Disk Operating System) şi BIOS (Basic Input Output System). Pe disc acestea se gasesc în fişierele

Page 122: limbaj de asamblare-ivan

520

MSDOS.SYS si IOSYS.SYS. Aceste straturi cuprind rutine de tratare a întreruperilor, primul oferind rutine din partea sistemului de operare, în timp ce al doilea strat, BDOS, oferă o legătură spre întreruperile oferite de BIOS. Astfel, o alta clasificare a întreruperilor, pentru MS-DOS, este:

întreruperi BIOS întreruperi DOS

11.3.1 Întreruperi BIOS

Rolul BIOS-ul este acela de a acţiona ca o interfaţă între software si hardware, impunând un standard ce trebuie respectat de toţi manufacturierii de calculatoare. O parte din rutinele BIOS, cum ar fi cele ce realizează testele de verificare a resurselor hardware (POST – Power On-Line Self Test) se găsesc în memoria ROM, în timp ce partea de BIOS cuprinsă în fişierul IOSYS.SYS se încarcă în partea inferioară a memorie RAM. Modul de mapare a memorie este standardizat, existând mai multe versiuni de BIOS: Award, Phoenix etc. Dacă în primele versiuni, BIOS-ul oferea întreruperi cu numere cuprinse între 00h şi 1Fh, arhitecturile moderne oferă seturi extinse de întreruperi, tabelul 11.2.

Numărul serviciilor oferite de BIOS este foarte mare, de aceea în tabel sau omis multe dintre acestea. După cum se observă, unele dintre acestea oferă un mod de întrerupere a procesorului de către dispozitivele ataşate la acesta, aşa numitele IRQ-uri (Interrupt Request), prezentate, în mare majoritate, în tabelul 11.1, în timp ce altele oferă o modalitate de accesare a dispozitivelor de către programator prin intermediul instrucţiunii int.

Modul de întrebuinţare a întreruperilor de acces la dispozitive, este comun atât serviciilor BIOS cât şi celor DOS. Pe lângă numărul de întrerupere, care indică dispozitivul ce se vrea accesat, programatorul trebuie să specifice şi funcţia pe care o doreşte realizată. Aceasta se specifică prin registrul ah (câteodată, în funcţie de serviciu şi funcţie, se foloseşte şi AL, BX şi DX), după cum se observă în exemplu următor:

;pozitionare cursor: ;pregatire parametrii mov ah, 02h ;functia 02h mov bh, 00h ;pagina video mov dh, 10 ;linia mov dl, 12 ;coloana ;apel intrerupere int 10h ;serviciu video ;nu se intoarce nici un rezultat

Acest exemplu poziţionează cursorul pe linia 10, coloana 12, pe prima pagina video, apelând funcţia 02h a serviciului 10h. Modul de folosire al întreruperilor

Page 123: limbaj de asamblare-ivan

521

este acelaşi, ceea ce diferă fiind doar modul de transmitere al parametrilor şi de primire a rezultatelor, acolo unde cazul.

Tabel 11.2 Număr întrerupere Semnificaţie (serviciu) 00h Divizare la zero 01h Modul single-step (vezi flag tf) 02h Nemascabil (NMI) 03h Breakpoint 04h Depăsire (overflow) (vezi flaguri of si ov) 05h Tipărire ecran 06h Cod de operaţie nevalid 07h Nu există coprocesor matematic 08h IRQ0 Timer 09h IRQ1 Tastatură 0Ah IRQ2 cascade (trecerea pe controllerul 2 de

întreruperi) 0Bh IRQ3 COM 2/4 . . . . . . . . . . 10h Serviciul video 11h Lista echipamentelor 12h Mărimea memoriei convenţionale 13h Disk I/O 14h Port serial . . . . . . . . . . 40h Vector către funcţiile int 13h referitoare la

dischetă 41h Vectori către structurile referitoare la hard

discuri 43h Tabela de caractere EGA si VGA 46h Vectori către structurile referitoare la hard

discuri 4ah Alarmă utilizator 67h Funcţii ale managerului de memorie expandată 70h IRQ8 Real Time Clock . . . . . . . . . . 76h IRQ14 hard disk 77h IRQ15 (rezervat)

11.3.2 Întreruperi DOS

Întreruperile oferite de stratul BDOS, oferind în special rutine de acces la disc, fac invariantă interfaţa în raport cu diferitele particularităţi hardware din

Page 124: limbaj de asamblare-ivan

522

modelele de PC-uri oferite de fabricanţi. Deşi mai puţine la număr, întreruperile DOS oferă un mare număr de funcţii, tabelul 11.3.

Tabel 11.3 Număr întrerupere Semnificaţie (serviciu) 20h* Terminare program 21h Servicii DOS 22h Adresa de revenire din program după terminarea

acestuia 23h Adresa de revenire după terminare cu Control-Break 24h Adresa de revenire după o eroare critică 25h/26h Acces direct la disc 27h* Terminare program cu rămânere rezindent în memorie 28h Procesor neocupat (idle) 29h Acces rapid la ecran 2Eh Executare comandă DOS 2Fh Multiserviciu (DoubleSpace, spooler, control TSR, etc.) 31h Servicii DPMI (folosite sub modul Windows 386Enh) 33h Suport mouse 67h Servicii ale managerului de memorie expandată

(HIMEM.SYS) *Aceste servicii sunt rămase de la versiunile iniţiale de DOS, existând

servicii DOS mai noi şi mai uşor de folosit care le înlocuiesc. În continuare se prezintă un exemplu de folosire a serviciului DOS 21h:

mov ah, 09h ;functia de afisare sir mov dx, sir ;in ds:dx se pune adresa sirului int 21h ;numarul serviciului

Secvenţa anterioară realizează afişarea unui sir terminat cu caracterul $ prin

apelul funcţiei 09h a serviciului 21h. Acest cod demonstrează modul similar de folosire al întreruperilor atât pentru stratul BIOS cât şi pentru stratul DOS. Pentru accesarea resurselor hardware în limbaj de asamblare există trei niveluri de programare:

la nivel DOS la nivel BIOS la nivel de bază. Aceste modalităţi au fost aşezate în ordinea crescătoare a complexităţii

programării. Astfel, nivelul DOS oferă servicii de un nivel mai înalt decât nivelul BIOS, în timp ce programarea la nivel de bază este cea mai grea folosind instrucţiunile in şi out de lucru cu porturi. Această modalitate de accesare a resurselor necesită cunoştinţe avansate si o documentaţie pe măsură, pe când în programarea cu întreruperi, cunoscând modul general de utilizare al acestora, programatorul trebuie doar să aibă la dispoziţie documentaţia pentru fiecare funcţie

Page 125: limbaj de asamblare-ivan

523

referitoare la modul de transmitere al parametrilor şi de preluare a rezultatelor. Cu toate acestea, unele sarcini realizate cu ajutorul întreruperilor necesită cunoştinţe profunde hardware, precum şi folosirea concomitentă cu instrucţiunile in şi out, după cum se va observa în exemplu următor. 11.4 Exemplu de folosire al întreruperilor

Folosirea întreruperilor necesită cunoştinţe despre efectele colaterale ale acestora. Programatorul, în funcţie de întreruperea folosită, trebuie să ştie mecanismele ce se află în spatele serviciului. Pentru a scrie un program care să nu afecteze buna funcţionare a sistemului, el trebuie să ţină cont atât de buna funcţionare a propriului program, luat separat, cât şi de funcţionarea a celorlaltor programe executate în paralel cu acesta, precum şi a dispozitivelor periferice care folosesc întreruperi. Toate aceste probleme pe care le are de înfruntat un programator nu au o forma generală, ele sunt specifice fiecărui tip de întrerupere, neputându-se pune un diagnostic concret unei erori date decât ţinându-se cont de întreruperile folosite.

În continuare, pentru exemplificarea problemelor ce apar la folosirea unei întreruperi, se prezintă un exemplu, devenit clasic, preluat din cartea “Mastering Turbo Assembler” a lui Tom Swan, cunoscut autor de cărţi de programare. Acest exemplu, relativ simplu la prima vedere, îşi propune să folosească întreruperea IRQ 0h, cu numărul 08h (Timer), pentru executarea unui cod la intervale regulate de timp. Acest cod nu face altceva decât să încetinească programul prin adăugarea de pauze mici, fiind folositor în cazul în care se doreşte depanarea unui program care se dovedeşte a fi prea rapid pentru observarea operaţiilor pe care le realizează, depanarea pas-cu-pas fiind o alternativă greoaie de folosit.

În toate calculatoarele compatibile IBM PC, există un timer hardware, care generează, la intervale egale de timp (de 18.2 ori pe secundă), un semnal de întrerupere pe linia 0h către controllerul 8259, producând o întrerupere cu numărul 08h, de maximă prioritate. Serviciul cu acest număr are două sarcini. Prima constă în incrementarea unei variabile pe 32 de biţi, care indică numărul de cicluri ale timerului de la pornirea calculatorului, folositoare pentru diferite funcţii ce indică timpul de rulare. A doua sarcină controlează modul de funcţionare al motorului unităţii de dischetă. Această sarcină este datorată timpului necesar motoraşului pentru a ajunge la turaţia necesară. Dacă acesta ar fi oprit după fiecare citire/scriere, între aceste operaţii există timpi morţi necesari rotorului pentru a ajunge la turaţia optimă, lucru care ar duce la o încetinire mare a operaţiilor cu discheta. Soluţia a constat în menţinerea în turaţie a motoraşului atâta timp cât un cronometru, controlat de întreruperea 08h, nu ajunge la zero.

În scrierea unui program care foloseşte întreruperea, trebuie ţinut cont de aceste sarcini suplimentare. Codul programatorului, care este apelat la intervale regulate de timp, nu trebuie să dureze mult. În cadrul acestuia nu trebuie să se

Page 126: limbaj de asamblare-ivan

524

oprească primirea întreruperilor de către procesor (instrucţiunea cli) pentru mai mult de 1/18,2 secunde, pentru a nu se afecta buna funcţionare a unor componente esenţiale ale sistemului. După cum se observă, folosirea unei întreruperi relativ banale, ridică în mod neaşteptat, pentru necunoscători, probleme mari în funcţionarea unor periferice şi a unor programe relativ independente.

Pe lângă aceste doua sarcini, serviciul timer execută o întrerupere software cu numărul 01Ch (User timer interrupt), care dă posibilitatea programatorului de a executa codul său. Prin instalarea propriei rutine de tratare a întreruperii 01Ch, aceasta este apelată de aproximativ 18.2 ori pe secundă. Regularitatea apelării acesteia nu este garantată. După cum s-a afirmat deja, întreruperile nu pot să întrerupă instrucţiunea curentă. Dacă aceasta necesită multe cicluri procesor, executarea întreruperii se amână nedorit de mult, astfel încât premisa executării de 18.2 ori pe secundă a codului să fie neadevărată.

În continuare se prezintă codul programului sursă, urmând ca fiecare grup de instrucţiuni să fie prezentat în amănunt:

;**************************************** %TITLE “Exemplu de folosire a intreruperii 01Ch" ;****************************************

IDEAL .MODEL small .STACK 256

delay EQU 0010h ; pauza BIOSData EQU 040h ; adresa segmentului de

;date BIOS LowTimer EQU 006Ch ; valoarea timerului (partea mai

; putin semnificativa) PIC8259 EQU 0020h ; adresa de port a

;cipului 8259 PIC EOI EQU 0020h valuarea “End of

;interrupt” DATASEG

codIesire DB 0 timerSeg DW ? ; adresa serviciului initial timerOfs DW ? ; Int 1Ch ISR

CODESEG Start:

mov ax, @data ;initializare registru mov ds, ax ; de date mov es, ax ; es = ds mov [word cs:difference],delay ; stabilire pauza

push es ;salvare registru es mov ax, 351Ch ;obtinerea vectorului de

; intrerupere 1C int 21h ; apel serviciu DOS mov [timerSeg], es ; salvare segment

Page 127: limbaj de asamblare-ivan

525

mov [timerOfs], bx ; salvare offset pop es ; restaurare es push ds ; salvare registru ds mov ax, 251Ch ; setarea serviciului 1C push cs ; ds = cs pop ds mov dx, offset Incetinire ; adresa de inlocuire int 21h ; setare adresa pop ds ; restaurare ds

@@10:

call KeyWaiting jz @@10 ; asteptare apasare tasta push ds ; salvare ds mov ax, 251Ch ; setare vector 1C mov dx, [timerOfs] ;adresa de segment

initiala mov ds, [timerSeg] ;offset-ul initial int 21h ; restaurare serviciu initial pop ds ; restaurare ds

Exit: mov ah, 04Ch mov al, [codIesire] int 21h

;**************************************** ; Incetinire - Procedura apelata de aprox. 18,2/sec ; **************************************** inProgress DB 0 ; flag difference DW 0 ; timpul de incetinire PROC Incetinire

;testare flag inProgress pentru a vedea daca o copie ;anterioara a procedurii ruleaza cmp [byte cs:inProgress], 0 ; verificare jne @@99 ; ruleaza,

terminare ; procedura

inc [byte cs:inProgress] ; nu ruleaza, ; incrementare flag

sti ; pot sa aiba loc alte

; intreruperi push ax ; salvare registrii

; folositi push ds push dx

mov al, EOI ; al = end-of-interrupt out PIC8259, al

Page 128: limbaj de asamblare-ivan

526

mov ax, BIOSData ; adresa de date BIOS mov ds, ax ; folosire ds mov ax, [word LowTimer] ; valuare timer

@@10: mov dx, [word LowTimer] ; incarcare valoare

; timer in dx sub dx, ax ; scadere valoare

; noua - veche cmp dx, [cs:difference] ; comparare cu

; pauza jb @@10 ; cicleaza pana la

; depasire cli ; dezactivare intreruperi

; in timpul restaurarii dec [byte cs:inProgress] ; flag pe zero pop dx ; restaurare registri pop ds pop ax

@@99: iret ; retur intrerupere

ENDP Incetinire PROC KeyWaiting

push ax ; salvare ax mov ah, 1 ; functia de

; verificare buffer int 16h ; serviciu tastatura

; BIOS pop ax ; restaurare ax ret ; retur

ENDP KeyWaiting

END Start ; sfarsit program

La rularea programului se instalează procedura Incetinire ca serviciu al întreruperii 1Ch, urmând ca, la apăsarea unei taste, programul să se termine. Listing-ul începe prin definirea unor simboluri. Pe lângă definirea pauzei (delay), se definesc o serie de simboluri, care presupun cunoaşterea modului de mapare al variabilelor BIOS în memoria RAM. Astfel, adresa 40h semnifică începutul zonei de mapare BIOS, pe când 40h::6Ch este locaţia de memorie unde valoarea curentă a timerului se află. Urmează segmentul de date, cu definirea unor variabile ce vor fi folosite pentru reţinerea adresei vechii rutine de tratare a întreruperii 1Ch (timerSeg: timerOfs).

După liniile de început a segmentului de cod, urmează obţinerea rutinei de tratare a întreruperii 1Ch. Se foloseşte întreruperea DOS 21h (int 21h), funcţia 35 (registrul ah = 35, registrul al = 1Ch). Vectorul obţinut se salvează în timerSeg:timerOfs. Urmează setarea ca serviciu a rutinei Incetinere prin funcţia 25 a întreruperii 21h. După stabilirea noului serviciu, se trece la executarea unei bucle din a cărei repetare se iese prin apăsarea unei taste. Pe parcursul acesteia, deşi

Page 129: limbaj de asamblare-ivan

527

aparent nimic nu se întâmplă, procedura Incetinere este apelată în mod repetat de către serviciul întreruperii 08h (timer). La ieşirea din ciclu, se reface vechea rutină de tratare a întreruperii 1Ch. Acest lucru este necesar, deoarece, odată cu terminare programului, adresa, spre care Incetinire puncta, devine invalidă, iar apelul realizat la această adresă de către serviciul timer duce, inevitabil, la o cădere de sistem.

Se trece acum la rutina de tratare a întreruperii, Incetinire. Deoarece această rutină poate fi apelată în orice moment, între două apeluri de instrucţiuni din programul principal, în timpul unui apel către sistemul de operare, sau în timpul unei alte întreruperi, valoarea segmentelor ES şi DS nu punctează întotdeauna către segmentele de date. Pentru accesarea unor variabile din cadrul segmentelor de date, trebuie să fim siguri că aceste registre identifică în mod corect aceste segmente. O soluţie este executarea unei secvenţe de cod care în mod uzual se găseşte la începutul programului principal: push ds

mov ax, @data mov ds, ax . . . . . urmată, la sfârşitul întreruperii, de refacerea segmentului ds: . . . . . pop ds iret

O altă soluţie, mai elegantă şi mai simplă, este declararea variabilelor în cadru segmentului de cod. Acest lucru se realizează în procedura Incetinire prin:

inProgress DB 0 ; flag difference DW 0 ; timpul de incetinire

Pentru accesarea variabilelor se indică în mod explicit folosirea registrului

CS, ca în cazul instrucţiunii:

cmp [byte cs:inProgress], 0 Dacă nu se specifică registrul CS, asamblorul consideră ca registru segment

registrul DS, iar adresa de memorie accesată, în cel mai bun caz, dacă este validă, nu este cea dorită.

După cum arătat deja, întreruperea 08h joacă un rol important în funcţionarea unui sistem de calcul. Rutina apelată de timer nu trebuie să dureze mult, iar pe parcursul acesteia celelalte întreruperi nu trebuiesc blocate. Această situaţie face posibilă apariţia apelului rutinei Incetinire chiar în timpul rulării acesteia. Nimic nu împiedică procesorul să primească încă o întrerupere 1Ch din

Page 130: limbaj de asamblare-ivan

528

partea timerului în timpul execuţiei procedurii Incetinire, dacă aceasta durează prea mult. Astfel, acestă rutină devine reentrantă. Din păcate scrierea unei funcţii reentrante ridică probleme sulpimentare, chiar rutinele din cadrul BIOS-ului si DOS-ului nefiind reentrante. Posibilitatea reapelării procedurii Incetinire poate să ducă la depăşirea stivei dacă procedura durează prea mult, iar lanţul de reapelării continuă fără oprire. Mai mult, folosirea unor variabile globale face ca execuţia să ia o întorsătură neprevăzută, o singură instanţă a acestora fiind folosită la toate apelurile. După cum se observă folosirea variabilei difference, face imposibilă folosirea rutinei Incetinire în mod reentrant. Soluţia la această problemă (soluţie pe care o folosesc şi unele funcţii BIOS/DOS, cum ar fi Print Screen) este folosirea unui flag (în cazul de mai sus inProgress), care să indice dacă o instanţă a rutinei este deja apelată:

cmp [byte cs:inProgress], 0

Dacă inProgress este mai mare ca zero, instanţa curentă a apărut ca urmare a blocării altui apel al rutinei, şi se sare la sfârşitul procedurii. Dacă este zero, instanţa curentă este singura apelată în acest moment şi se poate continua execuţia ei. Urmează incrementarea flagului pentru indicarea acestui lucru.

După luarea acestor măsuri, urmează altele: permiterea altor întreruperi (sti) şi salvarea regiştrilor folosiţi în stivă. Deşi, la prima vedere, instrucţiunea sti pare suficientă pentru activarea întreruperilor, datorită faptului că întreruperile timerului sosesc prin intermediul cipului 8259, acest lucru nu este adevărat. Deşi instrucţiunea sti setează flagul if, permiţând indicarea către procesor a semnalelor de întrerupere, primirea de către acesta a întreruperilor necesită trimiterea către portul 8259 (cu valoarea 20h) a unei comenzi end-of-interrupt (EOI):

PIC8259 EQU 0020h ; adresa de port a cipului 8259 PIC EOI EQU 0020h ; valuarea “End of interrupt”

. . . . . . mov al, EOI ; al = end-of-interrupt

out PIC8259, al . . . . . .

Urmează citirea valorii curente a timerului şi folosirea acesteia în cadrul

unei bucle pană la depăşirea pauzei (variabila difference cu valoarea luată din delay). Deşi la o simplă privire pare că variabila punctată de adresa LowTimer are o valoarea constantă, ea este actualizată automat de către serviciul timer, iar simpla recitire a acesteia preia o valoare actualizată.

Ultima parte a procedurii conţine o restaurare a regiştrilor folosiţi, precum şi o resetare a flagului inProgress, precedate de o mascare necesară a întreruperilor (instrucţiunea cli). O atenţie deosebită trebuie avută în vedere în legătură cu variabilele salvate pe stivă atunci când se scrie o rutină de tratare a unei întreruperi. Singurul registru de segment cu valoare corectă pe tot parcursul execuţiei rutinei

Page 131: limbaj de asamblare-ivan

529

este CS, în timp ce ES, DS şi SS au valori incerte, în funcţie de codul în curs de execuţi înainte de întrerupere. În exemplul anterior se salvează trei regiştri în stivă. Dar, în care stivă? În stiva proprie DOS-ului, în stiva programului principal sau în stiva locală altei rutine de tratare a unei întreruperi? Este această stivă suficient de mare? De obicei, se consideră sigură salvarea unei mici cantităţi de date într-o astfel de stivă necunoscută. Referinţe de programare prevăd rezervarea pentru spaţiul de stivă a unei memorii mai mari decât strictul necesar, deci presupunând ca există spaţiu de salvare pentru aceşti regiştrii pare rezonabil. Totuşi, rutine, care necesită salvări mari de date pe stivă, trebuie să apeleze la o stivă locală. De fiecare dată când se schimbă segmentul de stivă, trebuie ţinut cont de particularităţile specifice procesoarelor din familia 8086. Astfel, 8086 dezactivează în mod temporar întreruperile pentru următoarea instrucţiune de fiecare dată când se asociază nouă valoare unui registru segment. Deci, în secvenţa de cod: mov ax, offset segmStiva mov ss, ax mov sp, offset sfarsitStiva întreruperile nu pot opri execuţia codului între instrucţiunile mov ss, ax şi mov sp, offset sfarsitStiva. Dacă acest lucru nu s-ar realiza în mod automat, iar o întrerupere ar avea loc chiar între cele doua instrucţiuni, codul de tratare apelat va folosi vechea valoare a deplasamentului împreună cu noua valoare a segmentului de stiva, o situaţie cu urmări catastrofale. Prin urmare, pentru folosirea acestei metode de protecţie, orice modificare a unui segment, trebuie urmată de actualizarea deplasamentului.

Pentru o rutină de tratare a unei întreruperi, declararea unui segment de stivă locală de 512 baiţi face astfel: ALIGN stivaLocala DB 512 dub (0) sfarsitStiva = $ unde directiva ALIGN este necesară pentru alinierea stivei la un multiplu de cuvânt procesor. Se declară, apoi, o serie de variabile pentru reţinerea vechilor regiştri: ssVechi DW 0 spVechi DW 0 axVechi DW 0

În cadrul rutinei se salvează vechile valori ale regiştrilor şi se stabileşte noua stivă:

mov [cs:ssVechi], ss mov [cs:spVechi], ss mov [cs:axVechi], ax

Page 132: limbaj de asamblare-ivan

530

mov ax, cs mov ss, ax mov sp, offset sfarsitStiva

Urmând ca la terminarea rutinei să se revină la vechile valori: mov ss, [cs:ssVechi] ;aceasta este ordinea de setare mov sp, [cs:spVechi] ;a registrilor segment:offset !!! mov ax, [cs:axVechi]

După cum se observă, o sarcină aparent simplă, cum este cea de folosire a timer-ului, necesită cunoaşterea profundă a implicaţiilor pe care le implică utilizarea întreruperilor, nerespectarea regulilor mai sus amintite ducând inevitabil la terminarea anormală a programului sau, chiar, la o cădere de sistem. 11. 5 Concluzii

Folosirea întreruperilor nu este o sarcină uşoară. De multe ori soluţia cea mai simplă este folosirea unui limbaj de nivel înalt. Dar, de şi mai multe ori, ceea ce oferă aceste limbaje, referitor la folosirea directă a hardware-ului, nu este de ajuns. Prin urmare, folosirea limbajului de asamblare şi a întreruperilor devine inevitabilă. Sistemele de operare noi (Windows NT, Windows 2000) nu permit accesarea directă de hardware, oferind soluţii de nivel înalt pentru accesarea acestuia. Totuşi, scrierea de programe specifice şi dedicate unor sarcini precis determinate, cum este scrierea unor drivere, accesarea unor echipamente noi, necesită realizarea unui cod care să ruleze în mod nucleu şi care să acceseze resursele direct. Astfel, programarea cu întreruperi îşi păstrează actualitatea.

Page 133: limbaj de asamblare-ivan

531

12

MACRODEFINIŢII 12.1 Structura macrodefiniţiei

Macrodefiniţia este o secvenţă de program scris în limbaj de asamblare care corespunde unei prelucrări cu caracter repetat. Se construiesc macrodefiniţii pentru prelucrări precum:

împărţirea a două numere cu obţinerea câtului şi restului precum şi a unei informaţii asupra modului în care s-a efectuat împărţirea (de exemplu împărţitor zero);

preluarea unui caracter sau a unui şir de la tastatură; afişarea unui caracter sau a unui şir pe ecranul monitorului; diverse operaţii de formatare a ecranului, ca de exemplu ştergerea

ecranului, poziţionarea cursorului; operaţii cu fişiere: deschiderea, crearea, citirea, ştergerea, închiderea

fişierelor, poziţionarea cursorului; aflarea minimului sau maximului dintre elementele unui vector sau a

unei matrice. Pentru a construi o macrodefiniţie se impune:

definirea datelor de intrare; specificarea schemei logice de prelucrare a datelor de intrare; definirea rezultatelor.

Se constituie lista de parametri finali formată din: parametrii care corespund datelor de intrare; parametrii în care se vor găsi rezultatele; parametrii în care se regăsesc informaţii privind modul în care s-au

efectuat prelucrările. Macrodefiniţia pentru adunarea a două matrice conţine lista de parametrii

formali formată din: numele primei matrice (de fapt adresa zonei unde este memorată

matricea); numele celei de-a două matrice; numărul de linii al matricelor; numărul de coloane; numele matricei de rezultat;

Page 134: limbaj de asamblare-ivan

532

variabila booleană care are valoarea zero Dacă prelucrarea s-a efectuat corect, respectiv unu în caz contrar.

Parametrii p1, p2, .. pn sunt descrişi în prima linie sursă a macrodefiniţiei, în care se mai specifica numele macrodefiniţiei, cuvântul cheie MACRO, linie cu structura:

nume_macrodefinitie macro [ lista de parametrii ]

Macrodefiniţia conţine definiri de etichete cu caracter local, precum şi variabile de lucru. Se mai definesc:

instrucţiunile de salvare a regiştrilor; instrucţiunile de prelucrare specifice operaţiei; instrucţiunile de restaurare a regiştrilor; Delimitatorul de sfârşit al macrodefiniţiilor este cuvântul cheie ENDM. Numele macrodefiniţiilor trebuie să fie ales în aşa fel încât să permită

identificarea cu uşurinţă a acestora. Pentru împărţirea a două numere, antetul macrodefiniţie arată în felul următor:

divide macro deimpartit, impartit, cat, rest, stare

Pentru adunarea a două numere a, b cu obţinerea rezultatului în s, prima

linie a macrodefiniţiei va fi:sal

aduna macro a, b, s, stare

Pentru salvarea tuturor regiştrilor, se poate construi o macrodefiniţie având prima linie

salvreg macro

În acest caz absenţa parametrilor se impune pentru că regiştrii sunt

cunoscuţi. Când se construieşte o macrodefiniţie se are în vedere obţinerea unui grad

de generalitate foarte mare. De asemenea, este necesar ca parametrilor de intrare să nu li se modifice valorile, regiştrii să fie salvaţi, respectiv restauraţi pentru a nu influenţa celelalte operaţii din afara secvenţei care defineşte macrodefiniţia.

De exemplu, pentru efectuarea adunării numerelor a, b se construieşte macrodefiniţia: aduna macro a, b, s push ax mov ax, a add ax, b

Page 135: limbaj de asamblare-ivan

533

mov s, ax pop ax endm 12.2 Macroapelul şi macroexpandarea

Dacă procedurile se apelează prin instrucţiunea call, referirea macrodefiniţiilor se efectuează prin nume.

Macroapelul macrodefiniţiei având prima linie de definire: nume_macro macro pf1, pf2, .. pfn se face prin linia sursa:

nume_macro pr1, pr2, .. prn

unde:

nume_macro – numele macrodefiniţiei pfi – parametrul formal i pri – parametrul real i Dacă se consideră macrodefiniţia aduna, definită anterior în subcapitolul

12.1 şi expresia: e = a + b + c + d

se fac macroapelurile: aduna a, b, e aduna e, c, e aduna e, d, e

În cazul apelului de proceduri prin instrucţiunea call se efectuează salturi necondiţionate spre secvenţe de program. în cazul macroapelului intr-o etapă a asamblării programului are loc generarea de secvenţe de instrucţiuni corespunzătoare macrodefiniţiei. Se înlocuiesc parametri formali cu parametri reali. Programul astfel asamblat are o lungime mai mare decât programul iniţial.

Se considera macrodefiniţia:

aduna3 macro term1, term2, term3, rez push ax mov ax, term1 add ax, term2 add ax, term3

mov rez, ax pop ax

Page 136: limbaj de asamblare-ivan

534

endm

Programul principal arată în felul următor:

….. mov a, 1 mov b, 3 mov c, 7 mov x, 13 mov y, 2 mov z, 32 aduna3 a, b, c, rez1 aduna3 x, y, z, rez2 aduna3 0, rez1, rez2, e ….. Într-o etapă de asamblare are loc macroexpandarea. Această operaţie constă

în înlocuirea liniei sursă de macroapel cu instrucţiunile actualizate ale macrodefiniţiei.

Actualizarea constă în înlocuirea parametrilor formali cu parametri reali. Programul principal după macroexpandare este:

…... mov a, 1 mov b, 3 mov c, 7 mov x, 13 mov y, 2 mov z, 32 push ax mov ax, a add ax, b add ax, c mov rez1, ax pop ax push ax mov ax, x add ax, y add ax, z mov rez2, ax pop ax push ax mov ax, 0 add ax, rez1 add ax, rez2 mov e, ax pop ax …..

Page 137: limbaj de asamblare-ivan

535

Textul sursă obţinut este mai lung având un număr de instrucţiuni mai mare decât textul iniţial. Dacă programul asamblor este înzestrat cu componente de optimizare, atunci se obţine eliminarea din programul macroexpandat a secvenţelor de instrucţiuni

pop ax push ax

prima anulând efectul celeilalte. Dezavantajul principal constă în creşterea lungimii programului prin includerea de secvenţe repetate care diferă numai prin numele unor variabile. Este rezultatul înlocuirii parametrilor formali, cu parametri reali. 12.3 Etichete locale

Macrodefiniţiile conţin instrucţiuni de salt condiţionat, de salt necondiţionat si instrucţiuni pentru implementarea structurilor repetitive. Aceste instrucţiuni presupun definirea si referirea de etichete.

Pentru a elimina ambiguităţile după efectuarea macroexpandării se consideră soluţia care presupune parcurgerea următorilor paşi:

etichete utilizate în corpul macrodefiniţiei se definesc cu atributul LOCAL; la prima macroexpandare numelui etichetei i se asociază un număr format

din patru cifre compus intre 0000 si 9999; se obţine numele extins al etichetei;

referirile etichetelor în instrucţiunile de salt si de ciclare se efectuează cu numele extins al etichetei.

Macrodefiniţia de alegere a elementului minim dintre trei numere are textul sursă:

minim macro a, b, c, min local et1, et2 push ax mov ax, a cmp ax, b jlz et1 mov ax, b et1:

cmp ax, c jlz et2 mov ax, c et2:

mov min, ax pop ax

endm

Programul principal:

Page 138: limbaj de asamblare-ivan

536

... minim x, y, z, min1 minim w, v, u, min2 minim i, j, k, min3 minim min1, min2, min3, min ... determină minimul dintr-un şir format din nouă elemente. După macroexpandare, secvenţa de program va arăta astfel:

...

push ax mov ax, x cmp ax, y jlz et10000 mov ax, y et10000:

cmp ax, z jlz et20000 mov ax, z et20000:

mov min1, ax pop ax

push ax mov ax, w cmp ax, v jlz et10001 mov ax, v et10001:

cmp ax, u jlz et20001 mov ax, u et20001:

mov min2, ax pop ax push ax mov ax, i cmp ax, j jlz et10002 mov ax, j et10002:

cmp ax, k jlz et20002 mov ax, k et20002:

mov min3, ax pop ax

push ax mov ax, min1

cmp ax, min2

Page 139: limbaj de asamblare-ivan

537

jlz et10003 mov ax, min2

et10003: cmp ax, min3 jlz et20003 mov ax, min3

et20003: mov min, ax pop ax

... Cele patru cifre ataşate etichetei locale limitează macroapelurile la cel mult

10000 într-un program. 12.4 Variabile locale

Variabilele locale sunt variabile care sunt cunoscute doar în blocul în care au fost definite. Se pune problema zonelor de memorie, în care să fie alocate aceste variabile locale:

segmentul de date global segmentul de date propriu stiva

În primul caz se profită de faptul că directivele simplificate de definire a datelor (.date) şi a codului (.code) pot alterna în cadrul programului. Variabilele locale sunt mai întâi definite cu directiva LOCAL, pentru a nu fi duplicate în apeluri succesive, apoi declarate în cadrul segmentului de date .data.

Astfel se defineşte o constantă locală, care poate fi aplicată diferitelor date transmise ca parametri. Fie aceasta constantă

masca equ 00001111b.

Dacă se pune în relaţie cu o variabilă (definită pe un octet), prin operatorul

and, biţii cei mai semnificativi ai rezultatului vor avea valoarea 0, iar biţii mai puţin semnificativi vor fi asemănători primilor patru biţi ai variabilei .

mask macro x local masca .data

masca equ 00001111b .code push ax mov al, x and al, masca mov x, al pop ax endm

Page 140: limbaj de asamblare-ivan

538

În cazul al doilea se creează segmente de date proprii cu ajutorul directivei

SEGMENT, cea ce previne gruparea acestui segment cu alte segmente. În cazul memorării variabilelor în stivă, trebuie să se parcurgă următorii

paşi: se salvează registrul BP în stivă; se decrementează registrul SP cu numărul necesar de octeţi pentru

variabilele locale; se copiază SP în BP; se salvează ceilalţi regiştri folosiţi în program; se accesează variabilele locale conform şablonului stivei.

La terminarea macroapelului trebuie avut vedere: restaurarea regiştrilor; incrementarea registrului SP cu acelaşi număr de octeţi cu care a fost

decrementat la început; refacerea registrului BP. Fie variabilele a,b,c,d,e,f definite pe un octet. Se calculează expresia: ( a*b ) – ( c*d ) + ( e*f ) Se salvează în stiva registrul BP, rezervăm loc în stiva variabilelor var_1 si

var_2, decrementând SP cu patru. Şablonul variabilelor locale arată în felul următor:

şablon struc var_1 dw ? var_2 dw ? şablon ends

Imaginea stivei, figura 12.1.

Figura 12.1 – Conţinutul stivei Se calculează mai întâi e*f, iar rezultatul este depus în var_1 (accesul se

face prin [bp].var_1 ). Rezultatul c*d este depus în var_2 ( acces prin [bp].var_2 ).

var_1

var_2

BP

Page 141: limbaj de asamblare-ivan

539

După ce se face înmulţirea dintre a si b, se scade din ax var_2, după care se adună la ax var_1. calc macro a, b, c, d, e, f, rez push bp sub sp,4 mov bp,sp push ax xor ax,ax mov al, e

mul f ;;s-a presupus înmulţire fără semn mov [bp].var_1, ax xor ax, ax mov al, c mul d mov [bp].var_2, ax xor ax, ax mov al, a mul b sub ax, [bp].var_2 add ax, [bp].var_1 mov rez, ax pop ax add sp, 4 pop bp endm 12.5 Macrodefiniţii derivate

Se consideră macrodefiniţii derivate m1, m2, .. mk care formează mulţimea M a macrodefiniţiilor de bază, ce realizează funcţii elementare. Macrodefiniţii derivate conţin macroapeluri ale elementelor din mulţimea M. Macrodefiniţia pentru adunarea a patru numere se construieşte astfel: aduna4 macro a1, a2, a3, a4, s push ax mov ax, a1 add ax, a2 add ax, a3 add ax, a4 mov s, ax pop ax endm

Dacă în mulţimea M a macrodefiniţiilor de bază este definită macrodefiniţia pentru adunarea a două elemente: aduna2 macro a1, a2, sum push ax

Page 142: limbaj de asamblare-ivan

540

mov ax, a1 add ax, a2 mov sum, ax pop ax endm

Pentru adunarea a patru numere se construieşte macrodefiniţia derivată: aduna4 macro a1, a2, a3, a4, s aduna2 a1, a2, s aduna2 s, a3, s aduna2 s, a4, s

endm sau se apelează la variabile locale: aduna4 macro a1, a2, a3, a4, s local t1,t2 .data t1 dw ? t2 dw ? .code aduna2 a1, a2, t1 aduna2 a3, a4, t2 aduna2 t1, t2, s endm 12.6 Macrodefiniţii recursive

Macrodefiniţiile se pot autoapela. Un aspect important este oprirea recursivităţii. O modalitate pentru aceasta este controlul parametrilor actuali prin directivele IFNB ( If Not Blank ) si IFB ( If Blank ) care testează existenta sau non-existenţa unui parametru actual.

pushreg macro r1, r2, r3, r4, r5, r6 ifnb <r1> push r1 pushreg r2, r3, r4, r5, r6 endif endm

Dacă primul parametru nu este vid, macrodefiniţia se autoapelează, cu restul de parametri.

Page 143: limbaj de asamblare-ivan

541

12.7 Redefinirea macrodefiniţiilor

Asamblorul MASM permite redefinirea macroapelurilor. Se consideră macrodefiniţia aduna4. Dacă se foloseşte redefinirea, se ajunge la macroapelul: aduna4 macro a1, a2, a3, a4, s local t1,t2 .data t1 dw ? t2 dw ? .code aduna2 a1, a2, t1 aduna2 a3, a4, t2 aduna2 t1, t2, s aduna4 macro a1, a2, a3, a4, s

aduna2 a1, a2, t1 aduna2 a3, a4, t2 aduna2 t1, t2, s endm endm

Se observă că o macrodefiniţie s-a redefinit în corpul celeilalte. La primul apel, macrodefiniţia va aloca spaţiu pentru celelalte date, apoi se redefineşte, astfel încât nu va încerca să realoce datele la apelurile ulterioare. Cu instrucţiunea PURGE se şterge macroapelul înaintea redefinirii ei.

12.8 Macrodefiniţii uzuale Pentru înmulţirea unui număr cu 10 este folosită macrodefiniţia imul10. Macrodefiniţia este apelată cu doi parametrii, unul sursă şi unul destinaţie, operaţia efectuată fiind: destinaţie sursă * 10.

Tipul parametrilor este întreg pe 16 biţi. Se vor folosi în corpul macrodefiniţiei deplasările pe biţi, care sunt mult mai rapide decât înmulţirea propriu-zisă. Regiştrii folosiţi vor fi salvaţi pe stivă şi restauraţi după execuţia rutinei. ; implementarea macrodefiniţiei pentru înmulţirea

imul10 macro dst,src push ax ; salvarea regiştrilor folosiţi push bx mov ax,src ; ax va conţine sursa operaţiei sal ax,1 ; axax*2 mov bx,ax ; axax sal ax,1 ; axax*2

Page 144: limbaj de asamblare-ivan

542

sal ax,1 ; axax*2 ; avem ax=src*8 şi bx=src*2

add ax,bx ; ax src*10 mov dest,ax ; dstax pop bx ; refacerea regiştrilor folosiţi pop ax

endm După cum se observă, macrodefiniţia poate fi folosită cu succes pentru

operanzi care se găsesc în memorie sau regiştri care sunt diferiţi în general de AX şi BX. Dacă de exemplu destinaţia este BX, acesta nu va fi modificat de rutină, pentru că este salvat la început şi restaurat apoi. Pentru a fi mai clar, AX şi BX pot fi surse dar nu pot fi destinaţii ale operaţiei de mai sus. Pentru extragerea bitului de pe poziţia k se foloseşte macrodefiniţia xbit. Această macrodefiniţie va extrage bitul de pe poziţia k a sursei în indicatorul de zero (ZF) lăsând de asemenea în ax sursa din care s-au mascat toţi biţii mai puţin cel căutat.

Operaţia se face prin generarea unei măşti care se aplică apoi prin ŞI bit cu bit. Masca este generată prin deplasări succesive la stânga a unui cuvânt care iniţia avea valoarea 1.

Poziţia corespunde numărului bitului, adică 0 pentru bitul 0,1 pentru bitul 1 etc.

; implementarea macrodefiniţie de extragere a bitului xbit macro src,k push bx ; salvarea regiştrilor push cx

mov bx,src ; bxsursa mov cl,k ; cxnumărul bitului mov ax,1 ; se crează masca shl ax,cl ; ax=1 shl k and ax,bx ; se aplică masca asupra cuvântului pop bx ; se refac regiştrii pop cx ; rezultatul este în ax şi cx

endm Menţiunile referitoare la parametrii de apel de la precedenta macrodefiniţie

se aplică şi aici, în sensul că avem restricţia că BX nu poate conţine valoarea k. Valoarea k şi sursa pot fi date şi ca valori imediate. Pentru ridicarea la puterea a treia se foloseşte macrodefiniţia pow3. Ridicarea la puterea a 3-a se va face prin două înmulţiri.

Operanzi întregi pe 16 biţi cu semn.

; implementarea macrodefiniţiei de ridicare la puterea a 3-a pow3 macro dst,src push ax ; salvarea regiştrilor

Page 145: limbaj de asamblare-ivan

543

push bx mov ax,src ; axsrc mov bx,ax ; bxax (bcsrc) imul ax,bx ; axax*bx (ax=src^2) imul ax,bx ; axax*bx (ax=src^3) mov dst,ax ; dstax pop bx ; refacerea regiştrilor pop ax

endm Sursa poate fi valoare imediată, registru sau zonă de memorie, iar destinaţia

poate fi registru sau memorie, dar nu poate fi AX sau BX (care vor avea valorile dinainte de intrarea în blocul respectiv).

Împărţirii a două numere îi corespunde macrdefiniţia xdiv. Această macrodefiniţie ca avea patru parametrii:

x - deîmpărţit (sursă) y - împărţitor (sursă) c - cât (destinaţie) r - rest (destinaţie) k - test (destinaţie) Parametrul test va conţine după apelul macrodefiniţiei 0 dacă împărţirea se

putea efectua (împărţitorul diferit de 0) şi 1 dacă împărţitorul este 0 (împărţirea ar fi invalidă).

; Implementarea macrodefiniţiei x/y c,r xdiv macro x,y,c,r,k local xdiv_1,xdiv_2 push cx ; salvează cx

mov cx,y ; cx y test cx,cx ; test dacă bx=0 jnz xdiv_1 ; dacă nu atunci se continuă inc cx ; dacă da atunci k 1 (eroare) mov k,cx jmp xdiv_2 ; salt la sfârşit

xdiv_1: push dx ; salvează dx push ax ; salvează ax mov ax,x ; ax x cwd ;axdx,ax(extensia de semn de la 16 la 32

biţi) idiv cx ; împărţirea (axcât, dxrest) mov c,ax ; c ax (câtul) mov r,dx ; r dx (restul) xor dx,dx ; k 0 (totul e OK) mov k,dx pop ax ; refacerea regiştrilor pop dx

Page 146: limbaj de asamblare-ivan

544

xdiv_2: pop cx ; refacerea lui cx

endm

Se va analiza în cele ce urmează gradul de generalitate a definiţiei, parcurgând codul acesteia.

Operanzii sursă pot fi imediaţi sau prin adresare directă şi indirectă, iar cei destinaţie pot fi adresaţi direct sau indirect fără limitări. În cazul în care se transmit ca parametrii regiştri apar unele restricţii.

Instrucţiunea: mov cx,y

face ca registrul CX să nu poată fi parametrul x.

Instrucţiunea: mov c,ax

face ca registrul DX să nu poată fi parametrul c (el este accesat mai târziu).

Instrucţiunile: pop reg

de la sfârşit determină ca regiştrii AX, CX, DX să nu poată fi folosiţi pentru operanzii destinaţie (cât, rest, eroare) ai macrodefiniţiei, ei recăpătând valorile dinaintea apelului. Deci restricţiile sunt:

CX nu poate fi deîmpărţitul (parametrul x); AX, CX, DX nu pot fi câtul, restul sau parametrul de test (c, r, k). S-au definit cele două etichete ca local pentru că avem de-a face cu o

macrodefiniţie şi orice expandare a acesteia în program va genera noi definiri ale acestor etichete. Directiva local specifică asamblorului că referirile la acestea se vor face cât ţine corpul macroinstrucţiunii.

Macrodefiniţia pentru iniţializarea cu 0 a regiştrilor de lucru este:

Setzeroall Macro xor ax, ax xor bx, bx xor cx, cx xor dx, dx xor si, si xor di, di endm

Page 147: limbaj de asamblare-ivan

545

Macrodefiniţia care evaluează expresia din limbajul C

a1=a2=a3=a4=a5=0 sau expresia din limbajul COBOL

move 0 to a1 a2 a3 a4 a5 este :

Setzero macro a1, a2, a3, a4, a5 mov a1, 0 mov a2, 0 mov a3, 0 mov a4, 0 mov a5, 0 endm

Macrodefiniţia care iniţializează câmpurile a1, a2, a3, a4, a5 definite pe un bait cu o valoare dată, val, este:

Setval macro a1, a2, a3, a4, a5, val push ax mov ax, word ptr val mov a1, ax mov a2, ax mov a3, ax mov a4, ax mov a5, ax pop ax endm

Macrodefiniţia care interschimbă conţinutul a două zone de memorie a şi b definite pe un octet este:

Swap1 macro a, b push ax mov al, a mov ah, b mov b, al mov a, ah pop ax

endm

Macrodefiniţia care interschimbă conţinutul a două zone de memorie definite pe câte doi octeţi (un cuvânt) este:

Page 148: limbaj de asamblare-ivan

546

Swap2 macro a, b push ax push bx mov ax, a mov bx, b mov b, ax mov a, bx pop bx pop ax

endm

Macrodefiniţia care iniţializează o zonă de memorie definită pe un cuvânt cu conţinutul altei zone de memorie definite tot pe un cuvânt este:

Setmem macro dest, sursa push ax mov ax, ds:[sursa] mov ds:[dest], ax pop ax endm

Macrodefiniţia Strcmp compară două şiruri s1, de lungime l1, si s2, de

lungime l2. Registrul AX conţine deplasamentul primului şir, registrul DX conţine deplasamentul celui de-al doilea şir, iar BX conţine min{ l1, l2 }. Rezultatul se transmite prin registrul AX: -1 şir1 < şir 2 AX = 0 şir 1 = şir 2 1 şir 1 > şir 2

Strcmp macro push cx push di push si mov si, ax mov di, dx mov cx, bx et1: mov al, byte ptr ds:[si] mov dl, byte ptr ds:[di] cmp al, dl jne et2 inc si inc di loop et1 mov ax, 0 jmp et4 et2: jb et3 mov ax, 1

Page 149: limbaj de asamblare-ivan

547

jmp et4 et3: mov ax, -1 et4: pop si pop di pop cx endm

Macrodefiniţia care converteşte o literă mică într-o literă mare este: Lowtoupper macro char local final mov al, ‘a’ jb final cmp al, ‘z’ ja final sub al, 20h final:

nop endm

În acelaşi fel se definesc şi alte macrodefiniţii uzuale, care vor constitui într-o bibliotecă. 12.9 Concluzii

Se pune problema alegerii dintre macroapeluri sau proceduri. În cazul macrodefiniţiilor, texul sursă scris de program devine mai clar şi mai scurt. Un alt aspect important este câştigul de viteză pe care îl dobândim prin folosirea macrodefiniţiilor deoarece nu se mai pierde timpul cu memorarea adresei de revenire în stivă, precum şi cu descărcarea stivei la terminarea procedurii. Marele dezavantaj este creşterea dimensiunii programului, care după câteva expandări, poate atinge dimensiuni considerabile.

13

Page 150: limbaj de asamblare-ivan

548

PROCEDURI 13.1 Reutilizabilitatea

Secvenţele repetitive şi secvenţele repetabile sunt două lucruri diferite. Două secvenţe

repetitive sunt legate de implementarea structurii repetitive în una din forme (for, do-while, do-until) rezolvarea secvenţelor repetabile şi-a găsit diferite soluţii dintre care se enumeră:

plasarea secvenţei în afara modulului principal si referirea ei în diferite puncte ale acestuia; limbajul COBOL are soluţia elegantă prin utilizarea instrucţiunii PERFORM; reutilizabilitatea e legată numai de programul respectiv;

definirea de proceduri, ca unităţi program distincte, cu grad de reutilizabilitate extins şi la alte programe; procedura are o listă de parametri împărţită în trei subliste: sublista parametrilor ce conţin datele iniţiale care vor fi prelucrate în procedură, sublista ce conţine rezultatele prelucrării şi sublista de parametri care modul cum a decurs prelucrarea (starea procedurii); procedura nu returnează un rezultat, sau returnează un rezultat de tip vid;

definirea de funcţii în care lista de parametri conţine numai date de intrare şi rezultatul prelucrării este unul singur şi va fi returnat, putând fi utilizat direct ca termen în evaluarea de expresii; funcţiile se construiesc în vederea unei reutilizări foarte largi.

Procedurile şi funcţiile se constituie în biblioteci şi de aceea programatorul trebuie să dea dovadă de grijă când le construieşte pentru a le asigura corectitudinea şi un grad de generalizare ridicat. Fără aceste caracteristici nici o bibliotecă de subprograme nu este operaţională.

Cu atât mai mult soluţiile în limbaje de asamblare care sunt mult mai diversificate, trebuie realizate astfel încât gradul de reutilizabilitate să atingă nivele care să justifice efortul de a construi biblioteci de proceduri.

Pentru realizarea conversiilor atât de frecvent utilizate în programe scrise în limbaj de asamblare se construiesc proceduri. De asemenea, se construiesc proceduri pentru calcule matriciale, pentru realizarea unor validări, pentru operaţii de intrare/ieşire pe disc, pentru combinarea unei secvenţe de apeluri de întreruperi. Tot ce se reutilizează în viitor, e preferabil să fie proiectat ca o procedură.

13.2 Structura unei proceduri

O procedură se descrie prin: Parametrii de intrare p1, p2, .., pn ; Bloc de prelucrare S Parametrii de ieşire r1 ,r2, ..,rm Parametrii de stare s1, s2, .., sk,.

Definirea procedurilor este prezentată în figura 13.1

Page 151: limbaj de asamblare-ivan

549

Figura 13.1 – Definirea unei proceduri

La definirea unei proceduri se are în vedere ca n să fie rezonabil, k să fie în

general 1, dacă m=1 se va defini procedură, gestionând cu atenţie rezultatele pentru a obţine ceea ce caută, existând multe riscuri în a se obţine altceva.

În programul scris în limbaj de asamblare, procedura are structura din figura 13.2.

r1 = f1 (p1, p2, …, pn) r2 = f2 (p1, p2, …, pn, r1) … rm = fm(p1, p2, …,pn, r1, r2, …,rm-1) s1 = 1 s2 = 2 … sk = k

p1 p2

… pn

r1 r2 ... rm

s1 ... s2 ... sk

Page 152: limbaj de asamblare-ivan

550

Figura 13.2 – Structura unei proceduri

De exemplu, procedura PAR pentru criptarea unui text cu metoda lui Cezar după o parolă este:

date segment parola db 'parola' n db n-parola ;lungime parolă text db 'text secret ' db 12 dup(0) m db m-text ;lungime text textcod db 24 dup(),'$' date ends stiva segment

db 512h dup(0) stiva nds cod segment ssume cs:cod,ds:date,ss:stiva start: mov ax, date mov ds, ax call par

mov ax,4c00h int 21h

Salvare registre

Bloc prelucrare cu referirea parametrilor formali

Stocarea rezultatelor

Restaurare registre

NumeProcedura PROC [{FAR,NEAR}]

NumeProcedura ENDP

ret [n]

Page 153: limbaj de asamblare-ivan

551

par proc near mov cl, m xor ch, ch lea di, text lea si, parola

par1: mov al, byte ptr[di] ;încarcă

caracterul din text în ax add al, byte ptr[si] ;adună

caracterul din parolă add al, cl ;adună poziţia

caracterului add al,100 ;adună o constantă mov [di+m], al descarcă caracterul

codificat inc ah cmp ah, n jne par2 xor ax, ax lea si, parola dec si par2:

inc di inc si loop par1

ret par endp cod ends end start

13.3 Apelarea procedurilor

Apelarea unei proceduri presupune : existenţa unui program apelator existenţa procedurii apelate Dacă programul apelator nu este apelat la nivelul său, va fi numit program

principal, sau funcţie rădăcină intr-o structură arborescentă sau nod iniţial într-o structură de tip reţea asemănată unui program realizat modular.

Înainte de efectuarea apelării procedurii, în programul principal / procedura apelatoare trebuie să se găsească o secvenţa care stochează parametri care vor fi prelucraţi în procedură. Revenirea din procedură se efectuează de regulă la instrucţiunea care urmează instrucţiunii call. Adresa instrucţiunii ce urmează instrucţiunii call se gestionează prin intermediul registrului IP.

În procedura apelată există secvenţa pentru salvarea regiştrilor. Este o regulă de bază, aceea de a nu distruge conţinutul resurselor la care nu are acces în procedură.

Dacă aceeaşi procedură este apelată în mai multe puncte din programul principal schema fluxului de execuţie este dată în figura 13.2.

Page 154: limbaj de asamblare-ivan

552

Figura 13.2 – Apelarea multiplă a unei proceduri

Dacă programul principal apelează procedura Suma, iar procedura aceasta apelează procedura Compara, schema de execuţie este dată în figura 13.3.

Figura 13.3 – Apelarea în cascadă a procedurilor

call sume

call sume

call sume

sume PROC

ret sume ENDP

Call suma

Suma PROC

Call compara

RET

Compara PROC

RET

Page 155: limbaj de asamblare-ivan

553

Figura 13.4 – Schema standard de structurare/apel/revenire

Se observă apariţia a două concepte: parametri reali şi parametri formali. Pentru

programatorul ce utilizează limbajul de asamblare se evidenţiază semnificaţia acestora. În programul scris în limbajul de asamblare se lucrează cu registre şi cu

zone de memorie. Deci, dacă parametri sunt operanzi, în mod obligatoriu sunt ori registre, ori zone de memorie. Se lucreză cu: identificatori (registru sau zonă de memorie), adresă, lungime şi conţinut.

Cazul 1: parametri p1,p2, ...pn sunt în număr restrâns şi sunt stocaţi în registre; rezultatele r1,r2, ...rm sunt în număr restrâns şi sunt stocaţi în registre; informaţia de stare se stochează într-un registru. În această situaţie, pregătirea transmiterii parametrilor înseamnă iniţializare de registre,

apelul procedurii înseamnă salt către prima instrucţiune executabilă din procedură. În procedură se salvează registre pe stivă. Referirea parametrilor formali, va însemna utilizarea registrelor. Stocarea rezultatelor se face în registre. Stocarea stării se va face într-un registru (CX = 0 dacă prelucrarea s-a efectuat corect, CX = -1 dacă prelucrarea nu s-a efectuat corect, de exemplu). Se returnează registrele. Se are în vedere ca prin restaurare să nu se distrugă din rezultatele obţinute sau registru de stare al procedurii.

Parametrii reali sunt registrele care se pregătesc în programul apelator. Parametrii formali sunt aceleaşi registre pe care le utilizează procedura. Li se spune aşa,

probabil pentru faptul că programatorul când scrie procedura nu are în aceşti parametri valori efective cu care să lucreze. El numai îşi imaginează. Doar după ce a fost apelată, în registru se

Program principal

Definire parametri reali

Pregătirea transmiterii parametrilor reali

Prelucrarea rezultatelor prelucrării

Salvare registre

Prelucrare referind parametri formali

Stocarea rezultatelor

Restaurare registre

Revenire (RET)

Procedură (PROC)

APEL PROCEDURĂ (call)

Page 156: limbaj de asamblare-ivan

554

găsesc informaţii care fac obiectul prelucrării. Lucrul cu registre este cel mai simplu mod de transmitere a parametrilor, listele de parametri formali, respectiv, reali, suprapunându-se din toate punctele de vedere.

Cazul 2: Conţinutul zonelor de memorie care formează parametri reali se transmit pe stivă,

numărul parametrilor fiind fix; parametri formali sunt zonele din stivă care la procesarea listei au expresii de referire precis construite; aceasta este transmiterea parametrilor prin valoare; în procedură se lucrează cu copiile parametrilor reali, aflate în stivă; rezultatele se depun fie în registre (preferabil), fie pe stivă.

Parametrii reali sunt zone de memorie diferite în programul apelator. Parametri formali sunt zone ale stivei unde se află copii ale valorilor parametrilor reali. Cele două liste se referă la zone de memorie diferite care au însă acelaşi conţinut. Este cunoscut cazul procedurii de interschimb a conţinutului dintre două zone de memorie “a” şi „b”.

Parametrii reali

Procedură de interschimb

A - 15

B - 20

Stivă [BP + 4] - 15 [BP + 6] - 20 Registrele AX şi BX se folosesc pentru interschimb: AX = [BP + 4] BX = [BP + 6] [BP + 4] = BX [BP + 6] = AX

Cazul 3: Zonele de memorie ale căror conţinut urmează să fie prelucrată în proceduri sunt de

tipuri şi lungimi foarte diferite; este convenabil pe stivă să se transmită adresele zonelor de memorie care formează lista parametrilor reali se construieşte în acest fel listă de adrese a parametrilor reali, în procedură, parametri formali sunt de fapt variabile pointer care permit referirea din procedură a zonelor de memorie diferite în programul apelator; acesta este transmitere prin adresă.

Procedura de interschimb va realiza corect operaţia dacă pe stivă se vor găsi adresele zonelor de memorie “a” şi „b”; procedura va lucra direct pe ele si într-adevăr va efectua interschimbul.

Cazul 4: Se defineşte un segment de date comune tuturor procedurilor si programului apelator.

Printr-o încărcare corectă a adresei acestui segment se va opera cu parametri reali definiţi în el atât din programul apelator, cât şi din orice procedură segmentul de date este comun întregii operaţii. Se realizează transmiterea prin referinţă, variabilele cu anumiţi identificatori se află în segmentul comun aplicaţiei şi de acolo se utilizează conţinutul zonei de memorie.

13.4 Locul procedurilor

Page 157: limbaj de asamblare-ivan

555

Mecanismul de definire a procedurilor permit plasarea lor în diferite moduri, figura 13.5.

.code

start: a1 proc ... ret a1 endp a2 proc ... ret a2 endp ... call a1 ... call a2 ...

mov ax, 4c00h int 21h end start a)

.code start: ... call b1 ... call b2 ... mov ax, 4c00h int 21h b1 proc ...

… ret b1 endp b2 proc ... ret b2 endp

b)

Figura 13.5 – Poziţii ale procedurilor Astfel se observă că există: Proceduri definite în corpul programului principal, figura 13.5 a; Proceduri definite după secvenţa din segmentul de cod, figura 13.5 b:

mov ax, 4c00h int 21h

13.5 Instrucţiuni specifice lucrului cu proceduri

Instrucţiunile specifice lucrului cu proceduri sunt : call – pentru apelul unei proceduri ret – pentru revenirea în programul apelator. Semnificaţia instrucţiunilor diferă funcţie de modul în care sunt poziţionate cele două

elemente: Dacă procedura se află în acelaşi segment cu programul apelator şi este apelată direct prin

nume, instrucţiunea call pune în stivă valoarea curentă a lui IP iar apoi dă controlul procedurii:

SP = SP – 2 (SP) = IP ;push IP IP = IP + D16

unde D16 reprezintă deplasarea între destinaţie şi instrucţiunea ce urmează lui CALL în programul apelator.

Page 158: limbaj de asamblare-ivan

556

Instrucţiunea ret efectuează:

IP = (SP) ;salt la instrucţiunea următoare lui CALL SP = SP + 2 ;revenirea la baza stivei (pop IP). Se consideră programul:

Parametri segment a dw 10 b dw 15 c dw 21 d dw 37 s dw ? Parametri ends Stiva segment Dw 100h dup (?) varf label word Stiva ends Apelator segment ASSUME cs:code, ds:parametri, ss:stiva start: mov ax, parametri mov ds, ax mov ax, stiva mov ss, ax mov sp, offset varf mov ax, offset a push ax mov ax, offset b push ax mov ax, offset c push ax mov ax, offset s push ax call NEAR ptr suma mov ax, s add ax, d mov s, ax mov ax, 4c00h int 21h ;procedura în acelaşi segment cu programul apelator suma proc NEAR push bp mov bp, sp push ax xor ax, ax add ax, [bp+10] add ax, [bp+8] add ax, [bp+ 6]

Page 159: limbaj de asamblare-ivan

557

mov [bp+ 4], ax pop ax pop bp ret 8h Apelator ends

Dinamica stivei pentru apelarea procedurii suma din program este:

Stivă Semnificaţie Adresare Offset a Plasare adresa (offset) a [BP+10] Offset b Plasare adresa b [BP+8] Offset c Plasare adresa c [BP+6] Offset s Plasare adresa rezultat s [BP+4]

IP Ape procedura sumă [BP+2] BP Salvează baza stivei [BP+0] Ax Salvare pe stivă valoare registru AX [BP-2] Ax Refacere registru AX - BP Refacere registru BP - IP Reface IP (se pregăteşte return) -

SP = SP +8h Scoate parametri din stivă - Dacă procedura se află în acelaşi segment şi este apelată indirect instrucţiunea call

realizează:

SP = SP – 2 (SP) = IP (IP) = EA

unde EA reprezintă adresa efectivă (effective address).

Dacă programul apelator se află într-un registru şi procedura apelată se află în alt segment, instrucţiunea call realizează:

SP = SP – 2 (SP) = CS SP = SP – 2 (SP) = IP

Instrucţiunea ret pentru acest caz realizează:

IP = (SP) SP = SP + 2 CS = (SP) SP = SP + 2

Dinamica stivei când programul apelator se află în segmentul apelului, iar procedura se află

în segmentul procedurii este: Parametri segment a dw 10 b dw 15

Page 160: limbaj de asamblare-ivan

558

c dw 21 d dw 37 s dw ? Parametri ends Stiva segment Dw 100h dup (?) varf label word Stiva ends Apelator segment ASSUME cs:code, ds:parametri, ss:stiva start: mov ax, parametri mov ds, ax mov ax, stiva mov ss, ax mov sp, offset varf mov ax, offset a push ax mov ax, offset b push ax mov ax, offset c push ax mov ax, offset s push ax call far ptr suma mov ax, s add ax, d mov s, ax mov ax, 4c00h int 21h Apelator ends Procedura segment ASSUME cs:procedura suma proc FAR push bp mov bp, sp push ax xor ax, ax add ax, [bp+12] add ax, [bp+10] add ax, [bp+ 8] mov [bp+ 6], ax pop ax pop bp ret 8h suma endp procedura ends

Stivă semnificaţie Adresare

Page 161: limbaj de asamblare-ivan

559

Offset a Plasare adresa (offset) a [BP+12] Offset b Plasare adresa b [BP+10] Offset c Plasare adresa c [BP+8] Offset s Plasare adresa rezultat s [BP+6]

CS Salvare registru de cod pe stivă (CALL) [BP+4] IP Ape procedura sumă [BP+2] BP Salvează baza stivei [BP+0] Ax Salvare pe stivă valoare registru ax [BP-2] Ax Refacere registru ax - BP Refacere registru BP - IP Reface IP (se pregăteşte return) - CS Reface segmentul de cod -

SP = SP +8h Scoate parametri din stivă - Dacă programul apelator şi procedura se află în segmente diferite, iar apelul este direct,

instrucţiunea CALL realizează:

SP = SP – 2 (SP) = CS SP = SP – 2 (SP) = IP (IP) = (EA) (CS) = (EA) + 2

Instrucţiunea ret poate fi definită împreună cu o expresie constantă:

ret expresie care evaluează şi devine o valoare de adunare la registru de stivă (SP) ca parte din instrucţiune determinând executarea secvenţei:

SP = SP + expresie pentru revenire în programul apelator:

IP = (SP) SP = SP + 2 CS = (SP) SP = SP +2 SP = SP + expresie.

Aceasta este echivalentă cu scoaterea din stivă a unui număr de baiţi specificat prin expresie; se foloseşte la eliminare din stivă a parametrilor care au fost puşi înainte de apelul procedurii.

Făcând modificări asupra conţinutului stivei în procedura apelată este posibilă revenirea în programul apelator la altă instrucţiune şi chiar la instrucţiuni aflate în segment de cod diferit segmentului programului apelator. Se recomandă folosirea acestei facilităţi doar pentru protejarea codului.

Page 162: limbaj de asamblare-ivan

560

14

PRELUCRĂRI ÎN VIRGULĂ MOBILĂ 14.1 Scurtă istorie a prelucrărilor în virgulă mobilă

Procesoarele din familia 80x87 au fost construite cu scopul precis de a creşte viteza de lucru a calculatoarelor în operaţiile matematice complexe. Până la versiunea 80387 coprocesoarele puteau sa existe sau nu în calculatoare. De exemplu, un procesor 80386 lucrează foarte bine cu coprocesorul 80287 sau cu 80387. Mai târziu, la apariţia procesorului 80486, coprocesorul a fost înglobat în procesor, pentru îmbunătăţirea performanţelor. Astfel, un procesor 80486 conţine un procesor 80387. Instrucţiunile procesorului 80486 sunt cele ale procesorului 80386, cele ale coprocesorului 80487 şi câteva în plus. Toate coprocesoarele lucrează insa cu numere reprezentate pe 80 de biţi. Procesoarele nu trebuie sa se ocupe de conversia numerelor pe 8, 16, 32 sau 64 de biţi în cele pe 80 de biţi, deoarece instrucţiunile care mută datele din regiştrii procesorului în cei ai coprocesorului convertesc automat numerele în formatul de 80 de biţi. Unele din numerele de 80 de biţi au o semnificaţie speciala, ele pot reprezenta numărul infinit, un număr cu parte fracţionară în perioadă sau un NaN (Not a Number). NaN este o valoare care se obţine în momentul în care se încearcă rezolvarea unor calcule de genul 0/0 sau /.

O altă caracteristica a coprocesoarelor este aceea a executării secvenţiale a instrucţiunilor. De exemplu, dacă avem două instrucţiuni, cea de-a doua nu este executată până când prima instrucţiune nu a fost terminată. 14.2 Resurse

Pentru efectuarea prelucrărilor în virgulă mobilă este utilizat un coprocesor sau se construiesc proceduri care efectuează conversii şi calcule cu utilizarea aritmeticii binare cu biţii reprezentând caracteristica şi mantisa. Astfel dacă se aduna numerele A = 0,37·102 B = 0,123·104 se parcurg următorii paşi.

se omogenizează numerele, în aşa fel încât sa aibă acelaşi exponent: A’ = 0,0037·104 B = 0,123·104

Page 163: limbaj de asamblare-ivan

561

se efectuează operaţia de adunare: C = A’ + B = 0,1267·104

se procedează la normalizare, prin eliminarea zerourilor de după virgulă şi la diminuarea exponentului; ca în cazul scăderii:

C = A – B unde:

A = 0,736·104 B = 0,731·104 C = A – B = 0,005·104 = 0,5·102

Toate aceste operaţii se obţin prelucrând biţii din reprezentările constantelor în virgulă mobilă pe 4, 8, 10 baiţi, utilizând instrucţiunile aritmeticii binare. Procedurile emulează un coprocesor numai dacă asigură efectuarea gamei complete de operaţii de prelucrare în virgula mobila.

De exemplu, o procedura de normalizare se efectuează astfel:

se deplasează spre stânga K=5 poziţii. se scade din exponent cat înseamnă K poziţii, cu consecinţe asupra

semnului dacă este cazul. Pentru o procedura de adunare vor exista următoarele operaţii:

dacă E1 = E2 atunci M3 = M1 + M2

dacă E1 1 vedem cu cat şi mărim E3. Z1 reprezintă partea întreagă a mantisei, în acest caz a lui M3.

Dacă este utilizat un coprocesor, resursele disponibile ale acestuia sunt: opt registre ST0, ST1, ST2, ST3, ST4, ST5, ST6, ST7; fiecare registru

are o lungime de 10 baiţi; o structura de baiţi, denumită status word, destinată indicatorilor de

condiţie; astfel, coprocesorul 8087 are o structura de 16 biţi cu indicatorii de condiţie specificaţi în figura 14.1.

000001101……

E1 M1

E2 M2

Page 164: limbaj de asamblare-ivan

562

Utilizare

Invalid operation exception Denormalized operand exception Zerodivide exception

Overflow exception

Underflow exception

Precision exception

Interrupt exception

Condition code 0

Condition code 1

Condition code 2

Stack-top pointer

Condition code 3

Busy signal

Figura 14.1 – Indicatorii de condiţie ai coprocesorului

În caz de eroare biţii 0, 1, 2, 3, 4, 5, 8, 9, 10, 14 sunt setaţi pe 1. Biţii 13, 12, 11 arată care dintre cei 8 regiştri este vârf de stiva. Aceşti 3 biţi necesari gestionarii stivei vor fi numiţi în continuare PVST (pointer pentru gestionarea vârfului de stivă).

PVST {0, 1, 2, 3, 4, 5, 6, 7}

Oricare dintre regiştri poate fi vârf de stivă. Dacă PVST este 6, o operaţie de incrementare conduce ca PVST sa devină 7. Dacă PVST este 7, o operaţie de incrementare va face ca PVST sa devină 0. În cazul în care PVST are valoarea 0 şi se efectuează o operaţie de decrementare, PVST va avea valoarea 7. Regiştrii funcţionează ca stivă circulară. Coprocesorul accesează operanzi definiţi în memorie cu descriptori de tip DD, DT sau DQ. Regiştrii coprocesorului sunt construiţi în stivă (figura 14.2.), efectuarea operaţiilor fiind precedată de operaţii de

Page 165: limbaj de asamblare-ivan

563

modificare a vârfului de stivă, operaţii specifice introducerii în stivă (push) sau extragerii din stiva (pop) a operanzilor.

Figura 14.2 – Regiştrii coprocesorului 8087

Operaţiile de prelucrare a datelor efectuate de coprocesor sunt

împărţite în mai multe clase:

R – R ceea ce înseamnă că ambii operanzi se află în regiştri (pe stivă) şi prelucrările se efectuează în unul din următoarele doua moduri:

o un operand este conservat iar celalalt va conţine rezultatul prelucrării ;

o ambii operanzi sunt conservaţi şi rezultatul prelucrării este introdus la baza stivei; operanzii conservaţi, iar vârful stivei glisează.

Page 166: limbaj de asamblare-ivan

564

R – M ceea ce înseamnă că un operand se afla în registru şi altul se afla în memorie; operandul din registru este copiat în alt registru şi la baza stivei se va găsi rezultatul, fie operandul este modificat şi el reprezintă rezultatul prelucrării; poziţia operandului aflat în memorie determina uneori ca rezultatul prelucrării sa fie stocat în această zona, în cazul instrucţiunilor de forma: cod_operatie M,R.

Structura terminală a mnemonicelor indica prin prezenţa literei p, efectuarea de operaţii de introducere în stiva (push), sau de extragere din aceasta (pop). Utilitatea acestui mod de lucru este evidenţiată atunci când se evaluează expresii şi regiştrii conţin rezultate parţiale care se refera direct, fără a mai fi nevoie de operaţii de încărcare (load) sau memorare (store).

14.3 Setul de instrucţiuni

Instrucţiunile coprocesoarelor din familia 80x87 sunt clasificate în şase grupe distincte:

instrucţiuni de transfer al datelor (de încărcare) ; instrucţiuni aritmetice ; instrucţiuni de comparare ; instrucţiuni transcedentale ; constante ; instrucţiuni de control al procesorului . Pentru o gestionare corectă a operaţiilor care se desfăşoară în coprocesor a

fost implementată structura de control a operaţiilor, denumită control word. Comportamentul acestui registru este asemănător cu cel al registrului de stare. Registrul de control are o lungime de 16 biţi, valorile acestora fiind detaliate în figura de mai jos:

Utilizare

Invalid operation exception mask Denormalized operand exception mask Zerodivide exception mask

Overflow exception mask

Underflow exception mask

Page 167: limbaj de asamblare-ivan

565

Precision exception mask

Reserved Interrupt enable mask

0 = Interrupts enabled 1 = Interrupts disabled

Precision Control 00 = 24 bits 01 = reserved 10 = 53 bits 11 = 64 bits

Rounding Control 00 = round to nearest or even 01 = round down 10 = round up 11 = truncate

Infinity Control 0 = Projective 1 = Affine

Reserved

Figura 14.3 – Indicatorii de control

Instrucţiunile de încărcare au rolul de a copia conţinutul unei zone de memorie sau a unui registru numit sursa (SRS), intr-un registru specificat. Instrucţiunea FLD SRS memorează în ST conţinutul din SRS convertit de la tipul real scurt sau real lung la tipul real cu reprezentare pe 10 baiţi. Instrucţiunea FILD SRS memorează în ST conţinutul din sursa SRS convertit de la tipul întreg (scurt sau lung) la tipul real pe 10 baiţi. Instrucţiunea FDLD SRS converteşte conţinutul zecimal împachetat din SRS la tipul real reprezentat pe 10 baiţi şi încarcă în ST.

Instrucţiunile FLD, FDLD şi FDLD decrementează pointerul stivei formată din regiştrii ST(I), cu I=1,..,7 permiţând rămânerea pe stivă şi a celorlalţi operanzi. Instrucţiunea FST DST efectuează copierea din registrul ST în operandul DST a conţinutului convertit de la tipul real pe 10 baiţi la tipul real scurt sau real lung. Instrucţiunea FISI DST copiază din registrul ST în operandul DST, conţinutul convertit la tipul întreg. Instrucţiunea FBSTP DST converteşte la tipul zecimal împachetat conţinutul registrului ST şi îl memorează în zona de memorie referită de DST.

Instrucţiunile FSTP şi FISTP realizează aceleaşi prelucrări precum instrucţiunile FST şi FIST cu deosebirea ca în plus incrementează pointerul cu care se gestionează vârful stivei.

Page 168: limbaj de asamblare-ivan

566

Instrucţiunea FXCH STi schimbă conţinutul registrului STi cu ST. Dacă lipseşte operandul interschimbul se efectuează între regiştrii ST şi ST1.

Instrucţiunile aritmetice corespund operaţiilor elementare (adunare, scădere, înmulţire, împărţire, extragere modul, schimbare semn, extragere radical, scalare, obţinerea restului împărţirii). În cazul în care instrucţiunea are ca ultima litera a mnemonicii litera P, aceasta are semnificaţia operaţiei de eliberare a stivei (pop) ceea ce corespunde incrementării pointerului de stivă. De exemplu, instrucţiunile FADD şi FADDP realizează operaţia de adunare. În plus, instrucţiunea FADDP incrementează pointerul de gestionare a vârfului de stivă (PVST = PVST + 1). Instrucţiunea de adunare a numerelor reale FADD DST,SRS corespunde operaţiei:

(DST) := (DST) + (SRS) Instrucţiunea de adunare a numerelor reale FADDP DST,SRS corespunde operaţiilor:

(DST) := (DST) + (SRS) PVST := PVST + 1

Instrucţiunile de scădere numere reale FSUB DST, SRS şi FSUBP DST,SRS

corespund operaţiilor:

(DST) := (DST) - (SRS), respectiv

(DST) := (DST) - (SRS) PVST := PVST + 1

Instrucţiunea FISUB DST,SRS efectuează operaţia de scădere a datelor de

tip întreg, scurt sau lung.

(DST) := (DST) + (SRS) Instrucţiunile de înmulţire FMUL DST,SRS şi FMULP DST,SRS

efectuează operaţiile: (DST) := (DST) * (SRS)

respectiv:

(DST) := (DST) * (SRS)

Page 169: limbaj de asamblare-ivan

567

PVST := PVST + 1 Instrucţiunile FDIV DST,SRS şi FDIVP DST,SRS efectuează împărţirea:

(DST) := (DST) / (SRS)

În plus FDIVP incrementează pointerul pentru gestionarea stivei. Pentru

împărţirea numerelor întregi se utilizează instrucţiunea FIDIV. Pentru obţinerea valorii absolute se utilizează instrucţiunea FABS, fără

operanzi care realizează operaţia:

(ST) := | ST |

Schimbarea semnului se efectuează de către instrucţiunea FCMS cu operandul implicit ST, care efectuează operaţia:

(ST) := -(ST)

Pentru extragerea rădăcinii pătrate se utilizează instrucţiunea FSQRT cu operandul implicit ST, care realizează operaţia descrisa prin:

(ST) := )(ST Rotunjirea la întreg a operandului implicit ST este efectuată de instrucţiunea FRNDINT (Round to integer).

Operaţia de scalare corespunde înmulţirii unui număr aflat în registrul ST cu 2n, n aflat în registrul ST(1). Mnemonica instrucţiunii care implementează această instrucţiune este FSCALE.

În anumite operaţii este necesară preluarea repetată exponentului şi a părţii fracţionare, ceea ce impune memorarea lor ca operanzi distincţi, figura 14.4.

Figura 14.4 – Separarea ca operanzi distincţi a exponentului şi a părţii fracţionare

Instrucţiunile de comparare compara doi operanzi şi poziţionează indicatorii de condiţie.

exponent parte fractionara

ST(1) ST

Page 170: limbaj de asamblare-ivan

568

Instrucţiunea FCOM SRS efectuează operaţia (ST) – (SRS) şi poziţionează indicatorii de condiţie. Instrucţiunea FCOMP SRS efectuează operaţia (ST) – (SRS) şi incrementează pointerul pentru gestionarea stivei. Pentru compararea datelor de tip întreg se folosesc instrucţiunile FICOM şi FICOMP. Pentru compararea cu zero a elementului din vârful stivei este folosită instrucţiunea FTST, care efectuează operaţia (ST) – 0.0 .

Funcţiile transcedentale au implementate expresii precum:

f(x) = 2x – 1

g(x) = arctg(x)

r(x) = tg(x)

h(x,y) = y*log2(x), cu x (0, ) şi y R

p(x,y) = y*log2(x + 1), cu x (0, 1 - 2/2 ) şi y R Instrucţiunea F2XM1 evaluează funcţia f(x). Dacă x [0; 0.5] şi x este

memorat în ST, atunci

(ST) := 2(ST) - 1 Instrucţiunea FPATAN evaluează funcţia g(x). Instrucţiunea FPTAN evaluează funcţia r(x) pentru x (0; /4).

Instrucţiunea FYL2X evaluează funcţia h(x,y); operandul x se găseşte în registrul ST, iar operandul y se găseşte în registrul ST(1). Instrucţiunea FYL2XP1 evaluează funcţia p(x,y). Variabila x se memorează în registrul ST, iar variabila y se memorează în registrul ST(1).

Instrucţiunile pentru gestionarea şi manipularea constantelor decrementează pointerul PVST şi iniţializează registrul ST cu o anumită valoare, specificată prin instrucţiune.

Instrucţiunea FLDZ decrementează PVST şi iniţializează registrul ST cu valoarea zero real (0.0) .

Instrucţiunea FLD1 decrementează PVST şi iniţializează registrul ST cu constanta 1.0 . Instrucţiunea FLDPI decrementează PVST şi iniţializează registrul ST cu constanta PI.

Page 171: limbaj de asamblare-ivan

569

Instrucţiunea FLD2E decrementează PVST şi iniţializează registrul ST cu constanta log2 e .

Instrucţiunea FLD2T decrementează PVST şi iniţializează registrul ST cu constanta log2 10 .

Instrucţiunea FLDLG2 decrementează PVST şi iniţializează registrul ST cu constanta log10 2.

Instrucţiunea FLDLN2 decrementează PVST şi iniţializează registrul ST cu constanta loge 2.

Instrucţiunile de control vizează iniţializarea procesorului (FINIT), punerea pe unu a biţilor ce formează măşti pentru întreruperi (FDISI), punerea pe zero a biţilor ce formează măşti ale întreruperilor (FENI), incrementarea pointerului stivei (FINCSTP), decrementarea pointerului stivei (FDECSTP), memorarea registrului de stare (FSTSW), memorarea registrului de control (FSTCW), încărcarea registrului de control (FLDCW).

Instrucţiunea FNOP copiază registrul ST în el însuşi. Este folosită pentru întârzierea execuţiei proceselor.

Pentru salvarea regiştrilor se foloseşte instrucţiunea FSAVE, iar pentru restaurarea regiştrilor se foloseşte instrucţiunea FRSTOR. Zona de memorie în care se salvează regiştrii are o lungime de 94 de baiţi.

Pentru dealocarea unui registru se foloseşte instrucţiunea FFREE, care setează pe zero flagul corespunzător registrului respectiv, aceasta semnificând ca el este gol.

Pentru salvarea pe stivă a regiştrilor de stare se foloseşte instrucţiunea FSTSW, iar pentru salvarea informaţiilor privind procesele, regiştrii de control şi de stare, registrul tag şi pointerii operanzilor se foloseşte instrucţiunea FSTENV. Zona de memorie în care se salvează va avea o lungime de 14 baiţi. 14.4 Sintaxa instrucţiunilor

Instrucţiunile pentru operaţii în virgulă mobilă (floating point instructions) încep cu litera F. Mnemonicele conţin elemente comune cu instrucţiunile limbajului de asamblare (add – addition, st – store, ld – load, cmp – compare, div – divide, sub – subtract, sqrt – square root, abs – absolute value, chs – change sign, mul – multiply, cos – cosine, sin – sine, patan – partial arctangent, ptan – partial tangent).

Instrucţiunile sunt de tip R – R, R – M, R – I cu luarea în considerare a faptului ca registrul acumulator este considerat ST(0), utilizat în formulele implicite de scriere a instrucţiunilor.

Astfel putem întâlni următoarele tipuri de instrucţiuni: Fmnem [[op1][,op2]]

Page 172: limbaj de asamblare-ivan

570

sau Fmnem [[destinaţie][,sursa]]

Prin utilizarea parantezelor drepte se realizează opţionalitatea operandului în expresie. Definirea explicită a operanzilor corespunde construcţiei:

Fmnem destinaţie, sursa cu semnificaţia: destinaţie := destinaţie operaţie sursa

De exemplu, în instrucţiunea: FADD ST(1),ST

avem ST(1) := ST(1) + ST

Operanzii ST(1) – destinaţie, ST(0) – sursa sunt operanzii impliciţi pentru această operaţie. Dacă în program se scrie FADD şi lipsesc operanzii, se subînţelege ca registrul ST(1) este sursa, iar ST este destinaţie.

Dacă se utilizează lista de operanzi în mod explicit, are loc atribuirea de rol sursa a unui anumit registru, respectiv, de rol destinaţie pentru un alt registru. Instrucţiunea: FADD ST(i),ST corespunde operaţiei ST(i) := ST(i) + ST iar instrucţiunea FADD ST,ST(i) corespunde operaţiei ST := ST + ST(i)

Page 173: limbaj de asamblare-ivan

571

În cazul în care un operand este definit în memorie se utilizează formele de instrucţiuni: Fmnem [[reg][,numevariabila]] unde operandul sursa se afla numai în memorie, iar operandul destinaţie se afla în registru.

Instrucţiunea FADD ST(2), preţ corespunde expresiei: ST(2) := ST(2) + pret Iar instrucţiunea FADD pret corespunde expresiei: ST := ST + pret Efectul execuţiei instrucţiunilor

Instrucţiunile au ca efect atât realizarea de operaţii simple (adunări, scăderi, memorări, încărcări, iniţializări) cit şi extragerea sau depunerea pe stiva a rezultatelor cu efectele de translatare specifice acestui tip de structura de date care este stiva.

Pentru a evidenţia efectele execuţiei unei operaţii se impune: prezentarea conţinutului regiştrilor ST, ST(1), ST(2), …, ST(7) înainte de

execuţie; indicarea efectului execuţiei propriu-zise; prezentarea modului în care se schimba conţinutul regiştrilor prin

efectele operaţiilor de depunere pe stiva (push), respectiv, de extragere din stiva (pop) care însoţesc execuţia instrucţiunilor de program.

14.5 Forma externă a instrucţiunilor

Instrucţiunile fără operanzi presupun fie utilizarea implicită a registrului vârf de stivă ST(i), fie utilizarea implicită regiştrilor ST(0) şi ST(1). Instrucţiunea FADD realizează [ST(0)] := [ST(0)] + [ST(1)] adică aduna conţinutul registrului ST(1) la conţinutul registrului ST(0). Rezultatul se găseşte în registrul ST(0).

Instrucţiunea FXCH interschimbă conţinutul registrului ST(0) cu conţinutul registrului ST(1). Instrucţiunea FSCALE realizează operaţia:

Page 174: limbaj de asamblare-ivan

572

ST(0) := ST(0)*2ST(1)

Dacă registrul ST(1) conţine valoarea 4, conţinutul registrului ST(0) va fi multiplicat de 16 ori. Dacă registrul ST(1) conţine –3, conţinutul registrului ST(0) va fi împărţit la 8.

Instrucţiunea FCOM realizează compararea registrului ST(0) cu registrul ST(1) şi poziţionează indicatorii de condiţie.

Instrucţiunea FTST compară conţinutul registrului ST(0) cu 0.0 şi setează indicatorii de condiţie. Instrucţiunea FLDLG2 realizează încărcarea în ST(0) a valorii log102 = 0.30102…, cu o aproximaţie de 19 cifre zecimale. Instrucţiunea FLDPI încarcă în ST(0) numărul PI, cu o precizie de 19 cifre. Instrucţiunea FLDZ încarcă în registrul ST(0) valoarea constantei zero (0.0) . Instrucţiunea FLD2T încarcă în registrul ST(0) valoarea log210 cu o precizie de 19 cifre zecimale. Instrucţiunea FLD2E încarcă registrul ST(0) cu valoarea log2e cu o precizie de 19 cifre zecimale. Instrucţiunile cu un operand presupun utilizarea implicită a registrului ST(0). Astfel, dacă registrul ST(1) conţine valoarea 275, instrucţiunea FLD ST(1) are ca efect iniţializarea registrului ST(0) cu această valoare. După execuţia instrucţiunii regiştrii vor arata ca în figura 14.5.:

ST(0) ST(1) ST(2) 340 275 ?

FLD ST(1) 275 340 275 ? ? ?

Figura 14.5 – Modificările din regiştrii la execuţia instrucţiunii FLD ST(1) 14.6 Exemple

Se va construi un program care să înmulţească două numere în virgulă mobilă folosind instrucţiunile coprocesorului.

Variabilele x1 şi x2 sunt definite pe 64 biţi. Instrucţiunea de înmulţire are ca parametri impliciţi pe st(0) şi pe st(1) iar rezultatul este stocat în st(0). Pentru vizualizarea rezultatului se va utiliza facilitatea debugger-ului de a afişa conţinutul registrelor coprocesorului.

Page 175: limbaj de asamblare-ivan

573

;Program de demo lucru cu coprocesor ;Realizeaza inmultirea a doua numere x1 si x2 ; daca exista coprocesor. Altfel afiseaza un mesaj de eroare. .model small .286 .data

NuInstalat db 'CoProcesor neinstalat',10,13,'$' ctrl_87 dw lbffh; precizia =64 x1 dq 12.0 x2 dq 14.0 rezultat dq ?

.code start:

mov ax,@data mov ds,ax

mov ax,40h mov es,ax mov ax,es:10h

test ax,2 jnz Instalat mov dx,offset NuInstalat mov ah,9 int 21h jmp final

Instalat:

FINIT FLDC ctrl_87

FLD x1 ;st(0)=x1 FLD x2 ;st(1)=x1 si st(0)=x2 FMUL FST rezultat ;rezultat=st(0)

final:

mov ax,4c00h int 21h

end

În continuare se prezintă un program care calculează valoarea expresiei:

Page 176: limbaj de asamblare-ivan

574

ex

=

n

k=1

xk

k!

Transferul datelor către procedură se va face prin referinţă. Stiva coprocesorului va arăta astfel:

ST(0)

Va fi folosit pentru manevra

ST(1)

Va conţine xk

ST(2)

Va conţine k!

ST(3)

ST(4)

Va conţine

n

k=1

xk

k!

ST(5)

ST(6)

ST(7)

.model small .stack 512h .data

nnr dw 10 xnr dd 5 temp dw ? rez dd ?

.code e_la_x proc near

fld xnr ; incarca x din memorie

Page 177: limbaj de asamblare-ivan

575

fst st(1) ; pune x in st1 fld1 ; pune 1 in st fst st(2) ; initializeaza k! cu 1 fst st(4) ; initializeaza suma cu 1 mov cx,1

et: fnop fld xnr fmul st(1),st ; x la k

fxch st(2) ; schimba st cu st2 mov temp,cx fimul temp ; k! fxch st(2) ; schimba st cu st2 fld st(1) ; incarca in st pe x la k fdiv st,st(2) ; face impartirea fadd st(4),st ; aduna rezultatul fractiei la suma

din st4 inc cx cmp cx,nnr ; compara cx cu n jle et ; if <= sare la et fxch st(4) ; aduce rezultatul in st fst rez ; duce rezultatul in memorie ret

e_la_x endp start:

mov ax,@data mov ds,ax ; incarca segmentul de date xor cx,cx call e_la_x ; apeleaza procedura mov ax,4c00h ; functia de iesire int 21h

end start 14.7 Concluzii Trebuie realizat un echilibru între utilizarea resurselor coprocesorului şi celelalte tipuri de aritmetici, aşa fel încât să se obţină precizia rezultatelor şi în acelaşi timp un număr cât mai mic de cicluri maşină la execuţia programului.

Page 178: limbaj de asamblare-ivan

576

17

PROGRAMARE MIXTĂ: C – LIMBAJ DE ASAMBLARE 17.1 Programul principal este scris în limbaj de asamblare, procedurile apelate sunt scrise în limbajul C Variabilele transmise procedurii apelate se definesc cu atributul extrn. Procedurile apelate se definesc, de asemenea, cu atributul extrn. Se folosesc pentru apel proceduri scrise în limbajul C pentru a realiza funcţii de intrare/ieşire care presupun conversii. Programul ASM1 adună trei numere. Se apelează funcţia scanf() pentru a iniţializa trei variabile a, b, c. După însumare se apelează procedura printf() pentru a imprima variabila suma. Se definesc în programul C variabilele de tip char: format1, format2, format3, format4. ;ASM 1 .model small .stack 100h .data ; date externe extrn c format1:word extrn c format2:word extrn c format3:word extrn c format4:word extrn c format:word extrn c a:word extrn c b:word extrn c c:word ;date locale suma dw ? .code public _main ; functiile externe apelate extrn c printf:proc extrn c scanf:proc extrn c sablon:proc extrn c refac:proc ; intrarea in program _main proc ; apel functie C de formatarea a ecranului

Page 179: limbaj de asamblare-ivan

577

call sablon c ; citirea parametrilor de la tastatura lea ax,format1 call printf c,ax lea ax,format lea bx,a call scanf c,ax,bx lea ax,format2 call printf c,ax lea ax,format lea bx,b call scanf c,ax,bx lea ax,format3 call printf c,ax lea ax,format lea bx,c call scanf c,ax,bx ; insumarea celor trei valori mov ax,a add ax,b add ax,c mov suma,ax ; afisarea rezultatului lea ax,format4 call printf c,ax,suma ; apel functie C de refacere a ecranului call refac c ; iesirea din program ret _main endp end //C1 #include <stdio.h> #include <conio.h> char format1[20]="a="; char format2[20]="b="; char format3[20]="c="; char format4[20]="\nsuma=%d\n"; char format[20]="%d"; int a,b,c; void sablon(void) { int i; clrscr();

Page 180: limbaj de asamblare-ivan

578

for(i=0;i<80;i++) printf("-"); printf("Introduceti datele:\n"); for(i=0;i<80;i++) printf("-"); printf("\n\n\n"); for(i=0;i<80;i++) printf("-"); printf("\n"); for(i=0;i<80;i++) printf("-"); gotoxy(1,4); } void refac(void) { getch(); clrscr(); } Programul ASM2 compară două şiruri de caractere. Se citesc şirurile apelând întreruperea 21h cu funcţia 01h. Pentru comparare se apelează funcţia stringcmp() scrisă în limbajul C. Rezultatul returnat este interpretat în programul scris în limbaj de asamblare. ;ASM 2 .model small .stack 100h .data sir db 'parola',0 sirc db 10 dup(?) m1 db 10,'Siruri identice',0 m2 db 10,'Siruri diferite',0 m3 db 10,'Dati sirul:',0 .code public _main extrn c printf:proc extrn _stringcmp:proc _main proc lea ax,m3 call printf c,ax xor bx,bx citire: mov ah,01h int 21h cmp al,0dh je afisare mov sirc[bx],al inc bx jmp citire

Page 181: limbaj de asamblare-ivan

579

afisare: mov al,0 mov sirc[bx],al lea ax,sir lea bx,sirc push ax push bx call _stringcmp add sp,4 test ax,ax jnz diferite lea ax,m1 call printf c,ax jmp sfarsit diferite: lea ax,m2 call printf c,ax sfarsit: ret _main endp end //C2 int stringcmp(char* x,char* y) { while(*x==*y) { if(*x=='\0') return 0; x++; y++; } return *x-*y; } Programul ASM3 concatenează două şiruri de caractere. Se citesc şirurile şi se apelează funcţia stringcat() care primeşte pe stivă adresele celor două variabile, sir1, sir2. Adresa şirului rezultat este returnată în registrul AX (funcţia returnează pointer spre char). ;ASM 3 .model small .stack 100h .data sir1 db 'mergem la ',0,20 dup(?) sir2 db 'stadion',0 format db 'Sirul concatenat este : %s',10,13,0 .code public _main extrn c printf:proc

Page 182: limbaj de asamblare-ivan

580

extrn _stringcat:proc _main proc lea ax,sir1 lea bx,sir2 push bx push ax call _stringcat add sp,4 lea bx,format call printf c,bx,ax ret _main endp end //C3 #include <conio.h> char* stringcat(char* s1,const char* s2) { char* t=s1; while(*s1) s1++; while(*s1++=*s2++); return t; } char* stringcat(char* s1,char* s2) { int i; for(i=0;s1[i];i++); for(;s1[i++]=*s2++;); return s1; } Programul ASM4 afişează minimul sau maximul dintre două numere. Se transmit funcţiei scrise în limbajul C numerele şi opţiunea de calcul a valorii minime (1) sau maxime (2). ;ASM 4 .model small .stack 100h .data format1 db 'Introduceti a,b,optiune(1-min,2-max):',0 format2 db '%d%d%d',0 format3 db 'Functia este: %d',10,13,0 a dw ? b dw ? x dw ? pf dw ? .code public _main extrn _funct:proc extrn c printf:proc

Page 183: limbaj de asamblare-ivan

581

extrn c scanf:proc _main proc lea ax,format1 call printf c,ax lea ax,format2 lea bx,a lea cx,b lea dx,x call scanf c,ax,bx,cx,dx mov ax,x push ax mov dx,1 call _funct add sp,2 mov pf,ax call pf c,a,b lea bx,format3 call printf c,bx,ax ret _main endp end //C4 int min(int a,int b) { return a<b?a:b; } int max(int a,int b) { return a>b?a:b; } int (*funct(int x))() { int (*pfunct)(); switch(x) { case 1: pfunct=min; break; case 2: pfunct=max; break; } return pfunct; } Programul ASM5 stabileşte şi afişează maximul dintre trei numere. ;ASM 5 .model small mscanf macro par1,par2

Page 184: limbaj de asamblare-ivan

582

lea ax,par1 lea bx,par2 call scanf c,ax,bx endm mprintf macro par lea ax,par call printf c,ax endm .stack 100h .data format1 db "Maxim = %d",10,13,0 format2 db "a=",0 format3 db "b=",0 format4 db "c=",0 format5 db "%d",0 a dw ? b dw ? c dw ? rez dw ? .code public _main extrn _max:proc extrn c scanf:proc extrn c printf:proc _main proc mprintf format2 mscanf format5,a mprintf format3 mscanf format5,b mprintf format4 mscanf format5,c push c push b push a call _max add sp,6 lea bx,format1 call printf c,bx,ax ret _main endp end //C5 int max(int a,int b,int c) { return a>b?(a>c?a:c):(b>c?b:c); } 17.2 Programul principal este scris în limbajul C, procedurile apelate sunt scrise în limbaj de asamblare

Page 185: limbaj de asamblare-ivan

583

Programul C6 citeşte un vector de numere întregi şi îl afişează pagină cu pagină. Afişarea se face cu ajutorul funcţiei afis() definită în limbaj de asamblare; aceasta apelează la rândul ei procedurile pozit() şi sterg(). //C6 #include <stdio.h> #include <conio.h> extern void afis(void); void main(void) { float v,x[40]; int i,n; printf("n="); scanf("%d",&n); for(i=0;i<n;i++) { printf("x[%d]=",i); scanf("%f",&v); x[i]=v; } clrscr(); for(i=0;i<n;i++) if((i%23==0)&&(i!=0)) { afis(); printf("\nx[%d]=%f]",i,x[i]); } else printf("\nx[%d]=%8.2f",i,x[i]); afis(); } ;ASM 6

; Afisarea unui mesaj, asteptarea apasarii unei taste ; si stergerea ecranului .model small .data sir db 'Pentru a continua apasati orice tasta...','$' .code public _afis ; Interfata cu programul C pozit proc ; Pozitionarea pe linia 24,

coloana 1 mov ah,02h ; Functia BIOS mov dh,24 ; Linia mov dl,1 ; Coloana int 10h ; Apelul intr BIOS 10h (functii grafice) ret pozit endp

Page 186: limbaj de asamblare-ivan

584

sterg proc ; Stergerea ecranului mov ah,0 mov al,2 int 10h ret sterg endp _afis proc call pozit ; Pozitionare pe ecran mov ax,seg sir ; Afisare mesaj mov ds,ax mov dx,offset sir mov ah,09h int 21h ; Apel functie DOS afisare text

terminat cu '$' mov ah,08h ; Asteptarea apasarii unei taste int 21h call sterg ret ; Iesirea catre functia apelatoare _afis endp end

Programul C7 calculează suma a trei numere citite de la tastatură. Pentru efectuarea adunării se apelează funcţia scrisă în limbaj de asamblare suma(). Parametrii sunt transmişi prin valoare, pe stivă. //C7 #include <stdio.h> extern int suma(int,int,int); void main(void) { int a,b,c; printf("a="); scanf("%d",&a); printf("b="); scanf("%d",&b); printf("c="); scanf("%d",&c); printf("suma=%d\n",suma(a,b,c)); } ;ASM 7 .model small .code public _suma _suma proc push bp ; Salvarea registrului bp mov bp,sp ; bp<-sp mov ax,[bp+4] ; ax=par1 add ax,[bp+6] ; ax+=par2 add ax,[bp+8] ; ax+=par3

Page 187: limbaj de asamblare-ivan

585

pop bp ; Refacere bp ret ; Iesirea din functie _suma endp end Spre deosebire de programul precedent, programul C8 care efectuează aceleaşi calcule, transmite parametrii prin adresă, pe stivă.

//C8 #include <stdio.h> extern int suma(int*,int*,int*); void main(void) { int *pa,a,*pb,b,*pc,c; printf("a=");scanf("%d",&a); printf("b=");scanf("%d",&b); printf("c=");scanf("%d",&c); pa=&a;pb=&b;pc=&c; printf("suma=%d\n",suma(pa,pb,pc)); } ;ASM 8 .model small .stack 100h .code public _suma _suma proc push bp ; Salvare bp mov bp,sp ; bp<-sp mov di,[bp+4] ; di=pa mov ax,[di] ; ax=*pa; mov di,[bp+6] ; di=pb add ax,[di] ; ax+=*pb mov di,[bp+8] ; di=pc add ax,[di] ; ax+=*pc pop bp ; Refacere bp ret ; Iesire din functie _suma endp end Programul C9 exemplifică returnarea rezultatului pe stivă, prin adresă. //C9 #include <stdio.h> extern int* suma(int*,int*,int*); void main(void) { int *pa,a,*pb,b,*pc,c,*ps; printf("a="); scanf("%d",&a);

Page 188: limbaj de asamblare-ivan

586

printf("b="); scanf("%d",&b); printf("c="); scanf("%d",&c); pa=&a; pb=&b; pc=&c; ps=suma(pa,pb,pc); printf("suma=%d\n",*ps); } ;ASM 9 .model small .stack 100h .data rez dw ? .code public _suma _suma proc push bp mov bp,sp mov di,[bp+4] mov ax,[di] mov di,[bp+6] add ax,[di] mov di,[bp+8] add ax,[di] ; ax=*par1+*par2+*par3 mov rez,ax ; rez<-ax mov ax,offset rez ; ax<-&rez pop bp ret _suma endp end Programul C10 calculează şi afişează suma elementelor unui vector. Parametrii transmişi pe stivă sunt numărul de elemente al vectorului şi adresa primului element. //C10 #include <stdio.h> extern int suma(int*,int); void main(void) { int v,x[40]; int i,n; printf("n="); scanf("%d",&n); for(i=0;i<n;i++) { printf("x[%d]=",i);

Page 189: limbaj de asamblare-ivan

587

scanf("%d",&v); x[i]=v; } printf("Suma elementelor vectorului = %d",suma(x,n)); } ;ASM 10 .model small .stack 100h .code public _suma _suma proc push bp mov bp,sp mov cx,[bp+6] ; cx <- numarul de elemente mov si,[bp+4] ; si <- adresa primului element xor ax,ax ; ax=0 ciclu: ; do{ add ax,[si] ; ax+=x[i]; inc si ; i++ inc si loop ciclu ; }while(i<n) pop bp ret _suma endp end //C11 /* Produsul scalar a doi vectori. */ #include <stdio.h> extern int prodscalar(int*,int*,int); void main(void) { int x[40],y[40],v,i,n; printf("n="); scanf("%d",&n); for(i=0;i<n;i++) { printf("x[%d]=",i); scanf("%d",&v); x[i]=v; printf("y[%d]=",i); scanf("%d",&v); y[i]=v; } v=prodscalar(x,y,n); printf("Produsul scalar = %d\n",v); }

Page 190: limbaj de asamblare-ivan

588

;ASM 11 ; Produsul scalar a doi vectori .model small .stack 100h .code public _prodscalar _prodscalar proc push bp mov bp,sp mov cx,[bp+8] ; Numarul de elemente mov si,[bp+4] ; Adresa primului vector mov di,[bp+6] ; Adresa celui de-al doilea vector xor bx,bx ciclu: ; Bucla repetata de cx ori mov ax,[si] ; bx+=[si]*[di]; mul word ptr [di] add bx,ax inc si inc si inc di inc di loop ciclu mov ax,bx ; ax=bx (rezultatul) pop bp ret _prodscalar endp end //C12 /* Suma elementelor unei matrice. */ #include <stdio.h> #define N 5 extern int suma(int x[][N],int m,int n); void main(void) { int x[3][N],v; int i,j,m,n; printf("numarul de linii = "); scanf("%d",&m); printf("numarul de coloane = "); scanf("%d",&n); for(i=0;i<m;i++) for(j=0;j<n;j++) { printf("x[%d][%d]=",i,j); scanf("%d",&v); x[i][j]=v; } printf("suma elementelor matricii = %d\n",suma(x,m,n));

Page 191: limbaj de asamblare-ivan

589

} ;ASM 12 ; Suma elementelor unei matrici .model small .stack 100h .code c equ 5 ; Numarul de coloane al matricii public _suma _suma proc push bp mov bp,sp mov bx,[bp+4] ; Adresa primului element xor ax,ax ; ax=0 mov dx,[bp+6] ; Numarul de linii c1: mov cx,[bp+8] ; Numarul de coloane xor si,si ; si=0 c2: add ax,[bx][si] ; ax+=[bx][si] inc si ; Urmatorul element inc si loop c2 ; Mai sunt coloane ? add bx,c*2 ; Urmatoarea linie dec dx jnz c1 ; Mai sunt linii ? pop bp ret _suma endp end //C13 /* Realizarea unei functii specificate de utilizator (min sau max) asupra a doua numere introduse de la consola. Se realizeaza cu aplelu unei functii asm care primeste ca parametru functia dorita. */ #include <stdio.h> extern int suma(int*,int*,int(*)(int,int),int); int max(int a,int b) { return a>b?a:b; } void main(void) { int x[40],y[40],n,i,v; printf("n="); scanf("%d",&n); for(i=0;i<n;i++)

Page 192: limbaj de asamblare-ivan

590

{ printf("x[%d]=",i); scanf("%d",&v); x[i]=v; printf("y[%d]=",i); scanf("%d",&v); y[i]=v; } printf("Suma elementelor maxime = %d\n",suma(x,y,max,n)); } ;ASM 13 ; Suma elementelor maxime din doi vectori de elemente ; Functia de comparare e transmisa ca parametru ; (o adresa near) .model small .stack 100h .data rez dw 0 .code public _suma _suma proc push bp mov bp,sp mov cx,[bp+10] ; Numarul de elemente mov di,[bp+6] ; Adresa celui de-al doilea vector mov si,[bp+4] ; Adresa primului vector ciclu: mov ax,[si] mov bx,[di] call word ptr [bp+8] c,ax,bx ; Apel functie de comparare add rez,ax ; rez+=max inc si ; Urmatoarele elemente din vectori inc si inc di inc di loop ciclu mov ax,rez ; ax<-rez pop bp ret _suma endp end Celelalte programe realizează prelucrări care evidenţiază modalităţi de dezvoltare software conform programării mixte. În cazul în care se doreşte gestionarea strictă a ciclurilor maşină în secvenţe de program cu structuri repetitive imbricate în care numărul de referiri ale unei secvenţe este foarte mare, se preferă dezvoltarea unui program mixt în care secvenţa referită este scrisă direct în limbaj de asamblare. De exemplu, secvenţa:

Page 193: limbaj de asamblare-ivan

591

...

s=0; for (i=0; i<30000; i++) for (j=0; j<70000; j++) for (k=0; k<20000; k++) if(i= =j)

s+=k*2; else s+=k/2;

... se transformă în secvenţa mai performantă de mai jos:

... s=0; for (i=0; i<30000; i++) for (j=0; j<70000; j++) for (k=0; k<20000; k++) {

asm mov bx, k asm mov ax, i asm cmp ax, j asm jz egal asm shr bx asm egal: nop asm shl bx asm mov ax, s asm add ax, bx asm mov s, ax

} ...

Blocul instrucţiunilor limbajului de asamblare se delimitează cu atributul asm, obţinându-se secvenţa echivalentă

asm { mov bx, k mov ax, i cmp ax, j jz egal shr bx egal: nop shl bx mov ax, s add ax, bx mov s, ax

} 17.3 Concluzii

Page 194: limbaj de asamblare-ivan

592

Programarea mixtă are ca obiective atât creşterea performanţei programelor în execuţie, cât şi reutilizarea de software verificat în practică, din diferite generaţii, scris în limbaje de programare mai puţin utilizate în prezent. De asemenea prin programarea mixtă se integrează programele scrise în orice limbaj de programare formând un produs software complex.

Page 195: limbaj de asamblare-ivan

593

18

DEZVOLTAREA DE APLICAŢII ORIENTATE OBIECT IN LIMBAJ DE ASAMBLARE

18.1. Concepte folosite în dezvoltarea programelor orientate obiect

O clasă este definită ca abstractizare a trăsăturilor esenţiale ale unei colecţii de obiecte înrudite. Deci o clasă este o mulţime de obiecte care partajează o structură comună şi un comportament comun. Se extinde astfel conceptul datelor de tip structură, combinându-se datele cu codul ce le procesează.

O clasă are două componente, una statică şi alta dinamică. Prima componentă se referă la proprietăţile obiectelor caracterizate de clasă, şi este reprezentată de datele membre (câmpurile cu nume care posedă valoare şi care caracterizează starea obiectului în timpul execuţiei). A doua se referă la comportamentul obiectelor clasei, deci la modul în care acţionează şi reacţionează acestea, şi este constituită din metodele clasei respective.

Un obiect este definit ca materializarea sau concretizarea tipologiei descrise de o anumită clasă. El este un concept, abstractizare sau lucru cu limite precizate şi cu înţeles pentru problema în cauză.

Figura 18.1 – Relaţia dintre o clasă şi obiectele ei Relaţia dintre o clasă şi obiectele acesteia este echivalentă relaţiei dintre o variabilă şi tipul acesteia, subliniindu-se astfel ideea că obiectele şi clasa ce le defineşte nu sunt altceva decât un model mai avansat de variabile şi o extensie a tipurilor fundamentale specifice fiecărui limbaj. Fiecare obiect preia şablonul impus de clasa de care aparţine, şablon ce descrie caracteristicile sale (componenta statică, reprezentată de date), precum şi operaţiile ce sunt permise asupra acestora (interfaţa obiectului, descrisă prin metodele clasei). Operaţia de creare a unui obiect din informaţiile exprimate de o clasă, se numeşte instanţiere, iar operaţia inversă

Clasa

Obiect1 Obiect2

Page 196: limbaj de asamblare-ivan

594

(definirea unei clase pornind de la o mulţime de obiecte) poartă numele de clasificare sau tipizare. Prin intermediul metodelor, orice obiect este o entitate dinamică care poate fi creată, utilizată şi distrusă. Ca metode cu semnificaţie deosebită, atrag atenţia constructorii şi destructorii. Aşa cum ne sugerează şi denumirea, constructorii sunt acele operaţii apelate pentru crearea obiectelor şi iniţializarea stării lor, în timp ce destructorii realizează operaţia inversă, ştergând spaţiul de memorie ocupat de metodele şi datele membre ale acestora. Acest mod de organizare a datelor şi a metodelor, de manipulare a acestora, face apel la o serie de concepte precum abstractizarea, moştenirea şi polomorfismul.

Abstractizarea – reprezintă procesul de ignorare intenţionată a detaliilor nesemnificative şi reţinerea proprietăţilor definitorii ale unei entităţi. Astfel, se ascund detaliile de implementare, dezvăluindu-se, prin intermediul interfeţei obiectului, comportarea esenţială a acestuia relativ la viziunea exterioară pe care vrea să o imprime creatorul clasei.

Încapsularea – este mijlocul de separare a informaţiilor de manipulare a unei entităţi (aspectele externe, accesibile utilizatorului acesteia) de informaţiile de implementare. Ea poate fi întâlnită şi în alte tehnici de programare cum este, spre exemplu, modularizarea programelor prin folosirea bibliotecilor. Prin intermediul acesteia se realizează protejarea anumitor date membre ale obiectelor împotriva distrugerii lor accidentale, ajungându-se în acest fel la o robusteţe ridicată a programelor în implementarea orientată obiect comparativ cu cea tradiţională.

Moştenirea – se defineşte ca fiind o relaţie între clase prin intermediul căreia o clasă partajează structura şi comportamentul definite de una sau mai multe clase. În funcţie de numărul claselor de la se porneşte moştenirea, cunoscute sub numele de clase de bază sau superclase, putem avea moştenire simplă (când avem o singură clasă de bază) sau moştenire multiplă (când se folosesc caracteristicile mai multor clase). Clasa rezultantă se numeşte subclasă sau clasă derivată.

Page 197: limbaj de asamblare-ivan

595

Figura 18.2 – Moştenirea multiplă

Polimorfismul – este capacitatea unei entităţi de a îmbrăca mai multe

forme. În programarea clasică, funcţiile se diferenţiază prin numele sub care sunt cunoscute, parametri pe care îi acceptă, precum şi valoarea pe care o returnează. În programarea orientată obiect compilatorul poate să facă diferenţa între funcţii şi prin intermediul clasei de care aparţin. Funcţii care au acelaşi nume, parametri şi valoare de retur în clasa de bază, pot fi redefinite (supraîncărcate) în clasele derivate şi ajustate la noul context în care sunt apelate. Acest tip de polimorfism poartă denumirea de polimorfism de moştenire.

În cadrul polimorfismului ad-hoc, de care aparţine cel menţionat mai sus, se regăseşte şi polimorfismul coerciziune care este caracteristic limbajelor de programare care dispun de facilităţi de conversie internă între tipuri.

Un alt tip de polimorfism este şi polimorfismul parametric, dar care apelând la capacitatea compilatoarelor tradiţionale de a face diferenţa dintre funcţii cu nume şi valoare de retur identice, dar cu tip şi / sau număr de parametri diferit, nu aparţine neapărat de programarea orientată obiect.

Alături de aceste concepte mai este adăugat şi cel de persistenţă a obiectelor. Acesta se referă la timpul de viaţă al unui obiect, la capacitatea acestuia de a permite salvarea pe un suport de memorie externă şi de reconstituire ulterioară în memoria internă a calculatorului. Realizarea acestei cerinţe presupune identificarea clasei obiectului din informaţiile salvate şi construirea dinamică (în timpul execuţiei) a acestuia, acţiune ce necesită metode avansate de programare cum ar fi RTTI (Run-Time Type Identification - identificarea tipului în timpul execuţiei) şi folosirea metodelor virtuale.

Clasa de baza 1

Clasa de baza 2

Clasa derivată

Clasade

baza 1

Clasade

baza 2

Date şi metode clasa derivata . . . .

Structura clasei derivate

Page 198: limbaj de asamblare-ivan

596

Prin apelarea la aceste concepte, programarea orientată obiect aduce o serie de avantaje:

reutilizabilitate – se permite reutilizarea codului deja testat şi se creşte, astfel, robusteţea programelor concomitent cu diminuarea duratei de realizare a acestora;

scăderea complexităţii – fragmentarea aplicaţiei în entităţi şi relaţii, care au înţeles pentru utilizator, este o analiză convenţională şi o tehnică de design, care duce la o diminuare semnificativă a complexităţii iniţiale a problemei studiate.

flexibilitate – prin intermediul moştenirii se permite adăugarea sau extragerea de obiecte, fără schimbări profunde în logica aplicaţiei, realizându-se astfel o extindere sau o acomodare a aplicaţiei cu minim de efort.

înţelegere mai uşoară – punerea în corespondenţă unu la unu a obiectelor din structura aplicaţiei cu cele ale problemei studiate duce la o înţelegere mai uşoară a fenomenelor atât de către utilizator cât şi de către proiectant.

Toate aceste avantaje au ca rezultat o creştere a productivităţii şi a uşurinţei în întreţinere a aplicaţiilor orientate obiect, ceea ce impune tehnica de elaborare a programelor orientată obiect ca soluţia optimă pentru programatori şi proiectanţi de sisteme.

Orice tip de dată elementară sau derivată şi orice mecanism de gestionare a resurselor unui sistem de calcul se regăsesc mai întâi în descriptorii respectivi, funcţii şi operaţii în limbaj de asamblare.

Dezvoltarea de aplicaţii orientate obiect face excepţie numai aparent de la această cerinţă. Proprietăţile obiectelor sunt implementate mai întâi în limbajele evoluate (cum ar fi C++, Pascal) şi corespondenţele lor în modulele obiect sunt suplinite de proprietăţile şi funcţiile primare deja existente în limbajul de asamblare.

În [SWAN95] se prezintă modalitatea de dezvoltare a aplicaţiilor orientate obiect fără ca limbajul de asamblare să conţină atribuite, structuri specifice definirii de clase şi manipulării de obiecte.

Pentru descrierea mai multor modalităţi de implementare a programării orientate obiect în limbajul de asamblare, se va face apel la reprezentarea tipurilor de date întregi şi complexe şi a metodelor de manipulare a acestora. S-a ales astfel un exemplu simplu care să uşureze înţelegerea şi urmărirea codului prezentat, tehnicile folosite fiind uşor de extins la orice aplicaţie reală.

Page 199: limbaj de asamblare-ivan

597

18.2. Definirea obiectelor prin structuri

Una dintre cele mai simple metode de implementare a obiectelor în limbajul de asamblare este folosirea regulilor de construcţie a structurilor simple, fără folosirea unor construcţii specifice orientate obiect.

Se consideră o aplicaţie în care se manipulează numere întregi şi numere complexe (s-au ales numerele întregi în locul celor reale pentru o simplificare a codului). Se defineşte mai întâi o clasă care să încapsuleze comportamentul numerelor întregi şi, derivată din aceasta, una care să definească comportamentul numerelor complexe.

Pentru fiecare din cele două clase se definesc operaţiile de adunare şi de înmulţire (cele de scădere şi de împărţire sunt asemănătoare). Pentru clasa Integer se dezvoltă următoarele proceduri:

... .code adunare_integer proc push ax mov ax, [di].nr_int add ax, word ptr [si] mov [di].nr_int, ax pop ax retn adunare_integer endp inmultire_integer proc push ax mov ax, [di].nr_int mul word ptr [si] mov [di].nr_int, ax pop ax retn inmultire_integer endp ... Numărul implicat în adunare / înmulţire este trimis prin adresă prin registrul

SI, iar adresa obiectului este trimisă prin registrul DI. Pentru clasa Complex procedurile sunt: ... adunare_complex proc push ax mov ax, [di].nr_int add ax, word ptr [si] mov [di].nr_int, ax mov ax, [di].nr_complex

Page 200: limbaj de asamblare-ivan

598

add ax, word ptr [si+2] mov [di].nr_complex, ax pop ax retn adunare_complex endp inmultire_complex proc .data int_temp dw 0 .code push ax mov ax, [di].nr_complex mul word ptr [si+2] mov bx, ax mov ax, [di].nr_int mul word ptr [si] sub ax, bx mov int_temp, ax mov ax, [di].nr_complex mul word ptr [si] mov bx, ax mov ax, [di].nr_int mul word ptr [si+2] add ax, bx mov [di].nr_complex, ax mov ax, int_temp mov [di].nr_int, ax pop ax retn inmultire_complex endp ...

Numărul complex este format din două date membre, nr_int şi nr_complex, adunarea şi înmulţirea fiind definite astfel:

)1b2a2b1a,2b2a1b1(a)2b,1(b)2a,1(a

)2b2a,1b1(a)2b,1(b)2a,1(a

Transmiterea parametrilor se face prin adresa din registrul SI ([SI] = partea

întreagă de adunat / înmulţit, iar [SI+2] = partea imaginară), iar adresa obiectului se trimite prin registrul DI.

După definirea procedurilor urmează definirea obiectelor:

...

.data Integer struc nr_int dw 0 adun_int dw adunare_integer

Page 201: limbaj de asamblare-ivan

599

inm_int dw inmultire_integer Integer ends Complex struc baza Integer <> nr_complex dw 0 adun_compl dw adunare_complex inm_compl dw inmultire_complex Complex ends ...

Obiectele sunt definite ca nişte structuri obişnuite, având ca membri atât

datele cât şi pointeri spre metode. Obiectul Complex este derivat din obiectul Integer prin definirea unei date membre de tip Integer (baza Integer <>).

Pentru rularea celor două clase se poate construi un cod de genul celui următor:

...

ob_integer Integer <4> ob_complex Complex <7,5> numar1 dw 10 numar2 dw 5 .code start: mov ax,@data mov ds,ax lea si, numar1 ;[si] = numar1, [si+2] = numar2

lea di,ob_integer call ob_integer.adun_int ;nr_int = 4+10 = 14 call ob_integer.inm_int ;nr_int = 14*10 = 140 lea di,ob_complex call ob_complex.adun_compl ;nr_int = 7+10 = 17 ;nr_complex = 5+5 = 10 call ob_complex.inm_compl ;nr_int = 17*10-10*5 = 120 ;nr_complex = 17*5+10*10 = 185 sfarsit: mov ax,4c00h int 21h end start

Instanţierea claselor se face ca în cazul structurilor obişnuite. Se observă că

înaintea apelurilor procedurilor este necesară încărcarea în registrele SI şi DI a parametrilor şi a obiectelor asupra cărora se efectuează operaţia.

Pentru verificarea corectitudinii calculelor se încarcă programul executabil, rezultat prin asamblarea codului de mai sus, în turbo debugger dacă se foloseşte

Page 202: limbaj de asamblare-ivan

600

asamblorul oferit de firma Borland. Pentru aceasta trebuie ca programul să fie asamblat şi link-editat cu următoarele comenzi:

tasm /zi <nume_program.asm> tlink /v <nume_program.obj>

18.3. Definirea obiectelor prin macrodefiniţii

Limbajul de asamblare conţine facilităţi de definire a macrodefiniţiilor. Macrodefiniţiile sunt construcţii realizate cu ajutorul unor cuvinte cheie al căror efect constă într-un proces de expandare în textul programului sursă, permiţându-se în acest mod definirea în mod simbolic a unor secvenţe de program (instrucţiuni, definiţii de date, directive etc.), asociate cu un nume.

Macrodefiniţiile sunt structuri complexe în care se includ şi elemente ale macroexpandării condiţionale, repetitive, recursive şi stringizare (vezi lucrările [SOMN92], [MUS96]). Un aspect foarte important de care trebuie sa se ţină cont în utilizarea macroinstrucţiunilor este faptul că ele sunt procesate într-o fază preliminară asamblării.

Pentru definirea structurilor necesare modelării orientate obiect, precum şi a moştenirii şi încapsulării datelor acestora, există şi varianta folosirii macrodefiniţiilor. Macrodefiniţiile sunt deosebit de utile în realizarea programelor orientate obiect, putând fi folosite construcţii speciale de verificare a restricţiilor care să genereze eventualele erori nu în timpul execuţiei programului, ci în faza de analizare sintactică a codului sursă, exact ca în limbajele de nivel înalt orientate obiect.

În continuare se prezintă un model de implementare a obiectelor în limbajul de asamblare, ce face apel la un sistem de simboluri variabile, construite prin stringizare, folosite împreună cu elemente macrocondiţionale pentru verificarea restricţiilor impuse de modelul obiectual. Sintaxa rezultată prezintă asemănări evidente cu limbajele de nivel înalt orientate obiect, în special cu limbajul C++.

Mai întâi este necesară definirea unor date şi a unor variabile simbolice folosite în cadrul macrodefiniţiilor (semnificaţia acestora va rezulta ulterior, pe parcursul descrierilor din paginile următoare):

; V1.0 .data currOffset dw 0 ;offsetul obiectului apelator pt. o ;functie din aceeasi clasa ;VARIABILE DE CONSTRUIRE (GENERALE) currProt = 0 ;implicit public (folosita in declaratiile ;claselor) currId = 0 ;id-ul curent (id-ul primei clase = 1) currClass = 0 ;(nici o clasa initial) currApelator = 0 ;apelatorul curent al operatiei (id-ul ;obiectului)

Page 203: limbaj de asamblare-ivan

601

Id = 1 ;folosita de OBJECTS gasit = 0 ;folosita la cautarea in clase de baza deplasament = 0 ;deplasamentul folosit in cazul ;accesarii unor variabile din ;clasele de baza baseId = 0 ;id-ul clasei de baza la cautarea datelor ;membre publicDer = 1 ;indica daca derivarea se face ;public (=1) sau private (=0) ;VARIABILE CARE DEPIND DE CLASE (de numele clasei sau de id-ul ei) ;<numeClasa>Id id-ul clasei cu numele <numeClasa> ;<numeObiect>Id id-ul obiectului (EGAL ;INTOTDEAUNA CU ID-UL CLASEI ; DE CARE APARTINE!) ;no<Id> nr elementelor din clasa cu id-ul <id> ;baseNo<Id> nr claselor de baza pentru ;clasa cu id-ul <id> ;disable<Id>Prot indica daca se foloseste sau nu ;protectia datelor ; (folosita in apelul functiilor ;membre) ;friend<Id>Of<currId>Id indica, daca exista, prietenia ;intre clasele cu ; id-urile <Id> si <curcId> ;depl<baseId>In<currId> indica deplasarea clasei de baza ( <baseId> ) in clasa ; cu id-ul <currId>

Pentru definirea unei clase se foloseşte macrodefiniţia class cu următoarea

sintaxă: class <nume_clasa> ... <nume_clasa> ends

Această macrodefiniţie încapsulează o structură standard, iniţiind şi o serie

de simboluri variabile ce vor fi folosite la identificarea clasei: class MACRO nume IFB <nume> %OUT < **Error** Class name not specified ... > .ERR ELSE ;genereaza clasa %OUT < Creating class nume ... > currId = currId + 1 ;id-ul urmator va

fi +1 nume&Id = currId ;id-ul de clasa ;creaza variabila ce contine nr de elemente

setBaseAndNoToZero %currId

Page 204: limbaj de asamblare-ivan

602

;creaza variabila de control a protectiei ;clasei

setDisableProt %currId, 0 ;initial protectia este ;activata

nume struc ;creaza structura ENDIF ENDM În cazul în care nu se precizează un nume pentru clasă se generează eroare.

În caz contrar acest nume va fi folosit pentru declararea structurii, nu înainte ca id-ul clasei (format prin stringizare din numele clasei şi şirul de caractere “Id”) se fie stabilit în mod secvenţial cu ajutorul variabilei simbolice <currId>. Astfel, toate clasele vor avea un număr de identificare distinct (începând cu 1).

Macrodefiniţia class se foloseşte de alte două macrodefiniţii. Una dintre aceste este setBaseAndNoToZero:

setBaseAndNoToZero MACRO id baseNo&id = 0 No&Id = 0 ENDM care se foloseşte de parametrul transmis (<id> = id-ul clasei) pentru a stabili

numărul de clase de bază (baseNo<id>) şi numărul de elemente (No<Id>) ale clasei definite ca fiind zero.

Altă macrodefiniţie folosită este setDisableProt: setDisableProt MACRO id, val disable&id&Prot = val ENDM cu ajutorul căreia se creează o variabilă de protecţie a datelor ce va fi

folosită în macrodefiniţiile următoare (prin transmiterea parametrului val cu valoarea 0 protecţia este activă).

Protecţia membrilor clasei se realizează cu ajutorul variabilei simbolice <currProt> a cărei valoare se poate modifica cu macrodefiniţiile următoare:

public_ MACRO currProt = 0 ENDM private_ MACRO currProt = 1 ENDM

Pentru declararea datelor membre avem următoarele macrodefiniţii:

Page 205: limbaj de asamblare-ivan

603

date MACRO den, tip, v internDate %currId, den, tip, <v> ENDM internDate MACRO id, den, tip, v No&id = No&id + 1 ;alocare data IFB <v> den&id&@val@ tip ? ELSE den&id&@val@ tip v ENDIF ;alocare protectie IFE currProt ;; public %OUT < define den as public ... > ELSE %OUT < define den as private ... > ENDIF den&id&Prot = currProt ENDM Deci se foloseşte macrodefiniţia date cu următoarea sintaxă:

date nume_data, tip_standard, [valoare]

Cu ajutorul acestei macroinstrucţiuni se alocă o variabilă de un anumit tip (precizat prin parametrul <tip>) care are un nume modificat (format prin stringizare sub şablonul <den><id_clasa>@val@) faţă de cel indicat de utilizator) cu scopul de restricţionare a accesului direct. Totodată se creşte numărul de elemente ale clasei şi se defineşte o variabilă (<den><id_clasa>Prot) prin care se va reţine protecţia asupra datei introduse.

Declararea metodelor membre se face în mod asemănător folosindu-se sintaxa:

func nume_rutina

iar macrodefiniţiile folosite sunt: func MACRO den internFunc %currId, den ENDM internFunc MACRO id, den No&id = No&id + 1 IFE currProt ;; public %OUT < define function den as public ... > ELSE %OUT < define function den as private ... > ENDIF

Page 206: limbaj de asamblare-ivan

604

den&id&FuncProt = currProt ENDM Aceste macrodefiniţii nu fac altceva decât să crească numărul de elemente

ale clasei (No<id_clasa>) şi să stabilească protecţia pentru procedura respectivă (<den><id_clasa>FuncProt). În cadrul structurii nu este necesară reţinerea vreunei evidenţe a procedurii (pointer), rutina respectivă fiind identificată printr-un nume ce depinde de identificatorul obiectelor clasei respective. Pentru definirea funcţiei (după declararea claselor) se foloseşte următoarea sintaxă:

classFunc nume_clasa, nume_rutina ... endFunc nume_clasa, nume_rutina

ce are ca suport următoarele macrodefiniţii: classFunc MACRO ob,fun incepProc %(ob&Id), fun ;scoate temporar protectia pt datele din aceeasi clasa setDisableProt %(ob&Id), 1 ENDM incepProc MACRO id, fun currApelator = id ;folosit in functiile prietene currId = id fun&id&@func@ proc ENDM endFunc MACRO ob,fun ;repune protectia setDisableProt %(ob&Id), 0 ;se iese din functie currApelator = 0 currId = 0 ret sfarProc %(ob&Id), fun ENDM sfarProc MACRO id, fun fun&id&@func@ endp ENDM Se începe prin stabilirea unor variabile simbolice pentru identificarea

obiectului în al cărui context se vor executa instrucţiunile din rutina (<currApelator>, folosit pentru accesul la datele claselor pentru care clasa curentă a fost declarată ca prietenă, şi <currId> pentru accesul la datele membre clasei). După care se declară începutul procedurii (se remarcă numele schimbat al procedurii: <denumirea_functiei><id_clasa>@func@) şi se întrerupe, pe parcursul întregii rutine, protecţia datelor membre (deoarece se rulează în contextul clasei) prin setarea pe 1 a variabilei disable<id_clasa>Prot. Sfârşitul procedurii, pe

Page 207: limbaj de asamblare-ivan

605

lângă încheierea efectivă a rutinei, repune protecţia şi resetează variabilele simbolice folosite la identificarea contextului.

Declararea unei clase ca prietene a alteia (cu acces complet asupra datelor membre) se realizează prin definirea unei variabile simbolice cu ajutorul macrodefiniţiei friend:

friend MACRO clase IRP cl,clase friend2 <cl> %OUT < Class cl became friend of current class ... > ENDM ENDM friend2 MACRO cl friend3 %(cl&Id), %currId ENDM friend3 MACRO id, currId friend&id&of&currId = 1 ;defineste variabila

;de prietenie ENDM Pentru moştenire simplă sau multiplă, s-au folosit următoarele

macrodefiniţii (moştenirea este realizată doar pentru un singur nivel, dar poate fi extinsă prin folosirea de macrodefiniţii recursive):

publicExt MACRO b IRP den,b publicDer = 1 ;mostenire publica makeBaseName1 %currId, den %OUT < Current class is derived from den ... > ENDM ENDM privateExt MACRO b IRP den,b publicDer = 0 ;mostenire private makeBaseName1 %currId, den %OUT < Current class is derived from den ... > ENDM ENDM makeBaseName1 MACRO currId, den baseNo&currId = baseNo&currId + 1 makeBaseId currId, %(baseNo&currId), %(den&Id) makeBaseName2 %(baseNo&currId), currId, %(den&Id), den ENDM ;macro-uri interne pentru extend makeBaseId MACRO currId, currBaseNo, baseId ;creeaza

;variabila id a bazei baseNo&currBaseNo&Of&currId&Id = baseId ENDM makeBaseName2 MACRO currBaseNo, currId, baseId, den

Page 208: limbaj de asamblare-ivan

606

;aloca obiectul de baza baseNo&currBaseNo&@base@ den <> ;calculeaza deplasamentul fata de clasa derivata depl&baseId&In&currId = OFFSET baseNo&currBaseNo&@base@ ;mostenire publica IF publicDer ;declara clasa derivata ca prietena a clasei de baza friend3 currId, baseId ENDIF ENDM Se creşte mai întâi numărul de clase de bază ale clasei curente

(baseNo<id_clasa>), apoi se trece la definirea unei variabile de identificare a clasei de bază (baseNo<nr_baza>Of<id_clasa> = <id_baza>) şi la declararea efectivă a structurii clasei de bază în cea derivată (cu numele schimbat baseNo<nr_baza>@base@) şi a deplasării acesteia în cadrul clasei derivate (depl<id_baza>In<id_clasa>). Pentru accesul la toate datele clasei de bază (în cazul moştenirii publice) se face apel la un artificiu: se declară clasa derivată ca o clasă prietenă a clasei de bază.

După cum s-a arătat, datele membre ale unei clase au numele modificat şi deci necunoscut pentru utilizator. Pentru accesul la acestea, prin verificarea protecţiei şi căutarea în clasele de bază, s-au construit următoarele macrodefiniţii:

set MACRO ob, camp, v ;verifica daca suntem intr-o functie membra (nu se mai

specifica ; obiectul) IFB <v> operStanga movToOb, %currId, currOffset, ob, camp ELSE mov currOffSet,OFFSET ob operStanga movToOb, %(ob&Id), currOffset, camp, v ENDIF IFE gasit ;nu s-a gasit %OUT < **Error** camp is not a member of ob ... > .ERR ELSE gasit = 0 ;pt. urmatoarele cautari se revine

;la 0 ENDIF deplasament = 0 ENDM get MACRO unde, ob, camp ;verifica daca suntem intr-o functie membra (nu se mai

;specifica ; obiectul) IFB <camp> operDreapta movFromOb, unde, %currId, currOffset, ob ELSE

Page 209: limbaj de asamblare-ivan

607

mov currOffSet,OFFSET ob operDreapta movFromOb, unde, %(ob&Id), currOffSet, camp ENDIF IFE gasit ;nu s-a gasit %OUT < **Error** camp is not a member of ob ... > .ERR ELSE gasit = 0 ;pt. urmatoarele cautari se revine

;la 0 ENDIF deplasament = 0 ENDM operStanga MACRO oper, id, ob, camp, v IFDEF camp&id&@val@ ;se afla direct in ;clasa? ;s-a gasit si se face operatia in functie de protectie isProt? id, camp oper id, ob, camp, v gasit = 1 ;l-a gasit ELSE ;NU, poate se afla intr-o

;clasa de baza ;CAUTARE IN CLASELE DE BAZA: searchInBase %(baseNo&id), id, camp IF gasit ;se introduce operatia propriu-zisa ;baseId va contine id-ul la care s-a ajuns in

;cautare ;si a carui clasa cuprinde variabila isProt? %baseId, camp ;vede daca este

;protejata ;in clasa derivata oper %baseId, ob, camp, v

ENDIF ENDIF ENDM operDreapta MACRO oper, unde, id, ob, camp IFDEF camp&id&@val@ ;se afla direct in ;clasa? ;s-a gasit si se face operatia in functie de protectie isProt? id, camp oper unde, id, ob, camp gasit = 1 ;l-a gasit ELSE ;NU, poate se afla intr-o

;clasa de baza ;CAUTARE IN CLASELE DE BAZA: searchInBase %(baseNo&id), id, camp IF gasit ;se introduce operatia propriu-zisa ;baseId va contine id-ul la care s-a ajuns in

;cautare ;si a carui clasa cuprinde variabila

Page 210: limbaj de asamblare-ivan

608

isProt? %baseId, camp ;vede daca este ;protejata

;in clasa derivata oper unde, %baseId, ob, camp ENDIF ENDIF ENDM movToOb MACRO id, ob, camp, v push bx mov bx,word ptr ob mov DS:[bx][deplasament].&camp&id&@val@, v pop bx endm movFromOb MACRO unde, id, ob, camp push bx mov bx,word ptr ob mov unde, DS:[BX][deplasament].&camp&id&@val@ pop bx endm

Aceste macrodefiniţii, în cazul găsirii variabilelor, verifică protecţia

acestora cu ajutorul macroinstrucţiunii: isProt? MACRO id, den isProt?2 id, den, %currApelator ENDM isProt?2 MACRO id, den, currApelator ;verifica daca suntem intr-o functie a unei clase

;prietene IFNDEF friend&currApelator&of&id ;verifica daca se folosesc protectiile IFE disable&id&Prot IF den&id&Prot %OUT < **Error** Member &den is not accesible ... > .ERR ENDIF ENDIF ENDIF ENDM În cazul negăsirii variabilei în clasa curentă se realizează căutarea ei în

primul nivel de derivare cu ajutorul macrodefiniţiei: searchInBase MACRO no, id, camp noCurr = 1 REPT no ;cauta in toate clasele de baza searchInBase2 %noCurr, id, camp IF gasit ;l-a gasit si op. e deja

;introdusa EXITM ;se iese din macrourile ;recursive

Page 211: limbaj de asamblare-ivan

609

ENDIF noCurr = noCurr + 1 ENDM ENDM searchInBase2 MACRO noCurr, currId, camp baseId = baseNo&noCurr&Of&currId&Id searchInBase3 currId, %baseId, camp ENDM searchInBase3 MACRO currId, baseId, camp searchInBase4 camp&baseId&@val@ IF gasit ;la gasit si se calculeaza deplasamentul in ;functie de baseId addDeplas %(depl&baseId&In&currId) ENDIF ENDM searchInBase4 MACRO den IFDEF den ;data cu denumirea den a fost

;definita gasit = 1 ENDIF ENDM addDeplas MACRO deplCurr deplasament = deplasament + deplCurr ENDM Aceste macrodefiniţii, în cazul găsirii variabilei, setează <gasit> pe 1 şi

încarcă <baseId> cu identificatorul clasei în care aceasta a fost găsită. Totodată se calculează şi deplasamentul clasei de bază în derivată (folosit de funcţiile de acces la membru).

Pentru apelul procedurilor membre mecanismul este acelaşi, chiar simplificându-se datorită necalculării deplasamentului (pentru apel s-au construit macrodefiniţii run, isFuncProt?, searchFuncInBase). De asemenea, pentru accesul la datele membre se pot imagina şi funcţii mai complicate, cum ar fi aflarea adresei sau alte operaţii directe fără preluarea conţinutului într-un registru.

Pentru exemplificarea utilizării acestor macrodefiniţii se va folosi exemplul numerelor reale şi complexe prezentat în prima variantă de implementare:

.model large include object.asm .stack 100h class Integer public_ date nr_int, dw, 0 ;partea intreaga func Adunare func Inmultire Integer ends class Complex

Page 212: limbaj de asamblare-ivan

610

publicExt <Integer> public_ date nr_complex, dw, 0 ;partea complexa func Adunare func Inmultire Complex ends .code ;DEFINIREA METODELOR classFunc Integer, Adunare push ax get ax, nr_int add ax, word ptr [si] set nr_int, ax pop ax endFunc Integer, Adunare classFunc Integer, Inmultire push ax get ax, nr_int mul word ptr [si] set nr_int, ax pop ax endFunc Integer, Inmultire classFunc Complex, Adunare push ax get ax, nr_int add ax, word ptr [si] set nr_int, ax get ax, nr_complex add ax, word ptr [di] set nr_complex, ax pop ax endFunc Complex, Adunare classFunc Complex, Inmultire push ax push bx get ax, nr_complex mul word ptr [di] mov bx, ax get ax, nr_int mul word ptr [si] sub ax, bx push ax get ax, nr_complex mul word ptr [si] mov bx, ax get ax, nr_int mul word ptr [di] add ax, bx

Page 213: limbaj de asamblare-ivan

611

set nr_complex, ax pop ax set nr_int, ax pop bx pop ax endFunc Complex, Inmultire

Se observă modul de definire al claselor asemănător celui existent în

limbajul C++. Clasa Complex este moştenită din clasa Integer (publicext <Integer>) ceea ce îi asigură şi partea întreagă.

Numele procedurilor nu mai trebuie să fie diferit ca în exemplul anterior (deoarece numele procedurilor este format şi din identificatorul clasei), polimorfismul fiind realizat prin căutarea mai întâi în clasa curentă a procedurii şi apoi în cele de bază. În cadrul procedurilor accesul la datele membre se face prin funcţiile get şi set (obiectul nu mai trebuie precizat, se ia obiectul în contextul căruia are loc execuţia). Parametrii se trimit prin adresă prin registrele SI şi DI. Adresa obiectului nu mai trebuie trimisă ca în varianta anterioară, macrodefiniţiile folosindu-se de identificatorii obiectelor pentru a identifica structurile şi funcţiile corespunzătoare.

Pentru testare se poate folosi următoarea sursă:

.data define ob_integer, Integer define ob_complex, Complex numar1 dw 10 numar2 dw 5 .code start: mov ax,@data mov ds,ax set ob_integer, nr_int, 4 set ob_complex, nr_int, 7 set ob_complex, nr_complex, 5 lea si, numar1 run ob_integer, Adunare run ob_integer, Inmultire lea di, numar2 run ob_complex, Adunare run ob_complex, Inmultire sfarsit: mov ax,4c00h int 21h end start

Page 214: limbaj de asamblare-ivan

612

Instanţierea claselor se face prin macrodefiniţia define. Valorile iniţiale ale datelor membre se pot seta prin instrucţiunea set (trebuie precizat obiectul de care aparţin datele membre). Înaintea apelului procedurilor de adunare şi înmulţire, se încarcă în registrele SI şi DI adresele numerelor folosite pentru aceste operaţii. Rezultatele acestor apeluri se regăsesc în noile valori ale datelor membre.

Pentru verificarea operaţiilor se procedează identic ca în cazul variantei anterioare (se foloseşte opţiunea /zi pentru asamblare şi opţiunea /v pentru link-editare după care se verifică valorile membrilor celor două obiecte prin execuţie pas cu pas în turbo debugger).

La compilarea acestui program se va obţine următorul listing:

E:\TOOLS\TASM\WORKING>tasm intcomp2 Turbo Assembler Version 4.1 Copyright (c) 1988, 1996 Borland International Assembling file: intcomp2.ASM < Creating class Integer ... > < define nr_int as public ... > < define function Adunare as public ... > < define function Inmultire as public ... > < Creating class Complex ... > < Current class is derived from Integer ... > < define nr_complex as public ... > < define function Adunare as public ... > < define function Inmultire as public ... > Error messages: None Warning messages: None Passes: 1 Remaining memory: 422k

Macrodefiniţiile realizează o serie de afişări prin care indică structura

claselor create şi eventualele erori. Dacă se încearcă schimbarea definiţiei clasei Integer în următoarea (nr_int

devine private):

class Integer private_ date nr_int, dw, 0 ;partea intreaga public_ func Adunare func Inmultire Integer ends

macrodefiniţiile vor semnala o serie de erori datorită încercării de iniţializare a părţii întregi prin instrucţiunile set (set ob_integer, nr_int, 4 şi set ob_complex, nr_int, 7) de după instanţierea obiectelor:

Page 215: limbaj de asamblare-ivan

613

E:\TOOLS\TASM\WORKING>tasm intcomp2 Turbo Assembler Version 4.1 Copyright (c) 1988, 1996 Borland International Assembling file: intcomp2.ASM < Creating class Integer ... > < define nr_int as private ... > < define function Adunare as public ... > < define function Inmultire as public ... > < Creating class Complex ... > < Current class is derived from Integer ... > < define nr_complex as public ... > < define function Adunare as public ... > < define function Inmultire as public ... > < **Error** Member nr_int is not accesible ... > < **Error** Member nr_int is not accesible ... > **Error** intcomp2.ASM(96) ISPROT?2(8) User error **Error** intcomp2.ASM(97) ISPROT?2(8) User error Error messages: 2 Warning messages: None Passes: 1 Remaining memory: 422k

Se observă că apar două erori, una pentru set-ul pe clasa Integer, iar alta

pentru cel pentru clasa derivată Complex. Dacă cele două instrucţiuni set se şterg, procesul de compilare nu va mai semnala nici un fel de eroare, ceea ce demonstrează faptul că accesarea datelor membre private din cadrul procedurilor de adunare şi înmulţire sunt corecte datorită apartenenţei acestor rutine la cele două clase.

18.3.1. Optimizarea codului generat de macrodefiniţii

Aşa cum se va arăta spre sfârşitul capitolului, codul generat de

macrodefiniţii în varianta actuală este ineficient. Spre exemplu, secvenţa de macroinstrucţiuni:

set ob_integer, nr_int, 4 lea si, numar1 run ob_integer, Adunare run ob_integer, Inmultire

va genera următoarele instrucţiuni: mov currOffSet,offset ob_integer push bx mov bx,word ptr currOffset mov [bx][deplasament].nr_int1@val@, 4 pop bx

Page 216: limbaj de asamblare-ivan

614

lea si, numar1 mov currOffset,offset ob_integer call Adunare1@func@ mov currOffset,offset ob_integer call Inmultire1@func@

După cum se observă, pentru indicarea instanţei curente (cea asupra căreia se realizează operaţiile) se foloseşte o dată de tip word, currOffset. Aceasta se încarcă în mod repetat în registrul BX, deoarece între instrucţiunile prezentate pot exista şi alte instrucţiuni care afectează acest registru. În consecinţă, codul rezultat din expandarea macroinstrucţiunilor este departe de a fi optim.

O optimizare foarte uşor de făcut, este aceea de a sacrifica un registru (aşa cum în cazurile anterioare s-a folosit registrul SI), care urmează să ia locul variabilei currOffset, dar care nu mai poate fi utilizat decât cu precauţie de către utilizator (trebuie salvat în stiva înainte de blocul de operaţii în care se foloseşte şi restaurat înainte de folosirea uneia dintre macroinstrucţiuni). Pentru aceasta s-a ales registrul BX, pentru că acesta oricum se foloseşte la adresarea bazată din cadrul macrodefiniţiilor set şi get.

După modificare, macrodefiniţiile get şi set devin:

;setare valoare set MACRO ob, camp, v

;verifica daca suntem intr-o functie membra (nu se ;mai specifica obiectul)

IFB <v> operStanga movToOb, %curId, curOffset, ob, camp ELSE mov bx,OFFSET ob operStanga movToOb, %(ob&Id), ob, camp, v ENDIF IFE gasit ;nu s-a gasit %OUT < **Error** camp is not a member of ob ... > .ERR ELSE gasit = 0 ;pt. urmatoarele cautari se ;revine la 0 ENDIF deplasament = 0 ENDM ;incarcare valoare get MACRO unde, ob, camp ;verifica daca suntem intr-o functie membra (nu se ;mai specifica obiectul) IFB <camp>

Page 217: limbaj de asamblare-ivan

615

operDreapta movFromOb, unde, %curId, curOffset, ob

ELSE mov bx,OFFSET ob operDreapta movFromOb, unde, %(ob&Id), ob, camp

ENDIF IFE gasit ;nu s-a gasit %OUT < **Error** camp is not a member of ob ... > .ERR ELSE gasit = 0 ;pt. urmatoarele cautari se ;revine la 0 ENDIF deplasament = 0 ENDM

De asemenea, şi macroinstrucţiunile movToOb şi movFromOb folosite

indirect (prin intermediul lui operDreapta şi operStanga) suferă modificări, nemaifiind necesară încărcarea în registrul bx a offset-ului:

movToOb MACRO id, camp, v mov [BX][deplasament].&camp&id&@val@, v endm movFromOb MACRO unde, id, camp mov unde, [BX][deplasament].&camp&id&@val@ endm

În mod asemănător se procedează şi pentru macrodefiniţiile folosite la

apelul funcţiilor. Odată făcute aceste modificări, codul generat pentru secvenţa de instrucţiuni prezentată mai sus, se va micşora, producând o creştere semnificativă de viteză de execuţie: mov bx,offset ob_integer mov [bx][deplasament].nr_int1@val@, 4

lea si, numar1 mov bx,offset ob_integer call Adunare1@func@ mov bx,offset ob_integer call Inmultire1@func@

Page 218: limbaj de asamblare-ivan

616

Optimizarea s-a produs pentru funcţia set prin preluarea directă a

deplasamentului în registrul BX. O optimizare ce mai poate fi adusă codului, dar care ducă la o scădere a transparenţei implementării obiectelor, este încărcarea explicită în registrul BX a deplasamentului instanţei curente. Astfel, nu ar mai fi necesară încărcarea repetată a deplasamentului după cum se observă în codul următor: mov bx,offset ob_integer mov [bx][deplasament].nr_int1@val@, 4

lea si, numar1 call Adunare1@func@ call Inmultire@funct@

Făcându-se aceste simple modificări codul rezultat în urma expandării va fi

asemănător cu cel oferit de prima variantă, cea care foloseşte structuri simple, dar va oferi în plus încapsulare, moştenire şi polimorfism, toate realizate în faza de asamblare. În acest mod, viteza de execuţie a programelor scrise în cele două variante va fi identică, dar avantajele oferite de cea de-a doua implementare vor fi net superioare. 18.4. Folosirea specificaţiilor proprii limbajului de asamblare

Începând cu versiunea 3.0, asamblorul produs de firma Borland conţine elementele fundamentale ce permit programarea orientată obiect în limbaj de asamblare, şi anume structurile de tip clasă. În Turbo Assembler, datele şi codul se pot încapsula în declaraţii speciale de tip STRUC, numite clase. O clasă (numită clasă derivată) poate moşteni codul şi datele unei alte clase (numită clasă de bază). Subrutinele clasei, sau metodele, pot fi statice (apelate direct) sau virtuale (apelate prin căutarea adresei subrutinei într-o tabelă a metodelor virtuale).

Sintaxa unei declaraţii de clasă este:

<nume> STRUC <modificator> <nume_clasa> METHOD { declaraţii metode

} declaraţii date membre

ENDS <nume_clasa> unde modificator poate fi NEAR sau FAR, însoţit sau nu de directiva GLOBAL.

Declaraţia metodelor clasei are următoarea sintaxă:

[virtual] nume_metoda : tip = nume_eticheta

Page 219: limbaj de asamblare-ivan

617

Numele metodei nu este supus restricţiei de unicitate, ca în cazul declaraţiilor de membri ai tipului de date STRUC. Numele etichetei prin care este implementată metoda trebuie să fie unic. Tipul poate fi WORD sau DWORD.

Un exemplu de implementare a unei clase în limbaj de asamblare orientat obiect:

GLOBAL Baza_constructor: PROC GLOBAL Baza_actiune: PROC

Baza STRUC GLOBAL METHOD{

constructor:WORD = Baza_constructor VIRTUAL actiune:WORD = Baza_actiune } x DD 0.0 ENDS Baza

Următoarele declaraţii definesc două obiecte ale clasei Baza, cu iniţializare

sau fără:

DATASEG b1 Baza <1.1> b2 Baza < >

Prin folosirea moştenirii se creează clase noi din cele deja existente. Clasa

derivată este o copie a clasei de bază la care se pot adăuga noi metode sau variabile. Metodele adăugate pot fi complet noi sau le pot înlocui pe cele cu acelaşi nume în clasa de bază. În plus, metodele din clasa nouă pot apela metodele din clasa de bază pe care le înlocuiesc. Nu se pot înlocui date ale clasei de bază.

În continuare se prezintă un exemplu de implementare a unei clase derivate din cea anterioară, în limbaj de asamblare orientat obiect:

GLOBAL Deriv_constructor: PROC GLOBAL Deriv_actiune: PROC

Deriv STRUC GLOBAL Baza MEHOD{

constructor:WORD = Deriv_constructor VIRTUAL actiune:WORD = Deriv_actiune } TBLPTR y DD 0.0 ENDS Deriv

Metodele virtuale diferă de metodele statice prin modul în care sunt

adresate. În loc să calculeze adresa unei metode virtuale la asamblare, asamblorul generează instrucţiuni care extrag adresa la momentul execuţiei din VMT. Apelurile metodelor virtuale sunt indirecte, fiind făcute prin referinţă la intrările dintr-o VMT.

Page 220: limbaj de asamblare-ivan

618

Inserarea uneia sau mai multor metode virtuale într-o declarare a unei clase se realizează prin prefaţarea declarării metodei cu cuvântul cheie VIRTUAL. Apoi, în secţiunea de date a clasei se inserează un pointer la VMT folosind directiva TBLPTR.

Cuvântul cheie TBLINST declară o instanţă a tabelei metodelor virtuale (VMT), definind spaţiul în memorie pentru aceasta. Simpla declarare a unei instanţe VMT nu realizează şi iniţializarea pointerului la VMT pentru diversele obiecte ale respectivei clase.

DATASEG TBLINST La următorul pas, pentru fiecare obiect în parte se include în constructorul

său instrucţiunea TBLINIT <adresa>, unde adresa poate fi un registru sau o locaţie de memorie.

PROC Deriv_constructor TBLINIT Deriv PTR si

ret ENDP Deriv_constructor

Pointerul la metoda statică constructor este iniţializat cu adresa subrutinei

asociate, şi anume Deriv_constructor. Constructorul clasei trebuie apelat pentru toate obiectele clasei. Fiecare astfel de obiect are pointerul propriu la VMT, care trebuie iniţializat individual. Apelarea constructorului se realizează ca şi pentru oricare altă metoda statică. De exemplu, în segmentul de date, se defineşte mai întâi un obiect:

DATASEG d1 Deriv < >

În segmentul de cod, se adresează d1 prin intermediul ds:si şi se apelează

constructorul clasei:

CODESEG mov si, offset d1 CALL si METHOD Deriv:constructor

Apelarea metodei virtuale actiune din clasa Baza se exemplifică în

continuare:

CODESEG mov si, offfset b1 CALL Baza PTR si METHOD Baza:actiune

Page 221: limbaj de asamblare-ivan

619

Dacă SI adresează un obiect al unei clase derivate, atunci subrutina actiune a clasei derivate este apelată. Astfel, în secvenţa de mai jos, deşi instrucţiunea CALL...METHOD specifică Baza, instrucţiunea de fapt apelează metoda actiune a clasei derivate.

CODESEG mov si, offset d1 CALL Baza PTR si METHOD Baza:actiune

Aceasta este o dovadă a polimorfismului, proces prin care se creează clase

care folosesc metode virtuale pentru a selecta acţiuni la momentul execuţiei. Deci obiectul însuşi determină care metodă virtuală să fie apelată.

În anumite cazuri, apelul de forma CALL...METHOD se poate înlocui cu JMP...METHOD pentru a optimiza metodele care se termină cu apeluri către alt metode.

Pentru a apela o funcţie virtuală a clasei de bază din interiorul unei funcţii virtuale a clasei derivate se foloseşte un apel de funcţie statică. De exemplu, în metoda virtuală actiune din clasa derivată Deriv se apelează metoda virtuală actiune din clasa de bază Baza:

PROC Deriv_actiune call Baza_actiune

ret ENDP Deriv_actiune În continuare se reia exemplul numerelor întregi şi complexe pentru

exemplificarea acestei variante. Mai întâi este necesară folosirea unor directive:

IDEAL JUMPS LOCALS @@ MODEL large, PASCAL STACK 1000h

. . . . . Directiva IDEAL selectează modul de lucru Ideal al turbo asamblorului.

Acest mod face ca membrii structurilor să fie locali acestora, ceea ce ne permite să avem nume de date membre identice în structuri diferite. Acest mod impune folosirea directivei GLOBAL pentru metode.

Cea de-a doua directivă, JUMPS, permite salturi condiţionale automate, făcând posibilă generarea unui cod mai eficient din partea asamblorului. Directiva LOCALS @@ previne o serie de conflicte rezultate din folosirea variabilelor cu acelaşi nume la nivel global şi la nivel de procedură. Prin MODEL large, PASCAL se declară modelul de memorie ca fiind large şi folosirea modului de transmitere al

Page 222: limbaj de asamblare-ivan

620

parametrilor ca în Pascal (parametrii sunt transmişi în ordinea în care sunt declaraţi), mai potrivit programării orientate obiect în limbajul de asamblare. STACK 1000h declară mărimea stivei.

Urmează codul în care se declară şablonul oferit de cele două clase şi procedurile din cadrul acestora:

... GLOBAL Int_Adunare: PROC GLOBAL Int_Inmultire: PROC GLOBAL Complex_Adunare: PROC GLOBAL Complex_Inmultire: PROC STRUC Integer METHOD{ Adunare:WORD = Int_Adunare Inmultire:WORD = Int_Inmultire } nr_int DW 0 ENDS Integer CODESEG PROC Int_Adunare ARG @@int:word USES ax mov ax, [@@int] add ax, [(Integer PTR si).nr_int] mov [(Integer PTR si).nr_int], ax ret ENDP Int_Adunare PROC Int_Inmultire ARG @@int:word USES ax mov ax, [@@int] mul [(Integer PTR si).nr_int] mov [(Integer PTR si).nr_int], ax ret ENDP Int_Inmultire STRUC Complex Integer METHOD{ Adunare:WORD = Complex_Adunare Inmultire:WORD = Complex_Inmultire } nr_complex DW 0 ENDS Complex PROC Complex_Adunare

Page 223: limbaj de asamblare-ivan

621

ARG @@int:word, @@compl:word USES ax CALL si METHOD Integer:Adunare, [@@int] mov ax, [@@compl] add ax, [(Complex PTR si).nr_complex] mov [(Complex PTR si).nr_complex], ax ret ENDP Complex_Adunare PROC Complex_Inmultire ARG @@int:word, @@compl:word USES ax, bx mov ax, [@@compl] mul [(Complex PTR si).nr_complex] mov bx, ax mov ax, [@@int] mul [(Complex PTR si).nr_int] sub ax, bx push ax mov ax, [@@compl] mul [(Complex PTR si).nr_int] mov bx, ax mov ax, [@@int] mul [(Complex PTR si).nr_complex] add ax, bx mov [(Complex PTR si).nr_complex], ax pop ax mov [(Complex PTR si).nr_int], ax ret ENDP Complex_Inmultire ...

Fiecare metodă a unei clase trebuie să lucreze cu o anumită instanţă a clasei

respective. Această instanţă poate fi transmisă metodei prin oricare din modurile de transmitere a parametrilor, dar prin convenţie se transmite adresa acesteia prin perechea de registre DS:SI. De aceea accesul la o dată membră în cadrul unei metode a clasei se face prin (<nume_clasa> PTR si).<nume_camp>.

Parametrii sunt transmişi prin stivă, iar extragerea acestora se face prin directiva ARG, urmată de numele dat parametrului şi tipul acestuia. Folosirea prefixului @@ în numele parametrilor face ca aceştia să devină locali procedurii şi să nu fie confundaţi cu parametrii globali cu acelaşi nume. Se face uz de directiva USES prin care se indică regiştrii ce vor fi folosiţi, urmând ca asamblorul să includă push-urile şi pop-urile necesare pentru revenirea la valorile iniţiale ale acestor regiştri.

Pentru folosirea acestor clase se face apel la datele şi operaţiile de test prezentate şi la celelalte două variante:

Page 224: limbaj de asamblare-ivan

622

... DATASEG ob_integer Integer <4> ob_complex Complex <7, 5>

CODESEG start:

mov ax,@data mov ds,ax

mov si, offset ob_integer CALL si METHOD Integer:Adunare, 10 CALL si METHOD Integer:Inmultire, 10 mov si, offset ob_complex CALL si METHOD Complex:Adunare, 10,5 CALL si METHOD Complex:Inmultire, 10,5

sfarsit:

mov ax,4c00h int 21h

end start

Înaintea apelurilor procedurilor se încarcă în registrul SI deplasamentul instanţei. Parametrii au fost transmişi prin specificarea unor constante după numele procedurii (numele este cel din cadrul clasei şi nu cel efectiv) în cadrul instrucţiunilor CALL. În locul acestor constante se puteau specifica şi identificatori de regiştri sau zone de memorie.

18.5. Analiza comparativă a variantelor de implementare a obiectelor

Fiecare dintre cele trei variante de implementare a obiectelor în limbajul de asamblare are atât avantaje cât şi dezavantaje. Prima variantă, cea prin care se foloseau structuri simple, este uşor de folosit şi nu necesită nici un fel de construcţii speciale. Prezintă însă o serie de deficiente: nu asigură o protecţie a datelor şi metodelor, în cazul moştenirii multiple apare problema deplasamentelor datelor membre ale claselor de bază, indicarea obiectelor se face explicit prin încărcarea într-un registru a adreselor, lipsesc elementele avansate de programare orientată obiect, cum este polimorfismul, clasele prietene şi funcţiile virtuale.

Cea de-a doua variantă, se remarcă printr-o transparenţă mare a mecanismelor de implementare a obiectelor, ceea ce poate uşura substanţial munca programatorului, precum şi prin optimalitatea codului datorită folosirii macrodefiniţiilor (durează mai mult compilarea, dar codul executabil rezultat este optim). Sintaxa a fost construită să semene cât mai mult cu cea folosită de limbajul C++, cu care majoritatea programatorilor sunt familiarizaţi. Varianta prezentată nu suportă moştenirea pe mai multe niveluri (dar care nu este imposibil de realizat

Page 225: limbaj de asamblare-ivan

623

datorită existenţei construcţiilor macro recursive şi repetitive) şi nici funcţiile virtuale, care necesită cod efectiv (nu numai macroinstrucţiuni) folosit în timpul executării programului. De asemenea, este necesară scrierea tuturor acestor macroinstrucţiuni şi includerea lor în fişierele sursă.

Ultima variantă, care foloseşte construcţii speciale orientate obiect specifice implementării propuse de firma Borland, acoperă multe din aspectele programării orientate obiect. În plus faţă de celelalte două variante, oferă folosirea funcţiilor virtuale. Trebuie ţinut cont că această implementare este specifică doar produsului realizat de firma Borland şi, deci, nu reprezintă un standard. Asambloarele oferite de alte firme nu cuprind aceste construcţii, iar dacă se doreşte un cod sursă compatibil (cu atât mai mult cu cât chiar sintaxa oferită de Borland poate suferi schimbări) trebuie făcut apel la alte variante. De asemenea, la fel ca în prima variantă, indicarea instanţelor nu se face la fel de uşor ca în cea de-a doua metodă, ci prin încărcarea explicită a adreselor în regiştri. Sintaxa nu permite o verificare completă a codului chiar înainte de link-editare, ca în cazul variantei folosirii macrodefiniţiilor, erorile fiind uneori greu de depistat, iar efectul acestora apare doar în timpul execuţiei.

Pentru o comparaţie mai profundă a celor trei variante de implementare, trebuie calculaţi şi o serie de indicatori, cum ar fi: numărul de instrucţiuni scrise, numărul efectiv de instrucţiuni folosite de asamblor, timpul de execuţie pentru o problemă dată etc.

Pentru a afla valorile unor astfel de indicatori în fiecare din variante trebuie apelat la un alt exemplu decât cel prezentat anterior. Astfel, pentru aflarea duratei de execuţie trebuie construit un exemplu de calcul care necesită un timp de execuţie mai lung, şi astfel cuantificabil. Se va lucra tot pe cele două clase prezentate în paginile de mai sus, dar pentru testarea operaţiilor acestora se va opta pentru un cod ciclic. Ca număr de iteraţii s-a optat pentru 10.000.000, ceea ce face ca timpul de execuţie să fie de ordinul zecilor de secunde. În cadrul unei iteraţii se realizează înmulţiri pentru ambele clase, utilizându-se datele de test deja prezentate.

Pentru prima variantă, cea care foloseşte doar structuri simple, noul cod va fi următorul:

; vechea porţiune de cod prin care se declara clasele ; si operaţiile acestora ...

... contor1 dw 2000 contor2 dw 5000 ; 2000 x 5000 = 10.000.000 de iteraţii

.code start:

mov ax,@data mov ds,ax

Page 226: limbaj de asamblare-ivan

624

lea si, numar1 ;[si] = numar1, [si+2] = numar2 mov cx, contor1

next1: push cx

mov cx, contor2 next2: mov ob_integer.nr_int, 4 mov ob_complex.nr_int, 7 mov ob_complex.nr_complex, 5 lea di, ob_integer call ob_integer.inm_int lea di, ob_complex call ob_complex.inm_compl

loop next2 pop cx loop next1 sfarsit: mov ax,4c00h int 21h end start

Pentru varianta în care se folosesc macroinstrucţiuni, noul cod va fi:

; vechea porţiune de cod prin care se declara clasele ; si operaţiile acestora ... ... contor1 dw 2000

contor2 dw 5000 ; 2000 x 5000 = 10.000.000 de iteraţii

.code start:

mov ax,@data mov ds,ax

lea si, numar1 lea di, numar2 mov cx, contor1

next1: push cx

mov cx, contor2 next2: set ob_integer, nr_int, 4 set ob_complex, nr_int, 7 set ob_complex, nr_complex, 5 run ob_integer, Inmultire run ob_complex, Inmultire loop next2 pop cx

Page 227: limbaj de asamblare-ivan

625

loop next1 sfarsit:

mov ax,4c00h int 21h

end start

Iar pentru ultima variantă, în care se foloseşte implementarea Borland, codul folosit pentru testare va fi:

; vechea porţiune de cod prin care se declara clasele ; si operaţiile acestora ... ...

contor1 dw 2000 contor2 dw 5000 ; 2000 x 5000 = 10.000.000 ;de

iteraţii CODESEG start:

mov ax,@data mov ds,ax

mov cx, contor1 next1: push cx

mov cx, contor2 next2: mov si, offset ob_integer mov [(Integer PTR si).nr_int], 4 CALL si METHOD Integer:Inmultire, [numar1] mov si, offset ob_complex mov [(Complex PTR si).nr_int], 7 mov [(Complex PTR si).nr_complex], 5 CALL si METHOD Complex:Inmultire, [numar1], [numar2] loop next2 pop cx loop next1 sfarsit: mov ax,4c00h int 21h end start

Folosindu-se aceste secvenţe de cod se obţin indicatori atât cantitativi, cât şi

calitativi referitori la cele trei variante implementate, tabelul 18.5.

Page 228: limbaj de asamblare-ivan

626

Tabelul 18.1. Analiza comparativă a celor trei variante Varianta ce foloseşte

macroinstrucţiuni Indicator

Varianta ce

foloseşte structuri simple

Varianta

iniţială

Varianta optimizată

Implementarea Borland orientată

obiect

Linii de cod scrise 95 93 93 100

Linii de cod asamblate (după

expandare) 95 147 96 121

Linii de cod folosite în

timpul execuţiei 73 124 73 81

Timpul de execuţie

(secunde) 20 37 22 27

Încapsulare (protecţia

membrilor)

Moştenire

Polimorfism

Funcţii virtuale

18.5.1. Analiza indicatorilor rezultaţi

Tabelul prezintă patru indicatori, trei referindu-se la numărul liniilor de cod

(ceea ce în limbaj de asamblare este echivalent cu numărul instrucţiunilor), iar ultimul este timpul de execuţie exprimat în secunde. Primul indicator se referă la numărul de linii scrise de programator (neincluzând codul sursă al macrodefiniţiilor), în timp ce al doilea reflectă numărul de instrucţiuni după efectuarea expandărilor macrodefiniţiilor. Al treilea indicator cuprinde doar instrucţiunile ce intervin în mod activ în timpul execuţiei (se elimină unele instrucţiuni cum ar fi etichetele, directivele de compilare etc.). Analiza se poate dezvolta apelându-se la lungimile în octeţi a instrucţiunilor. După aceşti patru indicatori se indică existenţa sau inexistenţa a patru concepte fundamentale ale programării orientate obiecte: încapsulare, moştenire, polimorfism şi funcţii virtuale.

Page 229: limbaj de asamblare-ivan

627

Prima variantă de implementare prezintă cele mai puţine linii de cod şi cel mai mic timp de execuţie. Acest lucru este explicat prin lipsa celor mai elementare concepte ale programării orientate obiect (implementarea nu oferă nici măcar o protecţie a datelor sau polimorfism). Tot ceea ce scrie programatorul se reflectă în mod identic în codul sursă supus asamblării, singura abatere de la programarea tradiţională în cod de asamblare o constituie includerea pointerilor către proceduri în cadrul structurilor.

Cea de-a doua metodă de implementare este prezentată în cele două variante, observându-se câştigul substanţial de viteză al formei optimizate. Aceasta se apropie ca performanţă de metoda care foloseşte structuri simple (22 de secunde de execuţie faţă de 20), oferind în plus programatorului încapsulare, polimorfism şi moştenire multiplă. Prima variantă este mult mai lentă datorită folosirii unei date suplimentare pentru deplasare în cadrul structurilor şi a salvărilor repetate în stivă a registrului BX. Dar, aceasta oferă un grad mai ridicat de încapsulare. Utilizatorul macroinstrucţiunilor nu este supus nici unor restricţii în folosirea registrului BX, care în cazul variantei optimizate trebuie salvat şi restaurat de fiecare dată ţinând cont că şi codul expandat de macrodefiniţii foloseşte acest registru. De asemenea, varianta a doua, din motive de optimizare a codului, necesită încărcarea explicită în registrul BX a instanţei asupra căreia se lucrează, ceea ce duce la creşterea probabilităţilor de apariţie a erorilor.

Ultima variantă de implementare, cea oferită de firma Borland, oferă performanţe medii şi acest lucru se explică prin modul în care sunt trataţi parametrii procedurilor. Aceştia sunt transmişi prin stivă, iar pentru folosirea lor se rezervă regiştri care trebuiesc salvaţi înainte de încărcare şi restauraţi la ieşirea din procedură. Pentru a urmări codul generat, să luăm procedura de adunare a numerelor complexe:

PROC Complex_Adunare ARG @@int:word, @@compl:word USES ax CALL si METHOD Integer:Adunare, [@@int] mov ax, [@@compl] add ax, [(Complex PTR si).nr_complex] mov [(Complex PTR si).nr_complex], ax ret ENDP Complex_Adunare

Macrodefiniţiile ARG şi USES introduc următoarele modificări codului de

mai sus:

PROC Complex_Adunare PUSH BP MOV BP,SP PUSH AX

Page 230: limbaj de asamblare-ivan

628

CALL si METHOD Integer:Adunare, [@@int] mov ax, [@@compl] add ax, [(Complex PTR si).nr_complex] mov [(Complex PTR si).nr_complex], ax

POP AX POP BP

ret ENDP Complex_Adunare

Pentru accesul la variabilele din stivă, trebuie salvată vechea valoare a

registrului BP, care va fi folosit pentru accesarea stivei (mov BP, AX). Urmează salvarea registrului AX folosit pentru preluarea uneia dintre variabile. Înaintea revenirii din procedură se restaurează vechile valori ale regiştrilor AX şi BP. Pentru reutilizarea codului, este apelată metoda de adunare din clasa de bază, care, la rândul ei, va relua secvenţa de salvări şi restaurări de regiştri. Toate aceste operaţii duc la o încetinire semnificativă în execuţie atunci când apelurile de proceduri sunt folosite frecvent. Regiştrii, cum sunt AX şi BX, sunt salvaţi în mod repetat cu toate că păstrarea vechilor valori nu prezintă importanţă. Din acest punct de vedere, această metodă se aseamănă cu prima variantă a implementării prin macroinstrucţiuni, oferind un grad mare de transparenţă şi securitate, dar şi o încetinire a calculelor.

Alegerea uneia dintre variante trebuie făcută în strânsă corelaţie cu problema de rezolvat:

dacă se dezvoltă cod relativ mic, care se doreşte a fi rapid se optează pentru prima variantă;

dacă sunt necesare facilităţile de moştenire, încapsulare sau polimorfism pentru probleme mai complexe, dar pentru care timpul de execuţie este critic, se va opta pentru varianta optimizată ce foloseşte macroinstrucţiuni;

dacă codul rezultat trebuie să fie sigur, punându-se accentul pe securitatea oferită în timpul execuţiei şi mai puţin pe timpul de execuţie, implementarea Borland orientată obiect sau prima variantă ce foloseşte macrodefiniţii se dovedesc alegerile viabile;

dacă se doresc secvenţe de program complexe, care apelează la lucrul cu pointeri spre obiecte ale căror tipuri sunt cunoscute abia în timpul execuţiei, cea mai buna alegere este implementarea Borland care oferă suport pentru funcţii virtuale.

Fiecare variantă este optimă pentru anumite tipuri de probleme şi cerinţe ale acestora, programatorului revenindu-i responsabilitatea alegerii. Prima variantă este simplă şi nu necesită nici un fel de efort suplimentar, dar lipsurile acesteia sunt evidente când se dezvoltă programe complexe. Variantele ce folosesc macrodefiniţii necesită scrierea sau procurarea acestora, iar implementarea oferită de firma Borland este recunoscută doar de asamblorul acesteia.

Page 231: limbaj de asamblare-ivan

629

19

STRUCTURI DE PROGRAME

O structură de program se memorează în segmente de date (x), segmente de stivă (y) şi segmente de cod (z) şi este definită prin tripletul (nx, ny ,nz), unde:

nx – numărul de segmente de date; ny – numărul de segmente de stivă; nz – numărul de segmente de cod. În continuare se prezintă tipurile principale de structuri de program atât prin

plasarea în memorie (segmente) cât şi în raport cu raporturile dintre proceduri. 19.1 Programul ca singură secvenţă (0, 0, 1)

Această structură de program de tip monolit este caracteristică problemelor simple, cărora li se dă soluţie de către un singur programator. Operanzii (variabilele simple, masivele, tipurile derivate) se definesc în acelaşi segment în care se află secvenţa de instrucţiuni executabile. Programul ca singură secvenţă corespunde şi construcţiilor elaborate de către programatorii începători care nu au capacitatea de a structura la nivel de proceduri sau secvenţe apelabile, operaţii complexe cu caracter repetitiv. Tot atât de bine, acest mod de a soluţiona o problemă este specifică şi programatorilor care urmăresc minimizarea salturilor necondiţionate, generate de apelurile de proceduri (instrucţiunea call) şi de revenirile la procedura apelatoare (instrucţiunea ret).

De exemplu, evaluarea funcţiei: x>0

x+y+z+w y>0 z>0 w>0 F(x,z,y,w)= x<0 x2+y2+z2+w2 y<0 z<0

Page 232: limbaj de asamblare-ivan

630

w<0 |x|+|y|+|z|+|w| în rest

se efectuează cu un program în care se definesc proceduri pentru :

ADD3 – însumarea a trei elemente; PDW2 – ridicarea la putere a unui număr; MODUL – calculul modulului unui număr.

Textele sursă asociate procedurii pentru adunarea a 4 numere este:

ADD3 PROC ; s=a+b+c+d push bp mov bp, sp push ax mov ax, [bp+4] a ;ax:=0 add ax, [bp+6] b ;ax:=ax+b add ax, [bp+8] c ;ax:=ax+c add ax, [bp+10] d ;ax:=ax+d mov [bp+12], ax ;s:=ax pop ax pop bp ret endp Încărcarea pe stivă a adreselor parametrilor reali înainte de efectuarea

apelului procedurii ADD3 se realizează:

mov ax, offset s ;variabila unde se stochează suma push ax mov ax, offset d; push ax mov ax, offset c push ax mov ax, offset b push ax mov ax, offset a push ax call ADD3

Textul sursă pentru ridicarea la pătrat a unui număr este: POW2 proc

push bp mov bp, sp push ax

Page 233: limbaj de asamblare-ivan

631

push dx mov ax, [bp+4] mul [bp+4] mov [bp+6], ax pop dx pop ax pop bp ret endp

Pentru evaluarea expresiei e=a*a, încărcarea pe stivă a adresei parametrilor

este realizată astfel:

mov ax, offset a push ax mov ax, offset e push ax call POW2

Textul sursă pentru procedura de calcul a modulului unui număr s=|a| este:

modul proc push bp mov bp,sp push ax mov ax, [bp+4] neg ax mov [bp+6], ax pop ax pop bp ret endp Textul sursă al programului cu apelarea celor trei proceduri este:

start: mov ax, @data

mov ds, ax cmp x, 0 jle alfa cmp y, 0 jle alfa cmp z, 0 jle alfa mov ax offset fxyz push ax mov ax, offset w push ax mov ax, offset z push ax

Page 234: limbaj de asamblare-ivan

632

mov ax, offset y push ax mov ax, offset x push ax jmp suma alfa:

cmp x, 0 jz beta cmp y, 0 jz beta cmp z, 0 jz beta cmp w, 0 jz beta mov ax, offset xp push ax mov ax, offset x push ax call POW2 mov ax, offset yp push ax mov ax, offset y push ax call POW2 mov ax, offset zp push ax call POW2 mov ax, offset wp push ax mov ax, offset w push ax call POW2 mov ax, offset s push ax mov ax, offset wp push ax mov ax, offset zp push ax mov ax, offset yp push ax mov ax, offset xp push ax jmp suma beta:

mov ax, offset mx push ax mov ax, offset x push ax call modul mov ax, offset my push ax mov ax, offset y

Page 235: limbaj de asamblare-ivan

633

push ax call modul mov ax, offset mz push ax mov ax, offset z push ax call modul mov ax, offset mw push ax mov ax, offset w push ax call modul mov ax, offset s push ax mov ax, offset w push ax mov ax, offset mz push ax mov ax, offset my push ax mov ax, offset mx push ax suma:

call ADD3 ... ;procedura pentru conversia de la binar la şir de caractere ;şi reprezentare f(x, y, z, w) ...

mov 4c00h int 21h end start

x dw 2 y dw 3 z dw 4 w dw 1 px dw ? py dw ? pz dw ? pw dw ? mx dw ? my dw ? mz dw ? mw dw ?

s dw ?

Structura grafică a programului este dată în figura 19.1.

Page 236: limbaj de asamblare-ivan

634

push …

push …

push …

push …

start x>0

y>0

z>0

w>0

y=0

z=0

w=0

x=0

|y|

|z|

|w|

|x|

y2

z2

w2

x2

push …

push …

push …

push …

push …

push …

push …

push …

s=a+b+c+d

conversie

afisare

stop

Page 237: limbaj de asamblare-ivan

635

Figura 19.1 – Graficul asociat programului PP pentru evaluarea funcţiei într-un punct folosind proceduri

Pentru eliminarea salturilor necondiţionate impuse de apelurile procedurilor se procedează la conturarea unui program numit PM ca un tot unitar, în care secvenţele alcătuiesc o singură entitate, având textul sursă:

...

cmp x, 0 jle alfa cmp y, 0 jle alfa cmp z, 0 jle alfa cmp w, 0 jle alfa add ax, z add ax,y add ax,x mov s, ax jmp imprimare alfa: cmp x, 0 jz beta cmp y, 0 jz beta cmp z, 0 jz beta cmp w, 0 jz beta mov ax, x mov mx, ax neg mx mov ax, y mov my, ax neg my mov ax, z mov mz, ax neg z mov ax, w mov mw, ax neg w mov ax, mx add ax, my add ax, mz add ax, mw mov s, ax jmp imprimare beta: xar bx, bx

Page 238: limbaj de asamblare-ivan

636

mov ax, x mul x add bx, ax mov ax, z mul y add bx, ax mov ax, z mul z add bx, ax mov ax, w mul w add bx, ax mov s, ax

... Dezavantajul programului PM este dat de faptul că se construieşte de către un singur programator. Programul în care se definesc proceduri se elaborează în echipe de programatori, fiecărui programator revenindu-i sarcina de a elabora simultan un număr proceduri şi de a obţine reducerea duratei de elaborare a programului. 19.2 Programul din secvenţe distincte grupate într-un segment (1, 1, 1)

Acest tip de construcţie este compusă dintr-un segment de date, un segment stivă şi un segment de cod.

Segmentul de cod conţine secvenţe referite instrucţiuni de apel (instrucţiunea call). Evaluarea expresiei:

e=(a+min{xi}+max{xi})*(b+min{yi})-(c+max{wi})-(min{zi}+max{zi}) se realizează prin programul PEVAL al cărui text sursă este: mov si, offset x mov cx, n mov bx, a minim:

mov ax, [si] ciclul1:

cmp ax, [si+2] minim jle salt_1 mov ax, [si+2] salt1:

inc si inc si loop ciclul1 ret add bx, ax ; bx:=bx+min{xi} mov cx, n

Page 239: limbaj de asamblare-ivan

637

mov si, offset y maxim:

mov ax, [si] ciclul2:

cmp ax, [si+2] jge salt2 mov ax, [si+2] max salt2;

inc si inc si loop ciclul2 ret add bx, ax mov e, bx mov bx, b mov si, offset y mov cx, m call minim add bx, ax mov si, offset y mov cx, m call maxim add bx, ax mov ax, e mul bx mov e, ax mov ex2, dx mov bx, c mov si, offset w mov cx, m call maxim add bx, ax sub e, bx sbc ex2, 0 e:=e-(c+max{xi}) mov si offset z mov cx, k call minim mov bx, ax mov si, offset z mov cx, k call maxim add bx, ax shl bx, 1 sub e, bx sbc ex2, 0

Programului PEVAL i se asociază secvenţele:

Sx – iniţializări pentru operaţii pe masivul x, Smin – găsire element minim;

Page 240: limbaj de asamblare-ivan

638

Smax – găsire element maxim; Sy – iniţializări pentru operaţii pe masivul y; Se – evaluări expresie Se; Sw – iniţializări pentru operaţii cu masivul w; Sz – iniţializări pentru operaţii pe masivul z.

Secvenţele se activează astfel: Sx Smin Sx Smax Se Sy Smin Sy Smax Se Sw Smin Se Sz Smin Sz Smax Se. Acest mod de structurare a secvenţelor este asemănător construirii programelor în limbajul BASIC, cu utilizarea instrucţiunilor GOSUB. De asemenea, programatorii în limbajul COBOL structurează programele în paragrafe, delimitate prin etichete, paragrafe pe care le referă cu instrucţiunea PERFORM. Paragrafele se scriu în interiorul programului sau se poziţionează după instrucţiunea STOP RUN. De exemplu, programul TAB pentru afişarea unui cap de tabel având structura:

Denumire (30)

Cant (4)

Pret unitar (5)

Valoare (8)

0 1 2 3 Prod1 10 2 20 Prod2 5 3 150 Prod3 2 13 26

Total 196 are textul sursă: rând1 struc filler1 db 10 linii db 60 ends rând2 struc filler2 db 10 dup (‘’) col21 db ‘I ‘ denumire 2 db 30 dup (‘’) col22 db ‘ , ‘ cant2 db 4 dup (‘’) col23 db ‘ , ‘ pret2 db 5 dup (‘ ‘) col24 db ‘ , ‘

Page 241: limbaj de asamblare-ivan

639

valoare2 db 8 dup (‘ ‘) col25 db ‘ ,‘ rând3 struc filler db 10 dup (‘ ‘) col31 db ‘, ‘ denumire3 db ’Denumire/30’ col32 db ‘ , ‘ cant3 db ‘CANT’ col33 db ‘ , ‘ pretunit3 db ‘ PU’ col34 db ‘ , ‘ valoare3 db ‘ valoare’ col35 db ‘ ,’

Pentru afişarea capului de tabel se foloseşte secvenţa: call scrie-rand1 call scrie-rand2 call scrie_rand2 call scrie_rand3 call scrie_rand2 call scrie_rand2 call scrie_rand1 call scrie_rand4 (I0I1I2I3) call scrie_rand1

Pentru afişarea datelor tabelului se foloseşte secvenţa:

mov cx, n (număr produse) mov si offset vect_prod ciclu: ………….

…………. ;calcul valoare, total …………. ;conversii …………. ;call scrie_rând5 ………….

loop ciclu ………….

…………. ;conversie total …………. ;mută în rând 6 la coloana …………. ;de total şirul convertit …………. ;call scrie_rând6 ………….

mov ax, 4c00h int 21h serie_rând1:

…………. …………. ret serie_rând2:

Page 242: limbaj de asamblare-ivan

640

…………. …………. ret serie_rând6:

…………. …………. ret Secvenţele care se referă prin instrucţiunea call sunt dispuse după instrucţiunile care definesc transferul controlului programului către sistemul de operare: mov ax, 4c00h ret 21h 19.3 Proceduri incluse în segmentul programului principal (1, 1, 1) Aplicaţia conţine un segment de date, un segment pentru stivă şi un segment pentru cod. Corespunde situaţiei în care conducătorul echipei de programatori gestionează cu rigurozitate proiectul. El defineşte datele cu care se lucrează, structurează aplicaţia pe module (proceduri sau macrodefiniţii) pentru a fi elaborate simultan de către membrii echipei. Dacă A, B sunt matrice pătrate şi C, D, G sunt vectori, evaluarea expresiei: F=((A2+B’)*C+D)*G presupune scrierea de proceduri:

construirea unei matrice transpuse; efectuarea produsului a două matrice; calcul produs matrice vector coloană; adunarea a doi vectori; calcul produs scalar a doi vectori.

Şeful echipei de programatori construieşte segmentul de date:

Datele seg ‘DATE’ NCOL EQU 4 NLIN EQU 4 A dw 7, 5, 8, 13 dw 3, 1, 8, 6 dw 1, 0, 3, 7 dw 4, 5, 11, 2 B dw 2, 4, 9, 1 dw 32, 2, 5, 7 dw 4, 9, 7, 4

Page 243: limbaj de asamblare-ivan

641

dw 3, 5, 21, 17 C dw 8, 3, 5, 2 D dw 6, 1, 1, 4 G dw 9, 11, 2, 5 BT dw NLIN dup (NCOL dup(?)) ; B transpus A2 dw NLIN dup (NCOL dup(0)) ; patratul lui A SUMA dw NLIN dup (NCOL dup(0)) ; A^2+B’ VP dw NCOL dup(0) ;vector produs VS dw NCOL dup(0) ;vector suma i dw ? j dw ? ends Variabilele de lucru ale fiecărei proceduri se definesc între instrucţiunile RET şi ENDP. Programatorul şef stabileşte modul de transmitere a parametrilor şi de utilizare a regiştrilor, după cum urmează:

matricele şi vectorii se folosesc ca variabile globale; pentru referirea elementului aij al masivului bidimensional se foloseşte

macrodefiniţia:

emw macro a, n, i, j, poz push ax push bx mov ax, i mul ax, n mov bx, j add ax,bx shl ax, 1 mov bx, offset a add ax, bx mov poz, ax pop bx pop ax endm pentru referirea elementului bj al unui masiv unidimensional se

foloseşte macrodefiniţia:

evw macro b, j, poz push ax push bx mov ax, offset b mov bx, j shl bx, 1 add ax, bx mov poz, ax pop bx pop ax

Page 244: limbaj de asamblare-ivan

642

endm se foloseşte adresarea indirectă în care expresia de adresare este zonă de

memorie

Programatorii lucrează simultan la scrierea procedurilor. Astfel, programatorul P1 scrie procedura pentru adunarea elementelor a doi vectori, obţinând un al treilea vector:

Vsi:=Vpi+D unde i=1,NCOL

Pentru procedurile de efectuare a produsului scalar, programatorul P2

elaborează în paralel cu ceilalţi componenţi ai echipei procedura:

prodscal proc mov cx, NCOL-1 mov i,0 mov f,0 mov f+r, 0 ciclu: evw vs,i, poz mov ax, [poz] evw g, i, poz mul [poz] add f, ax adc f+2, dx inc i loop ciclu ret

endp Pentru transpunerea unei matrice, programatorul P3 scrie textul sursă: transp proc push ax push cx mov ax, NLIN mov i, 0 ciclu1:

push cx mov j, 0 mov cx, NCOL ciclu2:

emw b, i, j, NCOL, poz mov ax, [poz] emw bt, j, i, NLIN, poz mov [poz], ax inc j

Page 245: limbaj de asamblare-ivan

643

loop ciclu2 pop cx inc i loop ciclu 1 pop cx pop ax ret endp Pentru adunarea a două matrice AP şi BT programatorul P4 scrie procedura: admat proc push ax push ax mov ax, NLIN mov i, 0 ciclu 1:

push cx mov j, 0 mov cx, NCOL ciclu 2:

emw AP, i, j, NCOL, poz mov ax,[poz] emw BT, i, j, NCOL, poz add ax,[poz] emw MS, i, j NCOL, poz mov [poz], ax inc j loop ciclu 2 pop ax inc d loop ciclu 1 ret endp

Programatorul şef scrie programul apelator:

call prodmat call transp call admat call prodmv call advec call prodscal call convert call afisir mov ax, 4c00h int 21h end start

Page 246: limbaj de asamblare-ivan

644

Acest mod de a concepe un program vizează lucrul în paralel, figura 19.2, intercalând apelurile cu instrucţiuni de stocare pe stivă a listelor de parametri.

A1 A2 A3

A1 A4 Ax+1 A5 A6 A7

Figura 19.2 – Paralelizarea programelor

În figură:

a1 – definire segmentul de date a2 – elaborare procedura 1 ……………………………… at – elaborare procedura n at+1 – elaborează programul apelator

Decizia de a utiliza variabile globale în proceduri restrânge

gradul de generalitate al procedurilor dar elimină lucrul pe stivă cu parametrii simplificând expresiile de referire ale operanzilor.

19.4 Definiri distincte pentru toate componentele (m, k, n) În cazul aplicaţiilor deosebit de complexe programatorul şef defineşte structura pe module şi variabile, însă nu mai poate impune restricţii severe colaboratorilor în ceea ce priveşte variabilele intermediare şi structurarea modulelor la nivel de proceduri şi secvenţe. Mai mult, pentru a lăsa independenţă în procesul de testare a modulelor este necesar ca fiecare programator să-şi definească elementele specifice părţii din aplicaţie aşa fel încât să delimiteze precis momentul de început al lucrului, respectiv, momentul de predare al modulelor. În acest fel se creează posibilitatea

Page 247: limbaj de asamblare-ivan

645

trecerii la altă aplicaţie şi realizarea departajării programatorilor după nivelul performanţei individuale. Aplicaţia complexă presupune:

distribuirea modulelor; construirea de segmente de date; construirea de segmente de stivă; construirea de segmente cu proceduri şi cu program de test; stabilirea corelaţiilor dintre programatori şi segmentele elaborate; stabilirea interdependenţei dintre proceduri; stabilirea interdependenţelor dintre segmentele de date şi stive, respectiv

proceduri. Se consideră aplicaţia ‘GESTIUNE MATERIALE’ care conţine următoarele

funcţii, proceduri: Pcre – creare fişier materiale prin adăugare; Pdel – ştergere articol din fişier; Padd – adăugare articol; Pins – inserare articol; Psort – sortare fişier; Pmodif – modificare denumire material; Pscrie – scrie articol în fişier; Pmdpr – modificare preţ material; Pindv – identificare articol după o cheie; Paprv – aprovizionare cu o cantitate; Pies – ieşiri de materiale; Psupra – afişarea materialelor în stocuri supranormative; Pfără – afişarea stocurilor fără mişcare; Pzero – afişarea materialelor cu stoc zero; Pval – calculul stocului final în expresie valorică.

Pentru rezolvarea acestei aplicaţii se procedează astfel: a) se defineşte structura articolului în segmentul SEGART

SEGART SEG mat struc cod dw ? den db 30 dup (?) UM dw ? Pret dw ? Stocin dw ? Intrari dw ? Iesiri dw ? Val dw ? Ends b) Se stabilesc datele de test sub forma tabelului 19.1

Page 248: limbaj de asamblare-ivan

646

Tabelul 19.1. Datele de test ale aplicaţiei ‘GESTIUNEA MATERIALELOR’

Cod matrial Denumire UM Preţ Stoc

iniţial Intrari Ieşiri Stoc final

Valoare

1289 Tablă Foi 1200 25 12 5 32 38400 5423 Cuie Kg 150 10 15 7 19 2850 4566 Sârmă Kg 230 50 20 25 45 10350 3689 Cărămidă Buc 50 500 700 300 900 45000 TOTAL 96600

c) Se stabilesc intrările şi ieşirile modulelor. Pdel are ca intrare: nume fişier, codul materialului care va şters (cheia) Proceduri apelate: Pindex pentru localizare articol după cheie; Pscrie pentru rescrierea articolului dezactivat;

d) se scrie procedura pentru creare prin adăugare de articol şi se încarcă fişierul fişmat care este sursa de bază pentru toţi programatorii. Ei au obligaţia de a-l copia şi de a-l utiliza în programele lor proprii.

e) se scrie procedura de afişare a fişierului. f) se scrie procedura de numărare a articolelor din fişier şi de afişare a

numărului de articole. Fiecare îşi defineşte şi structurile proprii în segmente. În final există n

proceduri şi m segmente de date. Programatorul şef asamblează şi scrie programul apelator. Se procedează la efectuarea testării:

o testarea modulelor (de către realizatori) o testarea produsului asamblat.

19.5 Structuri liniare de program

Aplicaţiile sunt văzute ca funcţii de prelucrare care se execută obligatoriu, una după cealaltă.

Se consideră funcţiile de prelucrare f1, f2, …, fn care alcătuiesc o aplicaţie. Graficul asociat structurii liniare de program este dat în figura 19.3. f1 f2 fn-1 fn

● ● ● ● ● Figura 19.3 – Structură liniară de program

Acestei structuri îi corespunde secvenţa de program:

Page 249: limbaj de asamblare-ivan

647

... call f1 call f2 ... call fn mov ax,4c00h int 21h

f1 proc ... ret

endp f2 proc

... ret

endp ...

fn proc ... ret

endp Pentru asigurarea traversării secvenţei celor n instrucţiuni call se

procedează la definirea unui vector de pointeri spre funcţii vpf prin linia sursă: vpf dw f1, f2, f3 dw f4, f5, f6

... dw fn-2, fn-1, fn

care se referă prin secvenţa: mov si, offset vpf mov cx, n ciclu: call [si] inc si

in si loop ciclu mov ax, 4c00h int 21h

Structura liniară corespunde unei singure succesiuni de apelare a

procedurilor. Procedurile sunt referite în totalitate şi strict în ordinea impusă, indiferent de context. 19.6 Structura arborescentă

Page 250: limbaj de asamblare-ivan

648

Se consideră funcţiile de prelucrare: f0 f1 f2 f3 … fn. Referirea funcţiilor se efectuează selectiv după cum sunt îndeplinite o serie de condiţii. Fie şirul de elemente v1 v2 … vm definite pe mulţimea booleană unde vi{o,1} i=1, 2,…, m. Se construieşte structura arborescentă din figura 19.4.

Figura 19.4 – Structura asociată nodului rădăcină corespunzătoare programului principal (funcţia , f0)

Nodurile corespund apelurilor de funcţii(proceduri) call fi

... call fj şi testelor pentru verificarea unor condiţii. cmp vi, 0 jz apelj apeli: call fi jmp ...

... apelj: call fj

... În structura arborescentă asociată unui program se găsesc noduri interne

având forma dată în figura 19.5.

f0

vi

fj fi

fk

vk

fl fh

Page 251: limbaj de asamblare-ivan

649

Figura 19.5 – Secvenţa de structură arborescentă asociată

unui apel de funcţii fk – nod intern al structurii arborescente

În structura arborescentă există funcţii ‘terminale’ care corespund nodurilor frunză. Ele nu apelează alte funcţii şi corespund execuţiilor ce preced încheierea programului, figura 19.6.

Figura 19.6 – Funcţiile terminale

Se consideră variabile funcţiile f0 f1 f2 f3 f4. Se construiesc perechile (f1, v=1) (f2, v=2) (f3, v=3) (f4, vR\1, 2, 3) (f0, )

Se asociază acestui sistem de perechi structura arborescentă din figura 19.7.

fi fj fk

f0

Vk=1

f1 Vk=2

f2 Vk=3

f3 f

Page 252: limbaj de asamblare-ivan

650

Figura 19.7 – Structura arborescentă de program cu funcţie cu condiţie simplă

Figura 19.8 – Structură arborescentă cu selecţia procedurilor de tip nod terminal

Programul apelator pentru structura dată în figura 19.8, f0, se structurează

astfel: start:

mov ax, @data mov ds, ax cmp v1, 1

jz salt_11 cmp v2, 1

f0 V1=1

f1 f2 f3 f4 f5 f6 f7 f8

V2=1 V2=1

V3=1 V4=1 V3=1 V4=1

Page 253: limbaj de asamblare-ivan

651

jz salt_21 cmp v3, 1

jz salt_23 call f1

jmp final salt_23:

call f2 jmp final

salt_21: cmp v4, 1

jz salt_31 call f3

jmp final salt_31:

call f4 jmp final

salt_11: cmp v2, 1

jz salt_22 cmp v3, 1

jz salt_32 call f5

jmp final salt_32:

call f6 jmp final

salt_22: cmp v4, 1 jz salt_3y

call f7 jmp final

salt_3y: call f8

final: mov ax, 4c00h int 21h end start

pentru a evita scrierea secvenţei:

mov ax, 4c00h int 21h

după fiecare apel de forma: call fi ;i=1, 2, …, 8 s-a folosit instrucţiunea: jmp final

Page 254: limbaj de asamblare-ivan

652

Figura 19.9 – Structura arborescentă cu încheierea execuţiei într-un singur punct

În cazul în care selecţia procedurilor se efectuează după o variabilă ale cărei valori sunt a1, a2, …, ak, structura arborescentă are graful asociat prezentat în figura 19.10.

Figura 19.10 – Structura arborescentă de selecţie după o singură variabilă de control, cu toate procedurile de tip

terminal Programul asociat este:

... cmp v, a1 jz salt_1

cmp v, a2 jz salt_2

f1 f2 f3 f4 f5 f6 f7 f8

final: mov ax, 4c00h

int 21h

f0

V=a1

f1

Vk=a2

f2

Vk=a3

f3 fn fn+1

Vk=an

Page 255: limbaj de asamblare-ivan

653

cmp v, a3 jz salt_3

... cmp v, an jz salt_n call fn1; fn1=fn+1 mov ax, 4c00h int 21h

salt_1: call f1 mov ax, 4c00h int 21h

salt_2: call f2 mov ax, 4c00h int 21h

... salt_n:

call fn mov ax, 4c00h int 21h

f1 proc ...

ret endp f2 proc

... ret endp

... fn proc

... ret endp fn1 proc

... ret endp end nume_program

Program calcul salarii A - creare bază de date B - calcul salarii C - actualizare D - afişare rezultat E

- Creare bază de date B - după fişiere existente B1 - cu introducere de la tastatură B2

Page 256: limbaj de asamblare-ivan

654

- prin scanare B3 - concatenare de fişiere B4

- Calcul salarii C - pentru un salariat C1 - pentru salariaţii unui compartiment C2 - pentru toţi salariaţii C3

- Actualizare D - modificare salariu brut D1 - modificare impozit D2 - modificare vechime D3 - modificare volum produse D4

- Afişare rezultate E - pentru un salariat E1 - pentru toţi salariaţii E2 - afişare nume şi salariu net E3 - afişare toate datele E4

Figura 19.11 – Meniuri pentru programe de calcul salarii

Structurile arborescente organizate pe mai multe niveluri corespund

implementării interfeţelor cu regăsire a prelucrărilor din aproape în aproape. De exemplu, se consideră meniurile din figura 19.11. Pentru realizarea acestui meniu se dezvoltă structura arborescentă din figura 19.12 organizată pe trei niveluri. Selectarea se efectuează cu ajutorul unui mouse sau prin tastarea literelor A, B, C, D, E respectiv B1, …, B4; C1, C2, C3; D1, …, D4; E1, …E4 depinzând de nivelul pe care se efectuează selecţia.

A

B C D E

B1 B2 B3 B4 C1 C2 C3 D1 D2 D2 D4 E1 E2 E3 E4

Page 257: limbaj de asamblare-ivan

655

Figura 19.12 – Structura arborescentă organizată pe trei

niveluri

Structura de tip arborescent se caracterizează prin: apelul unic al unei funcţii (proceduri) în timpul lansării în

execuţie a programului; dispunerea condiţiilor şi funcţiilor (procedurilor) pe

niveluri; există un singur sens de apelare a funcţiilor

(procedurilor) de pe procedura părinte (de pe un nivel superior) către procedura descendentă (de pe nivel inferior) de pe nivelul imediat următor;

programul are un singur program apelator; funcţiile (procedurile) nu apelează la rândul lor alte

proceduri. Implementarea structurilor alternative multiple (GOTO

depending on–COBOL, GOTO calculat – Fortran, switch – C/C++) permite dezvoltarea de structuri arborescente de program complexe faţă de structurile binare bazate pe structura IF – THEN – ELSE, figura 19.13.

f0

f1 f2 f3 f4 f5 f6 f7 f8 f9 f10 f11

C1(1, 2, 3, 4)

1 2 3 4

f12

C2(1, 2) C3(1, 2, 3, 4)

C5(1, 2, 3, 4, 5)

C4(1)

Page 258: limbaj de asamblare-ivan

656

Figura 19.13 – Structuri arborescente oarecare

Algoritmii de transformare a structurilor arborescente oarecare, în structuri arborescente binare evidenţiază natura binară a structurilor GOTO depending on, GOTO calculat sau a structurii switch sau CASE ON prin care se implementează ca secvenţe în modulele obiect rezultat al procesului de compilare. Structurii din figura 19.13 îi corespunde structura binară din figura 19.14.

f0

C1=1

C1=2

C1=3

C2=1 C3=1

C3=2

C3=3

C5=1

C5=2

C5=3

C5=4

f1 f2

f3

f4

f5 f6

f7

f8

f9

f10

f11 f12

Page 259: limbaj de asamblare-ivan

657

Figura 19.14 – Structura binară asociată unei structuri oarecare de program

19.7 Structuri de tip reţea Se consideră funcţiile f0 f1 f2 …fn de prelucrare, din care f0 este nodul iniţial şi fn+1 este nodul final.

Succesiunea de apelare a procedurilor este dată de rezultatul testelor. Se stabileşte prin convenţie ca rezultatul adevărat (1) al condiţiei c să fie notat cu D, iar rezultatul fals (0) să fie notat cu N, figura 9.1:

Figura 19.15 – Rezultatul testării condiţiei c

Succesiunea de apel a procedurilor nu este restrictivă. O

procedură apelează la rândul ei proceduri care au mai fost apelate. De asemenea, apelul unei proceduri este realizat din mai multe locuri. În figura 19.16 este prezentată o structură de tip reţea:

fk

C=1

fj fi D N

f0

C1

N D f1 f2

C2

N D

C3

N D f3

C1:= C1+

f4

f5

f6

Page 260: limbaj de asamblare-ivan

658

Figura 19.16 – Structură de program tip reţea

Secvenţele de proceduri apelate corespunzător structurii de tip reţea dată în figura 19.16 sunt:

f0 (f1 f3) (f1 f3)… (f1 f3) f1 f4 f5 f7 f0 f1 f4 f5 f7 f0 (f1 f3) (f1 f3)… (f1 f3) f2 f5 f7 f0 (f1 f3) (f1 f3)… (f1 f3) f2 f6 f7 f0 (f1 f3) (f1 f3)… (f1 f3) f4 f5 f7 f0 f2 f5 f7 f0 f2 f6 f7 În cazul în care se dezvoltă aplicaţii de tip ‘serial încrucişat’

(figura 19.17), se obţin, de asemenea, structuri de tip reţea.

Figura 19.17 – Structură de tip serial încrucişat

Se execută apelurile de proceduri: f1 f2 f3 f4 f5

f1 f2 f3 f4 f5

q1 q2 q3 q4 q5

Page 261: limbaj de asamblare-ivan

659

f1 f2 q3 q4 q5 q1 q2 q3 q4 q5 q1 q2 f3 f4 f5 Mai nou, se construiesc structuri de tip reţea cu reluarea

prelucrărilor de la toate punctele de prelucrare, figura 19.18:

Figura 19.18 – Structură de tip reţea cu reluarea într-un punct (f1)

Se utilizează operatorul ()k pentru a specifica repetarea de

k ori a secvenţei de apeluri a procedurilor incluse în paranteze. Se execută următoare succesiuni de referiri ale

procedurilor: f0 (f1)k f2 … fn f0 (f1f2)k f3 … fn f0 (f1f2f3)k f4 … fn f0 (f1…fn-1)k fn Acestea sunt considerate repetări simple. De asemenea

se execută succesiuni de repetări compuse, precum: f0 (…(f1 f2 … fj)kr…)kr+1fn

De exemplu, secvenţa de repetări: f0 (f1 f2 f3 (f1 f2 f3 f4 (f1 f2 )2 f3 f3 f5) f6 f7)3 … fn se dezvoltă:

f0 f1 C=1 f2 f3 fn C=1 C=1

Page 262: limbaj de asamblare-ivan

660

f0 f1 f2 f3 f1 f2 f3 f4 f1 f2 f1 f2 f3 f4 f5

Cu cât structura produsului este mai mare, cu atât se impune găsirea unor modalităţi de simplificare a reprezentării interacţiunilor dintre proceduri. 19.8 Concluzii

La soluţionarea unei probleme se va alege acea structură care generează numărul cel mai mic de apeluri si de reveniri în programele apelatoare. Cunoaşterea celor mai importante tipuri de structuri de programe creează premisele comparării între structuri cu proprietăţi care nu diferă semnificativ din punct de vedere al performanţelor în vederea luării deciziei care să propage la un număr cât mai mare de utilizatori efecte pozitive.

20

OPTIMIZAREA PROGRAMELOR Optimizarea programelor reprezintă una din direcţiile spre care se concentrează realizatorii de software. Există numeroase aspecte ale optimizării programelor: optimizarea timpului de execuţie, optimizarea dimensiunii operanzilor, optimizarea textului sursă etc. Toate aceste criterii de optimizare, considerate cu diferite ponderi, contribuie la definirea conceptului de optimizare a unui program. Compilatoarele pentru limbajele evoluate (C, C++, Pascal etc) conţin facilităţi de generare de cod executabil optimizat în timp de execuţie, în dimensiune a codului, sau combinaţii între acestea. Datorită specificaţiilor de limbaj, la nivel de asamblare nu există asemenea facilităţi încorporate în asambloare, de aceea aspectele de optimizare revin în exclusivitate programatorului. În materialul de faţă s-a tratat problema optimizării timpului de execuţie, care survine uneori în defavoarea dimensiunii codului. S-a considerat această abordare deoarece la arhitecturile actuale, memoria nu mai este, în general, o resursă critică. Resursa critică devine timpul, mai ales la aplicaţiile de prelucrare în timp real, la prelucrări grafice, la jocuri etc.

Page 263: limbaj de asamblare-ivan

661

20.1 Criterii de optim Se consideră un număr de programe destinate rezolvării aceleiaşi probleme. Dintre toate, unul singur conduce la obţinerea rezultatului în timpul cel mai scurt. În raport cu criteriul timpul de rulare, acest program este optim, raportat la mulţimea de programe disponibile la un moment dat. Întrucât nu se poate vorbi de program optim în general, în continuare, prin program optim se înţelege acel program care are comportamentul cel mai bun, chiar şi numai statistic, pe baza unui eşantion de probleme rezolvate. Introducând sau eliminând programe sau seturi de probleme de test, este posibil ca programul optim să fie de fiecare dată altul. Criteriile după care se ierarhizează programele sunt:

durata de timp necesară rezolvării unei probleme; dimensiunea problemei de rezolvat; necesarul de memorie internă; precizia rezultatelor; nivelul de generalitate al problemei ce poate fi acceptată; costul rulării programului; nivelul unei caracteristici de calitate; lungimea textului programului.

Criteriile sunt contradictorii, ceea ce determină ca la obţinerea unor avantaje din punct de vedere ale unui criteriu să se estimeze care sunt efectele negative antrenate pentru celelalte. Programele scrise în limbajul de asamblare au particularităţi impuse de faptul că programatorul gestionează toate resursele şi are posibilitatea să aleagă dintre mai multe variante pentru orice construcţie pe care o realizează. Toate criteriile sunt importante şi ideal este să fie construit programul care le îndeplineşte pe toate. Se observă că lungimea textului, dimensiunea problemei, precizia rezultatelor, calitatea şi necesarul de memorie sunt strâns legate de durata execuţiei. Estimarea duratei de rulare a unui program scris într-un limbaj evoluat este dificilă. Optimizarea timpului de execuţie al unui program scris într-un astfel de limbaj ţine de caracteristicile limbajului şi ale compilatorului. De asemenea, compilatoarele evoluate suportă opţiuni pentru optimizarea dimensiunii codului sau a duratei de execuţie, dar aceste optimizări, sunt, deocamdată, relative, deoarece se fac la nivel local şi nu ţin cont de evoluţia în perspectivă a programului sau de stările prin care s-a trecut anterior. Dacă în cazul limbajelor evoluate complexitatea instrucţiunilor face dificilă estimarea duratei, pentru limbajele de asamblare durata necesară execuţiei unei instrucţiuni este dată cu precizie prin numărul de cicluri maşină.

Page 264: limbaj de asamblare-ivan

662

Dacă programatorul face opţiuni din aproape în aproape asupra instrucţiunilor selectate, căutând să reducă numărul de cicluri, va obţine un program bun în raport cu durata de execuţie. Optimalitatea programului trebuie demonstrată prin compararea cu alte programe sau chiar cu alte variante de secvenţe destinate respectivului program şi este privită ca îmbunătăţire. Optimizarea timpului de execuţie se pretează la sistemele cu capacitate mare de memorie care necesită prelucrarea unei cantităţi de informaţie foarte mare în timp critic (de exemplu, afişarea unor imagini dinamice pe un display grafic de rezoluţie mare). În cazul unor sisteme cu resursa de memorie de cod redusă şi care procesează un volum de date mai modest, se pune problema optimizării dimensiunii codului (de exemplu, procesoare pentru urmărirea unor instalaţii cu parametrii lent variabili în timp). În continuare se analizează modalităţi de optimizare a timpului de execuţie. 20.2 Cicluri maşină În general, mecanismele au o componentă principală care dezvoltă o operaţie de bază, esenţială pentru funcţia pentru care au fost realizate. Strungul are un ax a cărui rotaţie determină antrenarea celorlalte subansamble. Operaţia de bază este rotaţia completă. În cazul altor maşini, operaţia de bază este efectuarea completă a unei deplasări dute-vino. Ciclul maşină este fie o rotaţie completă, fie o mişcare rectilinie dus-întors. Calculatoarele sunt înzestrate cu circuite de tact care definesc coordonatele în timp ale operaţiilor ce se efectuează. Astfel, un calculator are un ciclu maşină de durata dată prin împărţirea unei microsecunde la numărul de MHz ai ceasului său. Un calculator 80286/10 MHz are un ciclu de 100 nanosecunde; un calculator 80286/5 MHz are un ciclu de 200 nanosecunde; un calculator 80486/50MHz are un ciclu de 20 nanosecunde. Când se proiectează un limbaj de asamblare se stabilesc implementările fizice ale instrucţiunilor, precizându-se cu exactitate numărul de cicluri necesare efectuării lor. Astfel, pentru procesoare 8088, când operanzii sunt specificaţi, numărul de cicluri este fix. De exemplu, instrucţiunea:

mov ax, 100

se execută în 4 cicluri, iar instrucţiunea:

mov ax, cx

se execută în 2 cicluri.

Page 265: limbaj de asamblare-ivan

663

Când operanzii sunt variabile a căror poziţie se calculează prin evaluarea unei expresii, iar poziţia lor efectivă este segmentul precizat sau adresa este un număr impar deşi se lucrează la nivel de cuvânt, numărul de cicluri se majorează. Instrucţiunea

mov ax, SUMA

se execută în 8+6 cicluri, expresia deplasamentului necesită pentru calculul adresei 6 cicluri. Când adresarea operandului este indirectă folosind fie registrul index, fie registrul de bază, sunt necesare încă 5 cicluri. Astfel, instrucţiunea

mov ax, [bx]

se execută în 8+5 cicluri. Dacă expresia pentru adresarea operandului conţine deplasare şi registru index sau registru de bază, sunt necesari încă 9 cicluri: instrucţiunea

mov ax, VECTOR[bx]

se execută în 8+9 cicluri. Expresia de calcul a adresei în care apar registrul de bază şi registrul index necesită încă 7 cicluri, iar expresia care determină adresa pe bază de deplasare, registru index şi registru de bază necesită încă 11 cicluri. Instrucţiunile :

mov ax, bx[si] mov ax, MATRICE[bx][di]

necesită 8+7, respectiv, 8+11 cicluri. Când sunt definite instrucţiunile, se specifică toţi factorii ce influenţează numărul de cicluri. La prezentarea instrucţiunilor, în tabele există o coloană destinată numărului de cicluri şi a variabilităţii. Dacă se consideră factorii f1,f2,...,fk ce influenţează numărul de cicluri pentru operaţii, după ce se determină exact contribuţia fiecărui factor, stabilind coeficienţii c1, c2, ..., ck, modelul de calcul al numărului de cicluri pentru instrucţiunea Ij este dat de ecuaţia:

NCI c Sc xj j i i 0 unde : c0j - numărul de cicluri fixat pentru instrucţiunea Ij; xi - variabila booleană cu valoarea 1 dacă factorul fi este prezent, şi cu valoarea 0 în caz contrar. Dacă un factor vizează încărcarea de segment, coeficientul său asociat este cu valoare 2. Dacă factorul de aliniere a adresei la nivel de cuvânt este prezent,

Page 266: limbaj de asamblare-ivan

664

când operandul are adresa efectivă un număr impar, "forţarea" la baitul următor cu adresa număr par presupune adăugarea a 4 cicluri. Instrucţiunea

mov SUMA, ax se execută în 20 cicluri dacă 8 cicluri se datorează faptului că un operand este în registrul AX iar celalalt este în memorie, 6 cicluri rezultă din calculul adresei de 16 biţi ai deplasamentului, 2 cicluri provin din încărcarea segmentului unde se află variabila SUMA, aşa cum rezultă din definirea ei şi din punerea în corespondenţă a segmentelor cu registrele de segment în directiva ASSUME şi 4 cicluri sunt necesare pentru a forţa încărcarea unei adrese pare, pentru că variabila SUMA este definită la o adresă impară. Numărul de cicluri este influenţat de distanţa la care se face saltul necondiţionat, dacă se lucrează în mod protejat sau nu, cât de lungă este zona de memorie care se transferă, dacă se lucrează în cadrul aceluiaşi segment sau între segmente diferite. Rezultatele pot fi scurte, în apropiere sau la distanţă. Modelul de calcul al numărului de cicluri se obţine din tabelele de descriere a instrucţiunilor cât şi din prezentarea fiecărei instrucţiuni în parte. Menţinând clasificarea instrucţiunilor după numărul de cicluri, se preferă acele instrucţiuni care nu depind de amplasamentul operanzilor sau se preferă acele moduri de adresare care generează sistematic un număr redus de cicluri. Instrucţiunea de salt necondiţionat din secvenţa:

.................. alfa: mov ax, 5 .................. jmp alfa

având distanţa de tip scurt necesită 1 ciclu, în timp ce un salt necondiţionat intersegmente, folosind adresarea indirectă ca în instrucţiunea:

jmp beta [bx][di] necesită 16 + 7 + 2 cicluri (c0j = 16, c1 = 7, c2 = 2). În documentaţii se fac referiri detaliate asupra expresiilor de adresă care intervin în calculul ciclurilor ce se asociază fiecărei variabile de instrucţiune. Este important să se cunoască efectele contradictorii ale opţiunilor la scrierea de programe în limbaj de asamblare. De regulă se obişnuieşte obţinerea unui text compact prin definirea şi apelarea de proceduri. Se minimizează lungimea textului dar fiecare apel de procedură înseamnă o instrucţiune CALL. Dacă procedura este referită prin adresa în cadrul aceluiaşi segment, numărul de cicluri necesar execuţiei este 11. Dacă se lucrează în cadrul aceluiaşi segment dar adresele sunt pe 4 baiţi, pentru un apel de procedură sunt necesare 26 cicluri. Gestionarea

Page 267: limbaj de asamblare-ivan

665

prin apeluri de proceduri a task-urilor sau lucrul în mod privilegiat conduce la un necesar de cicluri de 177, respectiv, 90+4*x cicluri (x reprezintă numărul de parametri). Orice procedură presupune revenirea în funcţia apelatoare folosind instrucţiunea RET, care consumă între 11 şi 55 cicluri, de asemenea, depinzând de tipul formulei de calcul a adresei instrucţiunii ce urmează lui CALL. Dacă această adresă este stocată cu instrucţiunea POP/PUSH înainte de apel şi procedura este în acelaşi segment în care se află funcţia apelatoare, sunt necesare 11 cicluri. Dacă tipul de pointer este FAR sunt necesari 55 cicluri. Deşi lucrând cu proceduri s-a obţinut reducerea lungimii textului, numărul de cicluri adăugat este de cel puţin 7 + 11 sau de cel mult 185 + 55 cicluri. 20.3 Volumul de operaţii Un program scris în limbaj de asamblare conţine linii sursă care la asamblare generează instrucţiuni executabile (maşină) şi linii sursă ce definesc contextul de alocare resurse şi de iniţializare. Volumul de operaţii se referă la instrucţiunile executabile. Procedura:

aduna PROC push bp mov bp,sp mov ax,[bp+4] add ax,[bp+6] add ax,[bp+8] pop bp ret aduna ENDP

conţine şapte instrucţiuni executabile, fiecare se execută o singură dată. Volumul de operaţii, ca număr de instrucţiuni ce se execută, în acest caz este chiar 7. Procedura:

generare PROC FAR push bp mov bp,sp push cx push bx mov cx,[bp+6] mov bx,[bp+8] mov ax,0 ciclu: add al,[bx] adc ah,0 inc bx loop ciclu pop cx

Page 268: limbaj de asamblare-ivan

666

pop bx pop bp ret generare ENDP

conţine 14 instrucţiuni executabile, dintre care:

ciclu: add al,[bx]

adc ah,0 inc bx loop ciclu

se execută de un număr de ori dependent de conţinutul registrului CX. Presupunând că registrul CX conţine o valoare notată generic n, volumul de operaţii executate la apelul procedurii generare va fi dat de relaţia:

V k n k n kp p 0 1 1 ... unde: k0 -numărul instrucţiunilor executate o singură dată; ni -numărul de repetări ale buclei de program i; ki -numărul de instrucţiuni cuprinse în bucla de program i. Valoarea sa este V=11+4*n. Există proceduri în care apar comparaţii şi se efectuează selecţii ale secvenţelor. Se notează Pj probabilitatea ca o condiţie Cj să fie îndeplinită, caz în care se execută o secvenţă având volumul Vt. Numărul repetărilor testului pentru condiţia Cj este nj. Volumul operaţiilor este dat de relaţia:

V P V P V nj f j t j 1 .

Volumul de operaţii apare ca un număr mediu de operaţii care se vor executa în timp. Procedura:

trans PROC NEAR mov al,[si] cmp al,0 jz final cmp al,'a' jb urmat cmp al,'z' ja urmat and al,NOT 20h mov [si],al urmat:

Page 269: limbaj de asamblare-ivan

667

inc si jmp trans final:

ret trans ENDP

converteşte un şir de lungime M caractere, terminat cu zero, într-un şir format numai din litere mari. Volumul de operaţii depinde de structura şirului de caractere. În structura textelor din limba română 6% dintre caractere sunt litere mari. Procedura trans va efectua transformarea and al,NOT 20h în 94% din cazuri. La stânga literelor mici se află 3,3 % dintre caracterele unui text, iar la dreapta restul de 96,7 %, aşa cum rezultă din observaţii parţiale. Pentru textul de lungime M caractere, delimitat prin zero, instrucţiunile procedurii trans se execută în medie de un număr de ori indicat în tabelul 20.1.

Tabelul 20.1. Instrucţiunea Număr de repetări mediu Mov al,[si] M Cmp al,0 M jz final M Cmp al,’a’ M jb urmat 0.033*M Cmp al,’z’ 0.967*M ja urmat 0.967*M And al,NOT 20h 0.96*M Mov [si],al 0.96*M Inc si M Jmp trans M Ret 1

Volumul mediu de operaţii executabile la un apel al procedurii trans este

V = M*(6+0.967+0.967+0.96+0.96+0.033)+1

operaţii. Conceptul de operaţie este general şi se observă de la început că posibilitatea de a compara operaţiile este dificilă datorită diferenţei de complexitate

Page 270: limbaj de asamblare-ivan

668

pe care fiecare operaţie o induce. Un transfer de date, intuitiv este mai simplu decât o înmulţire, iar apelul unei proceduri este mai complex decât o implementare. Se acceptă ipoteza conform căreia complexitatea operaţiilor este strâns legată de numărul de cicluri maşină asociate. Diversităţii de instrucţiuni îi corespunde o multitudine de numere de cicluri. Mai mult, tipurile de adresare modifică numărul de cicluri pentru fiecare instrucţiune. Instrucţiunile care manipulează un volum redus de informaţie sau au operanzi prefixati sunt puse în corespondenţă cu un număr redus de cicluri. Instrucţiunile care au adrese ce sunt calculate după formule complexe, care impun regăsiri, necesită un număr de cicluri maşină superior. Pentru a reflecta mai exact efortul de execuţie, volumul programului se va exprima ca număr de cicluri maşină. Astfel, secvenţa:

....................... mov ds,ax ; 2 cicluri masina xor ax,ax ; 3 cicluri masina inc ax ; 2 cicluri masina cmp ax,20 ; 3 cicluri masina ...............................

are un volum V = 10 cicluri, rezultat ca sumă a ciclurilor asociate instrucţiunilor care o alcătuiesc. Pentru procedura:

strlen PROC NEAR cld ; 2 cicluri push cx ; 10 cicluri mov cx,0ffffh ; 4 cicluri mov al,0 ; 4 cicluri repne scasb ; n*(6+15) cicluri jne eroare ; 8 cicluri pentru salt, 4 cicluri fara salt mov ax,0ffffh ; 4 cicluri sub ax,cx ; 3 cicluri dec ax ; 2 cicluri dec di ; 2 cicluri dec di ; 2 cicluri jmp SHORT final ; 1 ciclu eroare:

mov di,0 ; 4 cicluri mov es,di ; 2 cicluri final:

pop cx ; 8 cicluri ret ; 8 cicluri strlen ENDP

volumul de operaţii exprimat în cicluri este V n1 50 21

Page 271: limbaj de asamblare-ivan

669

în caz de eroare sau V n2 24 21 30

cicluri. 20.4 Secvenţe echivalente Dacă se urmăreşte optimizarea programelor scrise în limbaj de asamblare reducând volumul operaţiilor, în primul rând se caută folosirea de secvenţe echivalente care se execută într-un număr mai redus de cicluri. Iniţializarea unui registru se realizează în moduri diferite. Din secvenţa:

mov ax,0 ; 4 cicluri sub ax,ax ; 3 cicluri xor ax,ax ; 3 cicluri

rezultă necesitatea de a utiliza una din ultimele două variante, deşi prima instrucţiune este mai sugestivă. La tipul de adresare indexat este necesară incrementarea unui registru cu o raţie egală cu lungimea zonei de memorie care este referită. Dacă raţia este o unitate, din secvenţa:

add si,1 ; 4 cicluri inc si ; 2 cicluri

rezultă că este avantajoasă utilizarea instrucţiunii inc si, efectul fiind major mai ales pentru faptul că referirea este proprie unei secvenţe executate repetitiv. Dacă raţia cu care se modifică registrul index este un număr oarecare, repetarea instrucţiunii inc registru este ineficientă. Se optează spre una din variantele din secvenţa:

add si,57 ; ratia este 57, 4 cicluri add si,bx ; 3 cicluri, registrul bx a fost initializat mov bx,57 add si,ratia ; 16+6 cicluri

De cele mai multe ori nu este posibilă alocarea unui registru pentru memorarea raţiei şi se defineşte o constantă simbolică (RATIA EQU 57) după care incrementarea este realizată prin add si,RATIA. Lucrurile devin mult mai simple dacă se pune problema alegerii modalităţii de a înmulţi un număr cu 2k sau de a-l împărţi prin 2k. Pentru înmulţirea numărului 79 aflat în registrul AX cu 32 se alege secvenţa:

mov cl,5 mov ax,79

Page 272: limbaj de asamblare-ivan

670

sal ax,cl ; 8+4*5 cicluri

întrucât secvenţa:

mov bl,32 mov al,79 cbw b ; 2 cicluri mul bl ; 71 cicluri

necesită mai multe cicluri. În cazul în care o procedură este apelată de un număr mare de ori, este preferabil să se includă textul ei în locul instrucţiunii call. Chiar dacă lungimea textului sursă creşte, se obţine o reducere a numărului de cicluri generate de fiecare apel şi de fiecare revenire în secvenţa apelatoare. 20.5 Alegerea tipului de dată În limbajele evoluate lucrul cu diferite tipuri de date este netransparent, programatorul neavând la dispoziţie resursele antrenate. În programul scris în limbaj de asamblare lucrul cu date codificate binar înseamnă a defini variabile la nivel de bait sau pe cuvânt. Odată definită o variabilă la nivel de bait, se vor utiliza registrele AL, BL, CL, DL, AH, BH, CH, DH. Lucrul la nivel de cuvânt înseamnă lucrul cu registrele AX, BX, CX, DX. În ambele cazuri se are în vedere testarea indicatorului de condiţie CF pentru a gestiona corect rezultatele. Se pot folosi instrucţiunile setului, ca şi cum limbajul de asamblare este proiectat preponderent pentru a lucra cu numere codificate binar. Dacă se doreşte să se lucreze în aritmetica zecimală, mai întâi se construiesc toate procedurile care operează în această aritmetică: transferuri între operanzi, adunări, scăderi, înmulţiri, împărţiri, deplasări, alinieri. Procedurile vor fi cu un grad ridicat de generalitate. Evaluarea unei expresii va consta mai întâi în demontarea ei în paşi elementari, se pregătesc parametrii şi se vor apela funcţiile pentru efectuarea operaţiilor. Programatorul va gestiona şi rezultatele elementare şi pe cele intermediare. Dacă se lucrează în virgulă mobilă, fiecare limbaj de asamblare admite un set de instrucţiuni specifice acestui tip de date şi chiar registre specializate. În procesul de optimizare a programelor scrise în limbaj de asamblare, alegerea tipului de date este esenţială pentru efortul de programare în primul rând. Lucrul cu date de tip real presupune utilizarea resurselor unui coprocesor, iar aritmetica zecimală e folosită când operanzii au un număr foarte mare de cifre şi sunt întregi.

Page 273: limbaj de asamblare-ivan

671

Neomogenitatea operanzilor este aproape exclusă în programele scrise în limbaje de asamblare. Dacă un operand este definit pe un bait şi participă la evaluarea unei expresii în care un rezultat intermediar este în registrul AX, la nivel de cuvânt, este necesară o conversie de la bait la cuvânt pe care o asigură programatorul. În programele scrise în limbaj de asamblare construcţiile implicite sunt foarte rare. Secvenţa:

mov bx,ax mov al,operand_bait cbw add ax,bx

ilustrează faptul că programatorul gestionează şi rezultatele intermediare. Problema devine mai complicată când un operand este întreg iar altul este zecimal împachetat. Mai întâi se ia decizia cum se va lucra în continuare. Dacă se va continua lucrul în zecimal împachetat, operandul binar va fi convertit apelând la o procedură la forma de reprezentare zecimal împachetată. În continuare se vor apela proceduri de lucru pentru zecimal împachetat. Dacă opţiunea este de a lucra în binar, operandul zecimal împachetat va fi convertit în binar şi se vor folosi în continuare instrucţiunile setului definit pentru limbajul de asamblare considerat. În cazul în care este necesar să se lucreze în virgulă mobilă cei doi operanzi vor fi convertiţi folosind proceduri speciale şi cu instrucţiunile de virgulă mobilă se va continua lucrul. Lucrul cu operanzi neomogeni impune existenţa unei multitudini de proceduri de conversie care să acopere totalitatea cerinţelor de omogenizare a tipurilor. După omogenizare, programatorul va folosi numai instrucţiunile sau procedurile specifice tipului pentru care s-a hotărât să continue lucrul. Optimizarea programului este obţinută în această fază prin numărul de proceduri de conversie ce se apelează şi prin procedurile de operaţii. Un programator cu experienţă va şti când să nu folosească aritmetica binară, problematica alegerii tipului cel mai potrivit fiind destul de rară. Programatorul îşi defineşte din start operanzi omogeni şi exclude efectuarea de conversii tocmai pentru că neomogenitatea este transparentă în programele scrise în limbaj de asamblare prin creşterea lungimii datorate secvenţelor suplimentare specifice omogenizării. Chiar dacă procedurile de conversie sunt rezultatul unui proces de optimizare, adăugarea lor la un program determină creşterea volumului de operaţii. 20.6 Eliminarea subexpresiilor comune

Page 274: limbaj de asamblare-ivan

672

Limbajele evoluate au tendinţa de a reda forma algebrică a expresiilor. De aceea programatorul îşi pune distinct problema eliminării subexpresiilor comune. Expresia:

E = (a+b+c) * (a+b+c-d) + (c2+a+b+c) / (a+b+c)

va fi scrisă de cele mai multe ori direct cum apare şi în rare cazuri se va calcula E1=a+b+c după care se va calcula:

E = E1 * (E1-d) + (c2+E1) / E1.

Când se scrie programul în limbaj de asamblare, demontarea expresiei şi reaşezarea rezultatelor intermediare pentru a respecta priorităţile operatorilor îl determină pe programator să urmărească simultan şi modalităţi de a reduce lungimea secvenţei. Eliminarea subexpresiilor comune apare ca o necesitate firească pentru programator. Mai mult, el va căuta să gestioneze cu grijă rezultatele intermediare, lungimile zonelor de memorie asociate lor, pentru a nu deteriora omogenitatea operanzilor. Secvenţa:

mov ax,a ;5 add ax,b ;7 add ax,c ;7 mov E1,ax ;3 sub ax,d ;7 mul E1 ;24 mov prod1,ax ;3 mov prod1+2,dx ;3 mov ax,c ;5 mul c ;24 add ax,E1 ;7 adc dx,0 ;7 div E1 ;25 xor dx,dx ;2 add ax,prod1 ;7 adc dx,prod1+2 ;7 mov E,ax ;3 mov E+2,dx ;3 total 149 cicluri (80286)

ţine seama de ceea ce conţin registrele după efectuarea operaţiilor şi utilizează acest conţinut. Este puţin probabil ca programatorul să repete de patru ori secvenţa:

mov ax,a add ax,b add ax,c

Page 275: limbaj de asamblare-ivan

673

în programul său, fără ca cel puţin să se gândească la scrierea unei macrodefiniţii pentru a-şi uşura efortul de a scrie textul sursă. 20.7 Gestionarea corectă a invarianţilor În secvenţele care se execută repetitiv, din eroare sunt introduse iniţializări ale variabilelor care alterează rezultatele finale. Secvenţa:

mov cx,22 ciclu:

mov ax,0 mov si,0 add ax,x[si] inc si loop ciclu mov total,ax

va conduce nu la însumarea elementelor unui vector, ci la însumarea primei componente a vectorului, numai. Cele două iniţializări de registre, invarianţii din secvenţă, sunt scoşi în afară, obţinând variabilitatea cerută de orice structură repetitivă. Secvenţa:

mov ax,0 mov si,0 mov cx,22 ciclu:

add al,x[si] inc si loop ciclu

realizează corect obiectivul propus. 20.8 Regruparea ciclurilor În cazul secvenţelor repetitive în care rezultatele sunt independente, numărul de repetări este identic, secvenţele se regrupează reducând numărul operaţiilor induse de testul variabilei de control şi de modificarea acesteia. Secvenţa:

mov cx,37 ; 4 cicluri xor ax,ax ; 3 cicluri xor si,si ; 3 cicluri

ciclux: add al,x[si] ;18 cicluri * 37 inc si ; 2 cicluri * 37 loop ciclux ; 37 * 9 cicluri sau 5 cicluri

Page 276: limbaj de asamblare-ivan

674

mov suma,ax ; 14 cicluri mov cx,37 ; 4 cicluri xor ax,ax ; 3 cicluri xor si,si ; 3 cicluri

cicluy: add al,y[si] ; 18*37 cicluri inc si ; 2*37 cicluri loop cicluy ; 9*37 sau 5 cicluri mov sumy,ax ; 14 cicluri

se execută într-un volum V=10+29*37+24+10+29*37+14=2204 de cicluri. Secvenţa în care se regrupează ciclurile:

mov cx,37 ; 4 cicluri xor si,si ; 3 cicluri xor ax,ax ; 3 cicluri xor bx,bx ; 3 cicluri

ciclu: add al,x[si] ; 18*37 cicluri add bl,y[si] ; 18*37 cicluri inc si ; 2*37 cicluri loop ciclu ; 9*37 cicluri sa 5 cicluri mov sumax,ax ; 14 cicluri mov sumay,bx ; 14 cicluri

necesită un volum V=13+37*47+5+28=1785 de cicluri (8088). Dacă numărul de elemente ale unui şir este par se poate înjumătăţi numărul de repetări prin calculul a două sume (suma elementelor cu poziţie pară şi suma elementelor cu poziţie impară). La ieşirea din ciclu printr-o însumare se obţine rezultatul dorit. Secvenţa:

mov cx,2*N ;4 cicluri, N constanta simbolica xor ax,ax ; 3 cicluri xor si,si ; 3 cicluri

ciclu: add ax,x[si] ; 18*2*N cicluri inc si ; 2*2*N cicluri inc si ; 2*2*N cicluri loop ciclu ; 9*2*N cicluri sau 5 cicluri mov sum,ax ; 14 cicluri

Page 277: limbaj de asamblare-ivan

675

necesită un volum de operaţii V=29+2*N*31 cicluri.

Prin însumarea separată a elementelor cu poziţii pare, respectiv impare, secvenţa:

mov cx,N xor ax,ax xor bx,bx xor si,si

ciclu: add ax,x[si] add si,2 add bx,x[si] add si,2 loop ciclu add ax,bx

necesită un număr de operaţii V=13+2*N*26+N+8. Comparând cele două volume, rezultă o diferenţă D=8+9*N cicluri, ceea ce justifică o astfel de regrupare a termenilor din structurile de date omogene. 20.9 Eliminarea secvenţelor inconsistente Uneori în programe se introduc instrucţiuni care anulează efectele operaţiilor precedente. Programatorul va elimina acele instrucţiuni care nu corespund cerinţelor şi distrug rezultate create anterior. Eliminarea de instrucţiuni atrage reducerea volumului de operaţii. În secvenţa următoare:

mov bx,0 ;1 xor ax,ax ;2 add ax,2 ;3 mov bx,ax ;4

instrucţiunea 1 este inconsistentă, deoarece registrul BX este apoi modificat (instrucţiunea 4) fără ca valoarea stocată în el anterior să fie folosită. Pentru limbajele evoluate (C, Pascal) la compilare există posibilitatea ca utilizatorul să fie avertizat asupra variabilelor nefolosite. În asamblare o asemenea analiză la compilare este cvasi-imposibilă, de aceea programatorul trebuie să depisteze asemenea secvenţe încă din faza de scriere a codului sursă. 20.10 Eliminarea secvenţelor inactive (cod mort) Programele scrise în limbaje evoluate pot conţine secvenţe ce nu se activează indiferent de contextul în care se rulează programul. În programele scrise

Page 278: limbaj de asamblare-ivan

676

în limbaj de asamblare, pentru a reduce deplasarea operanzilor, la definire aceştia sunt incluşi în segmentul program, ca în secvenţa:

.CODE start:

jmp alfa x dw 10 y dw 20 z dw ? alfa:

mov ax,x add ax,b mov z,ax mov ah,4ch int 21h

END start

Astfel de instrucţiuni sunt frecvente şi corecte, poate chiar eficiente, şi de aceea este dificil a se identifica secvenţele care nu se activează niciodată în execuţie. Există şi situaţii când se construiesc teste în mod eronat, fără a asigura încheierea unui ciclu sau existenţa cel puţin a unei situaţii în care se traversează şi o altă ramură a structurii alternative. Expresiile booleene construite în limbajele evoluate pot conduce la evaluare la valori constante indiferent de variaţiile operanzilor. Complexitatea expresiilor şi manipularea eronată a operatorilor generează secvenţe numite cod mort, adică secvenţe ce nu se execută niciodată. În programele scrise în limbaj de asamblare astfel de construcţii apar ca incorecte relativ uşor, întrucât se identifică invariabilitatea operanzilor. Secvenţa:

mov ax,5 cmp ax,0 jz alfa .............. jmp beta

alfa: .........

beta: nop

este interpretată ca generatoare de cod mort dacă este inclusă chiar într-o structură repetitivă, pentru că atât timp cât ax va conţine 5 şi se va compara cu valoarea zero, secvenţa etichetată cu alfa nu se va executa. Volumul de operaţii nu este influenţat dacă se ia în considerare coeficientul zero al probabilităţii acestei secvenţe inactive.

Page 279: limbaj de asamblare-ivan

677

Codul mort afectează numai lungimea programului ca număr de baiţi ocupaţi de codul obiect asociat unui text sursă. 20.11 Reacoperirea segmentelor Segmentele se gestionează de către programator. Directivele de punere în corespondenţă a segmentelor cu registre de segmente, încărcarea adreselor de segment, sunt elemente la dispoziţia programatorului. Instrucţiunile de salt necondiţionat se diferenţiază după cum destinaţia este în acelaşi segment sau este în alt segment, adresarea fiind directă sau indirectă. Apelul de procedură din acelaşi segment are 7 sau 11 cicluri, în timp ce pentru proceduri din alte segmente numărul de cicluri poate fi 13 sau 26 cicluri. Optimizarea reacoperirii vizează programe complexe, care operează cu structuri de date ce necesită mai multe segmente de date care se încarcă alternativ. Pentru proceduri se va asocia o arborescenţă pentru a se identifica ce componente se află încărcate de pe fiecare ramură. 20.12 Alocarea optimă a regiştrilor Alocarea regiştrilor este o problemă de construire a compilatoarelor. Aceeaşi secvenţă se va utiliza în moduri diferite, obţinându-se de fiecare dată alt număr de cicluri maşină. Alocarea regiştrilor are ca obiectiv minimizarea numărului de cicluri. Se vor construi compilatoare care realizează alocări de registre şi tipuri de adresări care să conducă la atingerea acestui obiectiv. De exemplu, pentru evaluarea expresiei: e = a + b + c se construieşte secvenţa:

mov ax,a ;5 cicluri mov bx,b ;5 cicluri mov cx,c ;5 cicluri add ax,bx ;2 cicluri add ax,cx ;2 cicluri mov e,ax ;3 cicluri

căreia îi corespund 22 cicluri maşină. Secvenţa echivalentă:

mov ax,a ;5 cicluri add ax,b ;7 cicluri add ax,c ;7 cicluri mov e,ax ;3 cicluri

Page 280: limbaj de asamblare-ivan

678

conţine instrucţiuni care totalizează tot 22 cicluri. Problematica alocării este importantă pentru operanzii reutilizabili din expresii. Astfel, expresia: e = ( a + b - c ) * ( a - b + c ) se calculează în secvenţa:

xor dx,dx ;2 cicluri mov ax,a ;5 cicluri sub ax,b ;7 cicluri add ax,c ;7 cicluri mov bx,ax ;2 cicluri mov ax,a ;5 cicluri add ax,b ;7 cicluri sub ax,c ;7 cicluri mul bx ;21 cicluri

căreia îi corespund 63 cicluri. În secvenţa echivalentă:

xor dx,dx ;2 cicluri mov bx,b ;5 cicluri mov cx,c ;5 cicluri mov si,a ;5 cicluri mov ax,si ;2 cicluri sub ax,bx ;2 cicluri add ax,cx ;2 cicluri mov bx,ax ;2 cicluri mov ax,si ;2 cicluri add ax,bx ;2 cicluri sub ax,cx ;2 cicluri mul bx ;21 cicluri

se obţin 52 cicluri. Numărul de cicluri necesare pentru execuţia unei instrucţiuni cu operanzi din memorie este mai mare decât în cazul în care operanzii s-ar afla în regiştrii. Pentru reducerea timpului de execuţie, se va urmări păstrarea rezultatelor intermediare în regiştrii liberi, printr-o alocare optimă a acestora. Conform acestui principiu, secvenţa pentru calculul expresiei :

E=(a+b+c)*(a+b+c-d)+(c2+a+b+c)/(a+b+c)

devine, prin utilizarea regiştrilor BX, SI şi DI, următoarea:

mov ax,a ;5 cicluri add ax,b ;7 cicluri

Page 281: limbaj de asamblare-ivan

679

add ax,c ;7 cicluri mov bx,ax ;2 cicluri expresia E1 sub ax,d ;7 cicluri mul bx ;21 cicluri mov di,ax ;2 cicluri mov si,dx ;2 cicluri mov ax,c ;5 cicluri mul c ;24 cicluri add ax,bx ;2 cicluri adc dx,0 ;7 cicluri div bx ;14 cicluri xor dx,dx ;2 cicluri add ax,di ;2 cicluri adc dx,si ;2 cicluri mov E,ax ;3 cicluri mov E+2,dx ;3 cicluri total 117 cicluri

Se remarcă utilizarea regiştilor BX, SI şi DI pentru păstrarea unor rezultate intermediare. Este clar că după această secvenţă, valorile iniţiale din aceşti regiştri vor fi modificate. Dacă acestea sunt necesare pentru prelucrări ulterioare, se salveză, fie în stivă cu instrucţiunea push, fie în variabile din memorie. Utilizarea regiştrilor se face după o analiză a codului pe bază statistică, astfel încât costul salvării şi refacerii regiştrilor folosiţi să fie mai mic decât costul utilizării exclusiv a memoriei pentru salvarea rezultatelor intermediare. 20.13 Concluzii Particularităţile limbajelor de programare se regăsesc la optimizare. Se observă că optimizarea programelor scrise în limbaj de asamblare conţine acele elemente ce impun simplitate secvenţelor de program. Criteriile de optim, numeroase la celelalte limbaje de programare, se restrâng, atenţia fiind îndreptată spre minimizarea volumului de operaţii. Datorită faptului că programele scrise în limbaj de asamblare nu înlocuiesc aplicaţii scrise în limbaje de tip C, ci le presupun, dacă minimizează numărul de cicluri maşină, înseamnă că s-a realizat optimizare. Programatorul în limbaje de asamblare are multe restricţii de utilizare a registrelor, a instrucţiunilor. S-a făcut deosebire între optimizarea sistemelor de programe şi optimizarea pe textul sursă. De aceea, aplicaţiile în scrise în limbaj de asamblare sunt de regulă parţi ce se încorporează în construcţii mult mai complexe. Optimizarea va fi orientată spre viteza de calcul în principal; reducerea lungimii programului, creşterea intensităţii de utilizare a operanzilor trec pe un plan secundar. Microprocesoarele evoluate (ex. i386, 486, Pentium, etc) sunt proiectate astfel încât instrucţiunile sunt executate în pipeline. Aceasta presupune suprapunerea unor stări disjuncte din execuţia unor instrucţiuni. Astfel, după extragerea codului unei instrucţiuni şi trecerea la execuţia acesteia, se extrage în

Page 282: limbaj de asamblare-ivan

680

paralel codul instrucţiunii următoare. Aceste particularităţi ţin exclusiv de arhitectura microprocesorului pe care va rula programul. Se poate rafina optimizarea timpului de execuţie luând în calcul aceste particularităţi, dar cu referire strictă la un tip de procesor.

21

DESIGNUL LIMBAJELOR DE ASAMBLARE

21.1 Cerinţe ale designului Limbajele de asamblare apar ca rezultat al proiectării microprocesoarelor.

Limbajele evoluate de programare permit accesul la toate resursele sistemelor de calcul. Performanţa software este influenţată de modul în care a fost proiectat, de limbajul în care este elaborat şi de facilităţile oferite de limbajul de asamblare, la care se ajunge în final. Designul limbajelor de asamblare reprezintă un nou mod de abordare a construirii acestora în vederea ameliorării performanţei software aplicativ.

Limbajele evoluate de programare elimină suportul învăţării programării în limbaj de asamblare pentru a avea acces la toate resursele sistemelor de calcul. Utilizarea corectă a funcţiilor din bibliotecile standard din programele scrise în limbajele C++ sau PASCAL oferă programatorilor posibilităţile de acces la orice nivel al resurselor.

În acest nou context, studierea unui limbaj de asamblare se justifică pentru eficientizarea unor secvenţe de program. Există posibilitatea de a introduce direct în textele sursă C/C++ secvenţe asm.

O altă motivaţie este înţelegerea exactă a unor mecanisme de manipulare a informaţiei (lucru pe stivă, lucru cu variabile pointer, definirea şi utilizarea funcţiilor virtuale).

Dacă au existat perioade în care limbajul de asamblare a fost considerat un produs natural, uneori imperfect, al procesului de proiectare al procesoarelor, acum se pune şi problema de a realiza microprocesoare pornind de la un limbaj de asamblare dat. Limbajul de asamblare este definit aşa fel încât să asigure eficienţa, măsurată statistic, fie la implementarea unor mecanisme noi de gestiune a memoriei, fie în generarea de secvenţe compacte, fie în reducerea duratei de execuţie a prelucrărilor. În continuare sunt analizate aspecte de bază ale designului limbajelor de asamblare. Programele scrise într-un limbaj de asamblare proiectat cu luarea în considerare a anumitor criterii de performanţă, vor propaga efecte pozitive în toate fazele realizării codului sursă şi ale utilizării software-ului la beneficiari. Se

Page 283: limbaj de asamblare-ivan

681

pune în evidenţă legătura dintre caracteristicile de ordin cantitativ şi laturile calitative ale unul limbaj de asamblare şi efectele de antrenare multiplă induse. 21.2 Structura instrucţiunii limbajului de asamblare

Programatorul în limbajele de asamblare, înainte de orice, trebuie să cu-noască structura internă a instrucţiunilor, care diferă de la un limbaj la altul. Structura internă arată modul în care se dispun la nivel de biţi informaţiile privind: codul operaţiilor, etichetele şi expresiile de adresare ale operanzilor.

Şirul de biţi care memorează codul operaţiei are o lungime strict dependentă de numărul de operaţii de bază pe care le implementează limbajul de asamblare.

Astfel, dacă proiectantul limbajului de asamblare optează pentru un şir de opt biţi, limbajul de asamblare va fi înzestrat cu maxim 256 de mnemonice asociate unor instrucţiuni diferite. Numărul foarte mare de mnemonice disponibile permite definiri de instrucţiuni diferite pentru aceleaşi operaţii în cazul în care tipul operanzilor este altul. De exemplu, vor exista instrucţiuni distincte pentru efectuarea adunării în binar, în zecimal împachetat şi în virgulă mobilă. Programatorul ce utilizează limbajul de asamblare va alege aritmetica în care lucrează strict dependent de contextul problemei pe care o rezolvă.

Dacă proiectantul limbajului de asamblare defineşte codul operaţiei ca un şir de şapte biţi, numărul mnemonicelor cu care va manipula programatorul va fi de cel mult 127. Un astfel de limbaj este mai sărac, posibilităţile de alegere se reduc. Chiar dacă pot fi definiţi operanzi de tipuri diferite, implementarea aritmeticilor presupune apelarea funcţiilor care să prelucreze datele fiecărui tip folosind operaţii dintre cele 127 implementate în limbaj. Efortul de programare este mult mai ridicat.

Este posibil ca numărul de instrucţiuni N, să fie mai mic decât 2k unde k reprezintă lungimea şirului de biţi pe care este memorat codul instrucţiunilor. Gradul de ocupare, G este dat de relaţia:

N1 G = 2k *100

Gradul de neocupare Γ este dat de relaţia:

Γ = 100 – G

Page 284: limbaj de asamblare-ivan

682

Gradul de neocupare este cel care oferă posibilitatea designerilor să dezvolte un sistem de macrodefiniţii coerent sau să impună definirea de noi operaţii de bază ce conduc la reducerea efortului de programare.

Designul structural al limbajului de asamblare are la bază ipoteza conform căreia toate elementele limbajului au aceeaşi importanţă. Această viziune conduce la abordarea separată a componentelor instrucţiunii.

În designul structural resursele sunt privite de sine stătător. Operanzii sunt stocaţi în registre (R), în memorie (M) sau sunt definiţi în corpul instrucţiunii, imediat (I). Complexitatea limbajului de asamblare se modifică radical în cazul opţiunilor designerului pentru instrucţiunile cu doi operanzi.

Dacă se optează pentru întreaga gamă de structuri de operanzi, vor fi definite instrucţiuni de tip R-R, R-M, R-I, M-M, M-I. Structurile interne ale acestor tipuri vor ocupa zone de memorie de lungime variabilă.

În cazul în care există 16 registre de lucru codul operaţiei ocupă 8 biţi, o instrucţiune de tip R-R va fi definită pe cel puţin 16 biţi. Pentru operaţii R-R se asociază coduri distincte.

În cazul în care codul operaţiei este unic, nedepinzând de operanzi, sunt necesari încă 3 biţi, pentru a indica tipul (000 pentru tipul R-R, 001 pentru tipul R-M, 010 pentru tipul R-I etc.). Limbajele de asamblare ale generaţiilor de microprocesoare actuale evită tipul de instrucţiuni M-M, având definite mnemonice pentru lucrul cu şiruri de caractere, suficient de flexibile. Expresiile de adresare se evaluează în timpul asamblării, semnificaţiile fiind date de coduri memorate pe anumite poziţii din corpul structurii interne a instrucţiunii.

Folosind o zonă de 2 biţi se obţin toate combinaţiile ce corespund atributelor direct / indirect şi indexat / neindexat. Pentru tipologii identificate şi limitate ca număr de expresii de adresare se stabileşte o zonă care memorează coduri asociate care prin interpretare în timpul execuţiei permit utilizarea corectă a informaţiilor din zonele "operanzi" din structura internă a instrucţiunii. Codurile conduc la posibilitatea realizării unei variabilităţi a lungimii zonei de memorie ocupată de informaţiile despre operanzi.

Designul limbajului de asamblare pentru microprocesoarele 80x86 a condus la o structură de instrucţiune internă specifică tipurilor de instrucţiuni R-R, R-M, R-I, codul operaţiei are încorporate informaţii privind formatul datelor (bait, cuvânt, cuvânt dublu) şi direcţia de parcurgere (adresare sau traversare) a memoriei, figura 21.1.

T T T T T T d w M M reg r/m Biţii 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0

Figura 21.1 – Structura internă a instrucţiunii

unde: T - biţi pentru codul operaţiei;

Page 285: limbaj de asamblare-ivan

683

d - direcţie de parcurgere; w - tip operand (bait/cuvânt); MM - interpretarea deplasării; reg - registre (semiregistre) codificate; r/m - tipologii expresii de adresare. Limbajele de asamblare mai vechi, datorită numărului mare de registre şi a

aritmeticilor implementate, au regrupat expresiile de adresare pe un număr mai restrâns de biţi. Gradul de dependenţă a câmpurilor din structura instrucţiunilor era foarte slabă.

Limbajul de asamblare a microprocesorului convenţional x86 are definit un grad de dependentă ridicat între elementele din structura instrucţiunii. Astfel, deplasarea este interpretată funcţie de câmpul w (câmpul MM depinde de câmpul w), iar câmpul reg depinde ca interpretare tot de câmpul w. Câmpul r/m depinde de câmpul MM. Dacă valoarea MM este 11, câmpul r/m este interpretat reg.

21.3 Designul corectiv pentru limbajele de asamblare

Pentru limbaje precum Fortran, Cobol, PL/l în timp au fost efectuate analize statistice, toate evidenţiind neuniformitatea cu care programatorii utilizează instrucţiunile şi tipurile de date şi de structuri definite.

În mod normal, experienţa oferită de utilizarea facilităţilor trebuie să conducă la perfecţionarea limbajelor, aspect realizat prin trecerea de la FORTRAN IV Ia FORTRAN ‘77 şi acum deja există FORTRAN ‘95. Mai dinamic, limbajul C a înregistrat evoluţiile C++ şi VISUAL C++. Limbajele de asamblare sunt cele chemate să permită implementarea noilor mecanisme. Avantajele lucrului pe stivă a condus la implementarea de instrucţiuni (push, pop) absente la primele limbaje. Frecvenţa redusă de lucru în aritmetica zecimal împachetată a condus la excluderea din lista mnemonicelor a elementelor corespunzătoare operaţiilor acestei aritmetici.

Se presupune existenţa unui program, simplu de altfel, care citeşte fişiere cu texte sursă în limbaj de asamblare şi contorizează. instrucţiunile. Se observă clar că frecvenţa cu care apar instrucţiuni precum mov, add, inc, dec diferă radical de frecvenţa cu care apar instrucţiuni ca hlt, aaa, aad, das, lock, les, xchg. De asemenea, dacă alături de frecvenţele cu care sunt utilizate resursele (registre, zone de memorie) se vor putea defini asocieri operaţii - resurse care trebuie tratate distinct.

Existenţa metodelor statistice moderne oferă un instrument eficient de grupare a instrucţiunilor legate de operanzi ca un tot si tratarea distinctă a instrucţiunilor din punct de vedere al utilizării. Toate clasificările instrucţiunilor (după tipul operaţiei, natura operanzilor, tipul operanzilor) sunt strict legate de latura semantică a limbajului. Metodele de clasificare statistice oferă posibilitatea de a structura limbajul de asamblare după rigorile utilizatorilor.

Page 286: limbaj de asamblare-ivan

684

Se consideră o matrice a frecventelor F cu elemente fij care au semnificaţia: număr de apariţii ale instrucţiunii Ai cu utilizarea resursei Sj. Prin definirea unui sistem de ponderi adecvat, se procedează la agregarea informaţiilor, obţinându-se o măsură a intensităţii întrebuinţării unei instrucţiuni într-un context dat (operatori utilizaţi). Un algoritm de clasificare conduce la identificarea unei noi tipologii de instrucţiuni. Structura internă a instrucţiunii este rezultatul natural al utilizării. Se porneşte la proiectarea limbajului de asamblare de la cerinţele reale ale programatorilor. Se spune că se utilizează un design corectiv, întrucât clasele se obţin pornind de la programe scrise într-un limbaj existent. Noul limbaj de asamblare este rezultatul introducerii de corecţii la un limbaj existent. Pentru ca rezultatele să fie semnificative este necesar ca eşantionul de programe cu care s-a efectuat constituirea matricei F să fie suficient de mare şi să cuprindă o diversitate de programe care să acopere multitudinea de aplicaţii care se dezvoltă în limbaje de asamblare.

Clasele de instrucţiuni obţinute după frecvenţele de întrebuinţare în programe vor avea asociate coduri ale operaţiilor care din structură să permită interpretări diferite.

Dacă de exemplu, instrucţiunea inc ax are frecvenţa de apariţie cea mai mare, i se va asocia codul de operaţie format numai din zerouri.

Dacă se optează pentru un limbaj de asamblare cu codul operaţiei format din 8 biţi, zerourile nesemnificative ca număr permit crearea de grupe de instrucţiuni, fără o acoperire consecutivă a submulţimilor formate din codurile consecutive.

În ipoteza că în clasa de instrucţiuni foarte frecvent întâlnite se află 18 elemente, reprezentabile pe cinci poziţii binare, codurile acestei clase vor avea trei zerouri în faţă (000xxxxx). Din cele 32 de combinaţii binare cu cinci poziţii sunt utilizate numai 18.

Într-o primă etapă se realizează punerea în corespondenţă a claselor cu codurile. În cazul în care rămân instrucţiuni cărora nu li se asociază coduri datorită epuizării celor 256 de combinaţii binare, se trece la rafinarea claselor.

Rafinarea este un procedeu complex care vizează fie reducerea numărului de clase, fie translaţi de instrucţiuni de la o clasă la alta, obţinându-se o altă repartizare a instrucţiunilor în clase.

Dacă prin restrângerea claselor sau prin translaţie s-a obţinut creşterea numărului de elemente de la 18 la 31 în clasa instrucţiunilor cel mai frecvent folosite şi dacă ş-a obţinut încadrarea instrucţiunilor prin punere în corespondenţă cu coduri binare şi de opt poziţii, se poate trece la dezvoltarea în continuare a structurii interne a instrucţiunilor limbajului de asamblare.

Un astfel de limbaj prezintă particularitatea că structurarea instrucţiunilor începe chiar cu codul operaţiei. Este mai corect să se vorbească de codul operaţiei-operand, întrucât frecvenţele de apariţie ţin seama de operaţie şi de operanzii utilizaţi.

Page 287: limbaj de asamblare-ivan

685

Aparent un astfel de limbaj nu mai prezintă regularitaţile întâlnite la celelalte limbaje de asamblare.

Structurarea în continuare a instrucţiunilor se efectuează depinzând strict de alcătuirea claselor. Tipurile de adresare, modul de parcurgere (stânga/ dreapta), expresiile de adresare, se vor regăsi sau nu la fiecare clasă, după cum rezultă din elementele care o alcătuiesc. Cifrele semnificative de pe poziţiile zero, unu sau doi vor marca fiecare tip structural. Chiar dacă se înregistrează "întreruperi" în secvenţele de atribuire a codurilor, clasele neavând exact 32 de elemente, codificarea aceasta a instrucţiunilor tine seama de particularităţile de utilizare ale limbajului.

21.4 Stabilirea numărului de registre

Limbajele de asamblare consideră registrele ca resurse. Se definesc instrucţiuni în care se utilizează numai anumite registre, ceea ce restricţionează foarte mult manipularea datelor. Înseamnă că registrele nu au funcţiuni identice, unele dintre ele fiind dedicate.

Astfel, în cazul limbajului de asamblare definit pentru microprocesoarele x86, registrul AX este dedicat pentru numeroase operaţii (ajustări, înmulţiri, împărţiri) şi este destinaţie pentru multe operaţii. Problematica pe care o are designerul este de a stabili numărul optim de registre pe care să le gestioneze eficient (statistic) programatorii. Acest optim este rezultatul unui proces de definire la birou a secvenţelor în diferite ipoteze cu luarea în considerare a numărului de cicluri pe care le determină fiecare soluţie dată. Se consideră un lot de probleme frecvent rezolvate in limbaj de asamblare P1, P2, ... Pm. Se vor scrie programe în ipoteza în care limbajul este definit cu un singur registru de lucru. Se va observa abundenţa de instrucţiuni mov pentru stocare rezultate intermediare şi pentru iniţializări. Scriind un program de analiză a celor m programe care oferă soluţii pentru problemele Pi, i=1,2,..,m, se evaluează volumul de prelucrări exprimat ca număr de cicluri maşină.

Tot astfel se procedează şi pentru definirile de limbaje de asamblare în care există două, trei sau mai multe registre. Numărul de registre determină şi implementările de structuri fundamentale prin intermediul expresiilor de adresare. De exemplu, registrul index permite atât implementarea structurii de dată masiv, cât şi definirea structurii repetitive.

Utilizarea indicatorului număr de cicluri maşină, conduce la omogenizarea de rezultate, chiar dacă se pierd anumite informaţii. El permite agregări şi manipularea cu medii aritmetice şi dispersii.

Dacă se consideră mai multe loturi de probleme, este posibilă analiza stabilităţii limbajului de asamblare - tot din punct de vedere statistic. În cazul în care s-a obţinut un grad de stabilitate corespunzător, se poate trece la alegerea

Page 288: limbaj de asamblare-ivan

686

numărului eficient de registre în raport cu criteriul reducerii numărului de cicluri maşină.

Cu cât lotul problemelor este mai vast, există posibilitatea de a acoperi o gamă mare de aplicaţii şi de a face un studiu mai complet asupra comportamentului limbajului de asamblare. Indiferent de mărimea lotului, este riscant să se vorbească despre optimizarea limbajului sau de stabilirea numărului optim de registre de lucru, dedicate sau nu. Oricum atributul de optim se va referi la lotul de programe rezultat, orice extensie fiind riscantă dacă nu s-a studiat suficient reprezentativitatea acestui lot.

Cercetările efectuate până în prezent pe un lot de 60 de programe cu acelaşi grad de complexitate pune în evidenţă eficienţa unui limbaj de asamblare cu două registre acumulator (împerecheate corespunzător pentru a se face identificarea corectă a operanzilor şi pentru eliminarea ambiguităţii în cazul ajustărilor). De asemenea, tipurile de expresii de adresă ce intervin în registre pot fi dezvoltate pentru a introduce nivele de indirectare de ordin superior.

21.5 Concluzii

Designul limbajului de programare cu luarea în considerare a finalităţii,

scrierea de programe, determină proiectarea unui limbaj pentru utilizator. Numeroase dificultăţi care apar în asamblare, la generarea formei interne a instrucţiunilor, sunt probleme independente de programator. Odată rezolvate corect, programul asamblor va opera asupra unei mulţimi de programe în creştere, propagând efectele pozitive ale limbajului.

Dacă la proiectare sunt luate în considerare şi elemente de compresie a programelor în cod maşină, limbajul de asamblare oferă o trăsătură benefică tot la nivelul utilizatorilor finali. Preocupările de design pentru limbaje de asamblare capătă acum o nouă caracteristică, aceea de a fi deschis spre utilizatori. Opţiunea spre neomogenitate de tratare a instrucţiunilor nu determină complicaţii la nivelul utilizatorilor. La nivelul celor care implementează limbajul de asamblare, fiecare neomogenitate se traduce în modalitate distinctă de tratare. Diversităţii de tipuri de instrucţiuni îi va corespunde o creştere a complexităţii programului asamblor.

Pentru a obţine rezultate cu nivel de stabilitate ridicat este necesară crearea unei baze de programe scrise în limbaj de asamblare care să includă cât mai multe stiluri de programe şi cât mai multe tipuri de probleme rezolvate.

Ca şi în cazul altor limbaje, designul limbajelor de asamblare ia în considerare menţinerea unui nivel ridicat al ortogonalităţii instrucţiunilor. Studiul efectuat acum a presupus ortogonalitatea deja existentă a instrucţiunilor din limbajul de asamblare asociat microprocesoarelor x86, fără a se proceda la definirea de noi instrucţiuni sau noi tipuri de expresii de adresare. Dacă se vor efectua în viitor şi aceste modificări, designul limbajului de asamblare capătă un

Page 289: limbaj de asamblare-ivan

687

nivel de profunzime mult mai accentuat, influenţând portabilitatea limbajului, în sensul reducerii efortului de integrare a componentelor în software neomogen.

Page 290: limbaj de asamblare-ivan

688

22

ELEMENTE DE GRAFICĂ 22.1 Istoric al adaptoarelor grafice

Imaginea pe care un computer o reprezintă cu ajutorul unui monitor a fost încă de la începuturile erei informatice cea mai importantă metodă utilizată pentru a interacţiona cu utilizatorul. Ca multe alte familii de calculatoare, cele compatibile IBM-PC folosesc în acest scop un dispozitiv electronic numit adaptor grafic sau placă video şi dispozitivul de afişare propriu-zis (monitorul, ecranul cu cristale lichide şi altele).

Sistemele pentru afişarea informaţiei s-au dezvoltat foarte mult începând cu monitoarele monocrome folosite pentru procesarea de text şi sistemele bazate exclusiv pe modul text din jurul anilor 1970.

În anul 1981 compania IBM a introdus adaptorul grafic color CGA (Color Graphics Adapter). Acest sistem era capabil să reprezinte patru culori şi avea o rezoluţie de 320x200. Deşi adaptoarele CGA ofereau facilităţi pentru grafică, aceste facilităţi erau mult prea simple comparativ cu nivelul cerinţelor pentru procesare de text, procesare grafică sau alte aplicaţii grafice sofisticate.

În anul 1984 compania IBM a introdus adaptorul grafic EGA (Enhanced Graphics Adapter). Acest adaptor, cu performanţe mult mai bune decât precedentul, oferea o rezoluţie mult îmbunătăţită de 640x350 de pixeli şi posibilitatea de a reprezenta 16 culori simultan.

În 1987 compania IBM a făcut public ceea ce urma să devină standardul minim acceptat, păstrat şi astăzi, şi anume adaptorul VGA (Video Graphics Adapter). Rezoluţia maximă a acestui adaptor depinde de numărul de culori afişabile simultan. Astfel, se poate opta între 640x480 de pixeli cu 16 culori simultane sau 320x200 de pixeli cu 256 de culori simultane. Toate calculatoarele actuale compatibile IBM-PC au un adaptor grafic compatibil VGA.

În 1990 compania IBM a introdus adaptorul grafic extins XGA (Extended Graphics Array), ca succesor al sistemului 8514/A, propriu IBM. O versiune ulterioară a acestui adaptor, XGA-2, oferea o rezoluţie de 800x600 pixeli în mod „true color”, aproximativ 16 milioane de culori, sau 1024x768 de pixeli în mod „high color”, adică 65536 de culori afişabile simultan.

Cele mai multe adaptoare grafice vândute în ziua de azi sunt descrise ca SVGA (Super Video Graphics Array). Iniţial SVGA a însemnat doar „ceva mai bun decât VGA”, diverşi producători încercând să impună propriul standard.

Page 291: limbaj de asamblare-ivan

689

Ulterior, VESA (Video Electronics Standards Association) a stabilit un standard comun pentru adaptoarele SVGA, numit „Extensia BIOS VESA”.

În mod obişnuit un adaptor grafic SVGA suportă o paletă de 16000000 culori, deşi cantitatea de memorie video limiteză numărul de culori în anumite rezoluţii. În general, cu cât diagonala monitorului este mai mare cu atât el poate reprezenta rezoluţii mai mari. Astfel, un monitor SVGA cu diagonala de 14 inch de obicei poate afişa 800x600 pixeli, pe când un monitor mare, cu diagonala de 21 de inch reprezintă 1280x1024 sau chiar 1600x1200 pixeli.

O imagine reprezentată pe monitor are trei caracteristici principale: rezoluţia, aceasta fiind produsul dintre numărul de pixeli reprezentaţi pe

orizontală şi verticală (de exemplu 320x200, 640x480) numărul de culori afişabile simultan; din acest punct de vedere modurile

grafice sunt paletate sau nepaletate. rata de reîmprospătare a imaginii (sau refresh), care poate varia între

limite largi; VESA recomandă o rată de 72Hz (72 cadre/secundă). Un mod grafic este definit ca o combinaţie între rezoluţia afişată şi numărul

de culori afişabile. Un caz particular al modurilor grafice, prezent numai la calculatoarele IBM-PC, este aşa-numitul „mod text”. Prin acest mod se reprezintă caractere pe ecran prin scriere direct în memoria principală la adresa B800:0000h, rutinele BIOS ale calculatorului făcând scanarea zonei respective şi conversiile necesare afişării caracterelor pe ecran, folosind matriţe de fonturi.

S-a menţionat anterior că modurile grafice sunt paletate sau nu. Un mod grafic paletat are asociat o paletă de culori, iar valorile folosite pentru a stabili culoarea unui pixel pe ecran nu reprezintă culori absolute, ci indecşi în paleta de culori, care conţine culori în format RGB. Într-un mod grafic nepaletat fiecare pixel are asociat un grup de baiţi care conţin culoarea reală. Astfel, există următoarele moduri nepaletate:

pixel reprezentat pe 2 baiţi, cu 32768 de culori (5 biţi pentru fiecare componentă RGB)

pixel reprezentat pe 2 baiţi, cu 65536 de culori (5 biţi pentru roşu şi albastru, şi 6 biţi pentru verde), mod numit high color

pixel reprezentat pe 3 baiţi, cu 16777216 de culori (un bait pentru fiecare componentă RGB), mod numit true color

pixel reprezentat pe 4 baiţi – mod true color cu informaţie de transparenţă (un bait pentru fiecare componentă RGB şi un bait pentru canalul de transparenţă, numit alpha channel)

Producerea imaginii pe ecranul monitorului se face prin operaţia de scanare a memoriei video. Astfel, informaţia din memoria video este procesată de un convertor analogic-digital (DAC), care transformă imaginea înscrisă în mod liniar din memoria video în semnal analogic care este transmis mai departe monitorului.

Numărul de culori afişabil simultan depinde de capacitatea de stocare alocată fiecărui pixel. Astfel, 4 biţi permit 16 culori simultane, iar 3 baiţi permit

Page 292: limbaj de asamblare-ivan

690

aproximativ 16000000 de culori. Generalizând, cu ajutorul a n biţi se reprezintă 2n culori.

Regula principală pentru determinarea numărului maxim de culori afişabile la o anumită rezoluţie este următoarea: rezoluţie orizontală * rezoluţie verticală * memorie alocată pentru un pixel <= memoria video

Astfel, un adaptor grafic ce dispune de 4Mbytes memorie video poate reprezenta 1280x1024 cu maxim 65536 de culori sau 1024x768 cu maxim 16000000 milioane de culori. 22.2 Rutine BIOS şi moduri grafice

Rutinele BIOS furnizează un mod elegant de a programa adaptoarele video. Folosind întreruperea 10h se poate invoca un set de funcţii care permit stabilirea modului video, citi/scrie valorile pentru pixeli. În tabelul de mai jos sunt listate modurile video valabile în rutinele video BIOS atât în mod text cât şi grafic, fiecare specifice unui anumit tip de monitor (dacă adaptorul acceptă mai multe tipuri de monitor). BIOS-ul este folosit pentru operaţii primare de afişare. Pentru animaţie mai complicată şi mai rapidă este recomandată citirea/scrierea directă în memoria video.

Tabelul 22.1. Modurile video BIOS Modul Rezoluţie şi culori Tipul de adaptor 0 40X25 text în 16 nuanţe de gri CGA 1 40X25 text în 16 sau 8 culori CGA 2 80X25 text în 16 nuanţe de gri CGA 3 80X25 text în 16 sau 8 culori CGA 4 320X200 grafic în 4 culori CGA 5 320X200 grafic în 16 nuanţe de gri CGA 6 640X200 grafic în alb şi negru CGA 7 80X25 text în alb şi negru Monochrom 13 320X200 grafic în 16 culori EGA 14 640X200 grafic în 16 culori EGA 15 640X350 grafic în alb şi negru EGA 16 640X350 grafic în 4 sau 16 culori EGA 17 640X480 grafic în alb şi negru VGA 18 640X480 grafic în 16 culori VGA

Page 293: limbaj de asamblare-ivan

691

19 320X200 grafic în 256 culori VGA

Dacă se dezvoltă o aplicaţie, mai întâi trebuie determinat tipul adaptorului grafic şi ales cel mai bun mod video pe care hardware-ul îl poate suporta. Procedura “DetVideoAdap” face acest lucru şi îl întoarce în registrul AX. Folosind această informaţie, se alege cel mai bun mod video din tabelul 22.1 care se potriveşte aplicaţiei. De exemplu, dacă se găseşte VGA, se poate alege modul 8, care va permite afişarea în 16 culori folosind rezoluţie de 640X480. După ce se alege modul video, se va folosi funcţia BIOS de setare a modului video după cum urmează: ; In segmentul de date modv db 0 . . ; In segmentul de cod . . mov ah, 0 ; setarea modului video mov al, modv ; folosind intreruperea 10h int 10h . . ; determina modul video ;inainte de de a apela aceasta procedură, setati ES:DI cu adresa ;unui buffer de 64 octeti ;procedura returnează una din urmatoarele constante în registrul ;AX MDA = 1; HGC = 2; CGA = 3; EGA = 4; VGA = 5; .model SMALL .code Wich_adapter PROC

mov ax, 1B00h int 10h cmp al, 1Bh ; al va fi 1Bh pentru VGA jne TestIfEGA mov ax, VGA jmp SHORT DoneAdapter

Page 294: limbaj de asamblare-ivan

692

TestIfEGA : mov ax, 40h mov es, ax test BYTE PTR es:[87h], 0FFh jz TestIfCGAorHGC mov ax, EGA jmp SHORT DoneAdapter

TestIfCGAorHCG:

; mai intai obtine flag-ul echipamentului int 11h and al, 30h ; verifica modul video initial cmp al, 30h jne ItIsCGA mov cx, 800h mov dx, 3BAh

TestIfHGC: in al, dx ; in HGC, bit-ul 7 al port-ului 3BAh test al, 80h ; va fi 1 in timpul retragerii verticale jnz ItIsHGC ; ciclează pentru a vedea dacă bitul devine 1 loop TestIfHGC ;altfel este MDA mov ax, MDA jmp SHORT DoneAdapter

ItIsHGC: mov ax, HGC jmp SHORT DoneAdapter

ItIsCGA: mov ax, CGA

DoneAdapter: ret

WichAdapter ENDP END

Înainte de a apela procedura “DetVideoAdap” se va iniţializa valoarea registrului ES:DI cu adresa unui buffer (zone de memorie rezervată) de 64 octeţi. Dacă este detectat modul video VGA, funcţia 1Bh a întreruperii video BIOS 10h returnează 64 de octeţi de informaţie in acest buffer. 22.3 Lucrul în mod grafic folosind un adaptor VGA Modul video 13h, din VGA standard, este modul în care se realizează cu cea mai mare uşurinţă cele mai rapide rutine de grafică color.

Page 295: limbaj de asamblare-ivan

693

Acest mod foloseşte un singur segment de memorie liniar, mapat pe pixeli în aşa fel încât fiecare bait controlează exact un pixel. Astfel, sunt posibile 256 de culori simultan pe ecran, cu o rezoluţie de 320x200 pixeli.

Acest mod este paletat, în sensul că nu se lucrează cu valori absolute de culoare. Atributul de culoare al fiecărui pixel este un deplasament în interiorul unei palete care conţine valori RGB reale. Schimbarea valorilor din paleta de culori este o tehnică de animaţie specifică modurilor grafice paletate, deoarece schimbarea se reflectă imediat în imaginea prezentată pe ecran.

Folosind modul video VGA 13h se poate afişa o imagine bitmap de mărime 320x200 cu 256 de culori pe

ecran doar prin copierea acestei imagini la adresa A000h:0000h. Acest mod de lucru face posibile tehnici de

animaţie foarte rapide cu eliminarea fenomenului de “flickering” (clipirea ecranului sau ruperea frame-urilor

datorită unui refresh video prea lent). O astfel de tehnică este următoarea: presupunând că există un mic desen de

25x25 de pixeli care se doreşte a fi mutat în diverse poziţii pe ecran, metoda constă în punerea unei imagini de fond folosind tehnica prezentată anterior, copierea zonei de 25x25 pixeli unde se doreşte a fi desenul într-un buffer temporar, plasarea desenului pe ecran în poziţia salvată, aşteptarea unui anumit timp dictat de viteza sistemului şi a animaţiei dorite urmată de rescrierea bufferului salvat în vechea poziţie după care se trece la o nouă poziţie. Folosind această metodă nu se modifică în nici un fel imaginea de fond, iar calitatea animaţiei este foarte bună. Printre modurile VGA există un mod nedocumentat, numit “modul X”, care este foarte asemănător cu modul 13h cu excepţia faptului că dispune de mai mulţi pixeli pe verticală (320x240), la 256 de culori. Adresarea acestui mod presupune folosirea regiştrilor VGA, nemaifiind posibilă adresarea directă prin scriere la adresa A000h:0000h. De asemenea se poate programa in VGA folosind o rezoluţie de 320x400 cu 256 de culori. Acest mod este tot modul 13h dar se folosesc 4 plane de culoare. Modul 13h are această rezoluţie în mod nativ, dar designerii au dorit să folosească numai 64k din motive de segmentare a memoriei în modul de adresare real, şi astfel fiecare linie este afişată de două ori, de unde rezoluţia verticală înjumătăţită (320x400). Dacă se folosesc 4 plane de biţi se poate controla fiecare a doua linie ascunsă.

Page 296: limbaj de asamblare-ivan

694

Figura 22.1 – Iluzie optică

Se prezintă în continuare un program care realizează o iluzie optică, folosind modul VGA 13h, 320x200 cu 256 de culori, figura 22.1.

Bara din centrul imaginii este realizată folosind o singura culoare, dar pare construită dintr-un gradient datorită imaginii de fond. ; Acest cod a fost asamblat cu NBASM 00.23.xx .model tiny .code org 100h ; adresa de inceput al unui .COM .start push ds ; asigură ds=es pop es mov cx,64 ; setare paletă de culori cu ; 256 nuanţe de gri incepînd xor ax,ax ; cu 0.0.0, 1.1.1, 2,2,2, ... mov di,offset Palette ; PLoop: stosb ; stosb ; stosb ; inc ax ; loop PLoop ; mov ax,0013h; setăm modul video la ; rezoluţia 320x200x256 int 10h ; mov dx,offset Palette ; scriem paleta în DAC xor bx,bx ;

Page 297: limbaj de asamblare-ivan

695

mov cx,64 ; mov ax,1012h ; int 10h ; mov ax,0A000h ; pointer la mamoria VGA mov es,ax ; mov di,14464 ; plasăm imaginea în centrul ; ecranului call Fade ; afişăm partea superioară mov cx,10 ; afişăm bara din mijloc (10 ; linii) mov al,32 ; culoarea din mijloc ALoop: push cx ; mov cx,128 ; rep ; stosb ; add di,192 ; pop cx ; loop ALoop ; call Fade ; afişăm a treia parte xor ah,ah ; aşteptăm o tastă int 16h mov ax,0003h ; setăm modul text(80x25) int 10h ; .exit ; ieşire în DOS Fade proc near ; afişarăm prima şi a treia ; parte mov cx,50 ; 50 de linii PLoop1: push cx ; mov cx,64 ; 64 de culori PLoop2: mov al,cl ; dec al ; stosb ; câte două coloane pentru ; fiecare culoare stosb ; loop PLoop2 ; add di,192 ; pop cx ; loop PLoop1 ; ret ; Fade endp ; Palette dup 768,? ; paleta de culori

Page 298: limbaj de asamblare-ivan

696

.end

În continuare este prezentată o colecţie de proceduri sunt folosite pentru manipularea paletei de culori VGA. Sursa poate fi folosită numai cu DOS-extender-ul DOS4GW şi trebuie asamblată folosind Turbo Assembler.

; Manipularea paletei de culori VGA ; se foloseşte modul protejat (numai pentru procesoare >=80386) ideal ; modul TASM “ideal” p386 ; procesor 80386 radix 10 ; folosim numere zecimale locals @@ ; etichete locale @@ model flat ; mod protejat liniar pe 32 de biţi dataseg ; tabela de indecşi VGA (mod cu 16 culori) VGA DB 0,1,2,3,4,5,20,7,56,57,58,59,60,61,62,63 ; valori RGB implicite (16 culori) VGACOLOR DB 0, 0, 0, 0, 0,42, 0,42, 0, 0,42,42 DB 42, 0, 0, 42, 0,42, 42,42, 0, 42,42,42 DB 21,21,21, 21,21,63, 21,63,21, 21,63,63 DB 63,21,21, 63,21,63, 63,63,21, 63,63,63 codeseg PUBLIC SETRGB16, GETRGB16, SETDAC16, GETDAC16 PUBLIC RestoreVGA PUBLIC SETRGB256, GETRGB256, SETDAC256, GETDAC256 ; aşteaptă sincronizarea verticală proc VerticalSync near push EAX EDX mov DX,03DAH ; registru de stare mov AH,8 ; bit de sincronizare verticală @@1: in AL,DX ; citeşte starea test AL,AH ; aşteaptă sincronizarea jnz @@1 @@2: in AL,DX ; citeşte starea test AL,AH jz @@2 pop EDX EAX ret endp VerticalSync

Page 299: limbaj de asamblare-ivan

697

; aşteaptă sincronizarea orizontală proc HorizontalSync near push EAX EDX mov DX,03DAH ; registru de stare mov AH,1 ; bit de sincronizare orizontală @@1: in AL,DX ; citeşte starea test AL,AH ; aşteaptă sincronizarea jnz @@1 @@2: in AL,DX ; citeşte starea test AL,AH jz @@2 pop EDX EAX ret endp HorizontalSync ; setează paleta RGB pentru o anumită culoare (mod cu 16 culori) ; parametri [ESI=culoare EAX=roşu EBX=verde ECX=albastru] ; modifică [EDX] proc SETRGB16 near mov AH,AL add ESI,OFFSET EGA mov AL,[ESI] ; tabela de culori VGA mov DX,03C8h ; adresa registrului PEL VGA out DX,AL inc DX ; 03C9h = registru de date VGA PEL mov AL,AH out DX,AL ; scrie valoare roşu mov AL,BL out DX,AL ; scrie valoare verde mov AL,CL out DX,AL ; scrie valoare albastru ret endp SETRGB16 ; regăseşte paleta RGB pentru o anumită culoare (mod cu 16 culori) ; parametri [ESI=culoare EAX=roşu EBX=verde ECX=albastru] ; modifică [EDX] proc GETRGB16 near and EAX,0Fh add EAX,OFFSET EGA mov AL,[EAX] ; tabela de culori VGA mov DX,03C7h ; adresa registrului PEL VGA out DX,AL add DX,2 ; 03C9h = registru de date VGA PEL in AL,DX ; citeşte valoare roşu mov [EBX],AL in AL,DX ; citeşte valoare verde mov [ESI],AL

Page 300: limbaj de asamblare-ivan

698

in AL,DX ; citeşte valoare verde mov [EDI],AL ret endp GETRGB16 ; setează valori RGB pentru un bloc consecutiv de culori (mod cu 16 ; culori) ; parametri [EBX=culoare ECX=număr ESI=&sursa] ; modifică [EAX EDX] proc SetDAC16 near add EBX,OFFSET VGA call VerticalSync cli ; deactivează întreruperile @@1: mov DX,03C8h ; 03C8h = adresa registrului VGA PEL mov AL,[EBX] ; tabela de culori VGA inc EBX out DX,AL inc DX ; 03C9h = registru de date VGA PEL outsb ; scrie valoare roşu outsb ; scrie valoare verde outsb ; scrie valoare albastru dec ECX jnz @@1 sti ; activează întreruperile ret endp SetDAC16 ; regăseşte valori RGB pentru un bloc consecutiv de culori (mod cu 16 ; culori) ; parametri [EBX=culoare ECX=număr ESI=&sursa] ; modifică [EAX EDX] proc GetDAC16 near add EBX,OFFSET VGA call VerticalSync cli ; dezactivează întreruperi @@1: mov DX,03C7h mov AL,[EBX] ; tabela de culori EGA inc EBX out DX,AL ; adresa registrului de scriere add DX,2 ; 03C9h = registru de date VGA PEL insb ; citeşte valoare roşu insb ; citeşte valoare verde insb ; citeşte valoare albastru dec ECX jnz @@1 sti ; activează întreruperi ret endp GetDAC16

Page 301: limbaj de asamblare-ivan

699

; setează valori implicite RGB (mod cu 16 culori) ; modifică [EAX EBX ECX EDX ESI] proc RestoreVGA near mov EBX,0 ; index de start mov ECX,16 ; număr de culori mov ESI,OFFSET EGACOLOR ; tabela de culori implicită call SetDAC16 ; setează paletă culori ret endp RestoreVGA ; setează valorile RGB pentru o anumită culoare (mod cu 256 de culori) ; parametri [EAX=culoare EBX=roşu ECX=verde EDI=albastru] ; modifică [EDX] proc SETRGB256 near mov DX,03C8h ; adresă registru scriere VGA PEL out DX,AL inc DX ; 03C9h = registru de date VGA PEL mov AL,BL out DX,AL ; scrie valoare roşu mov AL,CL out DX,AL ; scrie valoare verde mov AX,DI out DX,AL ; scrie valoare albastru ret endp SETRGB256 ; regăseşte valorile RGB pentru o anumită culoare (mod cu 256 de ; culori) ; parametri [EAX=culoare EBX=roşu ECX=verde EDI=albastru] ; modifică [EDX] proc GETRGB256 near mov DX,03C7h out DX,AL add DX,2 ; 03C9h = registru de date VGA PEL in AL,DX ; citeşte valoare roşu mov [EBX],AL in AL,DX ; citeşte valoare verde mov [ESI],AL in AL,DX ; citeşte valoare albastru mov [EDI],AL ret endp GETRGB256 ; setează valorile RGB pentru un bloc consecutiv de culori (mod cu 256 ; de culori) ; parametri [EAX=culoare ECX=număr de culori ESI=&sursa] ; modifică [EBX EDX]

Page 302: limbaj de asamblare-ivan

700

proc SetDAC256 near mov BX,CX shl CX,1 add CX,BX call VerticalSync cli ; dezactivează întreruperi mov DX,03C8h ; 03C8h = adresă registru scriere VGA PEL out DX,AL inc DX ; 03C9h = registru de date VGA PEL rep outsb sti ; activează întreruperi ret endp SetDAC256 ; setează valorile RGB pentru un bloc consecutiv de culori (mod cu 256 ; de culori) ; parametri [EAX=culoare ECX=număr de culori ESI=&sursa] ; modifică [EBX EDX] proc GetDAC256 near mov BX,CX shl CX,1 add CX,BX call VerticalSync cli ; dezactivează întreruperi mov DX,03C7h out DX,AL inc DX ; 03C9h = registru de date VGA PEL rep insb sti ; activează întreruperi ret endp GetDAC256 end 22.4 Lucrul în mod grafic folosind un adaptor SVGA

Modurile SVGA au fost standardizate de asociaţia producătorilor de adaptoare video VESA. Există în acest sens două standarde larg implementate în marea majoritate a plăcilor video, şi anume:

extensia BIOS VESA 1.2 extensia BIOS VESA 2.0 Foarte recent VESA a elaborat standardul 3.0, ale cărui specificaţii pot fi

regăsite la adresa http://www.vesa.org. Extensia BIOS VESA 1.2 oferă în comparaţie cu VGA o mulţime de noi

moduri video, în general cu rezoluţii mari şi moduri de culoare high color - true

Page 303: limbaj de asamblare-ivan

701

color. Adresarea memoriei video se face printr-o operaţie de paginare. Astfel, porţiuni de 64kbytes din memoria video sunt aduse in segmentul A000h-AFFFh, modificate pentru a se afişa secţiunea de ecran corespunzătoare şi ulterior rescrise în memoria video. Astfel, este posibilă utilizarea VBE1.2 (VESA BIOS Extension) folosind modul real de adresare specific 8086.

Extensia BIOS VESA 2.0 este destinată exclusiv modului de lucru protejat, pentru procesoare din familia 80386, iar adresarea memoriei video se poate face direct, în mod liniar. Astfel, standardul prevede o rutină BIOS cu care se poate obţine adresa fictivă (de obicei peste 2Gbytes) la care memoria video este mapată peste memoria reală adresabilă în mod protejat.

Această secţiune prezintă modul de lucru folosind standardul VESA 1.2. Codul inclus conţine o procedură de desenare a unei linii, fiind bazat pe algoritmul lui Bresenham.

Lucrul cu VBE 1.2 presupune următoarele: memoria video – de obicei peste 1Mbyte - nu poate fi accesată integral memoria video se partajează in secţiuni cu o anumită granularitate pentru desenare pe ecran fiecare secţiune trebuie adusă la adresa A000h,

modificată conform algoritmului folosit, şi transferată în memoria video De exemplu, partajarea unei memorii video de 1Mbyte cu o granularitate de

64kbytes se face în exact 16 secţiuni care sunt aduse la adresa A000h. Dacă se partajează aceeaşi memorie cu o granularitate de 4 kbytes se obţin mai multe mapări posibile. Astfel, fereastra adresabilă este tot de 64 kbytes dar această fereastră poate fi aliniată din 4 în 4 kbytes.

O granularitate mai fină măreşte viteza de afişare. Se foloseşte exemplul de mai jos pentru a demonstra acest fapt.

Folosind secţiuni de 64kbytes, cuvântul semnificativ din adresa liniară a pixelului este localizarea secţiunii, iar cuvântul mai puţin semnificativ este poziţia în cadrul secţiunii.

Folosind secţiuni de 4kbytes, cei mai nesemnificativi 12 biţi sunt deplasamentul, iar biţii semnificativi care rămân sunt poziţia secţiunii în memoria video.

Un avantaj important al granularităţii de 4kbytes este faptul că secţiunile se pot alinia mult mai uşor. Cu o rezoluţie orizontală de 640 de pixeli, 32 de linii folosesc 20 kbytes, care se divide la 4kbytes. Dacă pentru afişarea unei linii algoritmul se limitează la ferestre de 20kbytes atunci nu trebuie testată depăşirea ferestrei.

O granularitate de 64kbytes se aliniază cu ecranul doar la 512 linii, ceea ce înseamnă 320kbytes, deci trebuie testate depăşirile de fereastră pentru fiecare linie de rastru orizontală.

Se schimbă, folosind rutine VESA 1.2, lungimea liniei orizontale de rastru la o mărime care este putere a lui 2 (de exemplu 1024). O astfel de lungime garantează că o secţiune se va termina întotdeauna la marginea ecranului, dar dacă

Page 304: limbaj de asamblare-ivan

702

rezoluţia orizontală este mai mică de 1024 se va pierde foarte multă memorie video în acest mod.

Procedura de trasare a unei linii prezentată mai jos foloseşte, ca optimizare, testarea faptului că ambele puncte extreme ale liniei sunt în aceeaşi secţiune. Dacă ambele puncte sunt în aceeaşi secţiune, atunci nu este necesară schimbarea secţiunii. Dacă ele nu sunt în aceeaşi secţiune, atunci pentru fiecare punct se testează depăşirea secţiunii curente. .486 code segment para public use16 assume cs:code PgDown macro push bx push dx xor bx,bx mov dx,cs:winpos add dx,cs:disp64k mov cs:winpos,dx call cs:winfunc pop dx pop bx endm PgUp macro push bx push dx xor bx,bx mov dx,cs:winpos sub dx,1 mov cs:winpos,dx call cs:winfunc add di,cs:granmask inc di pop dx pop bx endm mov ax,seg stk mov ss,ax ; setează stiva mov sp,200h call GetVESA ; iniţializează variabile VESA mov ax,4f02h ; mov bx,0101h ; modul VESA 101h (640x480, 256 culori) int 10h ; mov ax,0a000h mov ds,ax

Page 305: limbaj de asamblare-ivan

703

mov eax,10h mov ebx,13h mov ecx,20bh ; afişează linie mov edx,1a1h mov ebp,21h call Lin mov ax,4c00h int 21h GetVESA proc ; se iniţializează variabile în funcţie de granularitatea ferestrei mov ax,4f01h mov cx,0101h lea di,buff ; folosim rutina VESA pentru push cs ; a regăsi parametrii modului 101h pop es int 10h add di,4 mov ax,word ptr es:[di] ;granularitatea ferestrei (în KB) shl ax,0ah dec ax mov cs:granmask,ax ; = granularitatea - 1 (în Bytes) not ax clc GVL1:

inc cs:bitshift rcl ax,1 jc GVL1 add cs:bitshift,0fh inc ax mov disp64k,ax add di,8 mov eax,dword ptr es:[di] ; adresa ferestri de control mov cs:winfunc,eax ret buff label byte db 100h dup (?) endp Lin proc ; Date de intrare: eax: x1, ebx: y1, cx: x2, dx: y2, bp: culoare ; Modifică: ax, bx, cx, edx, si, edi ; Foloseşte: ; winfunc(dd),winpos(dw),page(dw),granmask(dw),disp64k(dw),bitshift(db) ; eax, ebx trebuie să aibă cuvintele semnificative setate pe 0 cmp dx,bx ja LinS1 ; sortare vârfuri

Page 306: limbaj de asamblare-ivan

704

xchg ax,cx xchg bx,dx LinS1:

sub cx,ax ja LinS2 ; calcul delta_x neg cx ; modifică bucla internă după semn xor cs:xinc1[1],28h LinS2:

sub dx,bx ;delta_y neg dx dec dx shl bx,7 add ax,bx ; calcul adresă liniară de start lea edi,[eax][ebx*4] mov si,dx xor bx,bx mov ax,cs:page shl ax,2 ; pageOffset=page*5*disp64K add ax,cs:page mul cs:disp64k push cx ; iniţializează fereastră CPU mov cl,cs:bitshift ; la punctul superior al liniei shld edx,edi,cl pop cx add dx,ax and di,cs:granmask mov cs:winpos,dx call cs:winfunc mov dx,si mov ax,bp mov bx,dx ;ax:culoare, bx:err-acumulator, cx:deltaX, dx:lungime verticală ;di:localizare în fereastra CPU, si:deltaY, bp:culoare LinL1:

mov [di],al add bx,cx jns LinS3 LinE1:

add di,280h jc LinR2 ; rutina de desenare inc dx jnz LinL1 jmp LinOut LinL2:

mov [di],al

Page 307: limbaj de asamblare-ivan

705

xinc1 label byte LinS3:

add di,1 ; trecerea la următorul pixel pe jc LinR1 ; orizontală LinE2:

add bx,si jns LinL2 jmp LinE1 LinR1:

js LinS7 PgDown ; mută pagina în jos cu 64k mov ax,bp jmp LinE2 LinS7:

PgUp ; sau în sus cu granularitatea mov ax,bp jmp LinE2 LinR2:

PgDown mov ax,bp ; mută pagina în jos cu 64k inc dx jnz LinL1 LinOut:

mov cs:xinc1[1],0c7h ret endp winfunc dd ? ; pointer la funcţia VESA setwindow winpos dw ? ; locaţia ferestrei CPU granmask dw ? ; masca de adresă in interiorul ferestrei disp64k dw ? ;număr de segmente de 64k page dw 0 ; pagina video (0,1,2 pentru 1MB video) bitshift db 0 ; pentru extragerea biţilor de adresă a ; ferestrei ends stk segment para stack use16 'STACK' dw 100h dup (?) ends end 22.5 Concluzii

Acest capitol oferă informaţii introductive şi exemple în domeniul programării grafice folosind limbajul de asamblare. Trebuie precizat faptul că toate aceste exemple rulează numai sub sistemul de operare DOS.

Page 308: limbaj de asamblare-ivan

706

Programarea grafică in mediul Windows foloseşte în mod obişnuit un strat de abstractizare care este specific fiecărui adaptor grafic, livrat de producătorul plăcii sub forma unui driver. La un nivel mai înalt este interfaţa Microsoft DirectX, care foloseşte numeroasele funcţii de accelerare grafică prezente în plăcile grafice moderne.

Se remarcă faptul că standardele VESA nu oferă suport pentru accelerare grafică, fiecare producător de plăci video având propriul set de funcţii API de nivel scăzut, la marea majoritate nedocumentate.

Resurse suplimentare se regăsesc pe World Wide Web la site-ul VESA, http://www.vesa.org.

23

PROGRAME REZIDENTE 23.1 Caracteristicile programelor rezidente

Pe microcalculatoarele care folosesc sistemul de operare DOS, la un moment dat poate rula un singur program. Acesta ocupă memorie RAM şi deţine controlul sistemului de calcul. La încheierea sa, memoria ocupată este eliberată şi sistemul de operare DOS preia controlul. Există însă posibilitatea de a realiza programe care odată încheiate să rămână în memorie, dând totodată posibilitatea altor programe să ruleze în spaţiul de memorie rămas liber. Ele pot fi activate de anumite evenimente externe sau interne sau pot fi activate de către un alt program ce este activ în memorie, în acel moment. Aceste programe se numesc programe rezidente sau programe TSR (Terminate and Stay Resident).

Bryan Pfaffenberger în „Dicţionar explicativ de calculatoare” defineşte programul rezident ca fiind „un program accesoriu sau utilitar realizat pentru a rămâne în memoria cu acces aleatoriu (RAM) în permanenţă, astfel că poate fi activat cu o comandă, chiar dacă mai este un alt program în memorie”.

Programele rezidente pot fi folosite în operaţii de monitorizare a sistemului. În acest caz programul poate fi activat la intervale de timp stabilite pentru a verifica starea sistemului sau este activat de evenimente cum sunt solicitările de resurse, având astfel posibilitatea de a măsura diferiţi parametri sau de a impune un control suplimentar la acordarea de resurse aplicaţiei solicitante. În acest sens se implementează sisteme de securitate care să permită accesul la anumite resurse doar a persoanelor sau aplicaţiilor autorizate. Un exemplu din această categorie îl

Page 309: limbaj de asamblare-ivan

707

reprezintă programele antivirus care instalează un modul rezident ce supraveghează sistemul şi declanşează alarma la orice activitate suspectă.

O altă categorie de utilizări a programelor rezidente o constituie utilitarele ce sunt apelate în mijlocul unei aplicaţii prin acţionarea unei combinaţii de taste. De exemplu, se poate activa un mic program de tip calculator de buzunar ori de câte ori se doreşte efectuarea unor calcule. Sau se apelează un program de comunicaţii care să permită transfer de date de la sau către aplicaţia activă. Sau pur şi simplu se poate apela o bază de date ce conţine informaţii utile pentru soluţionarea problemelor ivite în timpul rulării unei aplicaţii.

Tot programele rezidente oferă şi posibilitatea de a realiza îmbunătăţiri ale sistemului de operare. De exemplu se adaugă anumite funcţii care permit programelor să lucreze cu dispozitive sau cu structuri de date diferite de standardele DOS. De asemenea se pot adăuga noi comenzi sau mici utilitare celor oferite de sistemul original de operare, în funcţie de nevoile practice. 23.2 Întreruperi pentru programe rezidente

Întreruperile se clasifică după următoarea schemă:

Figura 23.1 – Clasificarea întreruperilor

Întreruperile hardware sunt cablate în circuitele procesorului, ale plăcii de bază şi ale perifericelor. Cele externe sunt provocate de diferite echipamente ale sistemului de calcul, care solicită procesorului tratarea unor evenimente. Ele sunt de două tipuri:

mascabile, care pot fi inhibate. Dacă indicatorul de condiţie IF (Interrupt Flag) al procesorului este 0, atunci procesorul nu mai ia în considerare solicitarea echipamentului respectiv. Exemple: întreruperi

Mascabile

Externe

Nemascabile Hardware

Interne

Întreruperi BIOS

Sistem

DOS Software

Utilizator

Page 310: limbaj de asamblare-ivan

708

solicitate de tastatură, porturile seriale şi cele paralele, discul hard, unitatea de floppy disk.

nemascabile, care nu pot fi inhibate. Procesorul ia întotdeauna în considerare aceste întreruperi. De regulă, astfel de întreruperi sunt solicitate când apare o eroare în memoria sistemului (Parity Check Error).

Întreruperile hardware interne sunt generate de către evenimente interne ale

procesorului, cum ar fi tentativa de a executa o împărţire la 0 sau după execuţia fiecărei instrucţiuni atunci când indicatorul de condiţie TF (Trap Flag) are valoarea 1.

Întreruperile software sunt generate de programele în execuţie care solicită procesorului execuţia unei instrucţiuni int n sau into. Aceste întreruperi sunt asociate unor servicii BIOS (întreruperile software BIOS), unor funcţii DOS (întreruperile software DOS) sau unor rutine scrise de utilizator (întreruperile software ale utilizatorului).

Pentru a şti cărei rutine trebuie să predea controlul la producerea unei întreruperi, sistemul de calcul foloseşte o tabelă numită tabela vectorilor de întrerupere. Această tabelă începe întotdeauna la adresa absolută 00000h, şi conţine pentru fiecare dintre cele 256 de întreruperi un pointer de tip FAR către rutina întreruperii respective, pointer care referă vectorul pointeri ai întreruperii. Primul pointer FAR conţine adresa rutinei de tratare a întreruperii cu numărul 0, al doilea pointer conţine adresa rutinei de tratare a întreruperii cu numărul 1, al treilea pointer conţine adresa rutinei pentru întreruperea cu numărul 2 şi aşa mai departe.

Figura 23.2 – Tabela vectorului de pointeri ai întreruperilor

Deci adresa pointerului ce conţine adresa rutinei de tratare a întreruperii cu numărul n este:

offset rutină INT 0 segment rutină INT 0

offset rutină INT 1 segment rutină INT 1

offset rutină INT 2 segment rutină INT 2

offset rutină INT 255 segment rutină INT 255

0000:0000h

0000:0004h

0000:0008h

0000:000Ch

0000:03FCh

0000:0400h

Page 311: limbaj de asamblare-ivan

709

adresa vector INT n = 4n

La solicitarea unei întreruperi, procesorul execută următoarele operaţii: pune în stivă registrele FLAGS, CS şi IP, în această ordine; pune pe valoarea 0 indicatorii de condiţie IF şi TF; primeşte un întreg pe 8 biţi care reprezintă numărul întreruperii; execută un salt la adresa indicată în tabela vectorilor de întreruperi, la

poziţia corespunzătoare numărului întreruperii primit de procesor. Codul întreruperii este furnizat procesorului în una din următoarele

modalităţi: în cazul întreruperilor software numărul este precizat în instrucţiune; în cazul întreruperilor hardware interne sau al celor externe nemascabile

numărul este implicit (este cablat în procesor); în cazul întreruperilor hardware externe mascabile procesul este

controlat de un circuit special numit PIC (Programable Interrupt Controller) şi se desfăşoară în felul următor:

o PIC primeşte de la periferice cereri de întrerupere, dintre care o selectează pe cea de nivel cel mai ridicat şi apoi emite către procesor o cerere de întrerupere pe linia INTR (Interrupt Request);

o procesorul verifică starea indicatorului IF; o dacă IF este 0 atunci cererea este ignorată; o dacă IF este 1 atunci procesorul emite către PIC un semnal pe

linia INTA (Interrupt Acknowledge); o la primirea semnalului INTA, PIC transmite pe magistrala de

date numărul întreruperii. Revenirea din rutina de tratare a întreruperii se face cu o instrucţiune IRET

care încheie rutina de tratare a întreruperii. Această instrucţiune redă controlul la adresa CS:IP din stivă şi reface indicatorii de condiţie pe baza conţinutului registrului FLAGS păstrat în stivă. Există însă şi întreruperi care nu refac indicatorii de condiţie la starea iniţială, deoarece ele comunică programului întrerupt un rezultat prin intermediul acestor indicatori. O astfel de rutină se încheie cu o instrucţiune RETF 2.

Pentru întreruperile interne ale procesorului revenirea se face la instrucţiunea ce a provocat întreruperea. Excepţie face întreruperea provocată de împărţirea la 0 pentru procesoarele 8086/8088, la care revenirea se face la instrucţiunea următoare instrucţiunii DIV sau IDIV care a provocat întreruperea. În cazul celorlalte întreruperi revenirea se face la instrucţiunea următoare.

Orice rutină de tratare a unei întreruperi trebuie să îndeplinească minimum următoarele condiţii:

să aibă grijă să păstreze contextul nealterat;

Page 312: limbaj de asamblare-ivan

710

să evite generarea de noi întreruperi; să minimizeze timpul de tratare a întreruperii. Acest lucru este necesar

pentru a nu bloca sistemul, deoarece pot exista foarte multe solicitări de întreruperi care trebuie să fie rezolvate. De asemenea, este de preferat ca timpul de tratare a unei întreruperi să fie independent de contextul în care s-a generat întreruperea (în sistemele de execuţie în timp real este necesar să cunoaştem cu precizie timpul necesar fiecărei rutine);

înainte de a reveni la programul întrerupt trebuie să asigure scoaterea de pe stivă a tuturor datelor temporare folosite în tratarea întreruperii (unele întreruperi lasă pe stive anumite informaţii; dacă a fost apelată o astfel de întrerupere, trebuie asigurată eliberarea stivei);

întreruperea nu trebuie să fie apelată din nou în timpul tratării ei. 23.3 Sistemul de operare MS-DOS

Sistemul de operare MS-DOS a fost realizat pentru microcalculatoare de tip PC, bazate pe procesoarele Intel 80x86. El este un sistem de operare monouser şi monotasking. Permite rularea unui singur proces la un moment dat, proces ce are controlul total al sistemului. Procesul activ poate utiliza orice resursă a sistemului şi poate accesa orice zonă de memorie, inclusiv zona sistem. Până la terminarea sa, programul activ nu poate fi întrerupt decât de întreruperile hardware. După încheierea programului activ, sistemul de operare primeşte controlul, putând încărca şi lansa în execuţie un alt program.

Sistemul de operare MS-DOS asigură următoarele mari categorii de activităţi:

controlul încărcării în memorie, al lansării în execuţie şi al terminării activităţii pentru programele utilizator;

gestiunea operaţiilor de intrare/ieşire; gestiunea colecţiilor de date pe suporţi magnetici. După cum arată şi numele său (Disk Operating System), MS-DOS este un

sistem de operare stocat pe disc. La pornirea sistemului, el se încarcă de pe disc, se autoconfigurează şi rămâne rezident în memoria internă până la oprirea sau resetarea sistemului. Pe disc, sistemul de operare MS-DOS este organizat în trei fişiere de bază, IO.SYS, MSDOS.SYS şi COMMAND.COM, şi mai multe fişiere ce conţin comenzi externe ale sistemului (fişiere executabile), drivere suplimentare sau diverse utilitare.

Componentele de bază ale sistemului de operare MS-DOS sunt următoarele:

DOS-BIOS – este memorat pe disc în fişierul IO.SYS (IBMBIO.COM sau IBMIO.SYS în alte versiuni ale sistemului de operare). Conţine drivere pentru dispozitivele CON (tastatură şi display), PRN (imprimantă), AUX (interfaţa serială) şi CLOCK (ceasul de timp real).

Page 313: limbaj de asamblare-ivan

711

De asemena, această componentă mai conţine şi drivere pentru discurile flexibile şi pentru discul hard.

DOS-Kernel (nucleul DOS) – este memorat pe disc în fişierul MSDOS.SYS (IBMDOS.COM în alte versiuni). Această componentă conţine proceduri de tratare a operaţiilor de intrare/ieşire la nivel logic, precum şi funcţii de control al alocării memoriei, de control al proceselor şi altele. Toate acestea sunt accesibile prin intermediul întreruperilor DOS, în special INT 21h care permite accesul la funcţiile DOS prin punerea în registrul AH a numărului funcţiei, la apelul întreruperii.

DOS-Shell (interpretorul de comenzi standard) – este memorat pe disc în fişierul COMMAND.COM. Interpretorul este cel ce afişează prompterul DOS la consolă şi acceptă şi execută comenzi de la tastatură. El cuprinde următoarele părţi:

rutina de iniţializare, care este încărcată în memorie şi utilizată doar în procesul de încărcare a sistemului de operare de pe disc;

partea rezidentă, care conţine rutine de tratare a erorilor critice, a situaţiei de acţionare a tastelor CTRL-BREAK sau CTRL-C, precum şi a încheierii programului în execuţie;

partea tranzitorie, care afişează prompter-ul la consolă, citeşte comanda de la consolă şi o execută. Această parte conţine comenzile DOS interne. Când se execută un program utilizator, această parte a interpretorului de comenzi poate fi suprascrisă. Partea rezidentă primeşte controlul la încheierea execuţiei programului şi verifică o sumă de control pentru a determina dacă partea tranzitorie a fost suprascrisă sau nu. Dacă a fost suprascrisă, atunci partea rezidentă reîncarcă partea tranzitorie de pe disc.

23.4 Resurse ale programelor rezidente

Organizarea memoriei După instalarea sistemului de operare MS-DOS, memoria este structurată în

felul următor:

Page 314: limbaj de asamblare-ivan

712

Figura 23.3 – Organizarea primului megaoctet de memorie internă sub sistemul de operare MS-DOS

Aceasta este structura pentru primul megaoctet al memoriei interne. El este

urmat de memoria extinsă. Primul megaoctet de memorie împreună cu primul segment (64ko) din memoria extinsă (numit zona de memorie înaltă) pot fi accesate direct în modul de lucru real al procesorului. Pentru a accesa restul memoriei extinse este nevoie de un driver special.

Tabela vectorilor de întrerupere şi zona de date BIOS sunt rezervate încă înainte de încărcarea sistemului de operare şi încep întotdeauna la locaţii fixe. În continuarea lor se încarcă nucleul sistemului de operare, format din conţinutul fişierelor IO.SYS şi MSDOS.SYS (pentru versiunea Microsoft). Urmează zona System Data care conţine structurile interne ale sistemului de operare: buffere, stive, tabela fişierelor deschise, drivere, tabelele cu blocurile de control a fişierelor (FCB), tabela directoarelor curente.

Zona ocupată de sistemul de operare se încheie cu zona System Code şi partea rezidentă a interpretorului de comenzi COMMAND.COM.

Restul memoriei convenţionale reprezintă memoria aflată la dispoziţia programelor utilizator. Pentru a putea rula, un program trebuie să încapă în această zonă de memorie, din care o parte poate să fie rezervată de către programele rezidente. Dacă are nevoie de mai multă memorie, un program poate folosi memoria extinsă (dacă este instalat driver-ul corespunzător), dar numai pentru segmente suplimentare de date. Partea de cod trebuie să se încadreze în zona de memorie convenţională.

Tabela vectorilor de întrerupere

Zona de date BIOS Nucleul sistemului de

operare Zona „System Data” Zona „System Code”

Partea rezidentă a COMMAND.COM

Zona proceselor

tranzitorii

Memoria video BIOS video

BIOS periferice ROM-BIOS

0000:0000h

0040:0000h

A000:0000h C000:0000h C800:0000h F000:0000h FFFF:000Fh

Memoria convenţională

(640ko)

Memoria superioară

(384ko)

Page 315: limbaj de asamblare-ivan

713

Memoriei convenţionale îi urmează un spaţiu de adrese de 384 kiloocteţi ce formează memoria superioară (Upper Memory). În acest spaţiu de adrese sunt mapate memoria video, memoriile ROM de pe plăcile de extensie (care conţin BIOS aferent perifericelor respective: placa video, discul fix sau alte echipamente), memoria ROM de pe placa de bază (care conţine ROM-BIOS, rutinele POST şi secvenţa ROM Bootstrap), precum şi alte extensii ROM. Tot aici se mapează memoria expandată şi, în spaţiul rămas liber, Upper Memory Blocks.

Adresele exacte la care sunt localizate diferitele zone de memorie în cadrul memoriei superioare diferă de la o generaţie la alta de echipamente, dar limitele generale sunt cele prezentate în figura de mai sus.

Blocul de control al memoriei Începând cu locaţia de memorie imediat următoare nucleului sistemului de

operare, orice zonă de memorie este precedată de un bloc de 16 octeţi, numit Memory Control Block, care oferă informaţii privind zona de memorie ce îi urmează. Structura unui astfel de bloc este următoarea:

Tabelul 23.1. Offset Lungime Semnificaţie

0 1 octet identificatorul blocului (M sau Z) 1 1 cuvânt adresa de segment a PSP al

proprietarului 3 1 cuvânt dimensiunea zonei de memorie

asociate (în paragrafe) 5 3 octeţi rezervat 8 8 octeţi numele proprietarului

Identificatorul blocului este întotdeauna caracterul „M”, cu excepţia

ultimului MCB din memoria convenţională, care are caracterul „Z” la deplasamentul 0. Pentru memoria superioară, blocurile MCB au de asemenea ca identificator caracterul „M”, cu excepţia ultimului MCB din memorie, care este identificat prin caracterul „Z”.

La deplasamentul 8 se găseşte numele programului proprietar, doar dacă zona de memorie rezervată este ocupată de un program. Dacă zona de memorie este rezervată pentru environment sau pentru date suplimentare, atunci aici nu se va găsi numele programului.

Se observă că aceste blocuri de tip MCB formează un lanţ: cunoscând conţinutul unui astfel de bloc se poate determina adresa de segment a următorului bloc de tip MCB, adunând la adresa de segment a blocului curent, dimensiunea în paragrafe a zonei de memorie pe care o precede, plus 1:

Page 316: limbaj de asamblare-ivan

714

adr_seg_MCBn+1=adr_seg_MCBn+dim+1 unde dim este dimensiunea în paragrafe a zonei de memorie pe care o precede MCBn .

Adresa de segment a primului MCB din lanţ se află la deplasamentul -2, în tabela INVARS a sistemului de operare MS-DOS. Adresa acestei tabele se determină cu ajutorul funcţiei DOS 52h.

Dacă adresa de segment a PSP al proprietarului, care se găseşte în MCB la deplasamentul 1, este 0, atunci zona de memorie precedată de către acel MCB este liberă.

Dacă valoarea de la deplasamentul 1 este 8, atunci avem de-a face cu o zonă sistem. În acest caz, dacă la deplasamentul 8 găsim numele „SD” înseamnă că zona ce urmează acelui MCB este zona SYSTEM DATA. Dacă aici găsim numele „SC”, atunci este vorba de zona SYSTEM CODE.

Zona SYSTEM DATA este la rândul ei organizată pe subblocuri de memorie, fiecare subbloc fiind precedat de către un MCB. Primul MCB de subbloc, din această zonă se află imediat după blocul MCB ce marchează întreaga zonă SYSTEM DATA. Subblocurile acestei zone se recunosc după identificatorul al MCB care le precede (deplasament 0). Acest caracter poate fi:

„D” – pentru subblocul ce aparţine unui driver încărcat printr-o comandă DEVICE din CONFIG.SYS. În acest caz, numele de la deplasamentul 8 este chiar numele driver-ului.

„F” – pentru subblocul OPEN FILE TABLES; „X” – pentru subblocul FILE CONTROL BLOCKS; „B” – pentru subblocul BUFFERS; „L” – pentru subblocul CURRENT DIRECTORIES; „S” – pentru subblocul STACKS.

Page 317: limbaj de asamblare-ivan

715

Dacă valoarea aflată la deplasamentul 1 al MCB ce precede o zonă de memorie este diferită şi de 0 şi de 8, atunci această valoare este adresa de segment a PSP al programului proprietar. În acest caz, putem avea una din următoarele subvariante:

adresa de segment a PSP al proprietarului este egală cu adresa de segment a MCB curent plus 1. În acest caz, zona de memorie precedată de MCB curent este ocupată chiar de către programul proprietar, iar la deplasamentul 8 al MCB se găseşte numele programului.

valoarea cuvântului de la deplasamentul 1 din MCB curent este egală cu valoarea cuvântului găsit la deplasamentul 2Ch în PSP al programului proprietar. În acest caz, zona de memorie precedată de MCB curent este ocupată de blocul de environment al programului indicat ca proprietar, prin intermediul PSP propriu.

dacă valoarea de la deplasamentul 1 din MCB curent nu verifică nici una din relaţiile de mai sus, atunci este vorba de un bloc de date alocat suplimentar de către programul indicat ca proprietar.

23.5 Controlul proceselor

Încărcarea în memorie şi lansarea în execuţie a unui program se poate face de către un alt program prin apelul funcţiei DOS 4Bh (EXEC) sau direct de la consolă prin introducerea la prompterul interpretorului de comenzi a numelui programului. Interpretorul de comenzi caută programul specificat în directorul curent şi, dacă nu îl găseşte acolo, în directoarele conţinute în variabila PATH a sistemului de operare. Când îl găseşte îl încarcă în memorie şi îl lansează în execuţie tot prin intermediul funcţiei EXEC.

Programele executabile au extensiile BAT, COM sau EXE. Fişierele cu extensia BAT sunt de fapt fişiere text ce conţin comenzi DOS. Dacă la prompterul interpretorului se introduce numele programului fără extensie şi în acelaşi director se găsesc mai multe fişiere cu numele respectiv, dar cu extensii diferite, atunci ordinea în care interpretorul de comenzi le ia în considerare este următoarea: mai întâi extensia COM, apoi cea EXE şi în final extensia BAT.

Programele de tip COM sau EXE sunt precedate în memorie de o zonă de 256 de octeţi numită PSP (Program Segment Prefix). Această zonă de memorie are următoarea structură:

Page 318: limbaj de asamblare-ivan

716

Tabelul 23.2.

Offset Lungime Semnificaţie 00h 1 cuvânt codul unei instrucţiuni INT 20h 02h 1 cuvânt adresa de segment a vârfului

memoriei de bază ocupate 04h 1 octet rezervat 05h 5 octeţi codul unei instrucţiuni CALL

seg_int21h:off_int21h 0Ah 2 cuvinte adresa rutinei de tratare INT 22h 0Eh 2 cuvinte adresa rutinei de tratare INT 23h 12h 2 cuvinte adresa rutinei de tratare INT 24h 16h 1 cuvânt adresa de segment a PSP al

procesului părinte 18h 20 octeţi rezervat 2Ch 1 cuvânt adresa de segment a blocului de

environment 2Eh 46 octeţi rezervat 5Ch 36 octeţi primul FCB standard, nedeschis 6Ch 20 octeţi al doilea FCB standard, nedeschis 80h 1 octet lungimea şirului cu parametrii

programului 81h 127 octeţi parametrii programului din linia de

comandă Instrucţiunea INT 20h de la deplasamentul 0 reprezintă o modalitate

perimată de a încheia execuţia programului (prin salt la această adresă). La deplasamentul 5 se găseşte o instrucţiune de apel FAR a rutinei de

tratare a întreruperii 21h, care este dispecerul funcţiilor DOS. Începând cu deplasamentul 0Ah se găsesc adresele de revenire la terminarea

programului (identică cu vectorul întreruperii 22h), a rutinei de tratare a acţionării combinaţiei de taste CTRL-BREAK sau CTRL-C (vectorul întreruperii 23h), respectiv a rutinei de tratare a erorilor critice (vectorul întreruperii 24h). Aceste adrese sunt cele valabile pe parcursul execuţiei programului proprietar al PSP. Ele se folosesc pentru restaurarea vectorilor de întrerupere 22h, 23h, respectiv 24h, atunci când se revine dintr-un proces fiu.

Adresa procesului părinte, de la deplasamentul 16h este pentru COMMAND.COM, 0 sau adresa propriului PSP.

Blocul de environment al programului, a cărui adresă de segment este precizată la deplasamentul 2Ch, conţine setări de variabile sistem cu valabilitate locală (modificarea lor nu influenţează procesul părinte). Aceste setări apar sub forma unei succesiuni de şiruri ASCIIZ (şir ASCII terminat printr-un octet de valoare 0), de forma variabila=valoare. Această succesiune se termină printr-un octet 0 suplimentar (deci la sfârşit vom avea doi octeţi cu valoarea 0). După aceasta urmează un cuvânt cu valoarea 1, urmat de numele programului proprietar

Page 319: limbaj de asamblare-ivan

717

specificat cu întreaga cale şi cu extensie. Specificatorul programului se încheie printr-un octet de valoare 0.

Cele două structuri FCB implicite, de la deplasamentele 5Ch şi 6Ch se suprapun parţial, deci nu se va putea lucra cu ambele în acelaşi timp. Oricum FCB reprezintă o modalitate perimată de a lucra cu fişiere. În locul lor se folosesc identificatori de fişiere.

La deplasamentul 81h se găseşte linia de comandă ce a apelat programul proprietar, mai puţin numele fişierului executabil ce conţine programul şi redirectările (aici pot fi găsiţi parametrii programului). Lungimea şirului astfel rămas este specificată la deplasamentul 80h. Peste această zonă, începând de la deplasamentul 80h, pe o lungime de 128 octeţi se întinde zona DTA implicită. Această zonă este folosită de către anumite funcţii DOS ce lucrează cu discul (cele ce folosesc FCB şi funcţiile FIND FIRST şi FIND NEXT). Pentru a nu pierde informaţiile din linia de comandă, înainte de a realiza operaţii cu discul, trebuie fie salvată linia de comandă într-o altă zonă de memorie, fie schimbată adresa zonei DTA (folosind funcţia DOS 1Ah).

Aspecte specifice ale formatului COM Programele de tip COM sunt cele cu model de memorie tiny. Acestea

folosesc un singur segment, unde se găsesc datele, codul şi stiva. În cadrul unui astfel de program se fac doar apeluri de tip NEAR, deci adresa de segment a programului nu apare în mod explicit în nici o instrucţiune din program (codul programului rămâne neschimbat indiferent de adresa de segment la care este încărcat în memorie).

La scrierea programului trebuie marcat în mod explicit spaţiul pentru PSP, care este şi el inclus în acelaşi segment cu restul programului, printr-o directivă ORG 100h. Aceasta are rolul de a asigura calculul corect al deplasamentelor în cadrul segmentului (toate deplasamentele se măresc cu 100h din cauza PSP, care ocupă primii 256 de octeţi din segment).

În fişierul cu extensia COM nu se rezervă spaţiu pentru PSP. PSP este creat şi iniţializat de către sistemul de operare, care încarcă în continuarea sa întreg conţinutul fişierului COM, fără nici o modificare. Apoi iniţializează regiştrii de segment DS, ES şi SS cu adresa segmentului programului; SP este iniţializat cu valoarea FFFEh, deplasament la care este pus un cuvânt cu valoarea 0. Acest lucru este făcut în ideea de a putea încheia execuţia programului printr-o instrucţiune RET, care ar extrage de pe stivă valoarea 0 şi s-ar executa în felul acesta un salt la deplasamentul 0 din cadrul segmentului programului, care este de fapt deplasamentul 0 al PSP şi care conţine întotdeauna codul unei instrucţiuni INT 20h. Aceasta este o modalitate învechită de a încheia execuţia programului.

În finalul procesului de încărcare a programului în memorie se iniţializează regiştrii CS şi IP cu adresa de segment a programului, respectiv valoarea 100h,

Page 320: limbaj de asamblare-ivan

718

pentru a executa un salt la locaţia de memorie imediat următoare PSP. Acesta este întotdeauna punctul de intrare într-un program de tip COM.

Aspecte specifice ale formatului EXE Programele executabile în format EXE folosesc orice model de memorie,

deci nu există restricţii privind numărul de segmente. De asemenea, codul, datele şi stiva se pot găsi în segmente separate, chiar mai multe pentru fiecare în parte, sau se pot combina între ele. Într-un astfel de program coexistă atât apeluri de tip NEAR, cât şi apeluri de tip FAR. Aceasta face ca programele de tip EXE să fie dependente de locul unde se încarcă în memorie, adică vor exista în program simboluri a căror valoare depinde de adresa de segment începând de la care se încarcă programul. De aceea este nevoie ca la încărcarea acestor programe în memorie să se facă o operaţie de relocare, adică de recalculare a acelor valori dependente de adresa de segment începând de la care se face încărcarea programului în memorie.

Punctul de intrare în program nu mai este fix, el putându-se găsi oriunde doreşte programatorul, care precizează acest punct în cadrul directivei END.

Deşi programul se încarcă tot imediat după PSP propriu, acesta nu mai este inclus în nici unul din segmentele programului şi de aceea, la scrierea programului nu mai este nevoie să se precizeze locul pe care îl va ocupa PSP.

Fişierul ce conţine un program în format EXE conţine pe lângă imaginea programului în memorie, mai puţin PSP şi un header ce conţine o serie de informaţii necesare la încărcarea şi lansarea în execuţie a programului. Acest header are următoarea structură:

Page 321: limbaj de asamblare-ivan

719

Tabelul 23.3.

Offset Lungime Semnificaţie 00h 2 octeţi „MZ” (semnătura fişierelor .EXE) 02h 1 cuvânt PartPag = lungime fişier modulo

512 04h 1 cuvânt PageCnt = lungime fişier în pagini

de 512 octeţi 06h 1 cuvânt ReloCnt = număr de elemente din

tabela de relocare 08h 1 cuvânt HdrSize = dimensiune header în

paragrafe de 16 octeţi 0Ah 1 cuvânt MinMem = necesar minim de

memorie după sfârşitul programului (în paragrafe)

0Ch 1 cuvânt MaxMem = necesar maxim de memorie după sfârşitul programului (în paragrafe)

0Eh 1 cuvânt ReloSS = deplasament segment stivă

10h 1 cuvânt ExeSP = offset vârf stivă la lansarea în execuţie

12h 1 cuvânt ChkSum = sumă de control 14h 1 cuvânt ExeIP = offset adresă de start 16h 1 cuvânt ReloCS = deplasament segment de

cod 18h 1 cuvânt TablOff = offset tabelă de relocare 1Ah 1 cuvânt indicator de overlay (0 pentru

modulele de bază) TablOff 2*ReloCnt

cuvinte tabela de relocare

? ? octeţi caractere până la limita de paragraf Necesarul minim de memorie după sfârşitul programului este determinat de

regulă de stivă, care nu este inclusă în fişier. Necesarul maxim de memorie după sfârşitul programului este implicit FFFFh. De aceea, de regulă, programele aflate în execuţie ocupă toată memoria disponibilă.

Deplasamentele segmentelor de stivă şi de cod sunt faţă de factorul de relocare (adresa de segment la care se încarcă programul).

Valorile regiştrilor SP şi IP la lansarea în execuţie a programului (ExeSP şi ExeIP) sunt valorile precizate în directivele STACK, respectiv END, din sursa programului.

Tabela de relocare conţine câte un dublu cuvânt pentru fiecare element al tabelei. Un astfel de dublu cuvânt este format din deplasamentul şi deplasamentul segmentului faţă de factorul de relocare corespunzătoare elementului din program căruia trebuie să i se aplice operaţia de relocare.

Page 322: limbaj de asamblare-ivan

720

Încărcarea şi lansarea în execuţie a unui program în format EXE presupune următoarele operaţii:

se citesc 1Ch octeţi din fişierul .EXE (porţiunea formatată a header-ului), într-o zonă locală de memorie.

se determină lungimea modulului inclus în fişierul .EXE:

lg_modul = (PageCnt - 1) 512 + PartPag – HdrSize 16 în cazul în care conţinutul variabilei PartPag este diferit de 0 sau

lg_modul = PageCnt 512 – HdrSize 16, în cazul în care PartPag = 0. se alocă memorie conform cu lungimea astfel determinată şi cu valorile

MinMem şi MaxMem. Dacă nu se pot aloca minimum 256 + lung_modul + MinMem*16 octeţi, atunci încărcarea şi lansarea în execuţie a programului nu este posibilă.

se creează PSP. se determină deplasamentul în fişier al modulului: HdrSize*16 . se citeşte modulul de program în memorie, la adresa START_SEG:0000,

unde START_SEG este uzual adresa de segment a PSP plus 10h. se setează poziţia de citire din fişier la începutul tabelei de relocare

(TablOff). pentru fiecare element al tabelei de relocare (ReloCnt) se execută

următoarele operaţii: se citeşte elementul tabelei din fişier (două cuvinte: I_OFF şi I_SEG). se determină adresa actuală a elementului ce trebuie realocat:

RELO_SEG = (START_SEG + I_SEG). se adună START_SEG la cuvântul de la adresa RELO_SEG:I_OFF. se iniţializează regiştrii semnificativi şi se dă controlul programului:

o ES = DS = adresa de segment a PSP; o SS = START_SEG + ReloSS; o SP = ExeSP; o CS = START_SEG + ReloCS; o IP = ExeIP.

23.6 Probleme specifice în realizarea programelor rezidente

Structura unui program rezident Pentru ca un program activ în memoria internă a calculatorului să rămână

rezident este nevoie ca acesta să apeleze funcţia TSR a sistemului fie prin INT 27h,

Page 323: limbaj de asamblare-ivan

721

fie prin funcţia DOS 31h. Această funcţie menţine rezervată prima parte a zonei de memorie ocupată de program, parte a cărei dimensiune este transmisă rutinei de tratare a întreruperii apelate. Zona de memorie imediat următoare părţii marcate ca rezidentă este eliberată şi apoi controlul este redat sistemului de operare sau programului ce a lansat în execuţie programul rezident, dacă există un astfel de program.

Orice program rezident este constituit din două mari părţi: o parte rezidentă şi o parte nerezidentă. Partea nerezidentă trebuie să fie plasată în program după partea rezidentă, deoarece funcţia TSR păstrează în memorie partea de început a programului şi eliberează memoria ocupată de partea finală a programului.

Figura 23.4 – Schema generală a imaginii în memorie a unui program

rezident, înainte de apelul funcţiei TSR

0

100h

etichetă_nerezident:

P.S.P.

jmp etichetă_nerezident

Date modul nerezident

Date modul rezident

Bloc decizie instalare/dezinstalare/alte

operaţii

Modul de instalare

apel funcţie TSR

Modul de dezinstalare

Alte module (configurare)

apel funcţie EXIT

Rutine de tratare a întreruperilor

............

int_x1:

int_x2:

Page 324: limbaj de asamblare-ivan

722

Partea nerezidentă a programului Partea nerezidentă conţine modulul de instalare a programului rezident şi,

eventual, cel de dezinstalare, module care nu se justifică să fie păstrate rezidente în memorie, deoarece ele se execută o singură dată în procesul de utilizare a unui program rezident. Aceasta înseamnă că după încărcarea şi predarea controlului programului, partea nerezidentă este cea care trebuie să se execute. Ca atare la punctul de intrare în program, care se găseşte la deplasamentul 100h, imediat după PSP, va trebui să existe o instrucţiune de salt necondiţionat la adresa de început a părţii nerezidente.

La începutul părţii nerezidente va trebui să existe un modul care să decidă acţiunea ce se va executa în continuare. De regulă, acest modul începe prin a verifica dacă în memoria RAM se găseşte deja o instanţă a programului rezident, după care decide ce acţiune să întreprindă, în funcţie de rezultatul acestei verificări:

dacă în memorie nu se mai găseşte o altă instanţă a programului rezident, se va trece la instalarea programului rezident;

dacă în memorie se găseşte deja o instanţă a programului rezident, atunci în continuarea se poate face o dezinstalare a programului rezident aflat în memoria RAM (de fiecare dată când se întâlneşte această situaţie sau doar când utilizatorul specifică în mod explicit că doreşte acest lucru).

Alegerea între execuţia instalării sau a dezinstalării se poate face şi altfel: de exemplu, prin specificarea unor parametri în linia de comandă, atunci când este lansat în execuţie programul sau prin alegerea unei opţiuni dintr-un meniu.

Partea rezidentă a programului Partea rezidentă a programului conţine PSP al programului, urmat de o

instrucţiune de salt la începutul părţii nerezidente şi apoi de datele şi codul modulului rezident.

În zona de date se poate găsi şi o stivă a modulului rezident. Aceasta nu este obligatoriu să existe. Dacă există, ea trebuie foarte atent dimensionată deoarece în cazul programelor rezidente risipa de spaţiu nu este permisă, dar în acelaşi timp stiva trebuie să fie suficient de mare pentru a nu se produce în nici un caz depăşire de stivă. O astfel de depăşire de stivă ar duce cu o foarte mare probabilitate la blocarea sistemului.

Partea de cod este formată din rutinele de tratare a întreruperilor deturnate de către programul rezident. În raport cu rutinele existente în sistem la momentul deturnării, rutinele programului rezident pot veni în completarea acestora sau urmează a fi executate alternativ cu vechile rutine, în funcţie de satisfacerea sau nesatisfacerea anumitor condiţii la momentul apariţiei întreruperii, sau pot să

Page 325: limbaj de asamblare-ivan

723

înlocuiască complet vechile rutine. În fapt, indiferent de varianta aleasă, noua rutină o înlocuieşte pe cea veche, dar dacă se doreşte şi executarea vechii rutine, atunci rutina nou instalată trebuie să apeleze rutina înlocuită prin simularea unui apel INT:

pushf cli call dword ptr CS:vechea_rutina

Toate rutinele conţinute de către partea rezidentă a programului trebuie să

respecte următoarele condiţii: păstrarea nealterată a contextului; respectarea modului de transmitere a parametrilor existent în rutina

originală (dacă este cazul); evitarea generării de noi întreruperi; evitarea activărilor multiple; minimizarea timpului de execuţie. În ceea ce priveşte revenirea la programul întrerupt, aceasta se poate face în

două moduri: cu o instrucţiune IRET, care este varianta normală de întoarcere şi care

presupune restaurarea automată a tuturor indicatorilor de condiţie la starea în care se aflau imediat înainte de întrerupere;

cu o instrucţiune RETF 2, variantă în care indicatorii de condiţie nu mai sunt restauraţi. Această variantă este folosită de anumite întreruperi software care comunică programului apelant anumite informaţii privind modul de încheiere a tratării întreruperii, prin intermediul indicatorilor de condiţie. La deturnarea unei întreruperi, programatorul trebuie să respecte convenţia de comunicare între rutina originală şi programele apelante şi să folosească metoda adecvată de revenire în programul apelant.

Comutarea contextului Comutarea contextului este obligatorie pentru orice rutină de tratare a unei

întreruperi, deoarece aceasta trebuie să păstreze contextul nealterat. De aceea, înainte de a începe tratarea propriu-zisă a întreruperii, orice rutină trebuie să se îngrijească de salvarea contextului programului întrerupt şi de comutarea către contextul rutinei, iar după încheierea tratării întreruperii trebuie să comute înapoi pe contextul salvat al programului.

Următoarele elemente trebuie să fie avute în vedere atunci când se face comutarea contextului:

regiştrii procesorului;

Page 326: limbaj de asamblare-ivan

724

comutarea pe stiva proprie a modulului rezident — acest lucru este necesar dacă modulul rezident a fost prevăzut cu o stivă proprie şi se realizează prin iniţializarea corespunzătoare a regiştrilor SS şi SP, care anterior trebuie să fie salvaţi într-o zonă de memorie special rezervată în modulul rezident. La încheierea tratării întreruperii este necesară comutarea înapoi pe stiva programului întrerupt.

salvarea stivei programului întrerupt — salvarea se va face într-o zonă rezervată special în modulul rezident. Salvarea stivei curente în momentul întreruperii este justificată doar pentru acele rutine care apelează alte întreruperi sau care nu se asigură de dezactivarea întreruperilor pe tot parcursul rutinei. Se recomandă salvarea a 64 de cuvinte din stiva programului întrerupt.

PSP şi DTA — este necesar să fie salvate adresele acestor structuri ale programului întrerupt atunci când se fac operaţii cu discul folosind funcţii DOS. După salvarea acestor adrese, trebuie să fie marcate ca active PSP şi DTA ale modulului rezident. La încheierea tratării întreruperii trebuie setate PSP şi DTA active, la valorile salvate.

conţinutul memoriei video, modul video, poziţia şi tipul cursorului, poziţia pointerului mouse-ului (dacă există driver de mouse).

Instalarea programelor rezidente Instalarea programelor rezidente presupune iniţializarea corespunzătoare a

datelor necesare modulului rezident şi realizarea legăturilor necesare acestuia şi se încheie prin apelul funcţiei TSR. De regulă nu este necesară prezenţa simultană în memorie a mai multor instanţe ale aceluiaşi program rezident. De aceea, înainte de a se trece la instalarea programului este necesară verificarea existenţei unei instalări anterioare. După ce a fost verificată existenţa unei instalări precedente şi s-a confirmat lipsa ei, deci posibilitatea realizării unei instalări, se trece la realizarea următoarelor operaţii necesare pentru instalarea programului rezident:

iniţializări ale parametrilor programului, salvarea în zona de date a modulului rezident a datelor necesare şi alte operaţii de iniţializare, în funcţie de particularităţile programului rezident;

salvarea adreselor rutinelor de tratare a întreruperilor ce vor fi deturnate; deturnarea întreruperilor; eliberarea, dacă este cazul, a blocurilor de date alocate suplimentar de

către modulul de iniţializare şi nenecesare modulului rezident; eliberarea blocului de environment (opţional); determinarea zonei de memorie ce va fi rezervată pentru modulul

rezident şi pregătirea regiştrilor pentru apelul funcţiei TSR;

Page 327: limbaj de asamblare-ivan

725

Modulul de instalare se încheie prin apelul funcţiei Terminate and Stay Resident care rezervă memoria pentru modulul rezident şi apoi redă controlul sistemului de operare sau programului apelant dacă este cazul.

Verificarea unei instalări precedente Această metodă se bazează pe faptul că programul rezident instalat

deturnează anumite întreruperi, astfel încât noii vectori ai întreruperilor respective indică rutine care sunt incluse în modulul rezident. De aceea, dacă programul rezident a fost instalat, atunci adresele rutinelor de tratare a întreruperilor pe care le foloseşte programul rezident vor indica locaţii de memorie din interiorul segmentului ocupat de acesta.

Pentru a stabili dacă în segmentul indicat de vectorul unei anumite întreruperi se află programul nostru rezident este suficient să verificăm conţinutul unei anumite zone de memorie aflată la un deplasament bine precizat în interiorul segmentului în cauză şi să vedem dacă găsim acolo semnătura programului rezident. Această semnătură constă într-un şir de valori prestabilite, pe care programul rezident le va conţine întotdeauna la acel deplasament.

În consecinţă, determinarea existenţei unei instalări precedente a programului rezident presupune următoarele operaţii:

în zona de date a modulului rezident, programatorul va rezerva un număr de octeţi ce vor conţine un şir de valori prestabilite.

în partea nerezidentă, blocul de verificare a unei instalări precedente va conţine următoarele operaţii: determinarea adresei de segment a uneia din întreruperile cunoscute ca fiind deturnate de către programul rezident; adăugarea la adresa de segment astfel obţinută a deplasamentului semnăturii; extragerea de la adresa fizică astfel obţinută a şirului de valori şi apoi compararea sa cu semnătura programului rezident. Dacă sunt identice, atunci programul a fost deja instalat, altfel programul nu este instalat.

Verificarea în lanţul de blocuri MCB O altă metodă de testare a existenţei unei instalări precedente a programului

rezident este aceea de a verifica toate blocurile de memorie MCB (Memory Control Blocks) şi a vedea dacă vreunul dintre ele este rezervat pentru programul nostru rezident.

Orice bloc de memorie ce urmează nucleului sistemului de operare este precedat de un bloc MCB de 16 octeţi, care conţine la deplasamentul 8 un şir de 8 caractere ce reprezintă numele proprietarului zonei respective de memorie, dacă zona este rezervată (vezi 2.1.2.). Deci aici se găseşte numele programului ce ocupă

Page 328: limbaj de asamblare-ivan

726

respectiva zonă de memorie. Adresa de segment a primului MCB este găsită folosind funcţia DOS 52h.

Adresa de segment a primului MCB se găseşte la deplasamentul negativ -2 în tabela INVARS. După apelul funcţiei DOS 52h, adresa de segment a primului MCB este dată de cuvântul indicat prin pointerul ES:BX-2.

Adresa de segment a următorului MCB se determină adunând la adresa de segment a MCB curent valoarea dim (dimensiunea în paragrafe a zonei de memorie referite) a cuvântului de la deplasamentul 3 din cadrul MCB curent şi valoarea 1, care reprezintă dimensiunea în paragrafe a unui MCB (vezi 2.1.2., formula (2)).

Ultimul MCB din lanţ are caracterul „Z” la deplasamentul 0. Folosirea întreruperii multiplex Această metodă se bazează pe întreruperea 2Fh, numită întrerupere

multiplex, care este pusă la dispoziţie de către sistemul de operare MS-DOS. Această întrerupere s-a intenţionat a fi o modalitate de comunicare între procese. Fiecare modul (proces) rezident trebuie să aibă un identificator multiplex, care pentru procesele utilizator poate fi orice număr între C0h şi FFh. Numerele cuprinse între 00h şi BFh sunt rezervate sistemului de operare DOS. Toate modulele rezidente trebuie să intercepteze această întrerupere. În rutina de tratare trebui să verifice identificatorul multiplex din registrul AH şi dacă acesta este identificatorul propriu, atunci execută funcţia solicitată prin registrul AL, pe care ar încheia-o printr-o instrucţiune IRET. Dacă în AX nu este găsit identificatorul propriu, atunci trebuie să predea controlul următorului handler din lanţ apelând rutina a cărei adresă era pusă în tabela vectorilor de întrerupere înainte ca programul să fi deturnat întreruperea.

Orice program rezident ce interceptează această întrerupere trebuie să implementeze măcar funcţia 00h care determină dacă un program rezident a fost sau nu instalat. Această funcţie trebuie să returneze în AL 00h dacă programul nu este instalat sau FFh dacă programul este instalat.

În concluzie, este necesar ca programul rezident să primescă un număr propriu de identificare cuprins între C0h şi FFh şi să prevadă o rutină de tratare a întreruperii 2Fh care să realizeze următoarele operaţii:

Page 329: limbaj de asamblare-ivan

727

testează conţinutul registrului AH; dacă acesta este identic cu numărul propriu de identificare multiplex,

execută următoarele operaţii; testează conţinutul registrului AL; dacă acesta este 00h, pune în AL valoarea FFh; dacă este alt număr decât 00h, apelează procedura de tratare a funcţiei respective, dacă există;

dacă AH nu conţine numărul propriu de identificare multiplex, apelează vechea rutină de tratare a întreruperii prin simularea unei instrucţiuni INT.

Blocul din modulul nerezident, care testează dacă programul rezident a fost sau nu instalat trebuie doar să apeleze întreruperea 2Fh (după ce a fost pus în AH identificatorul multiplex al programului, iar în AL valoarea 00h) şi să testeze rezultatul:

dacă AL are valoarea 00h, programul nu a fost instalat; dacă AL are valoarea FFh, programul a fost instalat.

Dezinstalarea programelor rezidente Dezinstalarea programelor rezidente nu este obligatorie, dacă realizatorul

programului consideră că este justificată prezenţa programului rezident în memoria calculatorului până la sfârşitul sesiunii curente de lucru.

Dacă este prezent, modulul de dezinstalare trebuie mai întâi să verifice dacă este posibil să se recurgă la dezinstalare fără a afecta buna funcţionare a sistemului. Această verificare este necesară deoarece este posibil ca după instalarea programului nostru să mai fi fost instalate şi alte programe rezidente ce au deturnat aceleaşi întreruperi. În acest caz, tentativa de refacere a vechilor legături va duce la tăierea legăturilor programelor rezidente instalate ulterior programului nostru.

Verificarea posibilităţii de dezinstalare se va face comparând toţi vectorii de întrerupere ce au fost interceptaţi de programul nostru, cu adresele rutinelor din modulul rezident.

Dacă în urma acestei verificări a rezultat că este posibilă dezinstalarea, modulul de dezinstalare va reface vechii vectori de întrerupere pe baza adreselor salvate la instalare, după care va elibera blocurile de memorie alocate pentru eventuale date suplimentare ale modulului rezident, pentru environment şi pentru modulul rezident al programului. 23.7 Activarea programelor rezidente

Activarea programelor rezidente presupune preluarea controlului de către programul rezident şi realizarea de către acesta a funcţiei proprii, atunci când sunt îndeplinite anumite condiţii.

Page 330: limbaj de asamblare-ivan

728

Un program rezident, pentru a-şi putea îndeplini funcţia, interceptează una sau mai multe întreruperi. În felul acesta, la orice solicitare a uneia dintre întreruperile respective, de către sistem sau de către un program utilizator, programul rezident primeşte controlul. Apoi el decide în funcţie de îndeplinirea sau neîndeplinirea anumitor condiţii, dacă să activeze rutina proprie sau să apeleze doar vechea rutină de tratare a întreruperii respective sau să facă alte operaţii. Aceste condiţii se referă la a testa dacă este necesară activarea programului rezident, dar trebuie să se refere şi la a testa dacă este posibilă activarea programului rezident fără a afecta buna funcţionare a sistemului. Cu alte cuvinte, există anumite restricţii cu privire la momentul în care un program rezident se poate activa.

Restricţii care trebuie să fie avute în vedere înainte de activarea programului rezident

Interdicţia întreruperii unei funcţii DOS se datorează faptului că funcţiile

DOS nu sunt reentrante. Cu alte cuvinte, dacă execuţia unei funcţii DOS este întreruptă, şi apoi este apelată din nou aceeaşi funcţie sau altă funcţie DOS, atunci datele primei funcţii pot fi suprascrise de noua funcţie apelată. Funcţiile DOS nu prevăd posibilitatea unor apeluri multiple şi folosesc aceleaşi zone de date.

Pentru acest motiv, o rutină ce apelează funcţii DOS nu are voie să fie activată în cursul execuţiei unei funcţii DOS. Interdicţia poate fi extinsă asupra tuturor rutinelor ce apelează alte întreruperi sau care nu menţin întreruperile dezactivate pe tot parcursul execuţiei indiferent dacă e vorba de apelarea unor funcţii DOS sau nu. Această precauţie trebuie luată deoarece este posibil ca întreruperile apelate în timpul execuţiei rutinei să apeleze ele funcţii DOS sau este posibil ca aceste întreruperi să fie interceptate de alte programe rezidente ce folosesc funcţii DOS.

Pentru a evita activarea programului rezident când a fost întreruptă o funcţie DOS, se va testa la intrarea în rutina de tratare a întreruperii un indicator intern al sistemului de operare DOS, indicator numit INDOS, care este diferit de 0 atunci când este în curs de execuţie o funcţie DOS. Adresa acestui indicator se poate obţine cu ajutorul funcţiei DOS 34h.

Această funcţie se va apela la instalarea programului rezident şi se va memora adresa indicatorului INDOS într-o locaţie din zona de date a modulului rezident. În felul acesta se va evita apelul funcţiei DOS pe parcursul tratării întreruperii interceptate. Adresa indicatorului INDOS se stabileşte la instalarea sistemului de operare şi nu se modifică pe parcursul sesiunii de lucru (până la resetarea sistemului).

Acest indicator nu rezolvă complet problema deoarece aproape întotdeauna el este diferit de 0. Acest lucru se întâmplă deoarece toate operaţiile pe care le execută sistemul de operare folosesc funcţii DOS. De exemplu, interpretorul de comenzi COMMAND foloseşte o funcţie DOS pentru a citi comanda de la

Page 331: limbaj de asamblare-ivan

729

prompter. Ceea ce înseamnă că mai mereu această funcţie este activă. Pentru a rezolva această problemă, sistemul de operare MS-DOS pune la dispoziţie întreruperea INT 28h pe care sistemul o apelează de fiecare dată când se află în aşteptare (de exemplu când interpretorul COMMAND aşteaptă introducerea unei comenzi).

Această întrerupere indică faptul că programele rezidente se pot activa în siguranţă. Rutina originală a întreruperii execută un simplu IRET. Programele rezidente pot intercepta această întrerupere pentru a se activa la apelul ei. Pe parcursul tratării acestei întreruperi pot fi apelate în siguranţă toate funcţiile DOS al căror număr este mai mare decât 0Ch, cu menţiunea că funcţiile DOS 3Fh şi 40h nu trebuie să folosească un handler ce referă dispozitivul CON.

Interdicţia întreruperii activităţilor critice în timp Programelor rezidente le este interzis să se activeze în timp ce se execută o

rutină critică din punct de vedere al timpului de execuţie. Dacă nu se respectă această interdicţie, atunci datorită intervenţiei programului rezident vor apare întârzieri în execuţia rutinei critice, deci desincronizări în lucrul cu perifericul la care se referă rutina şi ca urmare pot apare pierderi de date.

Pentru a preveni acest lucru, programul rezident va trebui să intercepteze toate rutinele critice în timp şi să execute următoarele operaţii în noua rutină:

setare indicator de execuţie rutină critică în timp; apel rutină originală; resetare indicator de execuţie rutină critică în timp. În felul acesta, pe parcursul execuţiei rutinei critice, programul rezident va

avea un indicator propriu setat corespunzător şi care îi va spune că nu se poate activa. Deci, la orice tentativă de activare, programul rezident va trebui să verifice mai întâi situaţia acestui indicator.

Interdicţia întreruperii tratării erorilor critice Programele rezidente nu trebuie să întrerupă rutina care tratează o eroare

critică pentru a nu provoca modificarea codului de eroare şi a nu afecta astfel execuţia programului întrerupt. Pentru a şti când are loc tratarea unei erori critice, programul rezident poate testa un indicator a cărui adresă se obţine cu ajutorul funcţiei DOS 5Dh, subfuncţia 6.

Indicatorul de eroare critică se reprezintă pe un cuvânt şi este diferit de 0 dacă este în curs de tratare o eroare critică.

La instalarea programului rezident se va apela funcţia DOS 5Dh, subfuncţia 6h şi se va memora adresa conţinută în DS:SI la o locaţie din modulul rezident. În felul acesta se va evita apelarea funcţiei DOS de către rutina de tratare a întreruperii interceptate. La apariţia unei tentative de activare a programului rezident, se va

Page 332: limbaj de asamblare-ivan

730

testa indicatorul de eroare critică şi dacă acesta este diferit de 0 se va renunţa la activarea programului rezident.

Evitarea activărilor multiple Trebuie avută în vedere şi posibilitatea ca în timp ce se execută programul

rezident să mai apară o cerere de activare a sa. Pentru ca şi această cerere să fie satisfăcută fără a afecta execuţia corectă a programului de la prima activare ar trebui ca programul rezident să fie reentrant. Dar cum scrierea unui cod reentrant ridică probleme mult mai dificile, este preferată varianta mai simplă de a împiedica o a doua activare a programului rezident în timp ce se execută deja o instanţă a sa.

Acest lucru se realizează folosind un indicator propriu al programului rezident care se setează înaintea fiecărei activări a programului şi se resetează la încheierea execuţiei. Înainte de a activa programul, se va testa acest indicator. Dacă este setat înseamnă că există deja în execuţie o instanţă a programului, care a fost întreruptă. În acest caz se va renunţa la activarea programului.

23.8 Program rezident pentru afişarea ceasului

Acest program rezident îşi propune să ofere programelor utilizator un serviciu de afişare a unui ceas de timp real şi servicii de cronometrare.

Programul rezident va pune la dispoziţia programelor o întrerupere software de utilizator care va cuprinde următoarele funcţii:

determinarea stării ceasului sau a cronometrului; afişarea ceasului; dezactivarea ceasului; activarea cronometrului pentru o numărătoare în sens crescător; activarea cronometrului pentru o numărătoare inversă; oprirea cronometrului; repornirea cronometrului; Funcţionarea ceasului sau a cronometrului se va face în paralel cu execuţia

oricărui program utilizator. Programul rezident va permite funcţionarea alternativă a ceasului sau a cronometrului.

Funcţia pentru determinarea stării ceasului sau cronometrului va returna modul de funcţionare a programului rezident (ceas, cronometru sau dezactivat), iar pentru cronometru va returna şi timpul indicat de acesta.

Cronometrul va fi iniţializat cu un timp de start, iar pentru cronometrul crescător se va preciza şi limita maximă de timp de funcţionare. Repornirea cronometrului după o oprire se va face de la momentul la care fusese oprit.

Afişarea ceasului sau a cronometrului nu este obligatorie dacă sistemul video funcţionează în mod grafic sau dacă este în execuţie o rutină critică, dar este obligatoriu ca scurgerea timpului să fie măsurată şi în aceste condiţii.

Page 333: limbaj de asamblare-ivan

731

Precizia oferită de aceste servicii va fi la nivel de secundă. Modul de utilizare a programului Pentru a oferi serviciile solicitate, programul rezident pune la dispoziţia

programelor utilizator întreruperea 88h, a cărei rutină nouă va oferi următoarele servicii:

serviciul 0 – determinarea stării ceasului: la apel AH=0; returnează în CX numărul total de secunde reprezentat de timpul indicat de cronometru, în AL returnează modul de lucru al programului rezident, iar în AH returnează modul de lucru anterior ultimei opriri a cronometrului;

serviciul 1 – oprirea ceasului sau cronometrului: la apel AH=1; nu returnează nimic;

serviciul 2 – activarea cronometrului: la apel AH=2, AL=0 pentru cronometru crescător sau AL=1 pentru cronometru descrescător, CX conţine numărul total de secunde reprezentat de timpul de start al cronometrului, iar pentru AL=0 DX conţine numărul total de secunde reprezentat de timpul final de funcţionare a cronometrului; nu returnează nimic;

serviciul 3 – activarea afişării ceasului de timp real: la apel AH=3; nu returnează nimic;

serviciul 4 – repornirea cronometrului din punctul în care a fost oprit: la apel AH=4; returnează AL=0 dacă cronometrul a fost pornit sau AL=1 dacă cronometrul nu poate fi repornit.

Modul de lucru al programului rezident poate avea una din următoarele

valori: 0 – dezactivat prin serviciul 1; 1 – dezactivat automat la expirarea timpului de funcţionare a cronometrului; 2 – cronometru crescător activ; 3 – cronometru descrescător activ; 4 – afişarea ceasului de timp real activată. Timpul maxim de funcţionare pentru cronometru este de 65535 de secunde

(18 ore, 12 minute şi 15 secunde). Solicitarea afişării ceasului de timp real în timpul funcţionării unui

cronometru duce la oprirea cronometrului. Acesta poate fi repornit ulterior. Repornirea cronometrului este posibilă doar dacă există un cronometru oprit

din funcţionare înainte de expirarea timpului său de funcţionare, prin serviciul 1 sau prin activarea afişajului ceasului de timp real. Activarea unui cronometru în timpul funcţionării altui cronometru duce la distrugerea cronometrului care era în funcţiune (care nu mai poate fi repornit). Pentru a obţine detalii privind cauza ce

Page 334: limbaj de asamblare-ivan

732

face imposibilă folosirea serviciului 4 (când returnează AL=1) se poate apela servicul 0, care indică atât modul de lucru curent, cât şi modul de lucru salvat la ultima oprire a unui cronometru.

Pentru a beneficia de aceste servicii, programul trebuie să fie instalat în memorie. Acest lucru se face lansând în execuţie programul executabil CEAS.COM.

Pentru dezinstalarea programului se lansează încă odată în execuţie programul CEAS.COM care va solicita confirmarea intenţiei de dezinstalare:

Programul este deja instalat! Doriti dezinstalarea lui? (d/n)

Dacă se apasă d sau D, atunci se încearcă dezinstalarea programului

rezident, după care se afişează rezultatul tentativei de dezinstalare: programul a fost dezinstalat sau acest lucru nu este posibil.

Modul de realizare a programului Modulul rezident al programului conţine datele acestuia (indicatorii

modului de lucru, indicatori pentru timpul scurs, indicatori de activare, adresele rutinelor deturnate, regiştrii SS şi SP salvaţi), o stivă proprie de 32 de cuvinte şi rutinele de tratare a întreruperilor 8h, 10h, 13h şi 88h: org 100h ;specificare zona ocupata de PSP intrare: jmp nerezident

mod_lucru db 0 ;mod de functionare mod_oprit db 0 ;mod de lucru inaintea dezactivarii semnatura db "CEAS/CRONOMETRU" lung_sem equ $-semnatura fract_secunda db 0 ;fractiuni de secunda corector db 0 ;corecteaza fractiunile pierdute crono_crt dw ? ;timp indicat de cronometru (s) crono_max dw ? ;timp functionare cronometru (s) inbios db 0 ;indicator de rutina critica activa activ db 0 ;indicator de TSR curent activ int8_veche label dword ;adresa rutina tratare INT 8 int8_veche_ofs dw ? int8_veche_seg dw ? int10h_veche label dword ;adresa rutina tratare INT 10h int10h_veche_ofs dw ? int10h_veche_seg dw ? int13h_veche label dword ;adresa rutina tratare INT 13h int13h_veche_ofs dw ? int13h_veche_seg dw ? ceas db ?,112,?,112,':',112,?,112,?,112,':',112,?,112,?,112

Page 335: limbaj de asamblare-ivan

733

sp_int dw ? ;SP al programului intrerupt ss_int dw ? ;SS al programului intrerupt sp_int2 dw ? ;SP al TSR-ului stiva dw 64 dup(?) ;stiva TSR-ului varf_stiva label word

int_8: ;codul rutinei de tratare int 8 int_10h: ;codul rutinei de tratare int 10h int_13h: ;codul rutinei de tratare int 13h int_88h: ;codul rutinei de tratare int 88h nerezident: ;modulul nerezident

Şirul de octeţi ceas conţine caracterele care reprezintă timpul ce trebuie afişat. Şirul cuprinde pentru fiecare caracter doi octeţi: primul conţine codul ASCII al caracterului şi celălalt conţine atributul de culoare al caracterului, care a fost iniţializat în acest caz cu valoarea 7*16 + 0 = 112, care înseamnă fond gri (7) şi caractere negre (0). Acest format este cerut de serviciul video INT 10h, funcţia 13, subfuncţia 2, care este folosit în acest program pentru afişarea timpului: afis_sir: ;afisez timpul curent obtinut mov cs:sp_int2,sp ;salvez SP curent mov ax,cs mov es,ax mov bp,offset ceas ;ES:BP=pointer la sirul de afisat mov cx,8 ;sirul are 8 caractere mov dx,0000h ;randul 0, coloana 0 mov ax,1302h ;sir cu atribute, nu modific cursor int 10h ;afisez mov sp,cs:sp_int2 ;restaurez SP

Salvarea şi restaurarea registrului SP înainte, respectiv după apelul serviciului video este necesară deoarece acesta distruge conţinutul registrului SP. Întreruperile 10h şi 13h au fost interceptate pentru a putea seta corespunzător indicatorul inbios al programului rezident, pe parcursul execuţiei rutinelor de tratare a acestor întreruperi, rutine care sunt critice din punctul de vedere al timpului de execuţie: int10h: ;rutina de tratare a INT 10h mov cs:inbios,1 pushf call cs:int10h_veche mov cs:inbios,0 iret

Page 336: limbaj de asamblare-ivan

734

int13h: ;rutina de tratare a INT 13h mov cs:inbios,1 pushf call cs:int13h_veche mov cs:inbios,0 retf 2

Rutina de tratare a întreruperii 88h nu a fost prevăzută cu nici una din restricţiile de activare specifice programelor rezidente, deoarece nu ea este rutina de bază a programului rezident. Întreruperea 88h nu este nici întrerupere hardware, nici întrerupere software de sistem. De aceea ea nu este apelată decât de programele utilizator şi este practic ca o procedură a programului ce o foloseşte. Rutina de tratare a întreruperii 88h nu poate întrerupe nici o altă rutină şi de aceea nu sunt necesare precauţii suplimentare. În plus ea nu execută decât operaţii secundare, de setare a parametrilor ceasului, conform cu solicitările programelor utilizator, realizate prin intermediul celor 5 servicii puse la dispoziţie.

În schimb, s-a avut grijă ca la intrarea în rutina de tratare a întreruperii 88h să fie reactivate întreruperile, pentru ca să nu fie împiedicată buna funcţionare a sistemului pe parcursul execuţiei acestei rutine.

Rutina principală a programului rezident este rutina de tratare a întreruperii hardware 8h, generată de ceasul sistem de 18,2 ori pe secundă. Această rutină începe cu un apel necondiţionat al rutinei originale, după care sunt actualizaţi indicatorii de scurgere a fracţiunilor de secundă dacă programul este activat (fie ceasul, fie cronometrul): ;rutina de tratare a INT 8 int8: pushf call cs:int8_veche ;apel rutina veche (simulare INT) push ax cmp cs:mod_lucru,2 jb sf_int8 ;mod=0 sau 1 ->program inactiv inc cs:fract_secunda ;mod=2, 3 sau 4 ->program activ cmp cs:fract_secunda,18 ;s-au facut 18 timer ticks ? jnz sf_int8 ;... NU->salt sfirsit inc cs:corector ;... DA->sec. noua in corector cmp cs:corector,5 ;s-au adunat 5 sec. in corector ? jnz sec_noua ;... NU->tratez cazul de sec. noua mov cs:corector,0FFh ;... DA->aplic corectie dec cs:fract_secunda jmp sf_int8 sec_noua: mov cs:fract_secunda,0 ;resetez fractiunile de secunda

Indicatorul fract_secunda marchează fiecare „timer tick” scurs. Când se fac 18 de „timer ticks” avem o nouă secundă, la care indicatorul fract_secunda este resetat. Dar un „timer tick” este generat de 18,2 ori pe secună şi nu de 18 ori. De

Page 337: limbaj de asamblare-ivan

735

aceea trebuie aplicată o corecţie de 1 „timer tick” la fiecare 5 secunde. Pentru aceasta se foloseşte indicatorul corector care este incrementat de fiecare dată când indicatorul fract_secunda ajunge la 18. Când indicatorul corector ajunge la 5 (secunde), atunci din fract_secunda se scade un „timer tick” (corecţia) şi se amână tratarea noii secunde până la următorul „timer tick”. În acest caz, indicatorul corector este pus pe valoarea 0FFh pentru că la următorul „timer tick” fract_secunda devine iar 18 şi corector este iar incrementat. În felul acesta este adus la valoarea corectă 0 (resetare). Dacă nu s-a produs trecerea la o secundă nouă, atunci rutina se încheie aici. Dacă s-a produs trecerea la o secundă nouă, atunci trebuie tratat acest caz. Aceasta înseamnă că trebuie actualizat cronometrul, dacă e activ, şi afişat timpul în varianta solicitată de modul curent de lucru. Dacă este activ cronometrul, atunci în continuare trebuie incrementată sau decrementată locaţia cronometrului, în funcţie de tipul său şi apoi testat dacă a expirat timpul de funcţionare a cronometrului: cmp mod_lucru,4 ;testez modul de lucru jz afis ;mod=4 ->nu e cronometru cmp mod_lucru,3 jz crono_j ;mod=3 ->cronometru desc. inc cs:crono_crt ;mod=2 ->cronometru cresc. mov ax,cs:crono_max cmp ax,cs:crono_crt ;verific final jnz afis ;... NU->afisez cronometru crt. mov cs:mod_lucru,1 ;... DA->dezactivez (timp expirat) jmp afis ;afisez ultima sec. a cronom. crono_j: dec cs:crono_crt ;cronometru descrescator cmp cs:crono_crt,0 ;verific final jnz afis ;... NU->afisez cronometru crt. mov cs:mod_lucru,1 ;... DA->dezactivez (timp expirat) afis: ;afisare timp curent

După aceasta urmează afişarea timpului curent. De aici înainte, rutina apelează alte întreruperi. De aceea trebuie să fie verificate restricţiile de activare înainte de a putea merge mai departe. Problema întreruperii funcţiilor DOS sau a rutinelor de tratare a unei erori critice nu se pune deoarece nu sunt utilizate nici un fel de funcţii DOS, respectiv întreruperi care să genereze erori critice. Rămân restricţiile privind întreruperea unei rutine critice în timp şi problema activării multiple: cmp cs:activ,0 ;tentativa de activare multipla ? jnz rel_sf_int8 cmp cs:inbios,0 ;intrerupere rutina critica ? jnz rel_sf_int8

Page 338: limbaj de asamblare-ivan

736

Dacă este posibilă activarea afişării, atunci trebuie realizată comutarea contextului, care constă în comutarea stivei şi salvarea regiştrilor. DTA sau stiva programului întrerupt nu sunt alterate, deci nu trebuie să fie protejate. De asemenea, cursorul video nu este afectat, deci nu trebuie salvat, iar conţinutul ecranului nu este afectat decât prin cele 8 caractere ale ceasului, dar această zonă oricum nu se justifică a fi salvată. Comutarea contextului este realizată astfel: cli mov ax,ss ;salvez SS al prog. intrerupt mov cs:ss_int,ax mov cs:sp_int,sp ;salvez SP al prog. intrerupt mov ax,cs mov ss,ax mov sp,offset varf_stiva ;comut pe stiva TSR-ului curent mov cs:activ,1 ;marchez TSR-ul curent activ sti ;reactivez intreruperile push bx push cx push dx push es push bp push si push di ;salvez registrii contextului

Reactivarea întreruperilor este realizată deoarece partea de cod ce urmează necesită un timp de execuţie ceva mai mare. După salvarea contextului se mai impune o restricţie privind modul video care trebuie să fie text ca să se facă afişarea. Dacă această restricţie este satisfăcută, atunci se poate trece la pregătirea şirului ce va fi afişat şi apoi la afişarea propriu-zisă a acestuia: mov ah,0Fh int 10h ;det. mod video si pag. activa cmp al,3 jbe afiseaza cmp al,7 je afiseaza jmp sf_afiseaza ;mod video <> text, nu mai afisez afiseaza:

Pentru a determina cifrele orelor, minutelor şi secundelor indicate de cronometru se aplică două împărţiri succesive la 60 a numărului total de secunde conţinut de contorul cronometrului crono_crt, după care, fiecare rest, respectiv ultimul cât sunt împărţiţi la 10 pentru a determina cifrele în baza 10. Această extragere a cifrelor în baza 10 este făcută cu ajutorul macroinstrucţiunii următoare:

Page 339: limbaj de asamblare-ivan

737

det_cifre macro cifra_ofs ;;det. cifrele sec., min. sau orelor mov cl,10 div cl ;;impart nr. la 10 (se afla in AX) add ah,'0' ;;fac conversia la ASCII mov cs:ceascifra_ofs+2,ah ;;in AH se afla cifra unitatilor add al,'0' ;;fac conversia la ASCII mov cs:ceascifra_ofs,al ;;in AL se afla cifra zecilor endm

Ceasul de timp real este citit la fiecare secundă cu ajutorul serviciului 2 al întreruperii 1Ah, care returnează ora, minutul şi secunda în format BCD împachetat: mov ah,02h int 1Ah ;citesc ceasul de timp real jc defect ;CF=1 -> ceas defect det_cifre_BCD ch,0 ;determin cifrele orelor det_cifre_BCD cl,6 ;determin cifrele minutelor det_cifre_BCD dh,12 ;determin cifrele secundelor Pentru conversia la ASCII se foloseşte macroinstrucţiunea următoare: det_cifre_BCD macro reg,cifra_ofs ;;det. 2 cifre din BCD impachetat mov ah,0F0h and ah,reg ;;extrag semioctetul sup. din reg shr ah,1 shr ah,1 shr ah,1 shr ah,1 ;;mut semioctetul in partea inf. add ah,'0' ;;convertesc cifra la ASCII mov cs:ceascifra_ofs,ah ;;scriu cifra zecilor mov ah,0Fh and ah,reg ;;extrag semioctetul inf. din reg add ah,'0' ;;convertesc cifra la ASCII mov cs:ceascifra_ofs+2,ah ;;scriu cifra unitatilor endm

După afişarea efectivă a şirului de caractere astfel obţinut, rutina se încheie cu restaurarea contextului şi IRET. Partea nerezidentă a programului este formată dintr-un prim bloc ce verifică existenţa unei instalări precedente, urmat de modulul de instalare şi de modulul de dezinstalare. Verificarea existenţei unei instalări precedente se face prin metoda verificării semnăturii. În acest scop, în blocul de date al modulului rezident a fost prevăzută semnătura „CEAS/CRONOMETRU”. Se compară şirurile de 15 caractere de la deplasamentul semnatura, din cadrul adresei de segment a rutinei de tratare a întreruperii 88h, care este egală cu adresa de segment a programului

Page 340: limbaj de asamblare-ivan

738

rezident dacă acesta este instalat, respectiv din cadrul adresei de segment a programului curent. Dacă şirurile sunt identice, atunci programul este deja instalat şi se apelează modulul de dezinstalare, iar dacă şirurile diferă, atunci programul nu a fost instalat şi se apelează modulul de instalare: mov ax,cs mov ds,ax mov ax,3588h int 21h ;ES:BX=adresa rutina INT 88h mov di,offset semnatura mov si,di ;DI si SI = offset semnatura cld mov cx,lung_sem repe cmpsb ;compar sirurile DS:SI si ES:DI jne instalare ;daca diferite, instalare jmp dezinstalare ;daca identice, dezinstalare

Modulul de instalare salvează adresele rutinelor originale ale întreruperilor 8h, 10h şi 13h, după care deturnează întreruperile 8h, 10h, 13h şi 88h, apoi eliberează memoria ocupată de blocul de environment şi în final apelează funcţia TSR. Eliberarea blocului de environment se face ţinând cont de faptul că adresa sa de segment se găseşte la deplasamentul 2Ch în cadrul PSP al programului: mov es,cs:î2Chş ;adr. seg. bloc de environment mov ah,49h int 21h ;eliberare environment Calculul numărului de paragrafe ocupate de modulul rezident se face astfel: mov ax,offset nerezident ;det. nr. paragrafe modul rezident xor dx,dx mov cx,16 div cx ;AX=paragrafe,DX=octeti suplim. cmp dx,0 jz fix_paragraf ;DX=0 ->nr. fix de paragrafe add ax,1 ;DX>0 ->plus 1 paragraf incomplet fix_paragraf: mov dx,ax mov ax,3100h int 21h ;apel functie TSR

Modulul de dezinstalare dă posibilitatea utilizatorului să aleagă dacă să fie executată dezinstalarea, iar dacă acesta doreşte, atunci testează dacă este posibilă dezinstalarea. Pentru aceasta verifică dacă adresa de segment a rutinelor curente de tratare a întreruperilor 8h, 10h şi 13h este egală cu adresa de segment a

Page 341: limbaj de asamblare-ivan

739

programului rezident instalat, găsită prin intermediul întreruperii 88h. Dacă o singură rutină dintre cele menţionate are o adresă de segment diferită, atunci înseamnă că un alt program rezident a deturnat acea întrerupere şi în acest caz nu mai este posibilă dezinstalarea: mov cx,es ;CX=adr. seg. prog. rezident mov ax,3508h int 21h ;citire vector int. 8 mov ax,es ;ES=adr. seg. rutina crt. INT 8 cmp cx,ax jnz nu_posibil ;INT 8 deturnat de alt TSR mov ax,3510h int 21h ;citire vector int. 10h mov ax,es ;ES=adr. seg. rutina crt. INT 10h cmp cx,ax jnz nu_posibil ;INT 10h deturnat de alt TSR mov ax,3513h int 21h ;citire vector int. 13h mov ax,es ;ES=adr. seg. rutina crt. INT 13h cmp cx,ax jnz nu_posibil ;INT 13h deturnat de alt TSR Dacă dezinstalarea este posibilă, atunci se refac vectorii de întrerupere la vechile lor valori, memorate în zona de date a programului rezident instalat, după care este eliberată memoria ocupată de programul rezident: mov ds,es:int8_veche_seg mov dx,es:int8_veche_off mov ax,2508h int 21h ;restaurare int. 8 mov ds,es:int10h_veche_seg mov dx,es:int10h_veche_off mov ax,2510h int 21h ;restaurare int. 10h mov ds,es:int13h_veche_seg mov dx,es:int13h_veche_off mov ax,2513h int 21h ;restaurare int. 13h mov ax,0 mov ds,ax mov dx,0 mov ax,2588h int 21h ;dezactivare int. 88h mov ah,49h int 21h ;eliberare memorie TSR Modulul de dezinstalare se încheie prin apelul funcţiei DOS 4Ch.

Page 342: limbaj de asamblare-ivan

740

23.9 Concluzii

Din conţinutul acestui capitol se poate constata faptul că realizarea de

programe rezidente ridică probleme deosebite faţă de programele clasice. Se remarcă faptul că este necesară respectarea riguroasă a unor reguli suplimentare de programare, precum şi luarea în considerare a mai multor detalii tehnice privind sistemul de calcul şi sistemul de operare. În felul acesta programatorul are mai puţină libertate de acţiune, iar programul rezultat este mult mai strâns legat de mediul de operare pentru care a fost creat.

Programele rezidente sunt programe cu grad foarte redus de portabilitate. Ele pot rula doar pe acea clasă de maşini pentru care au fost realizate (în cazul de faţă este vorba de maşini de tip PC), ce folosesc una din versiunile sistemului de operare DOS. De asemenea, ele funcţionează doar în modul real al procesorului, specific pentru funcţionarea sistemului de operare DOS. Mai mult decât atât, este posibil ca domeniul de funcţionalitate al unui anumit program rezident să fie şi mai restrâns dacă acesta se foloseşte de caracteristici particulare ale unei anumite versiuni de sistem de operare sau de detalii tehnice specifice unei anumite maşini.

O altă menţiune ce trebuie făcută este aceea că nu orice program rezident funcţionează în bune condiţii, concomitent cu oricare alt program, fie el rezident sau nu. Acest lucru se datorează faptului că sistemul de operare DOS este monotasking şi nu a fost prevăzut să trateze conflictele ce pot apare între două procese active, care solicită aceleaşi resurse. Conflictele apar, de regulă, între un program rezident şi un alt program, rezident sau nu, care se foloseşte de anumite particularităţi tehnice, ocolind interfaţa standard oferită de sistemul de întreruperi.

La realizarea programelor rezidente este nevoie să se acorde o mai mare atenţie optimizării codului, deoarece, pe de o parte, un program rezident trebuie să ocupe un minimum de memorie internă, iar pe de altă parte, rutinele programului rezident trebuie să aibă timpi minimi de execuţie pentru a nu încărca prea mult procesorul cu operaţii suplimentare. Această problemă se pune cu atât mai mult atunci când programul rezident intervine asupra unor rutine critice ale sistemului.

Efortul suplimentar depus în realizarea programelor rezidente este justificat deoarece aceste programe oferă o îmbunătăţire a utilizării unor programe cu frecvenţă mare de utilizare, în condiţii de utilizare redusă a resurselor calculatorului, precum şi posibilitatea de a realiza lucruri imposibil sau foarte dificil de obţinut cu ajutorul programelor clasice.

24

Page 343: limbaj de asamblare-ivan

741

PROGRAMARE IN MODUL PROTEJAT 24.1 Moduri de operare ale procesoarelor 80x86

Existenţa celor trei moduri de lucru a procesoarelor 80x86: modul real, modul protejat şi modul virtual 8086, se datorează cerinţei de păstrare a compatibilităţii în rularea programelor de către procesoarele mai noi faţă de primele procesoare ale familiei. Astfel procesoarele 8086, 8088 şi 80186 rulau doar în mod real. Începând de la procesorul 80286 s-a introdus şi modul de operare protejat, pentru ca procesoarele 80386 şi 80486 să ruleze nativ în mod protejat cu facilităţi extinse de memorie virtuală, multitasking şi protecţie a spaţiului de adrese, păstrându-se pentru acestea şi modul real de operare. De asemenea, începând cu aceste procesoare s-a mai introdus un mod de funcţionare: modul virtual 8086.

Cele trei moduri de operare se caracterizează prin: Modul protejat – în acest mod sunt disponibile toate facilităţile oferite de

procesoarele 80386/80486: posibilitatea extinderii memoriei fizice prin alocarea de memorie virtuală pe hard-disk, sistem multitasking implementat hardware, protejarea contextului de execuţie al unui program de acţiunile celorlalte programe care se execută pe sistem.

Modul real – este modul de lucru nativ al procesoarelor 8086,8088 şi 80186. În acest mod de lucru memoria disponibilă pentru un program este limitată la 1Mb, instrucţiunile folosesc regiştri pe 16 biţi, modul de adresare a memoriei este bazat pe segmentare iar multitasking-ul nu este suportat de hardware. Procesoarele 80386/80486 pot emula acest mod de lucru pentru a permite programelor scrise în acest fel să ruleze. Pentru aceste procesoare, modul real este independent de modul protejat. La pornirea calculatoarelor echipate cu procesoare i386/i486 acestea intră în mod real deoarece programele BIOS sunt scrise pentru acest mod de lucru. Comutarea între modul real şi modul protejat se face explicit, procesoarele i386/i486 neputând lucra simultan în aceste două moduri.

Modul virtual 8086 – reprezintă tot o emulare a procesoarelor 8086, dar sub modul protejat. Astfel, procesoarele i386/i486, aflate în mod de lucru protejat, pot simula maşini virtuale 8086. Toate facilităţile modului protejat sunt disponibile şi sub modul virtual 8086. Un program scris să ruleze sub modul virtual 8086 este identic cu un program scris pentru un procesor 8086, dar este posibil multitasking-ul controlat hardware. Pentru a se realiza acest lucru este necesar să existe un program care să monitorizeze execuţia în modul virtual 8086 pentru iniţializare şi tratarea excepţiilor în stilul modului protejat.

24.2 Regiştrii procesoarelor i386/i486

Majoritatea regiştrilor procesoarelor i386/i486 sunt pe 32 de biţi, astfel încât regiştrii procesoarelor din generaţiile anterioare (8086, 80186, 80286) sunt

Page 344: limbaj de asamblare-ivan

742

subseturi ai acestora. Câţiva regiştri sunt accesaţi direct prin nume, ceilalţi fiind setaţi şi citiţi prin instrucţiuni specifice. În continuare sunt reluate unele elemente privind resursele sistemelor de calcul (regiştri, indicatori de stare) dar în contextul lucrului protejat.

Regiştrii de segment

Adresele de segment ale modului real şi selectorul de segment din modul protejat sunt stocate în 6 regiştri pe 16 biţi. Registrul selector de segment este folosit în modul protejat pentru a selecta descriptorul de segment definit în tabela descriptorilor.

15 0 CS DS ES SS FS GS

Figura 24.1 – Regiştrii de segment

Regiştrii de segment se iniţializează cu următoarele valori: Registrul CS – adresa segmentului de cod sau selectorul segmentului de

cod curent. Registrul SS – adresa segmentului de stivă sau selectorul segmentului

de stivă curent. Regiştrii DS, ES, FS, GS – adresa segmentelor de date sau selectorii

segmentelor de date ai programului. Registrul “Instruction Pointer”

Registrul pe 32 de biţi EIP (“Instruction Pointer”) stochează offset-ul

următoarei instrucţiuni de executat relativ la adresa de segment CS. Primii 16 biţi ai registrului EIP pot fi accesaţi sub numele de IP şi sunt folosiţi pentru adresarea pe 16 biţi.

Regiştrii de uz general

Regiştrii de uz general pe 32 de biţi sunt numiţi EAX, EBX, ECX, EDX, ESI, EDI, EBP şi ESP. Primii 16 biţi ai lor pot fi accesaţi sub numele de AX, BX, CX, DX, SI, DI, BP, şi respectiv SP şi sunt folosiţi de către programele pe 16 biţi pentru procesoarele din generaţiile inferioare. Octeţii regiştrilor AX, BX, CX, DX

Page 345: limbaj de asamblare-ivan

743

pot fi referiţi ca AH, AL, BH, BL, CH, CL, DH şi respectiv DL. Aceşti regiştri sunt folosiţi pentru manipularea datelor în instrucţiuni.

31 16

15 8

7 0

EAX AH A X AL EBX BH B X BL ECX CH C X CL EDX DH D X DL ESI SI EDI DI EBP BP ESP SP EIP IP

Figura 24.2 – Regiştrii de uz general

Registrul EFLAGS

Reprezintă extinderea pe 32 de bit a registrului FLAGS de la procesoarele 8086, primii 12 biţi având aceeaşi funcţie ca la procesoarele pe 16 biţi.

31 17 16 15 7 0 Rezer- vat

V R 0 N IO O D I T S Z 0 A 0 P 1 C

M F T PL F F F F F F F F F

Figura 24.3 – Registrul EFLAGS la procesoarele i386

31 18 17 16 7 0 Rezer- vat

A V R N IO O D I T S Z 0 A 0 P 1 C

C M F T PL F F F F F F F F F

Figura 24.4 – Registrul EFLAGS la procesoarele i486

Semnificaţia biţilor suplimentari este următoarea: IOPL (biţii 12-13) (Input/Output Privilege Level) indică prioritatea

execuţiei operaţiilor de intrare/ieşire. Această prioritate poate lua valori

Page 346: limbaj de asamblare-ivan

744

între 0 şi 3, valoarea 0 semnificând cel mai înalt nivel de prioritate iar valoarea 3 nivelul cel mai scăzut.

NT (bitul 14) (Nested Flag) dacă este setat task-ul curent este încapsulat într-un alt task

RF (bitul 16) (Resume Flag) este folosit împreună cu instrucţiunea de breakpoint pentru excepţii. Dacă este setat interzice tratarea de excepţii multiple pentru o instrucţiune.

VM (bitul 17) (Virtual-8086 mode) când este setat procesorul intră în modul virtual 8086. Acest bit poate fi setat doar de o instrucţiune IRET sau prin permutarea între task-uri în modul protejat.

Pentru procesoarele i486 se mai defineşte un flag: AC (bitul 18) (Alignment Check) forţează verificarea alinierii datelor.

Procesorul i486 generează o eroare de aliniament cu cod de eroare 0 în momentul în care întâlneşte date incorect aliniate. Doar programele care rulează cu prioritatea 3 pot genera eroare de aliniament. Acest bit este activat de bitul AM definit în registrul CR0.

Regiştrii de control

Procesoarele i386/i486 au trei regiştri de control: CR0, CR2 şi CR3, registrul CR1 este rezervat pentru utilizări viitoare. Aceşti trei regiştri stochează starea globală sau controlul operaţiilor procesoarelor i386/i486.

Formatul registrului de control al maşinii (CR0) este dat în figura 24.5

31 4 3 2 1 0 PG Rezervat ET TS EM MP PE

Figura 24.5 – Registrul CR0 al procesorului i386

31 30 29 18 16 5 4 3 2 1 0 PG CD NW AM WP NE ET TS EM MP PE

Figura 24.6 – Registrul CR0 al procesorului i486

Semnificaţia biţilor este următoarea: PG (bitul 31) (Paging Enable) când este setat permite folosirea

modelului de memorie bazat pe pagini. ET (bitul 4) (Processor Extension Type) când este setat se foloseşte

modul de operare pe 32 de biţi compatibil 80387, altfel este folosit

Page 347: limbaj de asamblare-ivan

745

modul pe 16 biţi compatibil 80287 pentru funcţionarea coprocesorului matematic. La procesoarele i486 este întotdeauna setat.

TS (bitul 3) (Task Switched) este automat setat atunci când se comută între task-uri, pentru a permite coprocesorului să-şi salveze contextul în timpul comutării.

EM (bitul 2) (Emulate Coprocessor) când este setat generează excepţia “coprocesor indisponibil”. Instrucţiunea WAIT ignoră setarea acestui bit.

MP (bitul 1) (Math Present) când sunt setaţi biţii TS şi MP, execuţia instrucţiunii WAIT sau a instrucţiunilor de coprocesor generează excepţia “coprocesor indisponibil”.

PE (bitul 0) (Protection Enable) când este setat procesorul funcţionează în mod protejat, altfel lucrează în mod real.

Procesoarele i486 mai definesc următorii biţi: CD (bitul 30) (Cache Disabled) când este setat umplerea memoriei

cache internă a procesorului este dezactivată. NW (bitul 29) (Not Write-Through) controlează procesul de

reîmprospătare a memoriei cache, împreună cu bitul CD. AM (bitul 18) (Alignment Mask) controlează verificarea alinierii. Când

este setat şi prioritatea este 3, procesorul verifică alinierea potrivit cu valoarea bitului AC definit în EFLAGS.

WP (bitul 16) (Write Protect) când memoria este organizată la nivel de pagini, procesele aflate pe nivele prioritare pot citi şi scrie pagini aflate pe nivele neprioritare. La procesoarele i486, dacă acest bit este setat, paginile nivelelor neprioritare sunt read-only.

NE (bitul 5) (Numeric Error) datorită faptului că procesorul i486 conţine coprocesor matematic, poate efectua operaţii în virgulă mobilă. Când acest bit este setat procesorul generează o eroare nemascată prin excepţia 16 pentru a reporta o eroare de calcul în virgulă mobilă.

Registrul CR2 (Page Fault Linear Address Register) stochează adresa pe 32 de biţi care a cauzat o eroare de pagină.

Registrul CR3 (Page Directory Base Address Register) conţine adresa fizică de referinţă a tabelei de pagini, atunci când mecanismul de paginare a memoriei este activat. Primii 12 biţi ai acestui registru sunt întotdeauna 0 deoarece dimensiunea unei pagini este de 4K. În cazul procesoarelor i386 aceşti biţi sunt rezervaţi.

Registrul adreselor de sistem

Procesoarele i386/i486 creează următoarele tabele de sistem atunci când lucrează în mod protejat:

GDT (Global Descriptor Table) – Tabela descriptorilor globali LDT (Local Descriptor Table) – Tabela descriptorilor locali

Page 348: limbaj de asamblare-ivan

746

IDT (Interrupt Description Table) – Tabela descriptorilor întreruperilor

Regiştrii GDTR şi IDTR stochează adresa liniară şi limita tabelelor CDT şi IDT. Registrul LDTR stochează selectorul de segment al tabelei LDT curente, iar partea “invizibilă”, figura 24.7 (aria haşurată din figură stochează adresa de bază şi limita acestei tabele)

Figura 24.7 – Utilizarea regiştrilor LDTR şi TR Registrul proceselor (TR – task register) stochează selectorul de segment al

segmentului de stare (TSS – task state segment) pentru procesul curent. În segmentul de stare se salvează contextul procesului curent pe timpul comutării între task-uri. Partea invizibilă a registrului TR stochează adresa de bază şi limita segmentului de stare a procesului (TSS).

Registrul descriptorului de segment

Pentru fiecare registru de segment există câte un registru de descriptor de segment. Aceşti descriptori sunt “invizibili” şi nu pot fi accesaţi în nici un fel. Când se încarcă un selector de segment într-un registru de segment, descriptorul asociat definit în tabela descriptorilor se încarcă în registrul descriptor asociat. Aceşti regiştri permit referirea mai rapidă a segmentelor şi contribuie la implementarea mecanismului de protecţie la memoria segmentului.

Structuri de date ale modului protejat

Mediul de execuţie a programelor în modul protejat diferă semnificativ de execuţia lor în modul real. Pentru a lansa în execuţie un program în mod protejat este necesară construirea de structuri de date ale sistemului.

Tabela de descriptori globali, GDT este un vector de descriptori pe 8 octeţi şi este necesară pentru toate programele care se execută în mod protejat. Segmentele de date, de cod şi de sistem sunt definite în modul protejat. Fiecare descriptor de segment are propriul lui format după cum urmează, figura 24.8.

31 16 15 0 I/O Map Base Reserved 64 Reserved LDT 60 Reserved GS 5C Reserved FS 58

LDTR (selector) Base Register Limit Register

TR (selector) Base Register Limit Register

Page 349: limbaj de asamblare-ivan

747

Reserved DS 54 Reserved SS 50 Reserved CS 4C Reserved ES 48 EDI 44 ESI 40 EBP 3C ESP 38 EBX 34 EDX 30 ECX 2C EAX 28 EFLAGS 24 EIP 20 CR3 1C Reserved SS2 18 ESP2 14 Reserved SS1 10 ESP1 0C Reserved SS0 8 ESP0 4 Reserved Back link to previous TSS 0

Figura 24.8 – Formatul segmentului de stare a proceselor(TSS) Base 31..24

G B 0 V Limit 19..16

P DPL 1 0 E W A Base 23..16

4

Segment Base 15..0 Segment Limit 15..0 0 G – Granulaţia V – Disponibil pentru utilizare DPL – Nivelul de prioritate al descriptorului W – Permite scriere

B – Segment mare P – Existenţa segmentului E – Expandare A – Accesat

Figura 24.9 – Formatul descriptorului segmentului de date

Base 31..24

G D 0 V Limit 19..16

P DPL 1 1 C R A Base 23..16

4

Segment Base 15..0 Segment Limit 15..0 0

Page 350: limbaj de asamblare-ivan

748

D – dimensiunea implicită a operaţiilor

R – permite citirea C – bit de conformitate

Figura 24.10 – Formatul descriptorului segmentului de cod

Rezervat P DPL 0 0 1 0 1 Rezervat 4 Segment Base 15..0 Segment Limit 15..0 0

Figura 24.11 – Formatul descriptorului “Task Gate” Offsetul în segment 31..16 P DPL 0 X 1 0 0 0 0 0 Count 4 Segment Base 15..0 Segment Limit 15..0 0

Figura 24.12 – Formatul descriptorului “Call Gate” Offsetul în segment 31..16 P DPL 0 X 1 1 1 T 0 0 Rezerved 4 Segment Base 15..0 Segment Limit 15..0 0 T – 0 : “interrupt gate”, 1: “trap gate”

Figura 24.12 – Formatul descriptorului “Interrupt/Trap Gate” Base 31..24

G 0 0 V Limit 19..16

P DPL 0 0 0 1 0 Base 23..16

4

Segment Base 15..0 Segment Limit 15..0 0

Figura 24.13 – Formatul descriptorului LDT Base 31..24

G 0 0 V Limit 19..16

P DPL 0 X 0 B 1 Base 23..16

4

Segment Base 15..0 Segment Limit 15..0 0 X – 0: procesor 80286, 1:procesor i386 B – busy

Figura 24.14 – Formatul descriptorului segmentului sistem

Instrucţiuni

Procesoarele i486 suportă toate instrucţiunile procesorului i386, punând la dispoziţie încă şase instrucţiuni.

Page 351: limbaj de asamblare-ivan

8

Instrucţiuni utilizator BSWAP (Byte Swap) această instrucţiune inversează ordinea datelor

stocate într-un registru pe 32 biţi. XADD (Exchange and Add) această instrucţiune încarcă datele din

operandul destinaţie în operandul sursă, calculează suma între valoarea originală a operandului sursă şi operandul destinaţie şi o stochează în operandul destinaţie.

CMPXCHG (Compare and Exchange) această instrucţiune compară datele stocate în acumulator cu operandul destinaţie, dacă sunt egale încarcă operandul sursă în operandul destinaţie, altfel încarcă datele din acumulator în operandul destinaţie.

Instrucţiuni de sistem

INVD (Invalidate Cache) această instrucţiune goleşte memoria cache

internă şi trimite semnal către memoria cache externă pentru golire. INVLPG (Invalidate TLB Cache) instrucţiunea invalidează o intrare din

TLB. WBINVD (Write-Back and Invalidate Cache) această instrucţiune

goleşte memoria cache internă şi trimite semnale de write-back şi de golire către cea externă.

24.3 Moduri de gestiune a memoriei

Procesoarele i386/i486 suportă două tipuri de gestiune a memoriei; segmentarea şi paginarea. Ambele mecanisme oferă protecţie pentru un program împotriva interacţiunilor nedorite cu alte programe. Exemplul clasic este al sistemului de operare care trebuie protejat împotriva funcţionarii aleatorii a programelor de aplicaţie.

Prin mecanismul de segmentare, procesorul converteşte adresa logică de tip segment:offset în adresă liniară. Apoi, prin mecanismul de paginare converteşte adresa liniară în adresă fizică, atunci când paginarea este activă. Adresa liniară coincide cu adresa fizică atunci când paginarea nu este activă. Mecanismul de paginare este folosit pentru extinderea memoriei fizice prin alocarea de memorie virtuală pe hard-disk.

Mecanismul de segmentare

Segmentarea oferă o modalitate simplă dar nestructurată de gestiune a memoriei. Un program poate avea câteva spaţii de adresă protejate independente. Un segment este un bloc de memorie a cărei dimensiune este variabilă. Dimensiunea segmentelor folosite de către un program poate fi oricât de mare, cu condiţia să nu depăşească memoria totală disponibilă în sistem. Înainte de a accesa

Page 352: limbaj de asamblare-ivan

9

un segment trebuie setate adresa liniară de bază a segmentului, dimensiunea, tipul segmentului şi atributele acestuia.

Atunci când procesorul accesează un segment, verifică definirea acestuia din descriptorul de segment pentru a asigura faptul că accesul nu încalcă regulile de definire ale segmentului. Această procedură previne interacţiunea dintre segmente sau programe diferite. Politica de alocare a segmentelor depinde de sistemul de operare care rulează pe maşină. Mai multe segmente diferite pot împărţi acelaşi bloc de segment prin definirea descriptoarelor cu aceeaşi adresă de bază şi aceeaşi dimensiune.

Mecanismul de translatare a adresei la segmentare

În modul protejat selectorul de segment punctează către descriptorul de segment. Fiecare selector de segment trebuie să aibă o intrare în tabela descriptorilor(fie în cea globală – GDT, fie în cea locală – LDT). Fiecare referinţă de memorie este asociată cu un selector de segment. Descriptorul de segment trebuie să includă toate informaţiile despre segment. Procesorul i386/i486 obţine adresa liniară de bază din descriptorul corespunzător segmentului la care adaugă deplasamentul pentru a forma o adresă liniara pe 32 de biţi. În acelaşi timp procesorul verifică tipul segmentului, existenţa acestuia şi dimensiunea segmentului. Se generează o excepţie în cazul în care referinţa încalcă definiţia din descriptor, de exemplu, dacă deplasamentul este dincolo de limita segmentului. Limita maximă pentru fiecare segment este de 1Gb (dacă bitul granulaţie este setat pe valoarea 0) sau 4 Gb (dacă bitul granulaţie este setat pe valoarea 1).

Cei mai puţin semnificativi biţi ai selectorului de segment sunt consideraţi cu valoarea 0 în timpul indexării în tabela descriptorilor. Procesorul interpretează aceşti trei biţi, figura 24.15.

15 3 2 1 0 TI RPL

TI – indicator de tabelă RPL (Requested Privilege Level) – nivelul de prioritate solicitat

Figura 24.15 –Selectorul de segment Dacă TI = 0 descriptorul se selectează din GDT, dacă TI = 1 descriptorul se

selectează din LDT. Paginarea

Spre deosebire de segmentare, paginarea împarte memoria sistemului în

blocuri de memorie de dimensiune fixă, fiecare bloc fiind numit pagină. Pe sistemele i386/i486, o pagină ocupa 4Kb. O adresă liniară, obţinută prin

Page 353: limbaj de asamblare-ivan

10

translatarea la segmentare dintr-o adresă utilizator de tip segment:offset, este translatată la adresa fizică corespunzătoare prin mecanismul de traslatare a adresei la paginare, atunci când paginarea este activată.

Prin mecanismul de translatare, o parte a capacităţii de stocare a hard disk-ului poate fi tratată ca spaţiu de adrese de memorie. Atunci când procesorul trebuie să acceseze o pagină care nu se află în memoria fizică, generează o excepţie pentru a permite sistemului de operare să încarce pagina cerută, apoi reia execuţia din punctul de întrerupere. 24.4 Comutarea în modul protejat

Intrarea în modul protejat

La pornirea unui sistem i386 sau i486 programul de boot se rulează în mod real. Pentru a rula programe în mod protejat trebuie executate câteva proceduri pentru a comuta sistemul din mod real în mod protejat. Aceste proceduri se rulează în mod real şi trebuie să iniţializeze structurile de date şi regiştrii specifici modului protejat.

Cea mai importantă structură care trebuie iniţializată este tabela descriptorilor globali (GDT). Această structură trebuie să aibă definite cel puţin un descriptor de segment pentru segmentul de cod şi un descriptor de segment pentru segmentul de date pentru a permite accesarea instrucţiunilor şi a datelor conform cu regulile din modul protejat. De asemenea adresa de bază şi limita de adresă a tabelei GDT trebuie încărcate în registrul GDTR înainte de intrarea în modul protejat.

Dacă se doreşte lucrul cu vectori de întreruperi mai trebuie construită şi încărcată tabela descriptorilor de întreruperi (IDT). În acest caz adresa de bază şi limita de memorie a tabelei IDT se încarcă în registrul IDTR.

După setarea acestor structuri se poate intra în modul protejat prin setarea bitului PE în registrul CR0. După setarea acestui bit se execută o instrucţiune de salt necondiţionat pentru golirea cozii de instrucţiuni.

La intrarea în modul protejat toţi regiştrii de segment conţin valori setate în modul real. De aceea programul trebuie să reîncarce selectorul de segment cu regiştrii de segment. În acest moment programul a comutat în modul protejat cu nivelul de prioritate 0.

Ieşirea din modul protejat

Procesorul i386 poate reintra în modul real prin setarea bit-ului PE al registrului CR0 la valoarea 0. Pentru a continua execuţia programului în mod real anumite structuri de date ale sistemului trebuie resetate pentru a permite programului să ruleze în mediul de execuţie al modului real. Pentru reintrarea în modul real se execută următorii paşi:

Se setează prioritatea de execuţie a procesului curent la valoarea 0 – prioritatea modului real.

Page 354: limbaj de asamblare-ivan

11

Se schimbă limita segmentului CS la valoarea 0ffffh prin transferarea controlului către un segment de cod cu limita 0ffffh – limita segmentului de cod CS în mod real.

Se încarcă toţi regiştrii de segment cu excepţia registrului CS cu un selector de segment pentru care descriptorul este de forma: DPL = 0, writable = 1, expand = 0, present = 1, granular = 0, big = 0 şi limit = 0ffffh.

Se dezactivează întreruperile. Se setează bitul PE din registrul CR0 la valoarea 0. Se execută o instrucţiune de salt necondiţionat pentru a goli coada de

instrucţiuni. Se setează registrul IDTR pentru a puncta către tabela vectorilor de

întrerupere a modului real. Se activează întreruperile. Programul se află acum în mod real. Dacă este nevoie se reiniţializează

regiştrii de segment în modul real. Descriptorii segmentelor de cod şi de date pentru modul real sunt:

Reintrarea în modul real se realizează astfel : ;program în modul protejat executat cu prioritatea 0 db 0eah ;salt neconditionat dw offset real_l ;ip dw code_sel ;selector cs real_l: mov ax,dmy_selec ;selector fictiv mov es,ax ;resetarea registrilor de segment mov ds,ax

;formatul descriptorului segmentului de cod de nivel 0 code_sel dw 0ffffh ;limita(0-15) dw ? ;adresa de baza(0-15) db ? ;adresa de baza(16-23) db 9ah ;present=1,readable=1 db 0 ;limit a(16-19), G=0, D=0 db 0 ;baza(24-31) ;formatul descriptorului segmentului de date dmy_selec dw 0ffffh ;limita(0-15) dw ? ;adresa de baza(0-15) db ? ;adresa de baza(16-23) db 92h ;present=1, writable=1 db 0 ;limita(16-19), G=0, D=0 db 0 ;baza(24-31)

Page 355: limbaj de asamblare-ivan

12

mov fs,ax mov gs,ax mov ss,ax cli ;dezactivarea intreruperilor mov eax,cr0 and eax,not prot_enable ;dezactivarea modului protejat mov cr0,eax db 0eah ;salt neconditionat dw offset real_mode ;EIP dw code ;CS read_mode: mov ax,data mov ds,ax lidt [oldidt] ;reincarcarea tabelei de ;intreruperi pentru modul real sti ;reactivarea intreruperilor

Exemplu

Următorul exemplu demonstrează comutarea între mod real şi modul

protejat. Acest program pregăteşte structurile de date de bază pentru intrarea în modul protejat. Programul nu include paginare, multitasking sau protecţie în modul protejat.

În cadrul acestui program se folosesc două fişiere incluse: STRUCT –defineşte majoritatea structurilor de date folosite în modul

protejat MACRO1 –defineşte câteva macrodefiniţii folosite în programul

principal.

Fişierul STRUCT: ;Structura descriptorului de segment dscp struct D_lim1 dw 0 D_base1 dw 0 D_base2 db 0 D_type db 0 D_lim2 db 0 D_base3 db 0 dscp ends ;Structura stivei dupa intreruperea procesului în mod real stkdef struct oldeip dw 0 dw 0

Page 356: limbaj de asamblare-ivan

13

oldcs dw 0 dw 0 oldflg dw 0 dw 0 oldsp dw 0 dw 0 oldss dw 0 dw 0 oldes dw 0 dw 0 oldds dw 0 dw 0 oldfs dw 0 dw 0 oldgs dw 0 dw 0 stkdef ends ;Structura tabelei de pagini page_tbl struc pg_stat db ? pg_avail db ? pg_limit db ? page_tbl ends Fişierul MACRO1 ;Macrodefinitie pentru definirea stivei în TSS TSS_stack macro ss0,esp0,ss1,esp1,ss2,esp2 dd 0 dd offset esp0 dd ss0 dd offset esp1 dd ss1 dd offset esp2 dd ss2 endm ;Macrodefinitie pentru definirea CR3 în TSS TSS_cr3 macro dd 0 endm ;Macrodefinitie pentru definirea registrilor generali în TSS TSS_regs macro teip,tflg,teax,tebx,tecx,tedx,tesi,tedi,tebp,tesp dd offset teip dd tflg

Page 357: limbaj de asamblare-ivan

14

dd teax dd tecx dd tedx dd tebx dd offset tesp dd tebp dd tesi dd tedi endm ;Macrodefinitie pentru definirea registrilor de segment în TSS TSS_seg macro tes,tcs,tss,tds,tfs,tgs dd tes dd tcs dd tss dd tds dd tfs dd tgs endm ;Macrodefinitie pentru salt far neconditionat callf macro selector db 9ah dw 0 dw selector endm Program: EN.ASM

Acest program demonstrează trecerea din modul real al sistemului de operare DOS în modul protejat şi reîntoarcerea din modul protejat în modul real DOS. Programul setează, mai întâi, structurile de date ale modului protejat (GDT, IDT şi TSS) apoi intră în modul protejat, unde afişează un mesaj, după care părăseşte modul protejat şi restaurează contextul modului real. Paşii de execuţie ai programului sunt:

Pasul 0: Definirea EQU.

Pasul 1: Definirea tabelei descriptorilor globali (GDT).

Tabela descriptorilor locali nu este folosită în acest program, astfel încât toţi

descriptorii sunt definiţi în tabela descriptorilor globali. În această tabelă se defineşte doar dimensiunea şi tipul fiecărui descriptor, adresa de bază urmând a fi setată la momentul execuţiei.

Page 358: limbaj de asamblare-ivan

15

Primul descriptor trebuie să fie un descriptor NULL. Descriptorul segmentului video, neiniţializat la momentul execuţiei, are

adresa de bază B8000h.

Pasul 2: Definirea tabelei de întreruperi(IDT).

În acest program se definesc 21 de întreruperi în IDT. Selectorul de segment a acestor întreruperi este int_selec. Offset-ul întreruperilor este iniţializat la momentul execuţiei.

Pasul 3: Definirea variabilelor.

pGDT este un pointer către o structură de şase octeţi conţinând adresa

de bază şi dimensiunea GDT. pIDT este un pointer către o structură de şase octeţi conţinând adresa de

bază şi dimensiunea IDT. pold este un pointer către o structură de şase octeţi conţinând adresa de

bază şi dimensiunea tabelei vectorilor de întrerupere definiţi în modul real DOS.

Pasul 4: Definirea tabelei de mapare pentru fiecare selector către

segmentul corespunzător. Această tabelă conţine acei selectori pentru care adresa de bază trebuie

iniţializată în descriptor. Segmentul corespunzător este definit folosind selectorul asociat.

gdt_tab-size conţine numărul de intrări ale tabelei GDT.

Pasul 5: Definirea mesajelor.

Pasul 6: Definirea segmentelor de stivă de nivel 0, 1 şi 2.

Pasul 7: Setarea TSS.

Pasul 8: Definirea unui segment fictiv folosit pentru obţinerea valorii registrului descriptor de segment la întoarcerea în modul real.

Pasul 9: Iniţializarea punctului de intrare pentru fiecare descriptor de

întrerupere definit în IDT. Pentru fiecare rutină de întrerupere se rezervă 4 octeţi într-un vector în memorie.

Page 359: limbaj de asamblare-ivan

16

Pasul 10: Programul obţine adresa liniară de bază (32 de biţi) şi dimensiunea pentru GDT şi IDT şi le stochează.

Pasul 11: Pe baza adresei de segment definită în tabela gdt_phys_tab obţine

adresa liniară de bază şi o setează în descriptorul corespunzător selectorului de segment.

Pasul 12: Comută în modul protejat:

Încarcă adresa liniară de bază şi dimensiunea GDT în registrul GDTR Încarcă adresa liniară de bază şi dimensiunea IDT în registrul IDTR. Setează bitul PF în registrul CR0. Apelează o instrucţiune de salt necondiţionat pentru a goli coada de

instrucţiuni.

Pasul 13: Setează regiştrii LDTR, SS, SP, DS, ES, FS şi GS.

Pasul 14: Afişează mesajul pe ecran.

Datorită faptului că funcţiile de sistem DOS nu sunt disponibile în modul protejat, programul scrie mesajul direct în memoria video, în secvenţa:

scrie spaţii în memoria video pentru a şterge ecranul afişează mesajul

Pasul 15: Încarcă selectorul segmentului procesului curent în registrul

proceselor (TS)

Pasul 16: Apelează o întrerupere software prin intermediul descriptorilor de întreruperi definiţi în IDT. Rutina întreruperii afişează numărul întreruperii şi comută înapoi în modul real DOS. Fişierul EN.ASM .386p include struct include macro1 ;Pasul 0: definire EQU INTNO equ 21 DSCPSIZE equ 8 INTSIZE equ 4 TWO equ 2 prot_enable equ 01h

Page 360: limbaj de asamblare-ivan

17

attribute equ 07h space equ 20h ;Pasul 1: GDT GDT segment para public use16 ‘GDT’ gdt_tab label qword null_selec equ $-gdt_tab dscp <,,,,,> code_selec equ $-gdt_tab ;descriptorul selectorul

;segmentului de dscp <0ffffh,,,09h,,> ;cod task0_TSS_selec equ $-gdt_tab ;descriptorul selectorului dscp <task0_TSS_limit,,,089h,,> ;segmentului TSS stk0_selec equ $-gdt_tab ;descriptorul selectorului dscp <stk0_limit,,,92h,,> ;segmentului de stiva de nivel 0 stk1_selec equ $-gdt_tab or 1 ;descriptorul selectorului dscp <stk1_limit,,,0b2h,,> ;segmentului de stiva de

;nivel 1 stk2_selec equ $-gdt_tab or 2 ;descriptorul selectorului dscp <stk2_limit,,,0d2h,,> ;segmentului de stiva de

;nivel 2 dmy_selec equ $-gdt_tab dscp <0ffffh,,,92h,,> video_selec equ $-gdt_tab or 3 dscp <0ffffh,8000h,0bh,0f2h,,> gdata_selec equ $-gdt_tab dscp <gdata_limit,,,0f2h,,> int_selec equ $-gdt_tab dscp <0ffffh,,,09ah,,> gdt_limit equ $-gdt_tab GDT ends ;Pasul 2: IDT IDT segment para public use 16 ‘idt’ idt_tab equ $ REPT INTNO dscop <,int_selec,0,0eeh,,> ENDM

Page 361: limbaj de asamblare-ivan

18

idt_limit equ $ IDT ends ;Segmentul de date Gdata segment para public use16 ‘Gdata’ ;Pasul 3: Definirea variabilelor pGDT label fword pGDT_limit dw ? pGDT_addr dd ? pIDT label fword pIDT_limit dw ? pIDT_addr dd ? pold label fword dIDT_limit dw 03ffh dIDT_addr dd 0 ;Pasul 4: tabela maparilor intre descriptori şi segmente gdt_phys_tab label word dw task0_TSS_selec dw task0_TSS dw stk0_selec dw stk0 dw stk1_selec dw stk1 dw stk2_selec dw stk2 dw dmy_selec dw dmy dw code_selec dw code dw gdata_selec dw gdata dw int_selec dw code gdt_tab_size equ ($ - gdt_phys_tab) / 4 ;Pasul 5: Definirea mesajelor in_protected db ‘MOD PROTEJAT’,0 int_msg db ‘Intreruperea:’ int_num dw ? db ‘H’,0 Gdata_limit equ $ Gdata ends

Page 362: limbaj de asamblare-ivan

19

;Pasul 6: Definirea segmentelor de stiva de nivel 0, 1 şi 2 stk0 segment para public use16 ‘stk0’ db 100h dup(0) stk0_limit equ $ stk0 ends stk1 segment para public use16 ‘stk1’ db 100h dup(0) stk1_limit equ $ stk1 ends stk2 segment para public use16 ‘stk2’ db 100h dup(0) stk2_limit equ $ stk2 ends ;Pasul 7: TSS task0_TSS segment para public use16 ‘task0’ TSS_stack stk0_selec,stk0_limit,stk1_selec, stk1_limit,stk2_selec,stk2_limit TSS_cr3 0 TSS_regs 0,0,0,0,0,0,0,0,0,stk0_limit TSS_seg gdata_selec,code_selec,stk0_selec, gdata_selec,gdata_selec,gdata_selec dd 0 ;LDT dw 0 ;task trap flag dw 68h ;adresa de I/O task0_TSS_limit equ $ task0_TSS ends ;Pasul 8: Segmentul fictiv dmy segment para public use16 ‘dmy’ db 128 dup(0) dmy ends ;Segmentul de cod code segment para public use16 ‘code’ assume cs:code,ds:gdata main proc far mov ax,gdata mov ds,ax ;Pasul 9: Initializarea IDT mov ax,IDT mov es,ax mov di,offset idt_tab

Page 363: limbaj de asamblare-ivan

20

mov ax,offset int_entry mov cx,INTNO fillidt: mov es:[di],ax add di,DSCPSIZE axx ax,INTSIZE loop fillidt ;Pasul 10: Obtinerea adresei şi dimensiunii GDT/IDT

mov ax,offset gdt_kimit mov pGDT_limit,ax xor eax,eax

mov ax,GDT shl eax,4 mov pGDT_addr,eax mov ax,offset idt_limit mov pIDT_limit,ax xor eax.eax mov ax,idt shl eax,4 mov pIDT_addr,eax

;Pasul 11:Pe baza gdt_phys_tab se seteaza adresa ;de baza pentru fiecare descriptor mov ax,GDT mov es,ax mov si,offset gdt_phys_tab mov cx,gdt_tab_size bdt1: lodsw mov bx,ax and bx,0fff8h lodsw push ax shl ax,4 mov es:[bx][d_base1],ax pop ax shr ax,12 mov es:[bx],d_base2],al loop bdt1 ;Pasul 12: comutarea în mod protejat

Page 364: limbaj de asamblare-ivan

21

cli

lgdt [pGDT] lidt [pIDT] mov eax,cr0 or al,prot_enable mov cr0,eax jmp dword ptr cs:[enter_prot]

enter_prot: dw offset now_in_prot dw code_selec ;Pasul 13: Executie în mod protejat ; - setare LDTR,SS,SP,DS,ES,FS,GS now_in_prot: xor ax,ax lidt ax mov ax,stk0_selec mov ss,ax mov sp,offset stk0_limit

mov ax,gdata_selec mov ds,ax mov es,ax mov fs,ax mov gs,ax

;Pasul 14: Afisarea mesajului mov ax,video_selec mov es,ax mov cx,4000h xor di,di mov ah,attribute mov al,space rep stosw mov si,offset in_protected mov di,320 call disp_it ;Pasul 15: Incarcarea TSS în registrul TR mov ax,task0_TSS_selec ltr ax

Page 365: limbaj de asamblare-ivan

22

;Pasul 16: Intoarcerea în modul real DOS int 20 int_entry: REPT INTNO call disp iret ENDM disp: pop ax mov bx,gdata_selec mov ds,bx sub ax,offset int_entry shr ax,TWO mov si,offset int_num mov cx,TWO call htoa mov si,offset int_msg mov di,5*160 call disp_it cli mov ax,dmy_selec mov es,ax mov ds,ax mov fs,ax mov gs,ax mov ss,ax mov eax,cr0 and eax,not prot_enable mov cr0,eax db 0eah dw offset next_instruction dw code next_instruction: mov ax,Gdata mov ds,ax mov ax,stk0 mov ss,ax mov sp,offset stk0_limit lidt [pold] sti mov ax,4c00h

Page 366: limbaj de asamblare-ivan

23

int 21h main endp disp_it proc near mov ax,video_selec mov es,ax mov ah,attribute disp_itl: lodsb stosw cmp al,0 jne disp_itl ret disp_it endp htoa_tab db ‘0123456789ABCDEF’ htoa proc near xor ebx,ebx add si,cx dec si htoal: mov bl,al and bl,0fh mov bl,cs:[htoa_tab][ebx] mov byte ptr [esi],bl dec esi shr eax,4 loop htoal ret htoa endp code ends end main 24.5 Implementarea modului de lucru multitasking

În sistemele multitasking, este necesară salvarea stării maşinii în timpul comutării între procese. Procesoarele i386/i486 suportă această operaţie în mod nativ, putând comuta rapid între două procese. Atunci când este solicitată comutarea între procese, procesorul salvează starea procesului curent în segmentul de stare a procesului (TSS), încarcă contextul noului proces din segmentul său de stare, verifică respectarea integrităţii datelor folosite de acesta şi începe execuţia noului proces.

Comutarea între procese poate fi invocata printr-o instrucţiune de tip CALL sau JMP către un descriptor de segment TSS sau către un descriptor de tip “Task Gate”. De asemenea mai poate fi invocată prin intermediul unei întreruperi prin iniţializarea unui element din IDT cu descriptorul unui proces. Un descriptor de

Page 367: limbaj de asamblare-ivan

24

segment TSS poate fi stocat doar în GDT. Dacă este invocată o comutare către un proces al cărui descriptor este stocat în LDT, procesorul generează o excepţie.

Dacă a fost invocată o comutare prin intermediul unei instrucţiuni CALL sau INT, întoarcerea către procesul iniţial se realizează printr-o instrucţiune IRET.

Exemplul care urmează realizează comutarea între două procese numite: “task0” şi ”task1” folosind o instrucţiune JMP către descriptorul TSS al procesului “task1”. După ce se realizează comutarea, procesul “task1” realizează întoarcerea către procesul “task0” folosind o întrerupere software. Această întrerupere are descriptorul definit în IDT fiind ceea ce se numeşte un descriptor de tip “task gate” către descriptorul TSS al procesului “task0”. Program: mult.asm

Mai întâi se definesc cele două procese: TSS0 dd 0 ; dd 0,0 ;esp0,ss0 dd 0,0 ;esp1,ss1 dd 0,0 ;esp2,ss2 dd 0 ;cr3 dd 0 ;eip dd 0 ;eflags dd 0,0,0,0,0,0,0,0 ;eax,ecx,ebx,edx,esp,ebp,esi,edi dd 0,0,0,0,0,0 ;es,cs,ss,ds,fs,gs dd 0 ;LDT TSS0_limit equ $ TSS1 dd 0 dd task1_esp0,task1_ss0 ;esp0,ss0 dd task1_esp1,task1_ss1 ;esp1,ss1 dd task1_esp2,task1_ss2 ;esp2,ss2 dd 0 ;cr3 dd task1_eip ;eip dd task1_eflags ;eflags dd task1_eax,task1_ecx,task1_ebx,task1_edx ;eax,ecx,ebx,edx

dd task1_esp,task1_ebp,task1_esi,task1_edi ;esp,ebp,esi,edi dd task1_es,task1_cs,task1_ss,task1_ds ;es,cs,ss

dd task1_ds,task1_fs,task1_gs ;ds,fs,gs dd 0 TSS1_limit equ $

Apoi se definesc descriptorii pentru cele două procese: task0_TSS_selec label word dw TSS0_limit dw TSS0_base1

Page 368: limbaj de asamblare-ivan

25

db TSS0_base2 db 89h db 0 db 0 task1_TSS_selec label word dw TSS1_limit dw TSS1_base1 db TSS1_base2 db 89h db 0 db 0

Faţă de exemplul precedent mai intervin următorii paşi suplimentari:

Pasul 1.1 Definirea descriptorului, selectorului segmentului de cod şi TSS pentru procesul “task1”: task1_TSS_selec equ $-gdt_tab or 1 dscp <task1_TSS_limit,,,09ah,,> task1_code_selec equ $-gdt_tab or 1 dscp <task1_seg_limit,,,0bah,,>

Pasul 2.1 Definirea descriptorului “task gate” în IDT, punctând către TSS-ul procesului “task0”: dscp <,task0_TSS_selec,0,0e5h,,> ;intreruperea 21

Pasul 4.1 Definirea mapării între descriptor şi segment pentru procesul “task1” dw task1_TSS_selec dw task1_TSS dw task1_code_selec dw task1_seg

Pasul 7.1 Definirea TSS pentru procesul “task1”. task1_TSS segment para public use16 ‘task1’ TSS_stack stk0_selec,stk0_limit,stk1_selec, stk1_limit,stk2_selec,stk2_limit TSS_cr3 0 TSS_regs task1_entry,2,0,0,0,0,0,0,0,stk1_limit TSS_seg gdata_selec,task1_code_selec,stk1_selec, gdata_selec,gdata_selec,gdata_selec dd 0 dw 0

Page 369: limbaj de asamblare-ivan

26

dw 68h task1_TSS_limit equ $ task1_TSS ends

Pasul 15.1 Comutarea către procesul “task1” prin salt necondiţionat către selectorul segmentului TSS al procesului “task1” jmpf task1_TSS_selec

Pasul 17: Segmentul de cod al procesului “task1” task1_seg segment para public use16 ‘task1_seg’ assume cs:task1_seg,ds:gdata task1_entry proc near mov si,offset task1_msg mov di,160*3 call disp2 int21 task1_entry endp disp2 proc near mov ax,video_selec mov es,ax mov ah,attribute disp21: lodsb stosw cmp al,0 jne disp21 ret disp2 endp task1_seg_limit equ $ task1_seg ends 24.6 Concluzii

Spre deosebire de modul real, modul protejat oferă programatorului întreaga capacitate a procesoarelor i386/i486. În modul protejat multitasking-ul, memoria virtuală şi protejarea spaţiului alocat unui program fiind asigurate în mod nativ, prin hardware.

În contextul tendinţei de pe piaţa software-ului către aplicaţii care necesită un volum tot mai mare de resurse, precum şi datorită existenţei sistemelor de operare care lucrează în mod protejat (sistemele MS Windows) cunoaşterea principiilor de programare în acest mod este obligatorie pentru realizarea de programe performante sub aceste platforme.

Page 370: limbaj de asamblare-ivan

27

25

PROGRAMAREA APLICAŢIILOR WINDOWS ÎN LIMBAJ DE ASAMBLARE

25.1 Interfaţa de programare a aplicaţiilor Windows

API (Application Programming Interface) pentru sistemele Win32 este

implementat în biblioteci cu legare dinamică.. Aceste biblioteci sunt separate de programul executabil. Spre deosebire de DOS, API-urile nu sunt accesate prin întreruperi.

Codul funcţiilor Windows este stocat în fişiere din afara programului, numite DLL (Dynamic Link Libraries) - biblioteci cu legături dinamice. Atunci când este rulat un program Windows, interconectarea acestuia cu sistemul de operare se face prin procesul de legare dinamică. Un fişier executabil al unui program Windows conţine referinţe la biblioteci cu legături dinamice. Atunci când un program pentru Windows este încărcat în memorie, apelurile de funcţii sunt rezolvate astfel încât să indice intrările în funcţiile din DLL-urile utilizate, care sunt încărcate în memorie, în cazul în care nu se află deja acolo.

Principalele biblioteci cu legare dinamică sunt kernel32.dll, user32.dll şi gdi32.dll. Biblioteca cu legare dinamică kernel32.dll conţine funcţiile API pentru gestionarea memoriei şi a proceselor. Biblioteca cu legare dinamică user32.dll controlează aspectele interfeţei cu utilizatorul ale programului. Biblioteca cu legare dinamică gdi32.dll este responsabilă cu operaţiile grafice. În afară de aceste biblioteci principale se pot utiliza în program şi alte biblioteci cu legare dinamică.

Există două categorii de funcţii API: unele pentru ANSI şi altele pentru Unicode. Şirurile ANSI sunt vectori de caractere terminate cu NULL. Un caracter ANSI are dimensiunea de 1 byte. Un caracter Unicode are dimensiunea de 2 octeţi. Numele funcţiilor API pentru ANSI se termină cu caracterul "A" (de exemplu

Page 371: limbaj de asamblare-ivan

28

MessageBoxA), iar numele funcţiilor API pentru Unicode se termină cu W (MessageBoxW). În mod nativ Windows 95 suportă ANSI iar Windows NT Unicode. În general în fişierele cu prototipurile funcţiilor există declaraţii echivalente ale acestora în care se elimină caracterul A, respectiv W (de exemplu MessageBox).

25.2 Organizarea memoriei în Windows 9x

Programele Win32 rulează în modul protejat, acest mod existând începând cu microprocesorul 80286. Windows rulează fiecare program Win32 în spaţii virtuale separate. Aceasta înseamnă că fiecare program Win32 va avea propriul spaţiu de adresare de 4 GB. Deoarece programele Win32 au 4 GB de memorie adresabilă, pointerii pot adresa zone de memorie cuprinse între 00000000H şi FFFFFFFFH. În Windows 9x , fiecare proces obţine propriul spaţiu de memorie de 4 GB, spaţiu care este în totalitate al său. De fapt el partajează împreună cu alte aplicaţii care rulează, primii 2 GB de memorie. Este important să se considere că cei 4GB ai spaţiului procesul sunt numai şi numai ai acestuia. Acest model de memorie se numeşte flat.

Secţiunea cea mai joasă a memoriei (regiunea de 64 KB) este rezervată în Windows 95 pentru atribuirile de pointeri nuli. Restul memoriei, până la 4 MB este rezervată de sistem pentru compatibilitatea cu aplicaţiile Win16 şi cu aplicaţiile MS-DOS.

Programele Win32 sunt încărcate la adresa 00400000H (adresa de bază a aplicaţiilor Win32 în memorie), deci spaţiul de lucru al programului este până la 7FFFFFFFH. Începând cu adresa 80000000H, Windows 9x încarcă propriile DLL-uri de sistem. Începând cu adresa C0000000H până la sfârşitul celor 4 GB se încarcă sistemul de operare Windows 9x precum şi programele pentru dispozitive virtuale.

Puţine calculatoare au 4GB de memorie. Datorită acestui lucru, proiectanţii de hardware au introdus ideea de memorie

Page 372: limbaj de asamblare-ivan

29

virtuală şi memorie fizică. Memoria virtuală corespunde celor 4 GB de memorie adresabilă, memoria fizică este limitată de memoria RAM disponibilă în calculator şi de spaţiul pe hard disk. Memoria este împărţită în pagini, dimensiunea paginii fiind optimizată în funcţie de suportul hardware, pe sistemele 80x86, mărimea unei pagini fiind de 4 KB.

Figura 25.1 – Organizarea memoriei în Windows 9x Sistemul de gestiune a memoriei determină paginile

localizate în memoria virtuală şi în RAM şi furnizează un

FFFFFFFFH Sistemul de operare Windows 9x Drivere C0000000H BFFFFFFFH DLL-uri Win32 partajate Fişiere mapate în memorie 80000000H 7FFFFFFFH Programul Win32 00400000H 003FFFFFH Compatibilitate cu Win16 00001000H 00000FFFH Protejata, atribuiri de pointeri NULL Compatibilitate cu Win16 00000000H

4GB

2GB

4MB

Page 373: limbaj de asamblare-ivan

30

mecanism de conversie a adreselor din memoria virtuală în adrese fizice. Programul de gestiune a memoriei poate muta sau comuta paginile de memorie dacă este nevoie de spaţiu suplimentar de memorie fizică. Windows schimbă memorie în şi afară din fişierul de paginare de pe hard disk. Dimensiunea fişierului de paginare împreună cu dimensiunea memoriei RAM este considerată memoria fizică.

Când este rulat un program, fişierul EXE de pe disc (numit imaginea programului) este considerat ca făcând parte din fişierul de paginare de pe hard disk. În acest fel se economiseşte timpul necesar încărcării fişierului executabil în memoria RAM. Imaginea fişierului EXE de pe disc este considerată parte a fişierului de paginare şi secţiunile de cod (nu şi de date) sunt citite de pe disc atunci când este nevoie de ele.

25.3 Încărcarea programelor Win32

Aplicaţiile Win32 rulează în segmente cu adresare pe 32 de biţi, utilizând modelul de memorie FLAT. Astfel, programul este în mod automat în modul protejat. Adresele generate şi utilizate de program se numesc adrese liniare. Registrele de segment CS, DS, ES şi SS sunt iniţializate astfel că nu mai contează ce segment este utilizat pentru a adresa o locaţie dată (o adresă liniară). Adresa de bază la care este încărcat programul în memorie este 0040000h.

La lansarea în execuţie a programelor, valorile iniţiale ale regiştrilor sunt:

CS:EIP – începutul programului SS:ESP – începutul stivei DS=ES=SS – selectorul de date FS=TIB (Thread Information Block) GS=0, selectorul nul CS şi DS sunt mapate pe aceeaşi adresă liniară Indicatorul de direcţie, DF, este şters.

Sistemul Windows utilizează intern regiştrii ESI, EDI, EBP şi EBX şi de

aceea presupune că valorile acestor regiştri nu se vor schimba. Se recomandă deci în cazul utilizării acestor regiştri în cadrul unor funcţii callback să se salveze

Page 374: limbaj de asamblare-ivan

31

conţinutul lor înainte de modificare şi să se restaureze înainte de a da controlul sistemului Windows. O funcţie callback este o funcţie scrisă de programator, funcţie ce este apelată de Windows. Un exemplu de astfel de funcţie este funcţia fereastră.

25.4 Structura unui program în limbaj de asamblare Un program Win32 scris în limbaj de asamblare are următoarea structură: .386 .model flat, stdcall option casemap :none include \masm32\include\windows.inc includelib \masm32\lib\user32.lib .data ; ;datele iniţializate ; .data? ; ;datele neiniţializate ; .const ; ;constantele ; .code eticheta_de_inceput ; ;codul propriu zis ; end eticheta_de_inceput

Directiva .386 anunţă asamblorul că se va utiliza setul de instrucţiuni specifice procesorului 80386. Se pot utiliza şi directivele .486, .586. Pentru fiecare model al microprocesorul sunt două forme aproape identice ale acestei directive: .386/.386p, .486/.486p. Versiunile cu p sunt necesare atunci când programul utilizează instrucţiuni privilegiate. Instrucţiunile privilegiate sunt rezervate de microprocesor/sistem de operare

Page 375: limbaj de asamblare-ivan

32

în modul protejat. Ele pot fi utilizate numai de cod protejat, cum ar fi programele pentru echipamente virtuale. Directiva .model specifică modelul de memorie al programului. Sub Win32 există numai modelul de memorie flat. În directiva .model mai este precizata şi convenţia de transmitere a parametrilor (stdcall). Convenţia de transmitere a parametrilor specifică ordinea de transmitere a parametrilor (de la stânga la dreapta sau de la dreapta la stânga) şi de asemenea cine va echilibra stiva după apelul funcţiei.

Sub Win16 există două tipuri de convenţii de apel: C şi PASCAL.

Convenţia de apel C transmite parametri de la dreapta la stânga, cel mai din dreapta parametru fiind pus primul pe stivă. Apelatorul este responsabil cu echilibrarea stivei după apel. De exemplu apelul unei funcţii f(int a, int b) utilizând convenţia C de transmitere a parametrilor în limbaj de asamblare va fi:

push [b] push [a] call f add sp, 8 ; apelatorul echilibrează stiva

Convenţia de apel PASCAL presupune transmiterea

parametrilor de la stânga la dreapta, funcţia apelată fiind responsabilă cu echilibrarea stivei după apel.

Convenţia de apel C este utilă atunci când nu se ştie exact numărul de parametri ce vor fi transmişi funcţiei. O astfel de funcţie este wsprinf. Nu se ştie dinainte câţi parametri vor fi puşi pe stivă, deci în cadrul funcţiei nu se poate realiza echilibrarea stivei.

STDCALL este un hibrid între convenţiile de transmitere a parametrilor C şi PASCAL. Parametri sunt puşi pe stivă de la dreapta la stânga (ca în convenţia C), însă funcţia apelata este responsabilă cu echilibrarea stivei (convenţia PASCAL). Platforma Win32 utilizează exclusiv convenţia STDCALL cu excepţia funcţiei wsprintf.

Page 376: limbaj de asamblare-ivan

33

Directiva option casemap :none anunţă MASM că etichetele sunt case-sensitive.

Directiva include \masm32\include\windows.inc inserează fişierul windows.inc în acest punct în programul sursă. Acest fişier conţine declaraţiile pentru majoritatea constantelor şi structurilor utilizate de API. După această directivă se urmează includerea fişierelor ce conţin antetele funcţiilor utilizate din bibliotecile respective. De exemplu dacă se utilizează funcţia MessageBox se va scrie include \masm32\include\user32.inc, având în vedere că această funcţie există în user32.dll.

Directiva includelib \masm32\lib\user32.lib informează asamblorul ce biblioteci de import sunt utilizate. Când asamblorul întâlneşte această directivă, el pune o comandă a editorului de legături în fişierul obiect astfel încât editorul de legături să ştie ce biblioteci utilizează programul şi de unde să le ia.

Secţiunea .data conţine datele iniţializate ale programului. Secţiunea .data? conţine datele neiniţializate ale programului. Aceasta are avantajul că fişierul executabil nu îşi măreşte dimensiunea corespunzător cu mărimea datelor neiniţializate. Ea doar informează asamblorul cât spaţiu de memorie este necesar când programul este încărcat în memorie. Secţiunea .const

conţine declaraţiile de constante din program. Aceste nu pot fi modificate în program.

Secţiunea .code conţine codul programului. Eticheta_de_inceput este o etichetă ce marchează începutul codului programului. Prima instrucţiune executabilă se află după eticheta_de_inceput. Codul trebuie să fie între eticheta_de_inceput şi end eticheta_de_inceput.

Pe lângă programul sursă, dacă în program se utilizează resurse ca meniuri, pictograme, căsuţe de dialog, acestea se definesc într-un fişier special, numit fişier de resurse, fişier ce are extensia .rc. Acesta este un fişier text în care se descriu resursele şi se face asocierea cu fişiere existente pe disc. De exemplu, pentru resursa pictogramă este linia:

Page 377: limbaj de asamblare-ivan

34

ID_ICON ICON "WHAT1.ICO"

25.5 Programarea sub Windows

Majoritatea programelor Windows au o fereastră principală asociată. Atunci

când programul este lansat în execuţie, Windows creează o coadă de mesaje pentru programul respectiv. În această coadă de mesaje sunt păstrate mesajele trimise către toate ferestrele pe care le creează programul. Programul conţine o mică secvenţă de cod numită bucla de mesaje care preia mesajele din coada de mesaje şi le distribuie funcţiilor fereastră corespunzătoare. Anumite mesaje sunt trimise direct funcţiei fereastră fără a mai fi puse în coada de mesaje.

Paşii necesari creării unei ferestre sunt: obţinerea identificatorului instanţei programului obţinerea liniei de comandă (opţional) înregistrarea clasei fereastră (window class) crearea ferestrei afişarea ferestrei actualizarea zonei client a ferestrei intrarea în bucla de mesaje infinită dacă sunt mesaje pentru fereastră ele sunt prelucrate de o funcţie

specializată numită şi funcţia fereastră terminarea programului Obţinerea identificatorului instanţei programului se realizează prin apelul

funcţiei GetModuleHandle. Funcţia returnează identificatorul instanţei curente a programului. Prototipul funcţiei este:

HMODULE GetModuleHandle(LPCTSTR lpModuleName);

Prin transmiterea ca parametru a valorii NULL se obţine identificatorul instanţei curente. După apelul funcţiilor API, rezultatul returnat se găseşte în registrul EAX. .data?

;... hinst dd ? ;... .code

;... push 0 call GetModuleHandle mov hinst,eax ;...

Page 378: limbaj de asamblare-ivan

35

Obţinerea liniei de comandă se face cu funcţia GetCommandLine. Obţinerea liniei de comanda nu este necesară decât dacă programul prelucrează parametrii din linia de comandă.

Înregistrarea clasei fereastră se realizează având în vedere faptul că o fereastră este întotdeauna creată pe baza unei clase de fereastră. Aceasta identifică procedura de fereastră care prelucrează toate mesajele transmise către fereastră. Pe baza aceleiaşi clase se pot crea mai multe ferestre.

Înainte de crearea ferestrei programului trebuie înregistrată clasa fereastră prin apelul funcţiei RegisterClassEx ce are un singur parametru: un pointer la o structură de tipul WNDCLASSEX.

Structura WNDCLASSEX are următorii membri: WNDCLASSEX struc

cbSize dd ? ; dimensiunea structurii WNDCLASSEX style dd ? ; stilul clasei fereastră lpfnWndProc dd ? ; pointerul la funcţia fereastră cbClsExtra dd ? ; informaţii suplimentare ;pentru clasă cbWndExtra dd ? ; informaţii suplimentare ;pentru fereastră hInstance dd ? ; identificatorul instanţei ;curente hIcon dd ? ; identificatorul pictogramei ;asociata clasei hCursor dd ? ; cursorul asociat clasei hbrBackground dd ? ; culoarea de fundal a ;ferestrelor din clasă lpszMenuName dd ? ; pointer la şirul cu denumirea ;meniului lpszClassName dd ? ; pointer la şirul cu numele ;clasei hIconSm dd ? ; identificatorul pictogramei ;aplicatiei

WNDCLASSEX ends

Al treilea şi penultimul câmp (lpfnWndProc şi lpszClassName) sunt cele mai importante, ele conţinând adresa procedurii fereastră folosită de toate ferestrele create pe baza acestei clase, respectiv numele clasei fereastră. Celelalte câmpuri descriu caracteristicile tuturor ferestrelor create pe baza acestei clase.

Înainte de apelul funcţiei RegisterClassEx trebuie iniţializate câmpurile variabilei de tip WNDCLASSEX. Iniţializarea câmpurilor se realizează în următoarea secvenţă de cod:

;... .data?

;... wcex WNDCLASSEX <?>;var de tip structura clasa fereastra NumeFer db 'Programare sub Windows',0 ;Numele ferestrei

Page 379: limbaj de asamblare-ivan

36

hinst dd ? ;identificatorul instantei curente ;...

.code ;...

;completarea cimpurilor structurii fereastra mov wcex.cbSize, size WNDCLASSEX mov wcex.style, CS_VREDRAW or CS_HREDRAW or CS_DBLCLKS

mov wcex.lpfnWndProc, offset WndProc mov wcex.cbClsExtra,0 mov wcex.cbWndExtra,0 push hinst pop wcex.hInstance

;se incarca pictogtama, definita in fisierul .rc push ID_ICON ;icon id push hinst call LoadIcon mov wcex.hIcon,eax ;se incarca cursorul sageata standard push IDC_ARROW push NULL call LoadCursor mov wcex.hCursor,eax mov wcex.hbrBackground,COLOR_WINDOW+1 mov wcex.lpszMenuName,0 mov wcex.lpszClassName,offset NumeFer mov wcex.hIconSm,0

Se observă că pentru iniţializarea câmpurilor hIcon şi hCursor ale structurii

WNDCLASSEX s-au apelat funcţiile LoadIcon şi LoadCursor. Funcţia LoadIcon are prototipul:

HICON LoadIcon( HINSTANCE hInstance, // identificatorul instantei aplicatiei LPCTSTR lpIconName // numele identificatorul resursei pictograma );

Funcţia returnează un identificator al pictogramei asociat resursei

pictogramă definită în fişierul de resurse. În program se transmite ID_ICON, definit în fişierul de resurse.

Funcţia LoadCursor:

HCURSOR LoadCursor( HINSTANCE hInstance, // identificatorul instantei aplicatiei LPCTSTR lpCursorName // numele/identificatorul resursei cursor );

Page 380: limbaj de asamblare-ivan

37

returnează identificatorul de cursor asociat cursorului definit în fişierul de resurse, sau unuia din cursoarele standard. În aplicaţie s-a utilizat cursorul standard de tip săgeată, identificat prin IDC_ARROW. Atunci când se utilizează cursoare standard, parametrul hInstance al funcţiei LoadCursor va fi 0.

Apelul funcţiei RegisterClassEx prin care se înregistrează clasa fereastră: .code ;... ;Inregistrarea clasei fereastra push offset wcex call RegisterClassEx ;...

În cazul în care nu s-a reuşit înregistrarea clasei de fereastră, funcţia

RegisterClassEx returnează valoarea 0. Sunt definite câteva clase standard de ferestre înregistrate: "BUTTON",

"EDIT", "LISTBOX" şi altele, clase fereastră ce nu mai trebuie înregistrate, ele având predefinite funcţiile fereastră şi tratarea anumitor mesaje. Pe baza acestor clase de ferestre standard se pot crea ferestre de acel tip, modificându-se anumite caracteristici, modul de funcţionare rămânând acelaşi.

Clasa fereastră defineşte caracteristicile generale ale unei ferestre, permiţând folosirea acesteia pentru crearea mai multor ferestre. Când se creează o fereastră cu funcţia CreateWindowEx se pot specifica mai multe detalii despre fereastra respectivă (cum ar fi titlul ferestrei, stilul acesteia, poziţia, meniul asociat şi dimensiunea iniţială). Prototipul funcţiei CreateWindowEx este:

HWND CreateWindowEx( DWORD dwExStyle, // stilul extins al ferestrei LPCTSTR lpClassName, // pointer către numele clasei

înregistrate LPCTSTR lpWindowName,// pointer către numele ferestrei

DWORD dwStyle, // stilul ferestrei int x, // poziţia orizontală a ferestrei int y, // poziţia verticală a ferestrei int nWidth, // lăţimea ferestrei int nHeight, // înălţimea ferestrei HWND hWndParent, // indicator către fereastra părinte HMENU hMenu, // indicator către meniu sau fereastra copil HINSTANCE hInstance, // indicatorul instanţei curente a programului LPVOID lpParam // pointer către datele de creare a ferestrei

Page 381: limbaj de asamblare-ivan

38

); Funcţia returnează identificatorul ferestrei create. Apelul funcţiei CreateWindowEx este:

.data? ;... hwnd dd ? ;identificatorul de fereastra hinst dd ? ;identificatorul instantei curente hmeniu dd ? ;identificatorul meniului ;... .const ;... NumeFer db 'Programare sub Windows',0 ;Numele ferestrei NumeApp db 'Programare in Win32 ASM',0;Numele aplicatiei ;...

.code ;... ;Crearea ferestrei push ID_MENIU push hinst call LoadMenu mov hmeniu,eax push 0 ; lpParam push hinst ; hInstance push hmeniu ; identificator meniu push 0 ; identificator parinte push CW_USEDEFAULT ; inaltime push CW_USEDEFAULT ; latime push CW_USEDEFAULT ; y push CW_USEDEFAULT ; x push WS_OVERLAPPEDWINDOW ; stilul push offset NumeApp ; titlul ferestrei push offset NumeFer ; numele clasei push WS_EX_OVERLAPPEDWINDOW ; stilul extins call CreateWindowEx mov hwnd,eax ;...

Pentru a asocia resursa meniu definită în fişierul de resurse se apelează

funcţia LoadMenu ce are ca parametri identificatorul instanţei curente a aplicaţiei şi numele meniului definit în fişierul de resurse. Funcţia returnează identificatorul de meniu asociat.

După apelul funcţiei CreateWindowEx, fereastra a fost creată însă nu a fost şi afişată pe ecran. Pentru afişarea ferestrei sunt necesare încă două apeluri de funcţii: ShowWindow respectiv UpdateWindow.

Funcţia ShowWindow are prototipul:

Page 382: limbaj de asamblare-ivan

39

BOOL ShowWindow(HWND hWnd, // identificatorul ferestrei int nCmdShow // modul de afişare a ferestrei);

Apelul funcţiei în cadrul programului:

push SW_SHOWNORMAL push hwnd call ShowWindow Funcţia UpdateWindow redesenează zona client a ferestrei. Prototipul acesteia este:

BOOL UpdateWindow(HWND hWnd // identificatorul ferestrei );

În program, apelul se realizează astfel:

push hwnd call UpdateWindow

După apelul funcţiei UpdateWindow, fereastra devine vizibilă pe ecran. Programul trebuie să poată prelucra intrările de la tastatură şi de mouse.

Windows creează o coadă de mesaje asociată fiecărui program. Când apare un eveniment, acesta este transformat de Windows într-un mesaj care este pus în coada de mesaje a programului. Mesajele sunt preluate din coada de mesaje prin intermediul buclei de mesaje. În pseudocod bucla de mesaje se reprezintă astfel:

Aşteaptă_mesaj: Dacă este apare un mesaj, preia mesajul Dacă mesajul este QUIT Atunci Ieşire Apelează TranslateMessage Apelează DispatchMessage Trimite mesajul către procedura fereastră Înapoi la Aşteaptă_mesaj Ieşire

Funcţia GetMessage preia un mesaj din coada de mesaje. Prototipul

funcţiei GetMessage:

BOOL GetMessage( LPMSG lpMsg, // adresa structurii cu mesajul HWND hWnd, // identificatorul ferestrei UINT wMsgFilterMin, // primul mesaj UINT wMsgFilterMax // al doilea mesaj );

Page 383: limbaj de asamblare-ivan

40

Primul parametru este un pointer la o structură de tip MSG. Windows completează câmpurile structurii de mesaje cu următorul mesaj din coada de aşteptare. Structura este MSG este:

MSG STRUC hwnd DWORD ? ; identificatorul ferestrei căreia îi

este destinat mesajul message DWORD ? ; identificatorul mesajului wParam DWORD ? ; parametru dependent de mesaj lParam DWORD ? ; parametru dependent de mesaj time DWORD ? ; momentul inserării mesajului în coada

de mesaje pt POINT <>; poziţia mouse-ului în momentul

inserării mesajului MSG ENDS

Dacă în câmpul message al structurii este transmisă o valoare diferită de

WM_QUIT, funcţia GetMessage returnează o valoare diferită de zero. Mesajul WM_QUIT determină ieşirea din bucla de mesaje.

În cadrul buclei de mesaje se apelează două funcţii. TranslateMessage transmite structura MSG sistemului de operare pentru convertirea unor mesaje de la tastatură. Prototipul acesteia este:

BOOL TranslateMessage( CONST MSG *lpMsg // pointer la o structură MSG );

Funcţia DispatchMessage, al cărei prototip este:

LONG DispatchMessage( CONST MSG *lpmsg // pointer către structura ce conţine mesajul );

retransmite structura MSG sistemului de operare.

Windows trimite apoi mesajul către funcţia fereastră corespunzătoare în vederea prelucrării acestuia.

Bucla de mesaje este implementată astfel:

;Bucla de mesaje start_bucla: push 0 ; wMsgFilterMax push 0 ; wMsgFilterMin push NULL ; 0 - toate ferestrele push offset msg ; lpMsg call GetMessage ; returneaza FALSE pentru WM_QUIT or eax,eax

Page 384: limbaj de asamblare-ivan

41

jz iesire push offset msg call TranslateMessage push offset msg call DispatchMessage jmp start_bucla

Terminarea programului se face prin apelul funcţiei ExitProcess. Prototipul

acesteia este:

VOID ExitProcess(UINT uExitCode // codul de ieşire al procesului );

Secvenţa de cod în care se face apelul funcţiei este:

push 0 ;codul de iesire returnat de aplicatie call ExitProcess

Funcţia fereastră este responsabilă de afişarea în zona client a ferestrei,

precum şi de tratarea mesajelor provenite de la tastatură şi mouse. Funcţia fereastră poate avea orice nume, iar un program pentru Windows poate avea mai multe funcţii fereastră, asociate ferestrelor definte în cadrul acestuia. Funcţia fereastră este întotdeauna asociată unei clase fereastră.

Funcţia fereastră are următorul prototip:

LRESULT WndProc(HWND hwnd, //identificatorul ferestrei UINT msg, //identificatorul mesajului WPARAM wparam, //parametru asociat mesajului LPARAM lparam //parametru asociat mesajului

); Parametrii funcţiei corespund primelor patru câmpuri ai structurii MSG.

Fiecare mesaj recepţionat de fereastră este identificat printr-un număr (parametrul msg al funcţiei fereastră). În fişierele antet din Windows acestora le corespund identificatori prefixaţi de WM_ (de la window message)

Pentru tratarea diverselor mesaje se utilizează o instrucţiune switch pentru determinarea mesajelor primite de fereastră şi a modului de prelucrare a acestuia. Atunci când prelucrează un mesaj funcţia fereastră trebuie să returneze valoarea zero. Toate mesajele ce nu sunt prelucrate de funcţia fereastră sunt transmise funcţie DefWindowProc. Prototipul funcţiei DefWindowProc este:

LRESULT DefWindowProc( HWND hWnd, // identificatorul ferestrei UINT Msg, // mesajul WPARAM wParam, // primul parametru al mesajului

Page 385: limbaj de asamblare-ivan

42

LPARAM lParam // al doilea parametru al mesajului );

Un mesaj ce trebuie tratat este WM_DESTROY care este trimis atunci când

utilizatorul doreşte să închidă fereastra. Mesajul este tratat standard, prin apelul funcţiei PostQuitMessage, ce are prototipul:

VOID PostQuitMessage( int nExitCode // codul de ieşire );

Implementarea funcţiei fereastră, care tratează mesajul WM_DESTROY

este:

WndProc proc ;parametrii de pe stiva sunt: ;esp+4 --> hwnd ;esp+8 --> msg ;esp+12 --> wParam ;esp+16 --> lParam cmp dword ptr [esp+8],WM_DESTROY je msg_wm_destroy jmp DefWindowProc msg_wm_destroy: push 0 call PostQuitMessage xor eax,eax ret 16 WndProc endp

Celelalte mesaje sunt tratate de funcţia standard. În programul scris în limbaj de asamblare nu are importanţă dacă numele

funcţiei fereastră este WndProc, acest lucru fiind la latitudinea programatorului.

25.6 Asamblarea şi editarea de legături

Pentru realizarea programului s-a utilizat MASM32. Acesta se găseşte la adresa http://www.pbq.com.au/home/hutch/masm.htm.

Asamblarea programului sursă se realizează astfel:

ml /c /coff /Cp program.asm Dacă se folosesc fişiere de resurse, acestea trebuiesc compilate cu un

compilator de resurse (de exemplu rc.exe), obţinându-se un fişier de resurse obiect, fişier ce va fi link-editat împreună cu fişierul obiect al programului. Editarea de legături se realizează utilizând următoare linie de comandă:

Page 386: limbaj de asamblare-ivan

43

link /SUBSYSTEM:WINDOWS /LIBPATH:c:\masm32\lib program.obj

25.7 Exemplu de program Win32

În continuare se prezintă un program Windows, program care afişează o fereastră, în funcţia fereastră tratându-se mesajele WM_COMMAND (alegerea unei opţiuni din meniu), WM_LBUTTONDOWN (executarea unui clic de mouse în zona client a ferestrei) şi WM_DESTROY (la închiderea ferestrei).

Fereastra are asociat un meniu având submeniul "Fisiere" cu o un singur articol: "Iesire" şi submeniul "Help" cu articolul "Despre...". Când se selectează "Iesire" se va închide fereastra, iar când se selectează "Despre..." se afişează o căsuţă de dialog.

.386

.model flat, stdcall option casemap :none include \masm32\include\windows.inc include \masm32\include\kernel32.inc include \masm32\include\user32.inc include res.equ includelib \masm32\lib\user32.lib includelib \masm32\lib\kernel32.lib .data .const NumeFer db 'Programare sub Windows',0 ;Numele ferestrei NumeApp db 'Programare in Win32 ASM',0 ;Numele aplicatiei MesajDespre db 'Paul Pocatilu, 1999',0 Mesaj1 db 'Mouse click',0 .data? wcex WNDCLASSEX <?> ;var de tip structura clasa fereastra msg MSG <?> ;Var de tip MSG hwnd dd ? ;identificatorul de fereastra hinst dd ? ;identificatorul instantei curente hmeniu dd ? ;identificatorul de meniu .code start: ;obtinerea hInstance

Page 387: limbaj de asamblare-ivan

44

push 0 call GetModuleHandle mov hinst,eax ;completarea campurilor structurii fereastra mov wcex.cbSize, size WNDCLASSEX mov wcex.style, CS_VREDRAW or CS_HREDRAW or CS_DBLCLKS mov wcex.lpfnWndProc, offset WndProc mov wcex.cbClsExtra,0 mov wcex.cbWndExtra,0 push hinst pop wcex.hInstance ;se incarca iconul, definit in fisierul .rc push ID_ICON ;icon id push hinst call LoadIcon mov wcex.hIcon,eax ;se incarca cursorul sageata standard push IDC_ARROW push NULL call LoadCursor mov wcex.hCursor,eax mov wcex.hbrBackground,COLOR_WINDOW+1 mov wcex.lpszMenuName,0 mov wcex.lpszClassName,offset NumeFer mov wcex.hIconSm,0 ;Inregistrarea clasei fereastra push offset wcex call RegisterClassEx ;s-a inregistrat fereastra? or eax,eax jz iesire ;Crearea ferestrei push ID_MENIU push hinst call LoadMenu mov hmeniu,eax push 0 ; lpParam push hinst ; hInstance push hmeniu ; identificator meniu push 0 ; identificator parinte push CW_USEDEFAULT ; inaltime push CW_USEDEFAULT ; latime push CW_USEDEFAULT ; y

Page 388: limbaj de asamblare-ivan

45

push CW_USEDEFAULT ; x push WS_OVERLAPPEDWINDOW ; stilul push offset NumeApp ; titlul ferestrei push offset NumeFer ; numele clasei push WS_EX_OVERLAPPEDWINDOW ; stilul extins call CreateWindowEx mov hwnd,eax ;se afiseaza fereastra push SW_SHOWNORMAL push hwnd call ShowWindow ;se actualizeaza zona client push hwnd call UpdateWindow ;Bucla de mesaje start_bucla: push 0 ; wMsgFilterMax push 0 ; wMsgFilterMin push NULL ; 0 - toate ferestrele push offset msg ; lpMsg call GetMessage ; returneaza FALSE pentru WM_QUIT or eax,eax jz iesire push offset msg call TranslateMessage push offset msg call DispatchMessage jmp start_bucla iesire: ;Terminarea programului push 0 ; codul de iesire returnat de aplicatie call ExitProcess ;Functia Fereastra WndProc proc cmp dword ptr [esp+8],WM_COMMAND je msg_wm_command cmp dword ptr [esp+8],WM_DESTROY je msg_wm_destroy cmp dword ptr [esp+8],WM_LBUTTONDOWN je msg_wm_lbuttondown jmp DefWindowProc msg_wm_command: ;se testeaza alegerea optiunilor din meniu

Page 389: limbaj de asamblare-ivan

46

cmp dword ptr [esp+12],ID_FISIERE_IESIRE je msg_wm_destroy cmp dword ptr [esp+12],ID_HELP_DESPRE je help_despre xor eax,eax ret 16 msg_wm_lbuttondown: ;s-a facut click in zona client a ferestrei ;se afiseaza o casuta de dialog ;avind ca titlu NumeApp si mesaj Mesaj1 push MB_OK push offset NumeApp push offset Mesaj1 push NULL call MessageBox xor eax,eax ret 16 help_despre: ;s-a ales optiunea Despre... din meniul Help ;se afiseaza o casuta de dialog ;avind ca titlu NumeApp si mesaj MesajDespre push MB_OK push offset NumeApp push offset MesajDespre push NULL call MessageBox xor eax,eax ret 16 msg_wm_destroy: ;s-a inchis fereastra push 0 call PostQuitMessage xor eax,eax ret 16 WndProc endp end start

Fişierul de resurse asociat are următorul conţinut:

#define ID_ICON 101 #define ID_MENIU 102 ID_ICON ICON "MAIN.ICO" ID_MENIU MENUEX BEGIN

Page 390: limbaj de asamblare-ivan

47

POPUP "&Fisiere", , , 0 BEGIN MENUITEM "&Iesire", ID_FISIERE_IESIRE END POPUP "&Help", , , 0 BEGIN MENUITEM "&Despre...", ID_HELP_DESPRE END END

La lansarea în execuţie, programul va afişa o fereastră, dimensiunile şi

poziţia acesteia fiind generate de sistemul Windows (prin utilizarea CW_USEDEFAULT). Prin apăsarea butonului stâng al mouse-ului în zona client a ferestrei, prin tratarea evenimentului WM_LBUTTONDOWN se va activa o căsuţă de dialog. Închiderea ferestrei se realizează fie în mod clasic, fie prin alegerea opţiunii Iesire din meniul Fisiere.

BIBLIOGRAFIE

[ABEL95] IBM PC Assembly Language and Programming 3rd -

Edition Prentice Hall International, Inc, New Jersey, 1995 [ATHA92] Irina Athanasiu, Alexandru Panoiu Microprocesoarele

8086, 286, 386 Editura Teora, Bucureşti, 1992 [BARK90] Nabajyoti Barkakati, The Waite Group's Microsoft Macro

Assembler Bible, SAMS, Carmel Indiana, 1990 [BETH87] RWMc Beth, J. R. Ferguson, IBM Assembler John Wiley &

Sons, New York, 1987 [BREY90] Barry B. Brey, 8086/8088, 80286, 80386 and 80486

Assembly Language Programming, Macmillan Publishing Company, New York, 1990

[BRUM88] Penn Brumm, Don Brumm, 80386 – Assembly Language A Complete Tutorial and Subroutine Library TAB Books Inc, Blue Ridge Summit, 1988

[BUY96] Barry Buy, Programming the 80286, 80386, 80486 and Pentium - Based Personal, Prentice Hall Englewood Cliffs, New Jersey, 1996

[CAPR91] Vlad Căprariu, Sisteme de operare DOS - Funcţii Sistem, Microinformatica, Ediţia a III-a, Cluj Napoca, 1991

[CATO74] I. Catona, I. Teodorescu, C. Popescu, Sistemul Felix C - 256, Limbajul ASSIRIS, Editura Academiei, Bucureşti, 1974

[Coff87] James W. Coffron, Programming the 8086/8088, Ed. Kleidarifmos, Athens, 1987

Page 391: limbaj de asamblare-ivan

48

[COHE94] Le microprocesseur Pentium Architecture et programmation, Armand Colin, Paris, 1994

[CUOR93] Sen - Cuo Ro Shean - Chuen Her, i386/i486 Advanced Programming, Van Nosteand Reinhold, New York, 1993

[DAVI91] A. Davidoviciu, Gh. Dodescu, coordonatori MIX şi MACRO, vol. 2, Programarea în limbajul MACRO, Editura Tehnică, Bucureşti, 1991

[DETM90] Richard C. Detmer, Fundametals of Assembly Language Programming, PC Heath Comp., Lexington Masschusetts, 1990

[DIAC75] G. Diaconescu, I. Lungu, Assembler, Lito ASE, Bucureşti, 1975

[DIMO92] Dimopoulos K. Z., Paraskevopoulos A.S.80x86 Architictonici schediasi & Programatismos Papasotiriu Ed. Athens, 1992

[DORF90] Len Dorfman, Object Oriented Assembly Language Windcrest, 1990 Mc Graw Hill Inc. New York

[DORF90a] Len Dorfman, Structured Assembly Language, Windcrest Books Blue Ridgs Summit, 1990

[DOVI91] A. DOVIDOVICIU, Gh. DODESCU Mix si Macro (vol. 2) Programarea in limbajul MACRO Editura Tehnica, Bucureşti, 1991

[FITZ86] Robert M. Fitz, Larry Crokett, Universal Assembly Language, TAB Books Inc. Blue Ridge Summit PA 17214, 1986

[GHEO92] MARIAN GHEORGHE, BADICA COSTIN, PATRASCOIU OCTAVIAN Limbaje de asamblare MACRO-11, Intel 8086/88, Lito Universitate, Craiova, 1992

[GILL94] Frame van Gilluwe, The Undocumented PC, Addison Wesley Publishing Company Reading, 1994

[GURT89] A.L. Gurtovtev, S.V. Gudimenko Programi dlia microprotessorov, Editura Visaisaia Scola, Minsk, 1989

[HABI88] Stanley Habib Microprogramming and firmware engineering methods, van Nostrand Reinhold, New York, 1988

[HALL80] Douglas V. Hall, Microprocessors and interfacing Programming and hardware, Mc. Craw Hill International New York, 1980

[HAWK81] Gerry Kan, Danny Hawkins, Lance Leventhal 6800 Assembly Language Programming, Osborne, California, 1981

[HOLZ87] Steven Holzner, Advanced Assembly Language on the IBM

Page 392: limbaj de asamblare-ivan

49

PC, Brady - New York, 1987 [HOLZ90] Steven Holzer, Assembly Language for Pascal, Ed. Brady,

New York, 1990 [HOLZ96] Holzner, Steve – Advanced Visual C++ 4, M&T Books,

New York 1996 [HORS91] Gordon Horsington Programming in ANSI standard C,

Sigma Press, Wilmslow, England, 1991 [iAPX83] ***** iAPX 286. Programmer's Reference Manual, Intel

Product Guide, Intel Literature Dept., Santa Clara 1983 [IRVI93] KIP R. IRVINE, Assembly Language for the IBM – PC

Mcmillan Publishing Company, New York, 1993 [IVAN96] Ion Ivan , Cristian Codreanu, Optimizarea programelor

asembler, ASE – Departamentul de Informatică Economică, Bucureşti, 1996

[IVAN97] Ion Ivan Designul limbajelor de asamblare, Revista Informatica Economică, nr. 1/1997

[IVAN98] Ion Ivan, Cristian Codreanu Optimizarea programelor assembler, Revista Informatica Economică, nr. 6/1998

[IVAN98a] Ion Ivan, Marian Dardala, Gabriel Sutac, Valentin Dragomir, Referirea datelor in structuri complexe prin proceduri assembler, Informatica Economica, vol. III, nr. 7, Trim III/1998

[IVAN99] Ion Ivan, Adrian Licuriceanu, Sebastian Tcaciuc, Gheorghe Lupu, Aplicatii orientate obiect in limbaj de asamblare, "Studii si cercetari de calcul economic si cibernetica economica", nr 3/1999, Bucuresti, 1999

[JONE91] D.S. Jones 80x86 Assembly Programming, Oxford University Press, New York, 1991

[KACM95] Gary Kacmarcik, Optimizing Power PC code - Programming the Power PC chip in Assembly Language, Addison Wesley Publishing Company Massachusetts, 1995

[LEVE87] Lance Leventhal 80387 Programming guide, Bantan Books, Toronto, 1987

[LIUY86] Liu Yu-Cheng Microcomputer Systems: the 8086/8088, Family Prentice Hall Englewood Cliffs, New Jersey, 1986

[LUCA85] Dan Luca Serbanuta, Valentin Cristea, Claudiu Popescu Limbajul MACRO-11. Îndrumar de laborator, IPB, Bucureşti, 1985

[LUPU82] Cristian Lupu, Vlad Tepelea, Emil Purice Microprocesoare-aplicaţii, Editura Militară, Bucureşti, 1982

[LUPU82] Cristian Lupu, Vlad Ţepelea, Emil Purice Microprocesoare -

Page 393: limbaj de asamblare-ivan

50

Aplicaţii, Editura Militară, Bucureşti, 1982 [MARI92] Marian Gheorghe, Badica Costin, Pătrăşcoiu Octavian

Limbaje de asamblare Macro 11 – Intel 8086/88, Îndrumar de Laborator, Lito Universitatea Craiova, 1992

[MASM78] ***** MCS-86tm Macro Assembly language reference manual (manual order number 9806640-02), Intel Corporation, Santa Clara, 1978

[MASM86] ***** MStm - DOS 3.1 Macro ASSEMBLER Manual User's guide Masm Reference Manual, NEC Corporation 1986, Printed in Japan

[MAST95] * * * , ASSEMBLY Language Master Class, Wrox Press Ltd., Birmingham, 1995

[MATS76] Y. Matsumoto AA: Assembly Automation 1302XC 13O1XC [MCBE87] Robert W. Mc.Beth, J. Robert Ferguson IBM ASSEMBLER,

John Wiley & Sons, New York, 1987 [MORG84] Morgan Cristopher L., Assembly Language Routines for

the IBM PC & T, New York, Waite Group's, 1984 [MORRIS] A.J. T. Davie, R. Morrison Recursive descent compiling,

University of St. Andrews, Scotland [MUNT76] E. Munteanu, V. Costea, N. Mitrov Programare in limbajul

de asamblare ASSIRIS, Editura Tehnică, Bucureşti, 1976 [MURR88] Assembly Language Programming under OS/2, Osborne

Mc Gran Hill Berkley, 1988 [MUSC96] Gheorghe Muscă Programare în limbaj de asamblare,

Editura Teora, Bucureşti, 1996 [NORT89] Peter Norton, John Socha, Peter Norton's Assembly

Language Book for the IBM PC, Brady – New York, 1989 [PETZ98] Petzold, Charles, Yao, Paul Programare în Windows 95,

Editura Teora, Bucureşti, 1998 [PRET75] T. W. Prett Programming languages design and

implementation, Prentice Hall Inc. Englewood Cliffs NJ, 1975

[RICH94] Richard P. Paul, SPARC Architecture Assembly Language Programming & C., Prentice Hall, Englewood Cliffs, New Jersey, 1994

[RODE96] Liviu Rodean, Optimizarea programelor elaborate în limbaje de asamblare, Lucrare de licenţă, ASE - Bucureşti, 1996

[ROŞC77] V. Roşca, C. Apostol, I. Ivan, I. Roşca, Limbaje de programare, Limbajul ASSIRIS, vol 1, 2, Lito ASE, Bucureşti, 1977

[RUNN88] Wiliam C. Runnion, Structured Programming in Assembly

Page 394: limbaj de asamblare-ivan

51

Language for IBM PC, PWS - Kent Publishing Company, Boston, 1988

[SACH93] Holger SCHAKEL, Programmer en Assembleur sur PC, Edition Micro Application, Paris, 1993

[SALE95] Cristian Salescu, Implementarea mecanismelor de recursivitate în limbaje de asamblare – Lucrare de licenţă - ASE, Bucureşti, 1995

[SANC90] Julio Sanchez, Assembly Language tools and techniques for the IBM Micro computers, Prentice Hall Englewood Cliffs, New Jersey, 1990

[SANC94] Julio Sanchez, Maria P. Canton, PC Programming Hand book Mc Gron Hill Inc, New York, 1994

[SANC95] Julio Sanchez, Maria P. Canton, Numerical Programming the 387, 486 and PentiumTM, Mc Graw Hill, Inc., New York, 1995

[SCAN83] Leo J Scanlon, IBM PC & XT Assembly Language A guide for programmers, Brady Communications Company Inc., New York, 1983

[SCAN87] Leo J Scanlon, Assembly Language Subroutines for MS-DOS Computers, Blue Ridge Summit PA TAB Boccks, 1987

[SCAN88] Leo J. Scanlon, 8086/8088/80286, Assembly Language A Brady Book, New York, 1988

[SCHA93] Holger Schakel Programmer in assembleur sur PC, Edition MicroAplication, Paris, 1993

[SCHM95] Michael L. Schmit, Pentiumtm Processor Optimization tools AP Professional, New York, 1995

[SENC93] Sen-Cuo Ro, Shean Chuen Hen i386/i486 Advanced Programming, Van Nostrand Reinhold, New York, 1993

[SERA87] Luca Dan Şerbănaţi, Limbaje de asamblare şi asambloare în Limbaje de programare şi compilator, Editura Academiei, Bucureşti, 1987

[SERB85] Luca Dan Şerbănaţi, Valentin Cristea, Claudiu Popescu, Limbajul MACRO-11, Îndrumar de Laborator, Lito IPB, Bucureşti, 1985

[SOCE75] A. Soceneantu, G. Gavrilescu, T. Ilin Limbaje de asamblare pentru calculatoarele electronice numerice. Asambloare, Editura Facla, Timişoara, 1975

[SOMN92] Dan Somnea, Vlăduţ Teodor, Programarea în Assembler, Editura Tehnică, Bucureşti, 1992

[STRU92] Crişan Strugaru, Mircea Popa, Microprocesoare pe 16 biţi, Editura TM, Timişoara, 1992

Page 395: limbaj de asamblare-ivan

52

[SWAN95] Tom Swan, Mastering Turbo Assembler, SAMS Publishing Indianapolis, 1995

[TASM88] ***** Turbo ASSEMBLER version 2.0 User's guide, Borland International Inc., Green-Hils road, Scotts-Valley, 1988

[TASM99] ***** Turbo ASSEMBLER 3.0 User's guide, Borland International Inc., Scotts-Valley, 1988

[THOR90] Michael Thorne, Computer Organisation and Assembly Language Programming, The Benjamin Cummig Publishing Comp. , Bonn, 1990

[TISC94] Michael Tischer, La Bible PC 5e édition, Edition Micro Application, Paris, 1994

[TRIE90] Triebel A Walter, Singh Avtar, Microprocesorul 8086 Arhitectură, software şi tehnici de interfaţare, Editura Mitron, Timişoara, 1990

[TURL88] James L. Turley, Advanced 80386 Programming techniques , Osborne Mc Graw Hill Ber Kley, 1988

[WYAT87] Allen L. Wyatt, Using Assembly Language, QUE Corporation, Carmel, Indiana, 1987

[WYAT92] Allen L. Wyatt Jr., Advanced Assembly Language, QUE Corporation Carmel, 1992

[YUCH86] YU Cheng Liu, Glen A. Gibson Microcomputer Systems The 8086/8088 Family Arhitecture Programming and Designe Prentice Hall International, Inc. New Jersey, 1986

[******] http://win32asm.cjb.net, " Iczelion's Win32 Assembly HomePage"

[******] http://www.pbq.com.au/home/hutch/masm.htm, "MASM32"