Limbaj De Asamblare-ivan

  • April 2020
  • PDF

This document was uploaded by user and they confirmed that they have the permission to share it. If you are author or own the copyright of this book, please report to us by using this DMCA report form. Report DMCA


Overview

Download & View Limbaj De Asamblare-ivan as PDF for free.

More details

  • Words: 91,847
  • Pages: 395
399

Ion IVAN

Paul POCATILU

Doru CAZAN

Coordonatori

PRACTICA DEZVOLTĂRII SOFTWARE ÎN LIMBAJE DE ASAMBLARE

Editura Economică Bucureşti 2002

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Ş

401

CUPRINS 1 2

3

4

Introducere Caracteristicile limbajelor de asamblare 2.1 Prelucrări elementare 2.2 Caracterul neimplicit al definirilor si prelucrărilor 2.3 Secvenţialitatea instrucţiunilor 2.4 Absenţa priorităţilor 2.5 Contextul dinamic 2.6 Libera poziţionare 2.7 Adresarea generalizată 2.8 Necompactitatea construcţiilor 2.9 Incluziunea 2.10 Concluzii Reprezentarea informaţiei 3.1 Codificarea informaţiei 3.2 Organizarea datelor 3.3 Reprezentarea informaţiei numerice 3.4 Reprezentări ale şirurilor de caractere Moduri de adresare 4.1 Calculul adresei unui operand 4.2 Modul de adresare imediată 4.3 Modul de adresare registru 4.4 Adresarea directă 4.5 Adresarea indexată 4.6 Adresarea bazată 4.7 Adresarea bazată şi indexată

11 13 13 14 16 17 19 20 21 23 24 25 27 27 28 30 36 39 39 41 43 44 45 47 48

402 5

6

7

8

9

10

11

12

4.8 Adresarea indirectă Indicatorii de condiţie 5.1 Registrul FLAGS 5.2 Operaţii cu indicatorii de condiţie 5.3 Poziţionarea indicatorilor de condiţie la execuţie 5.4 Interacţiunea indicatori de condiţie - instrucţiuni Instrucţiuni 6.1 Clasificarea instrucţiunilor 6.2 Descrierea instrucţiunilor 6.3 Forma externă a instrucţiunilor 6.4 Forma internă a instrucţiunilor 6.5 Comentariile 6.6 Efectele execuţiei instrucţiunilor Definirea structurilor de date 7.1 Date elementare 7.2 Masive unidimensionale 7.3 Masive bidimensionale 7.4 Articolul 7.5 Variabile pointer 7.6 Variabile enumerative 7.7 Tabele de date 7.8 Obiecte Implementarea structurilor fundamentale 8.1 Programarea structurată 8.2 Structura liniară 8.3 Structura alternativă 8.4 Structura repetitivă standard 8.5 Structura repetitivă condiţionată posterior 8.6 Structura alternativă multiplă Aritmetici binare 9.1 Aritmetica binară pe 8 biţi 9.2 Aritmetica binară pe 16 biţi 9.3 Aritmetica binară pe 32 biţi Aritmetici zecimale 10.1 Ajustări 10.2 Adunarea şi scăderea 10.3 Înmulţirea şi împărţirea 10.4 Proceduri de calcul 10.5 Determinarea lungimii rezultatului evaluări unei expresii aritmetice Întreruperi 11.1 Întreruperi interne şi externe 11.2 Modul de funcţionare al întreruperilor 11.3 Tipuri de întreruperi 11.4 Exemplu de folosire al întreruperilor 11.5 Concluzii Macrodefiniţii 12.1 Structura macrodefiniţiei

49 53 53 55 56 56 61 61 63 65 71 77 78 81 81 82 82 84 88 89 90 91 95 95 95 96 99 102 103 107 107 109 112 117 117 119 120 122 129 131 131 131 134 138 145 147 147

403

13

14

15

16

17

12.2 Macroapelul şi macroexpandarea 12.3 Etichete locale 12.4 Variabile locale 12.5 Macrodefiniţii derivate 12.6 Macrodefiniţii recursive 12.7 Redefinirea macrodefiniţiilor 12.8 Macrodefiniţii uzuale 12.9 Concluzii Proceduri 13.1 Reutilizabilitatea 13.2 Structura unei proceduri 13.3 Apelarea procedurilor 13.4 Locul procedurilor 13.5 Instrucţiuni specifice lucrului cu proceduri Prelucrări în virgulă mobilă 14.1 Scurtă istorie a prelucrărilor în virgulă mobilă 14.2 Resurse 14.3 Setul de instrucţiuni 14.4 Sintaxa instrucţiunilor 14.5 Forma externă a instrucţiunilor 14.6 Exemple 14.7 Concluzii Lucrul cu şiruri de caractere 15.1 Caracteristicile instrucţiunilor de lucru cu şiruri de caractere 15.2 Setul de instrucţiuni pentru lucrul cu şiruri de caractere 15.3 Lucrul cu şiruri de caractere fără a folosi instrucţiuni specializate 15.4 Prefixul REP 15.5 Compararea a două şiruri 15.6 Poziţia unui subşir intr-un şir 15.7 Eliminarea spatiilor 15.8 Copierea unui şir sursă în şir destinaţie 15.9 Stabilirea lungimii unui şir 15.10 Conversie de la întreg binar la hexazecimal ca şir de caractere 15.11 Înlocuirea apariţiilor unui caracter 15.12 Concatenarea a două şiruri 15.13 Iniţializarea unui şir 15.14 Copierea unui şir dintr-o zonă de memorie într-o altă zonă de memorie 15.15 Numărarea caracterelor identice cu un caracter specificat Fişiere 16.1 Fişiere, articole, identificatori 16.2 Operaţii cu fişiere 16.3 Utilizarea de fişiere pentru calculul fondului de salarii 16.4 Operaţii cu directori si subdirectori Programarea mixtă C - limbaj de asamblare 17.1 Programul principal este scris în limbaj de asamblare, procedurile apelate sunt scrise în limbajul C

149 151 153 155 156 157 157 163 165 165 166 168 172 173 179 179 179 182 187 189 190 193 195 195 196 198 199 201 203 204 205 205 206 207 207 209 210 211 213 213 215 221 231 235 235

404

18

19

20

21

22

23

17.2 Programul principal este scris în limbajul C, procedurile apelate sunt scrise în limbaj de asamblare 17.3 Concluzii Dezvoltarea de aplicaţii orientate obiect in limbaj de asamblare 18.1 Concepte folosite în dezvoltarea programelor orientate obiect 18.2 Definirea obiectelor prin structuri 18.3 Definirea obiectelor prin macrodefiniţii 18.4 Folosirea specificaţiilor proprii limbajului de asamblare 18.5 Analiza comparativă a variantelor de implementare a obiectelor Structuri de programe 19.1 Programul ca singură secvenţă (0, 0, 1) 19.2 Programul din secvenţe distincte grupate într-un segment (1, 1, 1) 19.3 Proceduri incluse în segmentul programului principal (1, 1, 1) 19.4 Definiri distincte pentru toate componentele (m, k, n) 19.5 Structuri liniare de program 19.6 Structura arborescentă 19.7 Structuri de tip reţea 19.8 Concluzii Optimizarea programelor 20.1 Criterii de optim 20.2 Cicluri maşină 20.3 Volumul de operaţii 20.4 Secvenţe echivalente 20.5 Alegerea tipului de dată 20.6 Eliminarea subexpresiilor comune 20.7 Gestionarea corectă a invarianţilor 20.8 Regruparea ciclurilor 20.9 Eliminarea secvenţelor inconsistente 20.10 Eliminarea secvenţelor inactive 20.11 Reacoperirea segmentelor 20.12 Alocarea optimă a regiştrilor 20.13 Concluzii Designul limbajelor de asamblare 21.1 Cerinţe ale designului 21.2 Structura instrucţiunii limbajului de asamblare 21.3 Designul corectiv pentru limbajele de asamblare 21.4 Stabilirea numărului de registre 21.5 Concluzii Elemente de grafica 22.1 Istoric al adaptoarelor grafice 22.2 Rutine BIOS şi moduri grafice 22.3 Lucrul în mod grafic folosind un adaptor VGA 22.4 Lucrul în mod grafic folosind un adaptor SVGA 22.5 Concluzii Programe rezidente 23.1 Caracteristicile programelor rezidente 23.2 Întreruperi pentru programe rezidente

242 251 253 253 257 260 276 282 289 289 296 300 304 306 307 316 318 319 319 320 323 327 329 330 331 332 334 334 335 336 338 339 339 340 342 344 345 347 347 349 351 359 364 365 365 366

405 23.3 Sistemul de operare MS-DOS 23.4 Resurse ale programelor rezidente 23.5 Controlul proceselor 23.6 Probleme specifice în realizarea programelor rezidente 23.7 Activarea programelor rezidente 23.8 Program rezident pentru afişarea ceasului 23.9 Concluzii 24 Programarea în modul protejat 24.1 Moduri de operare ale procesorului 80x86 24.2 Regiştrii procesoarelor i386/i486 24.3 Moduri de gestiune a memoriei 24.4 Comutarea in modul protejat 24.5 Implementarea modului de lucru multitasking 24.6 Concluzii 25 Programarea aplicaţiilor Windows în limbaj de asamblare 25.1 Interfaţa de programare a aplicaţiilor Windows 25.2 Organizarea memoriei in Windows 9x 25.3 Încărcarea programelor Win32 25.4 Structura unui program în limbaj de asamblare 25.5 Programarea sub Windows 25.6 Asamblarea si editarea de legături 25.7 Exemplu de program Win32 Bibliografie Anexe A I Arhitectura procesoarelor din familia Intel 80x86 A II Mnemonicele instrucţiunilor procesorului 80x86 A III Setul de instrucţiuni ale procesoarelor Intel 80x86 A IV Regiştrii coprocesorului 80x87 A V Mnemonicele instrucţiunilor coprocesorului 80x87 A VI Primul program în limbaj de asamblare A VII Analiza comparată a utilizării diferitelor tipuri de reprezentări A VIII Calcul matriceal - studiu de caz

369 370 373 378 385 388 398 399 399 400 407 409 423 426 427 427 428 430 430 432 441 441 447 A-1 A-5 A-11 A-69 A-73 A-77 A-89 A-101

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.

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 ASSEMBLER

Instrucţiunea Adunare Scădere Înmulţire Împărţire Comparare Salt necondiţionat Salt condiţionat

ASSIRIS

MACRO-11

Limbaj asamblare microprocesor 486

PENTIUM II

add sub mul div cmp jmp

ad4, add sb4, sbd mp4, mpd dv4, dvd cp4, cp1 bru

add sub mul div cmp br

add sub mul div cmp jmp

add sub mul div cmp jmp

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;

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 Temp B

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 A

00 0D Temp B

+

00

28 C

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 B C

dw dw dw

27 0013h ?

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 add mov

ax, A ax, B 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.

B: A:

I1 I2 JMP A I3 I4 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:

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

B

proc A1 A2 A3 Ret endp 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

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 mul add mul

ax,a b c 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 mul mov mov mul add

ax,a b e,ax ax,c d 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.

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 Zecimal Zecimal Şir Întreg Display împachetat despachetat caractere Op1 Întreg Y Y Y Y Y Zecimal Y Y Y Y Y împachetat Zecimal Y Y Y Y Y despachetat Display Y Y Y Y Y Şir N N N N Z caractere 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

În instrucţiunea add

a, 2

80

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 y z .stack dw .code ... mov add mov

dw dw dw

3 5 ?

1000 dup (?)

ax,x ax,y z,dx

Aceeaşi soluţie se obţine şi dacă variabilele se definesc pe stivă: .data .stack

414 x y z

dw dw dw

3 5 ?

... mov mov add mov

bx, bp ax, [bx] ax, [bx+2] [bx+4], ax

.code

Dacă variabilele x, y, z se definesc în segmentul de cod al programului se obţine construcţia: .code mov mov jmp x y z

ax,@code ds,ax alfa dw 3 dw 5 dw ?

mov add mov

ax,a ax,y z,ax

alfa:

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.

415

În definirea: .data a b ... mov mov

dw dw

300 dup (?) 100 dup (?)

ax,a+200 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+2

b

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: b: a:

mov mov inc

ax,5 ax,5 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.

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 (mb) m=b; if (mc) m=c;

Compactitatea secvenţei creşte prin utilizarea operatorului condiţional:

417 (ab) ? (bc) ? m=c: m=b: (ac) ? 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.

e1:

e2

... mov cmp jle mov cmp jle mov mov ...

ax,a ax,b e1 ax,b ax,c e2 ax,c 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 unsigned unsigned };

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

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

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, ®); 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ă

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. Binar 0000 0001 0010 0011 0100 0101 0110 0111 1000 1001 1010 1011 1100 1101

Octal 0 1 2 3 4 5 6 7 10 11 12 13 14 15

Hexazecimal 0 1 2 3 4 5 6 7 8 9 A B C D

Tabelul 3.1. Zecimal 0 1 2 3 4 5 6 7 8 9 10 11 12 13

420

1110 1111

16 17

E F

14 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: 410.000 + 21.000 + 3100 + 410 + 91 = 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ă: 124 + 023 + 022 + 121 + 020 = 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 ‘dublucuvânt’). 3.2.1 Bit

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

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.

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 2n8-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.

Valoare 0 1 2 3 4 64 128 256 1024

Tabelul 3.2. Reprezentare (n=2) 0000 0000 0000 0000 0000 0000 0000 0001 0000 0000 0000 0010 0000 0000 0000 0011 0000 0000 0000 0100 0000 0000 0100 0000 0000 0000 1000 0000 0000 0001 0000 0000 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 n8-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

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 –2n8-1…2n8-1-1. În tabelul următor se găsesc câteva numere întregi negative reprezentate pe doi baiţi:

Valoare 0 1 2 3 -1 -2 -3 -4 -5

Tabelul 3.3. Reprezentare (n=2) 0000 0000 0000 0000 0000 0000 0000 0001 0000 0000 0000 0010 0000 0000 0000 0011 1111 1111 1111 1111 1111 1111 1111 1110 1111 1111 1111 1101 1111 1111 1111 1100 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).

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.

Caracter 0 1 2 3 4 5 6 7 8 9

Tabelul 3.4. Codul hexazecimal ASCII 30 31 32 33 34 35 36 37 38 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.

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:

Număr

semn 4823.493 0 -52.3 1 2.445 0

mii 4 0 0

sute 8 0 0

Tabelul 3.5. Reprezentare zeci unităţi zecimi Sutimi Miimi 2 3 4 9 3 5 2 3 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)SMBE 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ă

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 B15 E0 S

B6 B14 B22 E7

B5 B13 B21 E6

B4 B12 B20 E5

B3 B11 B19 E4

B2 B10 B18 E3

B1 B9 B17 E2

B0 B8 B16 E1

Figura 3.6 – Reprezentarea în virgulă mobilă, simplă precizie B7 B15 B23 B31 B39 B47 E3 S

B6 B14 B22 B30 B38 B46 E2 E10

B5 B13 B21 B29 B37 B45 E1 E9

B4 B12 B20 B28 B36 B44 E0 E8

B3 B11 B19 B27 B35 B43 B15 E7

B2 B10 B18 B26 B34 B42 B50 E6

B1 B9 B17 B25 B33 B41 B49 E5

B0 B8 B16 B24 B32 B40 B48 E4

Figura 3.7 – Reprezentarea în virgulă mobilă, dublă precizie B7 B15 B23 B31 B39 B47 B55 1 E7 S

B6 B14 B22 B30 B38 B46 B54 B62 E6 E14

B5 B13 B21 B29 B37 B45 B53 B61 E5 E13

B4 B12 B20 B28 B36 B44 B52 B60 E4 E12

B3 B11 B19 B27 B35 B43 B51 B59 E3 E11

B2 B10 B18 B26 B34 B42 B50 B58 E2 E10

B1 B9 B17 B25 B33 B41 B49 B57 E1 E9

B0 B8 B16 B24 B32 B40 B48 B56 E0 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.

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.

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'.

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.

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 0

Sk

d1

A

d2 B

d3 C

d4 D

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

x

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 c i 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 add xor mov

ax, 1ah ax, 2 bx, 0fa00h 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 sub add mov mov

cx, 4 si, 15 ax, 100 byte ptr[bx+1], 120 dx, 16

;b9 ;83 ;05 ;c6 ;ba

0004 ee 0f 0064 47 01 78 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

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 cmp jl cmp jg and add sub

ax,0 bx,100 alfa bx,400 beta bx,0ff0h si,4 di,2

;iniţializare registru ;comparare ;salt dacă este la stânga intervalului ;comparare ;salt dacă este la dreapta intervalului ;’şi’ pe biţi registru bx cu masca 0ff0h ;incrementare registru si ;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 mov mov

al, 2000 ax, 100 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

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 push pop xchg

ax ax ax cx,ax

40h 50h 58h 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 mov mov shr xor sub

cs,ax ds,ax ss,ax ah,cl ax,ax si,cx

8e 8e 8e d2 33 2b

c8 d8 d0 ec c0 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

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

1

13

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 mov mov add sub mov add

ax, alfa al, byte ptr beta gama, dx ax, sum cx, ratia ah, sir+2 sir-5, al

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 00a2 00a4 00ac

a b c e

dw dw dw dw

10 20 30, 40, 10, 20 ?

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

ax, a ax, b ax, c ax, c+6 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 ciclu: add loop

ax, alfa+10*3-4 ax, beta 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

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 cod_operaţie cod_operaţie

reg, deplasare [registru_index] reg, deplasare+registru_index registru_index deplasare reg.

Următoarea secvenţă evidenţiază modul de calcul al adresei pentru adresarea indexată: mov xor xor ciclu: add inc inc loop

cx,20 si,si ax,ax ax, x[si] si si 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 xor ciclu: add inc inc cmp jle

si,si ax,ax ax,a[si] si si si,2*(20-1) 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 lea xor ciclu: add add loop

cx,20 si,x ax,ax ax,[si] si, 2 ciclu

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

2  3 01

02

03

04

05

4A

4 4A 06

FC 07

08

09

0A

0B

0C

Figura 4.3 – Calculul adresei în cazul adresării bazate Secvenţa: mov mov add add

bp, ax, ax, ax,

offset alfa bp + 2 bp + 8 bp + 144

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 mov mov

ax, beta[bp + 52] bx, gama + bp 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:

ciclul_1: ciclul_2

xor xor xor mov push mov add add

bx, si, ax, cx, cx cx, ax, si,

bx si ax nr_linii nr_coloane x [bx] [si] 2

440 loop xor pop mov shl add loop

ciclul_2 si, si cx dx, nr_coloane dx,2 bx, dx 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 2

00

01

E5

26

3 26 02

E5 03

04

Figura 4.4 – Calculul adresei în cazul adresării indirecte Dacă se consideră secvenţa: mov xor xor add

si, bx, ax, ax,

offset x bx 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

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 mov mov mov mov mov mov

ax, offset a adr_a, ax ax, offset adr_a adr_adr_a, ax ax, offset adr_adr_a adr_adr_a, ax 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.

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.

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 -- -- -- -- OF DF IF TF SF ZF -- AF -- PF --

00 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.

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. 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.

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 PG

30-05 --

04 ET

03 TS

02 EM

01 MP

00 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.

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.

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.

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

OF

SF ZF

------M M M M M M M M M M M M M -M -M ----0 0 0 0

--M M M M M M M M M M M M M M ---

--M M M M M M M M M M M M M M ---

M M M --M M M M

M M M M -M M M M

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

Tabelul 5.1 AF P CF F TM -- M TM -- M -- M --- M -TM M TM TM M TM M M TM M M M M M TM M M M M M M M M M M M M M M M M M M M -- -- M -- -- M TM TM M M -- M M -- M M -- M M -- -- --- -- M -- M 0 -- M 0 -- M 0 -- M 0

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 AAA AAD AAM AAS ADC ADD AND ARPL BOUND BSF/BSR BT/BTS/BTR/ BTC CALL CBW CLC CLD CLI CLTS CMC CMP CMPS CWD DAA DAS DEC DIV ENTER ESC HLT IDIV IMUL IN INC

OF SF ZF

AF

PF CF

----M M 0

-M M -M M M

-M M -M M M M

TM --TM M M --

-M M -M M M

M --M TM M 0

---

---

M --

---

---

-M

TF IF DF NT RF

0 0 0 M M M

M M

M M

M M

M M

M M

--M --

M M M --

M M M --

TM TM M --

M M M --

TM TM

-M

---

---

---

---

-M

M

M

M

M

M

--

T

449 INS INT INTO

T 0 0

T

Indicatorul OF SF ZF Instrucţiunea IRET R R R Jcond T T T JCXZ JMP LAHF LAR M LDS/LES/LSS/ LFS/LGS LEA LEAVE LGDT/LIDT/LL DT/LMSW LOCK LODS LOOP LOOPE/LOOP T NE LSL M LTR MOV MOV control, ---debug MOVS MOVSX/MOV ZX MUL NEG NOP NOT OR OUT OUTS POP/POPA POPF PUSH/PUSHA /PUSHF RCL/RCR 1 RCL/RCR count REP/REPE/R EPNE RET ROL/ROR 1 ROL/ROR count SAHF

0 0

AF

PF CF

TF IF DF NT RF

R T

R T

R

R

R

R

T

T

--

--

-T

M M

-M

-M

-M

-M

M M

0

M

M

--

M

0 T

R

R

R

R

R

R

M --

TM TM

M --

M M R

R

R

R

R

R

R

R

R

450 SAL/SAR/SHL /SHR 1 SAL/SAR/SHL /SHR count SBB SCAS Indicatorul Instrucţiunea SET cond SGDT/SIDT/S LDT/SMSW SHLD/SHRD STC STD STI STOS STR SUB TEST VERR/VERR W WAIT XCHG XLAT XOR

M -M M

M M M M

M M M M

--M M

M M M M

OF SF ZF

AF

PF CF

T

T

T

--

M

M

--

M M TM M

T

T

M

M 1

T TF IF DF NT RF

1 1 T M 0

M M

M M M

M --

M M

M 0

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.

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;

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;

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: - variabilă de lucru; - 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 temp CONTOR

shl operand, contor

în care Operand este registrul AL, reprezentarea grafică este dată în figura 6.1.

454

7 C CF

0





0

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.

Mod adresare Exemplu Reg,1 Mem,1 Reg,CL Mem,CL Reg,imediat8 Mem,imediat8

SHL bx,1 SHL alfa,1 SHL ax,cl SHL alfa,cl SHL bx,5 SHL alfa,5

Tabelul 6.1. Număr cicluri maşină 286 386 486 2 3 3 7 7 4 5+n 3 3 8+n 7 4 5+n 3 2 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

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.

456

Codul operaţiei este de fapt o mnemonică. Programatorul care a lucrat întrun 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. Operaţia Addition Move Jump Compare Shift Increment

Mnemonicele limbajelor ASSEMBLERASSIRIS IBM A,AR,AD AD4,AD8 MVC,MVI MVZ MVSR,MVSL B BRU C,CD,CDR,CE CP4,CP2 SLA,SRA SLAx,SRAx IC2,IC4

MACRO11 ADD MOV BR CMP ASL,ASR INC

ASM ADD MOV JMP CMP SAL,SAR 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 b c

se va instrucţiunea:

equ equ equ

15 a + 4*6 a + b

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 b str x y

db dw struc dw db

? l0 dup (0) ? 8 dup (?)

458 str alfa

ends 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 xx yy zz st mat

struc db dw dt ends st 10

? ? ?

; lungime 1 ; lungime 2 ; lungime 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 mov mov

ax, type bx, length cx, size

mat ; ax=1+2+10 mat ; bx=50 (10*5 componente) 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 …… mov mov mov

dd

12345678h

ax, word high număr bx, word low număr al, byte high word low număr

; ax=1234 ; bx=5678 ; 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

459

specifică numele, tipul şi eventual valoarea cu care este iniţializată zona de memorie. În secvenţa: a b c d x c w

dw 7 db ‘abcd’ struc db ? dd 11 dup (?) ends 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 and and

al, byte ptr aa ;al:=78h byte ptr [si], 0fah ;operaţie pe 8 biţi 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:

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) mov bx, [bx+si+4*5] mov

cx, xxx.alfa

;parantezele modifică priorităţile ;ca în algebră ;[, ] se folosesc pentru evaluare ;şi extragere conţinut ;alfa e membru în structura xxx

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

Priorităţi 1 2 3 4 5 6 7 8 9 10 11 12 13

Tabelul 6.3. Operatori  () [] LENGTH, MASK, WIDTH . (operatorul de referire membri din structuri) HIGH, LOW +,- (operatori unari) : (specificare segment:offset) OFFSET, PTR, SEG, THIS, TYPE *, /, MOD, SHL, SHR +,- (operatori binari) EQ, NE, GT, GE, LT, LE NOT AND OR, XOR 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.

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.

462

Instrucţiune

Tabelul 6.4. Numărul de apariţii

mov pop cmp push sub ret add jmp call lea xor

567 152 89 77 32 28 20 14 6 6 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 c

6 c

5 c

4 c

3 c

2 c

1 c

0 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 dec inc

ax cx 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.

463

bit

7 c

6 c

5 c

4 c

3 c

2 r

1 r

0 r

Figura 6.3 – Forma internă a instrucţiunilor pe un bait cu un operand Tabelul 6.5. Registrul Operaţie pe cuvânt Operaţie pe bait AX AL CX CL DX DL BX BL SP AH BP CH SI DH DI BH

Cod 000 001 010 011 100 101 110 111

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 0 c

6 1 C

5 0 c

4 0 c

3 0 c

2 1 r

1 1 r

0 0 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

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 c

6 c

5 c

4 c

3 c

2 c

1 d

0 w

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

7 m

6 m

5 R

4 R

3 R

2

1 r/m

0

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.

465

Tabelul 6.6. Biţii 7, 6 Mm 00 01 10 11

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

Biţii 2, 1, 0 000 001 010 011 100 101 110 111

Tabelul 6.7. Formula de calcul a adresei + SI + deplasare + DI + deplasare + SI + deplasare + DI + deplasare SI + deplasare DI + deplasare BP + deplasare BX + deplasare

Deplasarea

BX BX BP BP

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 c c c c c c d w 1 0 1 1 1 0 1 0

7 6 5 4 3 2 1 0 m m R R R r/m 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;

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.

Cod 00 01 10 11

Tabelul 6.8. Registru segment ES CS SS 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:

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.

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 add and

bx, 1fach bx, x[si] + 0cch 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 sub cmp jle

ax, a ax, b ax, 0 alfa

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

ax, a ax, b alfa

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.

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 b c d e f g

db dw dd dt dq dw db

13 5 0fah 0 7 ? ?

: ; ; ; ; ; ;

a b c d e f g

ocupă ocupă ocupă ocupă ocupă ocupă ocupă

un bait iniţializat cu 13 2 baiţi iniţializaţi cu 5 4 baiţi iniţializaţi cu 0fah 10 baiţi iniţializaţi cu 0 8 baiţi iniţializaţi cu 7 2 baiţi neiniţializaţi 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 2263 –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.

471

7.2 Masive unidimensionale Masivele unidimensionale se definesc prin: nume

tip

n

dup (val_1, val_2,…,val_m)

nume tip n val_j

- identificator construit după regulile limbajului; - DB, DD, DQ, DT sau un tip definit de utilizator; - numărul de componente ale masivului; - valoarea obţinută după evaluarea unei expresii cu poziţia j din lista dacă sunt iniţializate componentele masivului.

unde:

În secvenţa: x y z w u

db dw dd db dw

10 dup (‘a’) 20 dup (0) 40 dup (?) 5 dup (3, 4, 5) 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;

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 b c d e

dw db dd dw dd

10 3 2 4 4

dup dup dup dup dup

(10 dup (3 dup (7, 4 dup (1, 2, 3, (5, 2 dup

(0)) (8), (19)) (3)) 4) (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:

7 7

3 3

3 3

3 3

3 3

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

1 1 1 1

2 2 2 2

3 3 3 3

4 4 4 4

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

5 5 5 5

0 0 0 0

0 0 0 0

9 9 9 9

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,

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

nume

struc membrul_1 membrul_2 …… membrul_n 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 vârsta salariu nr_copii reţineri persoana ends

db dw dd db dw

30 ? ? ? ?

dup

(?)

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;

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 culoare db vechime dw maşina ends . . . . . . . . opel maşina dacia maşina diverse maşina

20 dup (?) ‘roşie’ ?

(‘berlina’, ,15) 2 dup (‘break’, , 0) 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),

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 aa

record al:2, a2:4=11b, a3:2 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 (?) . . . . . . . .

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 cmp a1,0

; a1:=000000x00

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

nume

union membrul-1 membrul-2 membrul-3 . . . . . membrul-n 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

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ă

478 dw dw dw dw

offset offset offset offset

scade produs rest 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 zile calificat boolean unitate a b c d e

enum roşu, alb, verde, galben enum luni, marţi, miercuri, joi, vineri, sâmbăta enum satisfăcător, bine, excelent enum fals, adevărat enum bucată, litri, metru, tona culoare ( ? ) zile ( ? ) calificat 5 dup (bine) boolean (fals) 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, . . .

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 ccc

enum start=100, continuare, avansare, final 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 aaa articol

nume:BYTE:30, salariu:WORD,reţineri:WORD:4

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 matrice total minim raport

dw dd dw dd dd

100 10 0 ? ?

dup dup

( ? ) ( 20 )

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 ?

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: 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 nume

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,

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 add add sub mul mov

ax,0 ax, b ax, c ax, d ax, e prod, ax

; ; ; ; ;

ax:=ax+b ax:=ax+c ax:=ax-d dx:ax:=ax × e prod:=ax

483 mov

prod+2,dx ; prod+2:=dx

reprezintă un exemplu tipic de structură liniară. Secvenţa: cld mov call mov mov xor call mov mov int int

cx, OFFSET VAR prel x, dx y, c1 cx, cx prel ah, 9 dx, OFSET text2 21h 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, STOS, WAIT, XCHG, XALT.

Secvenţa: mov cmp jg

ax, a ax, b adevărat

mov

maxim, b

fals:

CUT, POP, PUSH, RET,

484 jmp adevărat : mov final: nop

final maxim, ax

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 cmp jle adevărat: mov jmp fals: mov final: nop

ax, a ax, b fals maxim, ax final maxim, b

este apropiată de instrucţiunea: IF(a>b) maxim:=a ELSE maxim:=b;

În cazul în care în program se cere implementarea structurii pseudoalternative 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 jge mov mov int final: mov int

ax,0 final ah, 9 dx, OFFSET text 21h ;afişare text ah, 4ch 21h

este preferată secvenţei:

485 cmp jl jmp negativ: mov mov int final: mov int

ax, 0 negativ final ah, 9 dx, OFFSET text 21h ah, 4ch 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:

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) cx:=n

secvenţa de repetat cx:=cx-1

nu cx=0

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

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 xor xor mov ciclu: add add loop mov

si, di, ax, cx,

si di ax 15

ax, x [si] si, 2 ciclu 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 mov

bx, bx cx, NR_LINII

xor xor push mov

ax, ax si, si cx ax, NR_COL

ciclul1:

ciclul2: add add loop mov add add pop loop

ax, mat [bx][si] si,2 ciclul2 sum [di], ax di, 2 bx, NR_COL * 2 cx 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.

488

iniţializări

expresia condiţie

secvenţa de repetat

modificare operanzi din expresia condiţie

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 mov mov

ax, 0 si, 0 cx, 0

489 ciclu: cmp jg cmp je add add inc jmp final: mov mov

ax, 1000 final cx, n final ax, x[si] si, 2 cx ciclu suma, ax 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

490 mov int mov mov int mov mov int mov int cmp jz cmp jz mov int

dx, OFFSET 21h ah, 0ah dx, OFFSET 21h ah,9 dx, OFFSET 21h ah,1 21h a1,”d” ciclu a1, “D” ciclu ah, 4Ch 21h

textl ;text ;text

invitaţie şir caractere

text2 ;text

introdus

text3 ;text

de continuare

;funcţia de ;introducere caractere:

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 je cmp je … cmp

var, VALI etich_1 var, VAL2 etich_2 var, VAL_n

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:

a  b a  b  a * b e b  1 a  1  0

daca x  1 daca x  2 daca x  3 daca x  4 daca x  5 in celelalte cazuri

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 je cmp je cmp je cmp je cmp je

x,1 aduna x,2 scade x, 3 produs x, 4 decrement x,5 increment

mov jmp

e, 0 final

zero:

aduna:

492 mov add mov jmp

ax, a ax, b e, ax final

mov sub mov jmp

ax, a ax, b e, ax final

mov mov mul mov jmp

ax, a bx, b bx e, ax final

mov dec mov

ax, a ax e, ax

scade:

produs:

decrement:

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 jne

x, 1 salt_1

mov add jmp

ax, a ax, b final

cmp jne

ax, 2 salt_2

mov sub jmp

ax, a ax, b final

cmp jne

x, 3 salt_3

mov mul jmp

ax, a b1 final

adună:

salt_1:

scade:

salt_2:

produs:

salt_3:

493 cmp jne

x, 4 salt_4

mov dec jmp

ax, b ax final

cmp jne

x ,5 salt_5

mov inc jmp

ax, a ax final

mov

ax, 0

mov

e, ax

decrement:

salt_4:

increment:

salt_5: final:

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:

494 a b e

db db db

mov add mov

al, a al, b e, al

20 13 ?

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 sub

ah, 0 bl, bl

xor mov

cl, cl ch, cl

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

db db db

25 7 ?

mov mov sub mov

ch, x dl, y ch, dl 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 b c e f g

db db db dw dw dw

mov mov mul

al, a bl, b bl

7 5 8 ? ? ?

495 mov mov mul mov mov mul mov

e, ax al, a c f, ax al, a 3 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 b cat rest

db db db db

mov cbw div mov mov

al, a

32 4 ? ?

b cat, ah 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

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 b e

dw dw dw

mov add mov

ax, a ax, b e, ax

20 13 ?

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

497 xor

cx, cx

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

dw dw dw

25 7 ?

mov mov sub mov

cx, x dx, y cx, dx 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 b c e f g

dw dw dw dd dd dd

mov mov mul mov mov mov mul mov mov mov mul mov mov

ax, a bx, b bx e, ax e+2, dx ax, a c f, ax f+2, dx ax, a 3 g, ax g+2, dx

evaluează expresiile : e = a * b f = a * c g = 3 * a

7 5 8 ? ? ?

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 b cat rest

dw dw dw dw

mov cwd div mov mov

ax, a

32 4 ? ?

b cat, ax 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.

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 dp2 dd dpsum dd mov add mov mov adc mov

A92Fh 49837h ?

ax, dp1 ax, dp2 dpsum, ax ax, dp1+2 ax, dp2+2 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+ 0004

A92F+ 9837

000B

4166

Scăderea pe 32 de biţi se face cu ajutorul instrucţiunii sbb (scăderea cu împrumut). Secvenţa: a b c

dd dd dd

6A92Fh 4B837h ?

mov sub mov

ax, b ax, a bx, b+2

500 sbb mov mov

bx, a+2 c, ax c+2, bx

evaluează expresia: c = a - b

0006A92F0004B837 0001F0F8 sau detaliat: 0 0 0 60 0 0 4carry 1

A92FB837

0001

F0F8

Î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 y z w

dd dd dd dd

?

mov mov add adc add adc sbb sbb mov mov

ax, x dx, x+2 ax, y dx, y+2 ax, 24 dx, 0 ax, z dx, z+2 w, ax 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ă

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 b1 2

32

a1 b2 216 + a2 b2 + + a2 b1 216

a1 b1 232 + (a1 b2 + a2 b1 ) 216 + a2 b2 Acestei descompuneri îi corespunde următoarea secvenţă de cod: mov mul mov mov mov mul mov add adc mov mul add adc mov mov adc mov mul add adc mov mov

ax, a b rez, ax cx, dx ax, a + 2 b bx, dx cx, ax bx, 0 ax, a b + 2 cx, ax bx, dx rez + 2, cx cx, 0 cx, 0 ax, a + 2 b + 2 ax, bx dx, cx rez + 4, ax 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:

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 b dd 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 jg add32 add32 jmp

s, a et c, 1 s, b iar

sub32 mov mov mov sub32

s, b ax, offset r word ptr [ax], a word ptr [ax+2], a+2 r, s

et:

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

503 mov cmp iesire:

ax, val + 2 ax, val2 + 2

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 0 0

1 6 7

0 0 0

3 6 9

0 0 0

5 1 6

0 0 0

8 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

+

504

0 0

8 F

0 0

7 C

0 0

9 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 0 0

3 7 A

7 8 F

8 5 D

5 8 D

4 7 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

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ă operandul dest. Ambii operanzi au reprezentare binară.

Operatori reg,reg mem,reg reg,mem reg,immed mem,immed accum,immed

Timpi 808x 286 3 2 16+EA 7 9+EA 7 4 3 17+EA 7 4 3

De exemplu, secvenţa: termen1 termen2

dw dw

11 23

386 2 7 6 2 7 2

486 1 3 2 1 3 1

rezultatul în

Lungime Bytes 2 2-4 (W88=24+EA) 2-4 (W88=13+EA) 3-4 3-6 (W88=23+EA) 2-3

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 Operatori 808x 286 reg,reg 3 2 mem,reg 16+EA 7 reg,mem 9+EA 7 reg,immed 4 3 mem,immed 17+EA 7 accum,immed 4 3

386 2 6 7 2 7 2

486 1 3 2 1 3 1

Lungime Bytes 2 2-4 (W88=24+EA) 2-4 (W88=13+EA) 3-4 3-6 (W88=25+EA) 2-3

Astfel, secvenţa: termen1 dw termen2 dw dif dw …………… mov ax, termen2 sub ax, termen1 mov dif, ax ……………

7 15 ?

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)

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.

Operatori reg8 reg16 reg32 mem8 mem16 mem32

Timpi 808x 286 386 70-77 13 9-14 118-113 21 9-22 9-38 (76-83)+EA 16 12-17 (124-139)+EA 24 -

486 13-18 13-26 13-42 13-18 12-25 12-21

Lungime Bytes 2 2 2-4 2-4 13-26 2-4 13-42 2-4

Astfel, secvenţa: factor1 dw factor2 dw produs dd ? …………….. mov ax, factor1 mul ax, factor2 mov produs, ax mov produs+2, dx ……………

41 15

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)

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=158176+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.

509

Tabelul 10.1. -128…127 0…255 -32768…32767 0…65535 -2^31…2^31-1 0…2^32-1

1 octet cu semn 1 octet fără semn 2 octeţi cu semn 2 octeţi fără semn 4 octeţi cu semn 4 octeţi fără semn

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.

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

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

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

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

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

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 }.

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:

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 (interruptenable 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.

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:

Nivelul PIC 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 NMI

Numarul Intreruperi 08h 09h 0Ah 0Bh 0Ch 0Dh 0Eh 0Fh 070h 071h 072h 073h 074h 075h 076h 077h 02h

Tabel 11.1. Dispozitiv Timer (ceasul software) Tastatura Slave 8259 Portul serial secundar (COM2) Protul serial primar (COM1) Hard Disk (Fix) Floppy Disk Imprimanta pe port paralel Ceasul hardware Master 8259 Nivel 2 Coprocesorul numeric Hard Disk (Fix) 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.

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

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

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 00h 01h 02h 03h 04h 05h 06h 07h 08h 09h 0Ah 0Bh ..... 10h 11h 12h 13h 14h ..... 40h 41h 43h 46h 4ah 67h 70h ..... 76h 77h

Semnificaţie (serviciu) Divizare la zero Modul single-step (vezi flag tf) Nemascabil (NMI) Breakpoint Depăsire (overflow) (vezi flaguri of si ov) Tipărire ecran Cod de operaţie nevalid Nu există coprocesor matematic IRQ0 Timer IRQ1 Tastatură IRQ2 cascade (trecerea pe controllerul 2 de întreruperi) IRQ3 COM 2/4 ..... Serviciul video Lista echipamentelor Mărimea memoriei convenţionale Disk I/O Port serial ..... Vector către funcţiile int 13h referitoare la dischetă Vectori către structurile referitoare la hard discuri Tabela de caractere EGA si VGA Vectori către structurile referitoare la hard discuri Alarmă utilizator Funcţii ale managerului de memorie expandată IRQ8 Real Time Clock ..... IRQ14 hard disk 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

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 20h* 21h 22h 23h 24h 25h/26h 27h* 28h 29h 2Eh 2Fh 31h 33h 67h

Semnificaţie (serviciu) Terminare program Servicii DOS Adresa de revenire din program după terminarea acestuia Adresa de revenire după terminare cu Control-Break Adresa de revenire după o eroare critică Acces direct la disc Terminare program cu rămânere rezindent în memorie Procesor neocupat (idle) Acces rapid la ecran Executare comandă DOS Multiserviciu (DoubleSpace, spooler, control TSR, etc.) Servicii DPMI (folosite sub modul Windows 386Enh) Suport mouse 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 mov int

ah, 09h dx, sir 21h

;functia de afisare sir ;in ds:dx se pune adresa sirului ;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

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

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 mov ds, ax ; mov es, ax ; mov [word cs:difference],delay push es mov ax, 351Ch ; intrerupere 1C int 21h mov [timerSeg], es

registru de date es = ds ; stabilire pauza

;salvare registru es ;obtinerea vectorului de ; apel serviciu DOS ; salvare segment

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 push ds mov ax, 251Ch mov dx, [timerOfs] initiala mov ds, [timerSeg] int 21h pop ds Exit: mov ah, 04Ch mov al, [codIesire] int 21h

; asteptare apasare tasta ; salvare ds ; setare vector 1C ;adresa de segment ;offset-ul initial ; restaurare serviciu initial ; restaurare ds

;**************************************** ; 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 push ax

; ; ; ;

pot sa aiba loc alte intreruperi salvare registrii folositi

push ds push dx mov al, EOI out PIC8259, al

; al = end-of-interrupt

526 mov mov mov @@10: mov

ax, BIOSData ds, ax ax, [word LowTimer]

; adresa de date BIOS ; folosire ds ; valuare timer

dx, [word LowTimer]

; incarcare valoare ; timer in dx ; scadere valoare ; noua - veche ; comparare cu ; pauza ; cicleaza pana la ; depasire ; dezactivare intreruperi ; in timpul restaurarii ; flag pe zero

sub dx, ax cmp dx, [cs:difference] jb @@10 cli

dec [byte cs:inProgress] pop dx ; restaurare registri pop ds pop ax @@99: iret ; retur intrerupere ENDP Incetinire PROC

KeyWaiting push ax mov ah, 1 int 16h

pop ax ret ENDP KeyWaiting END

Start

; ; ; ; ; ; ;

salvare ax functia de verificare buffer serviciu tastatura BIOS restaurare ax retur

; 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

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 mov mov . . .

ds ax, @data 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 difference

DB DW

0 0

; flag ; 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

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 EOI

EQU EQU . . . . mov out . . . .

0020h ; adresa de port a cipului 8259 PIC 0020h ; valuarea “End of interrupt” . . al, EOI ; al = end-of-interrupt 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

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 mov mov

ax, offset segmStiva ss, ax 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 sfarsitStiva

512 dub (0) = $

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 spVechi axVechi

DW DW DW

0 0 0

În cadrul rutinei se salvează vechile valori ale regiştrilor şi se stabileşte noua stivă: mov mov mov

[cs:ssVechi], ss [cs:spVechi], ss [cs:axVechi], ax

530 mov mov mov

ax, cs ss, ax sp, offset sfarsitStiva

Urmând ca la terminarea rutinei să se revină la vechile valori: mov mov mov

ss, [cs:ssVechi] sp, [cs:spVechi] ax, [cs:axVechi]

;aceasta este ordinea de setare ;a registrilor segment:offset !!!

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.

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;

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

533 mov s, pop ax endm

ax

12.2 Macroapelul şi macroexpandarea Dacă procedurile se apelează prin instrucţiunea call, macrodefiniţiilor se efectuează prin nume. Macroapelul macrodefiniţiei având prima linie de definire: nume_macro macro

referirea

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

534 endm

Programul principal arată în felul următor: ….. mov a, mov b, mov c, mov x, mov y, mov z, aduna3 aduna3 aduna3 …..

1 3 7 13 2 32 a, b, c, rez1 x, y, z, rez2 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 …..

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:

536 ... minim minim minim minim ...

x, y, w, v, i, j, min1,

z, min1 u, min2 k, min3 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

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

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. var_1 var_2 BP 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 ).

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 push ax

a1, a2, sum

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 nonexistenţa unui parametru actual. pushreg macro r1, r2, r3, r4, r5, r6 ifnb push r1 pushreg r2, r3, r4, r5, r6 endif endm

Dacă primul parametru nu este vid, macrodefiniţia se autoapelează, cu restul de parametri.

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 ; axax*2 mov bx,ax ; axax sal ax,1 ; axax*2

542 sal ax,1

; ; add ax,bx ; mov dest,ax ; pop bx ; pop ax

axax*2 avem ax=src*8 şi bx=src*2 ax src*10 dstax refacerea regiştrilor folosiţi

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 ; bxsursa mov cl,k ; cxnumă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

543 push bx mov ax,src mov bx,ax imul ax,bx imul ax,bx mov dst,ax pop bx pop ax

; ; ; ; ; ;

axsrc bxax (bcsrc) axax*bx (ax=src^2) axax*bx (ax=src^3) dstax refacerea regiştrilor

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 ;axdx,ax(extensia de semn de la 16 la 32 biţi) idiv cx ; împărţirea (axcât, dxrest) 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

544 xdiv_2: pop cx endm

; refacerea lui cx

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 xor xor xor xor xor xor endm

ax, bx, cx, dx, si, di,

Macro ax bx cx dx si di

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:

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, mov di, mov cx, et1: mov al, mov dl, cmp al, jne et2 inc si inc di loop et1 mov ax, jmp et4 et2: jb et3 mov ax,

ax dx bx byte ptr byte ptr dl

0

1

ds:[si] ds:[di]

547 jmp

et4

mov

ax, -1

pop pop pop

si di cx

et3: et4:

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

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, dountil) 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

549

p1 p2

r1 = f1 (p1, p2, …, pn) r2 = f2 (p1, p2, …, pn, r1) … rm = fm(p1, p2, …,pn, r1, r2, …,rm-1)

r1 r2 ... rm



pn

s1 = 1 s2 = 2 … sk = k

s1 ... s2

... sk

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.

550

NumeProcedura

PROC

[{FAR,NEAR}]

Salvare registre

Bloc prelucrare cu referirea parametrilor formali

Stocarea rezultatelor

Restaurare registre ret [n]

NumeProcedura

ENDP

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

551 par

proc near mov cl, m xor ch, ch lea di, text lea si, parola

par1: mov al, byte ptr[di] caracterul din text în ax add al, byte ptr[si] caracterul din parolă add al, cl caracterului add al,100 mov [di+m], al 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

;încarcă ;adună ;adună poziţia ;adună o constantă descarcă caracterul

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.

552

call sume sume PROC call sume

ret

call sume

sume ENDP

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. Suma PROC

Figura 13.3 –

Call suma

Compara PROC

Apelarea în cascadă a procedurilor

Call compara

RET

RET

553 Program principal Definire parametri reali Procedură (PROC)

Pregătirea transmiterii parametrilor reali APEL PROCEDURĂ (call)

Prelucrarea rezultatelor prelucrării

Salvare registre

Prelucrare referind parametri formali

Stocarea rezultatelor

Restaurare registre Revenire (RET)

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 sa 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

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

Stivă

B - 20

[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

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

.code start: ... call b1 ... call b2 ... mov ax, 4c00h int 21h b1 proc ... … ret b1 endp b2 proc ... ret b2 endp

a)

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 IP = IP + D16

;push IP

unde D16 reprezintă deplasarea între destinaţie şi instrucţiunea ce urmează lui CALL în programul apelator.

556 Instrucţiunea ret efectuează: IP = (SP) SP = SP + 2

;salt la instrucţiunea următoare lui CALL ;revenirea la baza stivei (pop IP).

Se consideră programul: Parametri a b c d s Parametri

segment dw 10 dw 15 dw 21 dw 37 dw ? ends

Stiva

segment Dw 100h dup (?) label word ends

varf Stiva Apelator

segment ASSUME cs:code, ds:parametri, ss:stiva

start: mov mov mov mov mov mov push mov push mov push mov push call mov add mov mov int

ax, parametri ds, ax ax, stiva ss, ax sp, offset varf ax, offset a ax ax, offset b ax ax, offset c ax ax, offset s ax NEAR ptr suma ax, s ax, d s, ax ax, 4c00h 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]

557

Apelator

mov pop pop ret ends

[bp+ 4], ax ax bp 8h

Dinamica stivei pentru apelarea procedurii suma din program este: Stivă Offset a Offset b Offset c Offset s IP BP Ax Ax BP IP SP = SP +8h

Semnificaţie Plasare adresa (offset) a Plasare adresa b Plasare adresa c Plasare adresa rezultat s Ape procedura sumă Salvează baza stivei Salvare pe stivă valoare registru AX Refacere registru AX Refacere registru BP Reface IP (se pregăteşte return) Scoate parametri din stivă

Adresare [BP+10] [BP+8] [BP+6] [BP+4] [BP+2] [BP+0] [BP-2] -

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 a b

segment dw 10 dw 15

558 c d s Parametri

dw dw dw ends

Stiva

segment Dw 100h dup (?) label word ends

varf Stiva Apelator

21 37 ?

segment ASSUME cs:code, ds:parametri, ss:stiva

start:

Apelator Procedura suma

suma procedura Stivă

mov mov mov mov mov mov push mov push mov push mov push call mov add mov mov int ends

ax, parametri ds, ax ax, stiva ss, ax sp, offset varf ax, offset a ax ax, offset b ax ax, offset c ax ax, offset s ax far ptr suma ax, s ax, d s, ax ax, 4c00h 21h

segment ASSUME cs:procedura 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 endp ends semnificaţie

Adresare

559 Offset a Offset b Offset c Offset s CS IP BP Ax Ax BP IP CS SP = SP +8h

Plasare adresa (offset) a Plasare adresa b Plasare adresa c Plasare adresa rezultat s Salvare registru de cod pe stivă (CALL) Ape procedura sumă Salvează baza stivei Salvare pe stivă valoare registru ax Refacere registru ax Refacere registru BP Reface IP (se pregăteşte return) Reface segmentul de cod Scoate parametri din stivă

[BP+12] [BP+10] [BP+8] [BP+6] [BP+4] [BP+2] [BP+0] [BP-2] -

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.

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

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: 000001101……

 

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: E1

M1

E2

M2

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. 

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

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ă.

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

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.

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)

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. exponent

ST(1)

parte fractionara

ST

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.

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.

569

Instrucţiunea constanta log2 e . Instrucţiunea constanta log2 10 . Instrucţiunea constanta log10 2. Instrucţiunea constanta loge 2.

FLD2E decrementează PVST şi iniţializează registrul ST cu FLD2T decrementează PVST şi iniţializează registrul ST cu FLDLG2 decrementează PVST şi iniţializează registrul ST cu FLDLN2 decrementează PVST şi iniţializează registrul ST cu

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]]

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)

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:

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.:

FLD ST(1)

ST(0) 340 275 ?

ST(1) 275 340 ?

ST(2) ? 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 debuggerului de a afişa conţinutul registrelor coprocesorului.

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:

574

n xk ex   = k=1 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 ) n xk ST(4 Va   ) conţine k=1 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

575 fst st(1) ; fld1 ; fst st(2) ; fst st(4) ; mov cx,1 et: fnop fld xnr fmul st(1),st

pune x in st1 pune 1 in st initializeaza k! cu 1 initializeaza suma cu 1

fxch st(2) ; mov temp,cx fimul temp ; fxch st(2) ; fld st(1) ; fdiv st,st(2) fadd st(4),st din st4 inc cx cmp cx,nnr ; jle et fxch st(4) ; fst rez ret e_la_x endp start: mov ax,@data mov ds,ax ; xor cx,cx call e_la_x ; mov ax,4c00h int 21h end start

schimba st cu st2

; x la k

k! schimba st cu st2 incarca in st pe x la k ; face impartirea ; aduna rezultatul fractiei la suma

compara cx cu n ; if <= sare la et aduce rezultatul in st ; duce rezultatul in memorie

incarca segmentul de date apeleaza procedura ; functia de iesire

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.

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

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 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();

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

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

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 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

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 ab?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

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

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 extern void afis(void); void main(void) { float v,x[40]; int i,n; printf("n="); scanf("%d",&n); for(i=0;i
584 sterg proc mov ah,0 mov al,2 int 10h ret sterg endp _afis proc call pozit mov ax,seg sir mov ds,ax mov dx,offset sir mov ah,09h int 21h terminat cu '$' mov ah,08h int 21h call sterg ret _afis endp end

; Stergerea ecranului

; Pozitionare pe ecran ; Afisare mesaj

; Apel functie DOS afisare text ; Asteptarea apasarii unei taste

; Iesirea catre functia apelatoare

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 mov bp,sp mov ax,[bp+4] add ax,[bp+6] add ax,[bp+8]

; ; ; ; ;

Salvarea registrului bp bp<-sp ax=par1 ax+=par2 ax+=par3

585 pop bp ret _suma endp end

; Refacere bp ; Iesirea din functie

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 mov bp,sp mov di,[bp+4] mov ax,[di] mov di,[bp+6] add ax,[di] mov di,[bp+8] add ax,[di] pop bp ret _suma endp end

; ; ; ; ; ; ; ; ; ;

Salvare bp bp<-sp di=pa ax=*pa; di=pb ax+=*pb di=pc ax+=*pc Refacere bp Iesire din functie

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);

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] mov rez,ax mov ax,offset rez pop bp ret _suma endp end

; ax=*par1+*par2+*par3 ; rez<-ax ; ax<-&rez

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
; ; ; ; ; ;

cx <- numarul de elemente si <- adresa primului element ax=0 do{ ax+=x[i]; i++

; }while(i
//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
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
589 } ;ASM 12 ; Suma elementelor unei .model small .stack 100h .code c equ 5 public _suma _suma proc push bp mov bp,sp mov bx,[bp+4] xor ax,ax mov dx,[bp+6] c1: mov cx,[bp+8] xor si,si c2: add ax,[bx][si] inc si inc si loop c2 add bx,c*2 dec dx jnz c1 pop bp ret _suma endp end

matrici

; Numarul de coloane al matricii

; Adresa primului element ; ax=0 ; Numarul de linii ; Numarul de coloane ; si=0 ; ax+=[bx][si] ; Urmatorul element ; Mai sunt coloane ? ; Urmatoarea linie ; Mai sunt linii ?

//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
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:

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 delimitează cu atributul asm, obţinându-se secvenţa echivalentă asm

17.3 Concluzii

{ 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 }

se

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.

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ă. Clasa

Obiect1

Obiect2

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ă

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ă.

595

Structura clasei derivate Clasa de baza 1

Clasa de baza 2

C la sa de baza 1

C lasa de baza 2

Clasa derivată

Date şi metode clasa derivata . . . .

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.

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ă.

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

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: (a , a )  (b , b )  (a  b , a  b ) 1 2 1 2 1 1 2 2 (a , a )  (b , b )  (a b  a b , a b  a b ) 1 2 1 2 1 1 2 2 1 2 2 1

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 adun_int dw

dw 0 adunare_integer

599 inm_int Integer ends Complex struc baza nr_complex adun_compl inm_compl Complex ends

dw

inmultire_integer

Integer <> dw 0 dw adunare_complex dw inmultire_complex

...

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 call call lea call

di,ob_integer ob_integer.adun_int ;nr_int = 4+10 = 14 ob_integer.inm_int ;nr_int = 14*10 = 140 di,ob_complex 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

600

asamblorul oferit de firma Borland. Pentru aceasta trebuie ca programul să fie asamblat şi link-editat cu următoarele comenzi: tasm /zi tlink /v

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)

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 idul ei) ;Id id-ul clasei cu numele ;Id id-ul obiectului (EGAL ;INTOTDEAUNA CU ID-UL CLASEI ; DE CARE APARTINE!) ;no nr elementelor din clasa cu id-ul ;baseNo nr claselor de baza pentru ;clasa cu id-ul ;disableProt indica daca se foloseste sau nu ;protectia datelor ; (folosita in apelul functiilor ;membre) ;friendOf<currId>Id indica, daca exista, prietenia ;intre clasele cu ; id-urile si <curcId> ;deplIn<currId> indica deplasarea clasei de baza ( ) in clasa ; cu id-ul <currId>

Pentru definirea unei clase se foloseşte macrodefiniţia class cu următoarea sintaxă: class ... 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 %OUT < **Error** Class name not specified ... > .ERR ELSE ;genereaza clasa %OUT < Creating class nume ... > currId = currId + 1 ;id-ul urmator fi +1 nume&Id = currId ;id-ul de clasa ;creaza variabila ce contine nr de elemente setBaseAndNoToZero %currId

va

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 idul 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-ul clasei) pentru a stabili numărul de clase de bază (baseNo) şi numărul de elemente (No) 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:

603 date MACRO den, tip, v internDate %currId, den, tip, ENDM internDate MACRO id, den, tip, v No&id = No&id + 1 ;alocare data IFB 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 ) care are un nume modificat (format prin stringizare sub şablonul <den>@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>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

604 den&id&FuncProt = currProt ENDM

Aceste macrodefiniţii nu fac altceva decât să crească numărul de elemente ale clasei (No) şi să stabilească protecţia pentru procedura respectivă (<den>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>@func@) şi se întrerupe, pe parcursul întregii rutine, protecţia datelor membre (deoarece se rulează în contextul clasei) prin setarea pe 1 a variabilei disableProt. Sfârşitul procedurii, pe

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 %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

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), apoi se trece la definirea unei variabile de identificare a clasei de bază (baseNoOf = ) şi la declararea efectivă a structurii clasei de bază în cea derivată (cu numele schimbat baseNo@base@) şi a deplasării acesteia în cadrul clasei derivate (deplIn). 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 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 operDreapta movFromOb, unde, %currId, currOffset, ob ELSE

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

608 isProt? %baseId, camp ;protejata

;vede

daca

este

;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

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ă pe 1 şi încarcă 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 func Adunare func Inmultire Integer ends class Complex

;partea intreaga

610 publicExt 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

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 ) 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 run run lea run run

si, numar1 ob_integer, ob_integer, di, numar2 ob_complex, ob_complex,

sfarsit: mov ax,4c00h int 21h end start

Adunare Inmultire Adunare Inmultire

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 linkeditare 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 Borland International

(c)

1988,

1996

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 public_ func Adunare func Inmultire Integer ends

;partea intreaga

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:

613 E:\TOOLS\TASM\WORKING>tasm intcomp2 Turbo Assembler Version 4.1 Copyright Borland International

(c)

1988,

1996

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 lea run run

ob_integer, nr_int, 4 si, numar1 ob_integer, Adunare 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

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 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

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@

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: STRUC <modificator> METHOD { declaraţii metode } declaraţii date membre ENDS

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

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 b2 Baza

<1.1> < >

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.

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 , 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

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

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 GLOBAL GLOBAL GLOBAL

Int_Adunare: PROC Int_Inmultire: PROC Complex_Adunare: PROC 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

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 ( PTR si).. 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:

622 ... DATASEG ob_integer Integer <4> ob_complex Complex <7, 5> CODESEG start: mov ax,@data mov ds,ax mov si, CALL si CALL si mov si, CALL si CALL si

offset METHOD METHOD offset METHOD METHOD

ob_integer Integer:Adunare, 10 Integer:Inmultire, 10 ob_complex Complex:Adunare, 10,5 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

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 10.000.000 de iteraţii .code start: mov ax,@data mov ds,ax

;

2000

x

5000

=

624 lea mov next1: push mov next2: mov mov mov

si, numar1

;[si] = numar1, [si+2] = numar2

cx, contor1 cx cx, contor2 ob_integer.nr_int, 4 ob_complex.nr_int, 7 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 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, set ob_complex, set ob_complex, run ob_integer, run ob_complex, loop next2 pop cx

nr_int, 4 nr_int, 7 nr_complex, 5 Inmultire Inmultire

de

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.

626

Tabelul 18.1. Analiza comparativă a celor trei variante Varianta ce foloseşte Varianta macroinstrucţiuni ce Indicator foloseşte Variant Varianta structuri a optimizată simple iniţială Linii de cod 95 93 93 scrise Linii de cod asamblate (după 95 147 96 expandare) Linii de cod folosite în 73 124 73 timpul execuţiei Timpul de execuţie 20 37 22 (secunde) Încapsulare (protecţia   membrilor)

Implementarea Borland orientată obiect 100 121

81

27

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.

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

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.

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+y+z+w

F(x,z,y,w)= x2+y2+z2+w2

x>0 y>0 z>0 w>0

x<0 y<0 z<0

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 push ax mov ax, offset push ax mov ax, offset push ax mov ax, offset push ax mov ax, offset push ax call ADD3

s

;variabila unde se stochează suma

d; c b a

Textul sursă pentru ridicarea la pătrat a unui număr este: POW2

proc push bp mov bp, sp push ax

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

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 push ax mov ax, offset push ax call POW2 mov ax, offset push ax mov ax, offset push ax call POW2 mov ax, offset push ax call POW2 mov ax, offset push ax mov ax, offset push ax call POW2 mov ax, offset push ax mov ax, offset push ax mov ax, offset push ax mov ax, offset push ax mov ax, offset push ax jmp suma

xp x

yp y

zp

wp w

s wp zp yp xp

beta: mov ax, offset push ax mov ax, offset push ax call modul mov ax, offset push ax mov ax, offset

mx x

my y

633 push ax call modul mov ax, offset push ax mov ax, offset push ax call modul mov ax, offset push ax mov ax, offset push ax call modul mov ax, offset push ax mov ax, offset push ax mov ax, offset push ax mov ax, offset push ax mov ax, offset push ax

mz z

mw w

s w mz my mx

suma:

end

call ADD3 ... ;procedura pentru conversia de la binar la şir de caractere ;şi reprezentare f(x, y, z, w) ... mov 4c00h int 21h start x y z w px py pz pw mx my mz mw s

dw dw dw dw dw dw dw dw dw dw dw dw dw

2 3 4 1 ? ? ? ? ? ? ? ? ?

Structura grafică a programului este dată în figura 19.1.

634

start x>0 y>0 z>0 w>0 x=0

push …

y=0 push … z=0 push …

w=0 |x|

push …

s=a+b+c+d

x2

|y|

y2

|z|

z2

|w|

w2

push …

conversiepush … afisare

push … push …

stop push …

push … push … push …

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

636 mov mul add mov mul add mov mul add mov mul add mov

ax, x x bx, ax ax, z y bx, ax ax, z z bx, ax ax, w w bx, ax 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

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;

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) 0 Prod1 Prod2 Prod3

Cant (4) 1 10 5 2

Pret unitar (5) 2 2 3 13

Total are textul sursă: rând1 filler1 linii rând2 filler2 col21 denumire 2 col22 cant2 col23 pret2 col24

struc db 10 db 60 ends struc db db db db db db db

db 10 dup (‘’) ‘I ‘ 30 dup (‘’) ‘ , ‘ 4 dup (‘’) ‘ , ‘ 5 dup (‘ ‘) ‘ , ‘

Valoare (8) 3 20 150 26 196

639 valoare2 col25 rând3 filler col31 denumire3 col32 cant3 col33 pretunit3 col34 valoare3 col35

db 8 dup (‘ ‘) db ‘ ,‘ struc db 10 dup (‘ ‘) db ‘, ‘ db ’Denumire/30’ db ‘ , ‘ db ‘CANT’ db ‘ , ‘ db ‘ PU’ db ‘ , ‘ db ‘ valoare’ db ‘ ,’

Pentru afişarea capului de tabel se foloseşte secvenţa: call call call call call call call call call

scrie-rand1 scrie-rand2 scrie_rand2 scrie_rand3 scrie_rand2 scrie_rand2 scrie_rand1 scrie_rand4 (I0I1I2I3) 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:

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

A

B

seg NCOL NLIN dw dw dw dw dw dw dw

‘DATE’ EQU 4 EQU 4 7, 5, 8, 13 3, 1, 8, 6 1, 0, 3, 7 4, 5, 11, 2 2, 4, 9, 1 32, 2, 5, 7 4, 9, 7, 4

641 C D G BT A2 SUMA VP VS i j

dw dw dw dw dw dw dw dw dw dw dw ends

3, 5, 21, 17 8, 3, 5, 2 6, 1, 1, 4 9, 11, 2, 5 NLIN dup (NCOL dup(?)) ; NLIN dup (NCOL dup(0)) ; NLIN dup (NCOL dup(0)) ; NCOL dup(0) ;vector NCOL dup(0) ;vector ? ?

B transpus patratul lui A A^2+B’ produs suma

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

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

ciclu:

proc mov cx, NCOL-1 mov i,0 mov f,0 mov f+r, 0 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 mov emw mov inc

b, i, j, NCOL, poz ax, [poz] bt, j, i, NLIN, poz [poz], ax j

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

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

A1 A2 A3 A4 A5 A6 A7

Ax+1

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

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 cod den UM Pret Stocin Intrari Iesiri Val Ends

struc dw db dw dw dw dw dw dw

? 30 dup (?) ? ? ? ? ? ?

b) Se stabilesc datele de test sub forma tabelului 19.1

646

Tabelul 19.1. Datele de test ale aplicaţiei ‘GESTIUNEA MATERIALELOR’ Cod Stoc Stoc Valoa Denumire UM Preţ Intrari Ieşiri matrial iniţial final re 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:

647

f1

... call f1 call f2 ... call fn mov ax,4c00h int 21h 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ă

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.

f0

vi fi

fj

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. fk vk

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.

fi

fj

fk

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, vR\1, 2, 3) (f0, )

Se asociază acestui arborescentă din figura 19.7.

sistem

de

perechi

f0 Vk=1 f1

Vk=2 f2

Vk=3 f

structura

650

Figura 19.7 – Structura arborescentă de program cu funcţie cu condiţie simplă

f0 V1=1 V2=1 V3=1

V2=1 V4=1

f1

f2

f3

V3=1

f4

f5

f6

V4=1

f7

f8

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, mov ds, cmp v1, jz salt_11 cmp v2,

@data ax 1 1

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

652

f1

f2

f3

f4

f5

f6

f7

f8

final: mov ax, 4c00h int 21h

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. f0 V=a1 Vk=a2 Vk=a3 Vk=an fn+1

fn

f3

f2

f1

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

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:

f1

f2

fn

fn1

call fn mov ax, 4c00h int 21h proc ... ret endp proc ... ret endp ... proc ... ret endp 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

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

B1 B2

B3 B4

C

C1 C2 C3

D

D1 D2 D2 D4

E

E1 E2 E3 E4

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 C1(1, 2, 3, 4) 1

2 3 C3(1, 2, 3, 4)

C2(1, 2)

f1

f2

f3

f4

f5

f6

4 C4(1)

f7

f8

C5(1, 2, 3, 4, 5)

f9

f10 f11

f12

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. f0 Structurii din figura 19.13 îi corespunde structura binară din figura 19.14. C1=1 C2=1

C1=2 C3=1

f1

C1=3

f2

C3=2 f3 C3=3

f4

C5=1 f7

C5=2 f8

f5

C5=3

f6 f9

C5=4 f10 f11

f12

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:

fk C=1 N

D

fi

fj

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: f0

C1 N

D

f1

f2 C2

N

C3 D

f3

N f4

C1:= C1+

D f6

f5

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. f1

q1

f2

q2

f3

f4

f5

q3

q4

q5

Figura 19.17 – Structură de tip serial încrucişat Se execută apelurile de proceduri:  f1 f2 f3 f4 f5

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: f0

f1

C=1

f2

C=1

f3

C=1

fn

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ă:

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.

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ă.

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.

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 j  c 0 j  Sc i  x i unde : c0j - numărul de cicluri fixat pentru instrucţiunea I j; 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,

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

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 mov mov add add pop ret aduna ENDP

bp bp,sp ax,[bp+4] ax,[bp+6] ax,[bp+8] bp

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 push mov push push mov mov mov ciclu: adc inc loop pop

PROC FAR bp bp,sp cx bx cx,[bp+6] bx,[bp+8] ax,0 add al,[bx] ah,0 bx ciclu cx

666 pop pop ret generare

bx bp ENDP

conţine 14 instrucţiuni executabile, dintre care: ciclu: add adc inc loop

al,[bx] ah,0 bx 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  k0  n1  k1 ...  n p  k p 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ă P j 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  Pj  Vf  1  Pj  Vt  n j . Volumul de operaţii apare ca un număr mediu de operaţii care se vor executa în timp. Procedura: trans PROC mov cmp jz cmp jb cmp ja and mov urmat:

NEAR al,[si] al,0 final al,'a' urmat al,'z' urmat al,NOT 20h [si],al

667 inc jmp final: ret trans ENDP

si trans

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.

Instrucţiunea 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 Inc si Jmp trans Ret

Tabelul 20.1. Număr de repetări mediu M M M M 0.033*M 0.967*M 0.967*M 0.96*M 0.96*M M M 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

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 cld push mov mov repne jne fara salt mov sub dec dec dec jmp eroare: mov mov final: pop ret strlen ENDP

NEAR cx cx,0ffffh al,0 scasb eroare

; ; ; ; ; ;

2 cicluri 10 cicluri 4 cicluri 4 cicluri n*(6+15) cicluri 8 cicluri pentru salt, 4 cicluri

ax,0ffffh ax,cx ax di di SHORT final

; ; ; ; ; ;

4 3 2 2 2 1

di,0 es,di

; 4 cicluri ; 2 cicluri

cx

; 8 cicluri ; 8 cicluri

cicluri cicluri cicluri cicluri cicluri ciclu

volumul de operaţii exprimat în cicluri este V1  50  21  n

669

în caz de eroare sau V2  24  21  n  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 sub xor

ax,0 ax,ax ax,ax

; 4 cicluri ; 3 cicluri ; 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 inc

si,1 si

; 4 cicluri ; 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 add si,bx initializat mov add si,ratia

; ratia este 57, 4 cicluri ; 3 cicluri, registrul bx,57 ; 16+6 cicluri

bx

a

fost

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 mov

cl,5 ax,79

670 sal

ax,cl

; 8+4*5 cicluri

întrucât secvenţa: mov bl,32 mov al,79 cbw b mul bl

; 2 cicluri ; 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.

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 mov cbw add

bx,ax al,operand_bait 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

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: 2 E = (a+b+c) * (a+b+c-d) + (c +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: 2 E = E1 * (E1-d) + (c +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 add add mov sub mul mov mov mov mul add adc div xor add adc mov mov

ax,a ax,b ax,c E1,ax ax,d E1 prod1,ax prod1+2,dx ax,c c ax,E1 dx,0 E1 dx,dx ax,prod1 dx,prod1+2 E,ax E+2,dx

;5 ;7 ;7 ;3 ;7 ;24 ;3 ;3 ;5 ;24 ;7 ;7 ;25 ;2 ;7 ;7 ;3 ;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 add add

ax,a ax,b ax,c

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 ciclu: mov mov add inc loop mov

cx,22 ax,0 si,0 ax,x[si] si ciclu 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 mov mov ciclu: add inc loop

ax,0 si,0 cx,22 al,x[si] si 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 xor xor ciclux: add inc loop

cx,37 ax,ax si,si

; 4 cicluri ; 3 cicluri ; 3 cicluri

al,x[si] si ciclux

;18 cicluri * 37 ; 2 cicluri * 37 ; 37 * 9 cicluri sau 5 cicluri

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 xor si,si xor ax,ax xor bx,bx ciclu: add al,x[si] add bl,y[si] inc si loop ciclu mov sumax,ax mov sumay,bx

; ; ; ;

4 3 3 3

cicluri cicluri cicluri cicluri

; ; ; ; ; ;

18*37 cicluri 18*37 cicluri 2*37 cicluri 9*37 cicluri sa 5 cicluri 14 cicluri 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 xor ax,ax xor si,si ciclu: add ax,x[si] inc si inc si loop ciclu mov sum,ax

;4 cicluri, ; 3 cicluri ; 3 cicluri ; ; ; ; ;

N constanta simbolica

18*2*N cicluri 2*2*N cicluri 2*2*N cicluri 9*2*N cicluri sau 5 cicluri 14 cicluri

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 xor add mov

bx,0 ax,ax ax,2 bx,ax

;1 ;2 ;3 ;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

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 x dw y dw z dw alfa: mov add mov mov int END start

alfa 10 20 ? ax,x ax,b z,ax ah,4ch 21h

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.

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 mov mov add add mov

ax,a bx,b cx,c ax,bx ax,cx e,ax

;5 ;5 ;5 ;2 ;2 ;3

cicluri cicluri cicluri cicluri cicluri cicluri

căreia îi corespund 22 cicluri maşină. Secvenţa echivalentă: mov add add mov

ax,a ax,b ax,c e,ax

;5 ;7 ;7 ;3

cicluri cicluri cicluri cicluri

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 mov sub add mov mov add sub mul

dx,dx ax,a ax,b ax,c bx,ax ax,a ax,b ax,c bx

;2 cicluri ;5 cicluri ;7 cicluri ;7 cicluri ;2 cicluri ;5 cicluri ;7 cicluri ;7 cicluri ;21 cicluri

căreia îi corespund 63 cicluri. În secvenţa echivalentă: xor mov mov mov mov sub add mov mov add sub mul

dx,dx bx,b cx,c si,a ax,si ax,bx ax,cx bx,ax ax,si ax,bx ax,cx bx

;2 cicluri ;5 cicluri ;5 cicluri ;5 cicluri ;2 cicluri ;2 cicluri ;2 cicluri ;2 cicluri ;2 cicluri ;2 cicluri ;2 cicluri ;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 : 2 E=(a+b+c)*(a+b+c-d)+(c +a+b+c)/(a+b+c) devine, prin utilizarea regiştrilor BX, SI şi DI, următoarea: mov add

ax,a ax,b

;5 cicluri ;7 cicluri

679 add mov sub mul mov mov mov mul add adc div xor add adc mov mov

ax,c bx,ax ax,d bx di,ax si,dx ax,c c ax,bx dx,0 bx dx,dx ax,di dx,si E,ax E+2,dx

;7 cicluri ;2 cicluri expresia E1 ;7 cicluri ;21 cicluri ;2 cicluri ;2 cicluri ;5 cicluri ;24 cicluri ;2 cicluri ;7 cicluri ;14 cicluri ;2 cicluri ;2 cicluri ;2 cicluri ;3 cicluri ;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

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

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ă cunoască 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

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 RM, 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.

Biţii

T T T 7 6 5

T 4

T 3

T 2

d 1

w 0

M M 7 6

reg 5 4 3

Figura 21.1 – Structura internă a instrucţiunii unde: T - biţi pentru codul operaţiei;

r/m 2 1 0

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.

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ţieioperand, întrucât frecvenţele de apariţie ţin seama de operaţie şi de operanzii utilizaţi.

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

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

687

nivel de profunzime mult mai accentuat, influenţând portabilitatea limbajului, în sensul reducerii efortului de integrare a componentelor în software neomogen.

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.

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

690

aproximativ 16000000 de culori. Generalizând, cu ajutorul a n biţi se reprezintă 2 n 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 0 40X25 text în 16 nuanţe de gri 1 40X25 text în 16 sau 8 culori 2 80X25 text în 16 nuanţe de gri 3 80X25 text în 16 sau 8 culori 4 320X200 grafic în 4 culori 5 320X200 grafic în 16 nuanţe de gri 6 640X200 grafic în alb şi negru 7 80X25 text în alb şi negru 13 320X200 grafic în 16 culori 14 640X200 grafic în 16 culori 15 640X350 grafic în alb şi negru 16 640X350 grafic în 4 sau 16 culori 17 640X480 grafic în alb şi negru 18 640X480 grafic în 16 culori

Tipul de adaptor CGA CGA CGA CGA CGA CGA CGA Monochrom EGA EGA EGA EGA VGA VGA

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 HGC CGA EGA VGA

= = = = =

1; 2; 3; 4; 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

692 TestIfEGA : mov mov test jz mov jmp

ax, 40h es, ax BYTE PTR es:[87h], 0FFh TestIfCGAorHGC ax, EGA 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.

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ă.

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 pop es mov

PLoop:

; ; ax,ax ; di,offset cx,64

xor mov stosb stosb stosb inc ax loop PLoop mov

; asigură ds=es

setare paletă de culori cu 256 nuanţe de gri incepînd cu 0.0.0, 1.1.1, 2,2,2, ... Palette ; ; ; ; ; ;

int

ax,0013h; setăm modul video la ; rezoluţia 320x200x256 10h ;

mov xor

dx,offset Palette bx,bx

; scriem paleta în DAC ;

695

ALoop:

mov mov int

cx,64 ax,1012h 10h

; ; ;

mov mov

ax,0A000h ; pointer la mamoria VGA es,ax ;

mov

di,14464

; plasăm imaginea în centrul ; ecranului

call Fade

; afişăm partea superioară

mov

; afişăm bara din mijloc (10 ; linii) ; culoarea din mijloc ; ; ; ; ; ; ;

cx,10

mov al,32 push cx mov cx,128 rep stosb add di,192 pop cx loop ALoop call Fade

; afişăm a treia parte

xor int

ah,ah 16h

; aşteptăm o tastă

mov int

ax,0003h 10h

; setăm modul text(80x25) ;

.exit

; ieşire în DOS

proc near

Fade

stosb loop PLoop2 add di,192 pop cx loop PLoop1 ret endp

; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ;

Palette

dup

; paleta de culori

Fade

PLoop1: PLoop2:

mov cx,50 push cx mov cx,64 mov al,cl dec al stosb

768,?

afişarăm prima şi a treia parte 50 de linii 64 de culori

câte două coloane pentru fiecare culoare

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 p386 radix 10 locals @@ model flat

; ; ; ; ;

modul TASM “ideal” procesor 80386 folosim numere zecimale etichete locale @@ 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 mov AH,8 @@1: in AL,DX test AL,AH jnz @@1 @@2: in AL,DX test AL,AH jz @@2 pop EDX EAX ret endp VerticalSync

; ; ; ;

registru de stare bit de sincronizare verticală citeşte starea aşteaptă sincronizarea

; citeşte starea

697 ; aşteaptă sincronizarea orizontală proc HorizontalSync near push EAX EDX mov DX,03DAH mov AH,1 @@1: in AL,DX test AL,AH jnz @@1 @@2: in AL,DX test AL,AH jz @@2 pop EDX EAX ret endp HorizontalSync

; ; ; ;

registru de stare bit de sincronizare orizontală citeşte starea aşteaptă sincronizarea

; citeşte starea

; 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] mov DX,03C8h out DX,AL inc DX mov AL,AH out DX,AL mov AL,BL out DX,AL mov AL,CL out DX,AL ret endp SETRGB16

; tabela de culori VGA ; adresa registrului PEL VGA ; 03C9h = registru de date VGA PEL ; scrie valoare roşu ; scrie valoare verde ; scrie valoare albastru

; 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] mov DX,03C7h out DX,AL add DX,2 in AL,DX mov [EBX],AL in AL,DX mov [ESI],AL

; tabela de culori VGA ; adresa registrului PEL VGA ; 03C9h = registru de date VGA PEL ; citeşte valoare roşu ; citeşte valoare verde

698 in AL,DX mov [EDI],AL ret endp GETRGB16 ; ; ; ;

; citeşte valoare verde

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 @@1: mov DX,03C8h mov AL,[EBX] inc EBX out DX,AL inc DX outsb outsb outsb dec ECX jnz @@1 sti ret endp SetDAC16

; deactivează întreruperile ; 03C8h = adresa registrului VGA PEL ; tabela de culori VGA

; ; ; ;

03C9h scrie scrie scrie

= registru de date VGA PEL valoare roşu valoare verde valoare albastru

; activează întreruperile

; 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 @@1: mov DX,03C7h mov AL,[EBX] inc EBX out DX,AL add DX,2 insb insb insb dec ECX jnz @@1 sti ret endp GetDAC16

; dezactivează întreruperi ; tabela de culori EGA ; ; ; ; ;

adresa registrului de scriere 03C9h = registru de date VGA PEL citeşte valoare roşu citeşte valoare verde citeşte valoare albastru

; activează întreruperi

699 ; setează valori implicite RGB (mod cu 16 culori) ; modifică [EAX EBX ECX EDX ESI] proc RestoreVGA near mov EBX,0 mov ECX,16 mov ESI,OFFSET EGACOLOR call SetDAC16 ret endp RestoreVGA

; ; ; ;

index de start număr de culori tabela de culori implicită setează paletă culori

; 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 out DX,AL inc DX mov AL,BL out DX,AL mov AL,CL out DX,AL mov AX,DI out DX,AL ret endp SETRGB256 ; ; ; ;

; adresă registru scriere VGA PEL ; 03C9h = registru de date VGA PEL ; scrie valoare roşu ; scrie valoare verde ; scrie valoare albastru

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 in AL,DX mov [EBX],AL in AL,DX mov [ESI],AL in AL,DX mov [EDI],AL ret endp GETRGB256

; 03C9h = registru de date VGA PEL ; citeşte valoare roşu ; citeşte valoare verde ; citeşte valoare albastru

; 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]

700 proc SetDAC256 near mov BX,CX shl CX,1 add CX,BX call VerticalSync cli mov DX,03C8h PEL out DX,AL inc DX rep outsb sti ret endp SetDAC256

; dezactivează întreruperi ; 03C8h = adresă registru scriere VGA

; 03C9h = registru de date VGA PEL ; activează întreruperi

; 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 mov DX,03C7h out DX,AL inc DX rep insb sti ret endp GetDAC256

; dezactivează întreruperi

; 03C9h = registru de date VGA PEL ; activează întreruperi

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

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ă

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 push push xor mov add mov call pop pop endm PgUp

macro bx dx bx,bx dx,cs:winpos dx,cs:disp64k cs:winpos,dx cs:winfunc dx bx

macro push push xor mov sub mov call add inc pop pop

bx dx bx,bx dx,cs:winpos dx,1 cs:winpos,dx cs:winfunc di,cs:granmask di dx bx

mov mov mov

ax,seg stk ss,ax sp,200h

; setează stiva

call

GetVESA

; iniţializează variabile VESA

mov mov int

ax,4f02h bx,0101h 10h

; ; modul VESA 101h (640x480, 256 culori) ;

mov mov

ax,0a000h ds,ax

endm

703 mov mov mov mov mov call

eax,10h ebx,13h ecx,20bh edx,1a1h ebp,21h Lin

mov int

ax,4c00h 21h

; afişează linie

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(d b) ; eax, ebx trebuie să aibă cuvintele semnificative setate pe 0 cmp ja

dx,bx LinS1

; sortare vârfuri

704 xchg xchg

ax,cx bx,dx

LinS1: sub ja neg xor

cx,ax LinS2 cx cs:xinc1[1],28h

LinS2: sub neg dec

dx,bx dx dx

; calcul delta_x ; modifică bucla internă după semn

;delta_y

shl add lea

bx,7 ax,bx edi,[eax][ebx*4]

mov xor mov shl add mul push mov shld pop add and mov call mov

si,dx bx,bx ax,cs:page ax,2 ; pageOffset=page*5*disp64K ax,cs:page cs:disp64k cx ; iniţializează fereastră CPU cl,cs:bitshift ; la punctul superior al liniei edx,edi,cl cx dx,ax di,cs:granmask cs:winpos,dx cs:winfunc dx,si

mov mov

ax,bp bx,dx

; calcul adresă liniară de start

;ax:culoare, bx:err-acumulator, cx:deltaX, dx:lungime verticală ;di:localizare în fereastra CPU, si:deltaY, bp:culoare LinL1: mov add jns LinE1: add jc inc jnz jmp LinL2: mov

[di],al bx,cx LinS3 di,280h LinR2 dx LinL1 LinOut [di],al

; rutina de desenare

705 xinc1 LinS3: add jc LinE2: add jns jmp

label byte ; trecerea la următorul pixel pe ; orizontală

di,1 LinR1 bx,si LinL2 LinE1

LinR1: js LinS7 PgDown mov ax,bp jmp LinE2 LinS7: PgUp mov ax,bp jmp LinE2

; mută pagina în jos cu 64k

; sau în sus cu granularitatea

LinR2: PgDown mov ax,bp inc dx jnz LinL1 LinOut: mov ret endp

cs:xinc1[1],0c7h

winfunc winpos granmask ferestrei disp64k page video) bitshift a ends stk

; mută pagina în jos cu 64k

dd dw dw

? ? ?

; pointer la funcţia VESA setwindow ; locaţia ferestrei CPU ; masca de adresă in interiorul

dw dw

? 0

;număr de segmente de 64k ; pagina video (0,1,2 pentru

db

0

; pentru extragerea biţilor de adresă ; ferestrei

1MB

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.

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

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ă: Mascabile Externe Nemascabile

Hardware Interne Întreruperi

BIOS Sistem DOS

Software Utilizator

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

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. 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

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:

709

adresa vector INT n = 4n 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;

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).

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:

712

Memoria convenţională (640ko)

Tabela vectorilor de întrerupere Zona de date BIOS Nucleul sistemului de operare Zona „System Data” Zona „System Code” Partea rezidentă a COMMAND.COM

0000:0000h 0040:0000h

Zona proceselor tranzitorii

Memoria superioară (384ko)

Memoria video BIOS video BIOS periferice ROM-BIOS

A000:0000h C000:0000h C800:0000h F000:0000h FFFF:000Fh

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ă.

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:

Offset 0 1

Lungime 1 octet 1 cuvânt

3

1 cuvânt

5 8

3 octeţi 8 octeţi

Tabelul 23.1. Semnificaţie identificatorul blocului (M sau Z) adresa de segment a PSP al proprietarului dimensiunea zonei de memorie asociate (în paragrafe) rezervat 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:

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.

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ă:

716

Tabelul 23.2. Offset 00h 02h

Lungime 1 cuvânt 1 cuvânt

04h 05h

1 octet 5 octeţi

0Ah 0Eh 12h 16h

2 cuvinte 2 cuvinte 2 cuvinte 1 cuvânt

18h 2Ch

20 octeţi 1 cuvânt

2Eh 5Ch 6Ch 80h

46 octeţi 36 octeţi 20 octeţi 1 octet

81h

127 octeţi

Semnificaţie codul unei instrucţiuni INT 20h adresa de segment a vârfului memoriei de bază ocupate rezervat codul unei instrucţiuni CALL seg_int21h:off_int21h adresa rutinei de tratare INT 22h adresa rutinei de tratare INT 23h adresa rutinei de tratare INT 24h adresa de segment a PSP al procesului părinte rezervat adresa de segment a blocului de environment rezervat primul FCB standard, nedeschis al doilea FCB standard, nedeschis lungimea şirului cu parametrii programului 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

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,

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ă:

719

Tabelul 23.3. Offset 00h 02h

Lungime 2 octeţi 1 cuvânt

04h

1 cuvânt

06h

1 cuvânt

08h

1 cuvânt

0Ah

1 cuvânt

0Ch

1 cuvânt

0Eh

1 cuvânt

10h

1 cuvânt

12h 14h 16h

1 cuvânt 1 cuvânt 1 cuvânt

18h 1Ah

1 cuvânt 1 cuvânt

TablOff

2*ReloCnt cuvinte ? octeţi

?

Semnificaţie „MZ” (semnătura fişierelor .EXE) PartPag = lungime fişier modulo 512 PageCnt = lungime fişier în pagini de 512 octeţi ReloCnt = număr de elemente din tabela de relocare HdrSize = dimensiune header în paragrafe de 16 octeţi MinMem = necesar minim de memorie după sfârşitul programului (în paragrafe) MaxMem = necesar maxim de memorie după sfârşitul programului (în paragrafe) ReloSS = deplasament segment stivă ExeSP = offset vârf stivă la lansarea în execuţie ChkSum = sumă de control ExeIP = offset adresă de start ReloCS = deplasament segment de cod TablOff = offset tabelă de relocare indicator de overlay (0 pentru modulele de bază) tabela de relocare 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.

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 headerului), î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,

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. 0 P.S.P. 100h

jmp etichetă_nerezident Date modul rezident Rutine de tratare a întreruperilor int_x1:

int_x2:

............ etichetă_nerezident:

Bloc decizie instalare/dezinstalare/alte operaţii Date modul nerezident Modul de instalare apel funcţie TSR Modul de dezinstalare

Alte module (configurare) apel funcţie EXIT

Figura 23.4 – Schema generală a imaginii în memorie a unui program rezident, înainte de apelul funcţiei TSR

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ă

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;

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;

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ă

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:

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.

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

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

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.

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

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

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 int_10h: ;codul int_13h: ;codul int_88h: ;codul

rutinei de tratare int 8 rutinei de tratare int 10h rutinei de tratare int 13h 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: mov mov mov mov mov mov mov int mov

;afisez timpul curent obtinut cs:sp_int2,sp ;salvez SP curent ax,cs es,ax bp,offset ceas ;ES:BP=pointer la sirul de afisat cx,8 ;sirul are 8 caractere dx,0000h ;randul 0, coloana 0 ax,1302h ;sir cu atribute, nu modific cursor 10h ;afisez 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

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

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 jnz cmp jnz

cs:activ,0 ;tentativa de activare multipla ? rel_sf_int8 cs:inbios,0 ;intrerupere rutina critica ? rel_sf_int8

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 propriuzisă 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:

737 det_cifre mov div add mov add mov endm

macro cifra_ofs ;;det. cifrele sec., min. sau orelor cl,10 cl ;;impart nr. la 10 (se afla in AX) ah,'0' ;;fac conversia la ASCII cs:ceascifra_ofs+2,ah ;;in AH se afla cifra unitatilor al,'0' ;;fac conversia la ASCII cs:ceascifra_ofs,al ;;in AL se afla cifra zecilor

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

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

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 mov int mov cmp jnz mov int mov cmp jnz mov int mov cmp jnz

cx,es ax,3508h 21h ax,es cx,ax nu_posibil ax,3510h 21h ax,es cx,ax nu_posibil ax,3513h 21h ax,es cx,ax nu_posibil

;CX=adr. seg. prog. rezident ;citire vector int. 8 ;ES=adr. seg. rutina crt. INT 8 ;INT 8 deturnat de alt TSR ;citire vector int. 10h ;ES=adr. seg. rutina crt. INT 10h ;INT 10h deturnat de alt TSR ;citire vector int. 13h ;ES=adr. seg. rutina crt. INT 13h ;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 mov mov int mov mov mov int mov mov mov int mov mov mov mov int mov int

ds,es:int8_veche_seg dx,es:int8_veche_off ax,2508h 21h ;restaurare int. 8 ds,es:int10h_veche_seg dx,es:int10h_veche_off ax,2510h 21h ;restaurare int. 10h ds,es:int13h_veche_seg dx,es:int13h_veche_off ax,2513h 21h ;restaurare int. 13h ax,0 ds,ax dx,0 ax,2588h 21h ;dezactivare int. 88h ah,49h 21h ;eliberare memorie TSR

Modulul de dezinstalare se încheie prin apelul funcţiei DOS 4Ch.

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

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

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 CS DS ES SS FS GS

0

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

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 AH BH CH DH SI DI BP SP IP

EAX EBX ECX EDX ESI EDI EBP ESP EIP

A B C D

7

0

X X X X

AL BL CL DL

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 Rezervat

17 V

16 15 R 0 N IO

O D

7 I T S Z

M

F

F F

F F F F

T PL

0

A 0

P

0 1 C

F

F

F

Figura 24.3 – Registrul EFLAGS la procesoarele i386

31 Rezervat

18 17 16 A V R

N

C

T PL F

M

F

IO O D I F

F

T

7 S

Z

F

F

F

0

A 0 P F

F

1

0 C 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

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 PG

4 ET

Rezervat

3 TS

2 EM

1 0 MP PE

Figura 24.5 – Registrul CR0 al procesorului i386

31 PG

30 CD

29 NW

18 AM

16 WP

5 NE

4 ET

3 TS

2 EM

1 0 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

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

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) LDTR (selector)

Base Register

Limit Register

TR (selector)

Base Register

Limit Register

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 I/O Map Base Reserved Reserved Reserved

16

15

0 Reserved LDT GS FS

64 60 5C 58

747

Reserved Reserved Reserved Reserved EDI ESI EBP ESP EBX EDX ECX EAX EFLAGS EIP CR3 Reserved ESP2 Reserved ESP1 Reserved ESP0 Reserved

DS SS CS ES

SS2 SS1 SS0 Back link to previous TSS

54 50 4C 48 44 40 3C 38 34 30 2C 28 24 20 1C 18 14 10 0C 8 4 0

Figura 24.8 – Formatul segmentului de stare a proceselor(TSS)

Base G B 0 V Limit P DPL 1 0 E W A Base 31..24 19..16 23..16 Segment Base 15..0 Segment Limit 15..0 G – Granulaţia B – Segment mare V – Disponibil pentru utilizare P – Existenţa segmentului DPL – Nivelul de prioritate al E – Expandare descriptorului A – Accesat W – Permite scriere Figura 24.9 – Formatul descriptorului segmentului de date Base G D 0 V Limit 31..24 19..16 Segment Base 15..0

P DPL 1 1 C Segment Limit 15..0

R A

Base 4 23..16 0

4 0

748

D – dimensiunea operaţiilor

implicită

a

R – permite citirea C – bit de conformitate

Figura 24.10 – Formatul descriptorului segmentului de cod Rezervat Segment Base 15..0

P DPL 0 0 1 0 Segment Limit 15..0

1

Rezervat

4 0

Figura 24.11 – Formatul descriptorului “Task Gate” Offsetul în segment 31..16 Segment Base 15..0

P DPL 0 X 1 0 0 0 0 0 Count Segment Limit 15..0

4 0

Figura 24.12 – Formatul descriptorului “Call Gate” Offsetul în segment 31..16 Segment Base 15..0

P DPL 0 X 1 1 1 T 0 0 Rezerved Segment Limit 15..0

4 0

T – 0 : “interrupt gate”, 1: “trap gate” Figura 24.12 – Formatul descriptorului “Interrupt/Trap Gate” Base G 0 0 V Limit 31..24 19..16 Segment Base 15..0

P DPL 0 0 0

1

0

Base 23..16

Segment Limit 15..0

4 0

Figura 24.13 – Formatul descriptorului LDT

Base G 0 0 V Limit 31..24 19..16 Segment Base 15..0

P DPL 0 X 0

B 1

Base 23..16

Segment Limit 15..0

4 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.

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

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 TI

1 0 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

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 diskului 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.

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: ;formatul descriptorului segmentului de cod de nivel 0 code_sel

dw dw db db db db

0ffffh ? ? 9ah 0 0

;limita(0-15) ;adresa de baza(0-15) ;adresa de baza(16-23) ;present=1,readable=1 ;limit a(16-19), G=0, D=0 ;baza(24-31)

;formatul descriptorului segmentului de date dmy_selec

dw dw db db db db

0ffffh ? ? 92h 0 0

;limita(0-15) ;adresa de baza(0-15) ;adresa de baza(16-23) ;present=1, writable=1 ;limita(16-19), G=0, D=0 ;baza(24-31)

Reintrarea în modul real se realizează astfel : ;program în db dw dw real_l: mov mov mov

modul protejat executat cu prioritatea 0 0eah ;salt neconditionat offset real_l ;ip code_sel ;selector cs ax,dmy_selec es,ax ds,ax

;selector fictiv ;resetarea registrilor de segment

12 mov mov mov cli mov and mov db dw dw read_mode: mov mov lidt

fs,ax gs,ax ss,ax ;dezactivarea intreruperilor eax,cr0 eax,not prot_enable ;dezactivarea modului protejat cr0,eax 0eah ;salt neconditionat offset real_mode ;EIP code ;CS

ax,data ds,ax [oldidt]

;reincarcarea tabelei de ;intreruperi pentru modul real ;reactivarea intreruperilor

sti

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

dscp

struct D_lim1 D_base1 D_base2 D_type D_lim2 D_base3 ends

db

dw dw db db db 0

0 0 0 0 0

;Structura stivei dupa intreruperea procesului în mod real stkdef oldeip

struct dw dw 0

0

13 oldcs

dw dw

oldflg dw dw dw dw dw dw dw dw dw dw dw dw dw ends

oldsp oldss oldes oldds oldfs oldgs stkdef

0 0 dw 0 0 0 0 0 0 0 0 0 0 0 0 0

0

;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 dd dd dd dd dd dd dd endm

macro ss0,esp0,ss1,esp1,ss2,esp2 0 offset esp0 ss0 offset esp1 ss1 offset esp2 ss2

;Macrodefinitie pentru definirea CR3 în TSS TSS_cr3 dd endm

macro 0

;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

14 dd dd dd dd dd dd dd dd

teax tecx tedx tebx offset tesp tebp tesi tedi

endm ;Macrodefinitie pentru definirea registrilor de segment în TSS TSS_seg dd dd dd dd dd dd endm

macro tes,tcs,tss,tds,tfs,tgs tes tcs tss tds tfs tgs

;Macrodefinitie pentru salt far neconditionat callf macro db dw dw endm

selector 9ah 0 selector

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.

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.

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 DSCPSIZE INTSIZE TWO prot_enable

equ equ equ equ equ

21 8 4 2 01h

17 attribute space

equ equ

;Pasul 1:

GDT

07h 20h

GDT segment para public gdt_tab label qword null_selec dscp

equ $-gdt_tab <,,,,,>

code_selec

equ $-gdt_tab

dscp

<0ffffh,,,09h,,>

use16

‘GDT’

;descriptorul selectorul ;segmentului de ;cod

task0_TSS_selec equ $-gdt_tab ;descriptorul selectorului dscp ;segmentului TSS stk0_selec dscp 0

equ $-gdt_tab <stk0_limit,,,92h,,>

;descriptorul selectorului ;segmentului de stiva de nivel

stk1_selec dscp

equ $-gdt_tab or 1 <stk1_limit,,,0b2h,,>

;descriptorul selectorului ;segmentului de stiva de ;nivel 1

stk2_selec dscp

equ $-gdt_tab or 2 <stk2_limit,,,0d2h,,>

;descriptorul selectorului ;segmentului de stiva de ;nivel 2

dmy_selec dscp

equ $-gdt_tab <0ffffh,,,92h,,>

video_selec equ $-gdt_tab or 3 dscp <0ffffh,8000h,0bh,0f2h,,> gdata_selec equ $-gdt_tab dscp int_selec dscp

equ $-gdt_tab <0ffffh,,,09ah,,>

gdt_limit GDT ends

equ

;Pasul 2:

IDT

$-gdt_tab

IDT segment para public use 16 ‘idt’ idt_tab equ $ REPT INTNO dscop <,int_selec,0,0eeh,,> ENDM

18 idt_limit IDT ends

equ

$

;Segmentul de date Gdata segment para public use16 ‘Gdata’ ;Pasul 3:

Definirea variabilelor

pGDT pGDT_limit pGDT_addr

label fword dw ? dd ?

pIDT pIDT_limit pIDT_addr

label fword dw ? dd ?

pold dIDT_limit dIDT_addr

label fword dw 03ffh 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 int_msg int_num

Gdata_limit equ Gdata ends

db dw db $

db ‘MOD PROTEJAT’,0 ‘Intreruperea:’ ? ‘H’,0

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 TSS_stack

segment para public use16 ‘task0’ 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: dmy dmy

Segmentul fictiv

segment db 128 ends

para public dup(0)

use16 ‘dmy’

;Segmentul de cod code segment assume main

proc mov mov

;Pasul 9: mov mov mov

para public use16 ‘code’ cs:code,ds:gdata

far ax,gdata ds,ax Initializarea IDT ax,IDT es,ax di,offset

idt_tab

20 mov

ax,offset

int_entry

mov fillidt: mov add axx loop

cx,INTNO

;Pasul 10:

Obtinerea adresei şi dimensiunii GDT/IDT

es:[di],ax di,DSCPSIZE ax,INTSIZE fillidt

mov mov xor mov shl

ax,offset gdt_kimit pGDT_limit,ax eax,eax ax,GDT eax,4

mov

pGDT_addr,eax

mov mov xor mov shl

ax,offset idt_limit pIDT_limit,ax eax.eax ax,idt eax,4

mov

pIDT_addr,eax

;Pasul 11:Pe baza gdt_phys_tab se seteaza adresa ;de baza pentru fiecare descriptor mov mov mov

ax,GDT es,ax si,offset gdt_phys_tab

mov

cx,gdt_tab_size

bdt1: lodsw mov bx,ax and bx,0fff8h lodsw push shl mov

ax ax,4 es:[bx][d_base1],ax

pop shr mov

ax ax,12 es:[bx],d_base2],al

loop

bdt1

;Pasul 12: comutarea în mod protejat

21 cli lgdt lidt

[pGDT] [pIDT]

mov or mov

eax,cr0 al,prot_enable cr0,eax

jmp

dword ptr

cs:[enter_prot]

enter_prot: dw offset dw code_selec

now_in_prot

;Pasul 13: Executie în mod protejat ; - setare LDTR,SS,SP,DS,ES,FS,GS now_in_prot: xor lidt mov mov mov mov mov mov mov mov

ax,ax ax ax,stk0_selec ss,ax sp,offset stk0_limit ax,gdata_selec ds,ax es,ax fs,ax gs,ax

;Pasul 14: Afisarea mesajului

mov mov mov xor mov mov rep mov

ax,video_selec es,ax cx,4000h di,di ah,attribute al,space stosw si,offset in_protected

mov call

di,320 disp_it

;Pasul 15: mov ltr

Incarcarea TSS în registrul TR ax,task0_TSS_selec ax

22 ;Pasul 16: int

int_entry: REPT call iret ENDM

Intoarcerea în modul real DOS 20

INTNO disp

disp: pop mov mov sub

ax bx,gdata_selec ds,bx ax,offset int_entry

shr mov mov call mov mov call

ax,TWO si,offset cx,TWO htoa si,offset di,5*160 disp_it

cli mov mov mov mov mov mov mov and mov

ax,dmy_selec es,ax ds,ax fs,ax gs,ax ss,ax eax,cr0 eax,not prot_enable cr0,eax

db dw dw

0eah offset code

next_instruction: mov ax,Gdata mov ds,ax mov ax,stk0 mov ss,ax mov sp,offset lidt

[pold]

sti mov

ax,4c00h

int_num

int_msg

next_instruction

stk0_limit

23 main

int endp

21h

disp_it mov mov mov disp_itl: lodsb stosw cmp jne ret disp_it

proc near ax,video_selec es,ax ah,attribute

htoa_tab htoa proc xor add dec htoal: mov and mov

db ‘0123456789ABCDEF’ near ebx,ebx si,cx si

htoa

mov dec shr loop ret endp

code

ends

al,0 disp_itl endp

bl,al bl,0fh bl,cs:[htoa_tab][ebx] byte ptr esi eax,4 htoal

[esi],bl

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

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 dd dd dd dd dd dd dd dd dd TSS0_limit

0 0,0 0,0 0,0 0 0 0 0,0,0,0,0,0,0,0 0,0,0,0,0,0 0 equ $

; ;esp0,ss0 ;esp1,ss1 ;esp2,ss2 ;cr3 ;eip ;eflags ;eax,ecx,ebx,edx,esp,ebp,esi,edi ;es,cs,ss,ds,fs,gs ;LDT

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

25 db db db db

TSS0_base2 89h 0 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_code_selec equ $-gdt_tab or 1 dscp

Pasul 2.1 Definirea descriptorului “task gate” în IDT, punctând către TSS-ul procesului “task0”: dscp

<,task0_TSS_selec,0,0e5h,,>

Pasul 4.1

;intreruperea 21

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 task1_TSS TSS_stack TSS_cr3 TSS_regs TSS_seg

Definirea TSS pentru procesul “task1”. segment para public use16 ‘task1’ stk0_selec,stk0_limit,stk1_selec, stk1_limit,stk2_selec,stk2_limit 0 task1_entry,2,0,0,0,0,0,0,0,stk1_limit gdata_selec,task1_code_selec,stk1_selec, gdata_selec,gdata_selec,gdata_selec dd 0 dw 0

26 task1_TSS_limit task1_TSS

dw equ ends

68h $

Pasul 15.1 Comutarea către procesul “task1” prin salt necondiţionat către selectorul segmentului TSS al procesului “task1” jmpf

Pasul 17:

task1_TSS_selec

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 mov mov mov disp21: lodsb stosw cmp jne ret disp2 endp

near ax,video_selec es,ax ah,attribute

al,0 disp21

task1_seg_limit task1_seg ends

equ

$

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.

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

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

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. FFFFFFFFH Sistemul de operare Windows 9x Drivere C0000000H 4GB BFFFFFFFH DLL-uri Win32 partajate Fişiere mapate în memorie 80000000H 7FFFFFFFH Programul Win32 2GB 00400000H 003FFFFFH Compatibilitate cu Win16

00001000H 00000FFFH Protejata, atribuiri de pointeri NULL Compatibilitate cu Win16

4MB

00000000H

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

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

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

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.

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:

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 ;...

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 style lpfnWndProc cbClsExtra

dd dd dd dd

? ? ? ?

cbWndExtra

dd ?

hInstance

dd ?

hIcon

dd ?

hCursor hbrBackground

dd ? dd ?

lpszMenuName

dd ?

lpszClassName

dd ?

hIconSm

dd ?

; dimensiunea structurii WNDCLASSEX ; stilul clasei fereastră ; pointerul la funcţia fereastră ; informaţii suplimentare ;pentru clasă ; informaţii suplimentare ;pentru fereastră ; identificatorul instanţei ;curente ; identificatorul pictogramei ;asociata clasei ; cursorul asociat clasei ; culoarea de fundal a ;ferestrelor din clasă ; pointer la şirul cu denumirea ;meniului ; pointer la şirul cu numele ;clasei ; 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

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 mov mov mov mov

wcex.hCursor,eax wcex.hbrBackground,COLOR_WINDOW+1 wcex.lpszMenuName,0 wcex.lpszClassName,offset NumeFer 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 );

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, // LPCTSTR lpClassName, // înregistrate LPCTSTR lpWindowName,// DWORD dwStyle, // int x, // int y, // int nWidth, // int nHeight, // HWND hWndParent, // părinte HMENU hMenu, // fereastra copil HINSTANCE hInstance, // programului LPVOID lpParam // a ferestrei

stilul extins al ferestrei pointer către numele clasei pointer către numele ferestrei stilul ferestrei poziţia orizontală a ferestrei poziţia verticală a ferestrei lăţimea ferestrei înălţimea ferestrei indicator către fereastra indicator către meniu sau indicatorul instanţei curente a pointer către datele de creare

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 push hinst push hmeniu push 0 push CW_USEDEFAULT push CW_USEDEFAULT push CW_USEDEFAULT push CW_USEDEFAULT push WS_OVERLAPPEDWINDOW push offset NumeApp push offset NumeFer push WS_EX_OVERLAPPEDWINDOW call CreateWindowEx mov hwnd,eax

; ; ; ; ; ; ; ; ; ; ; ;

lpParam hInstance identificator meniu identificator parinte inaltime latime y x stilul titlul ferestrei numele clasei stilul extins

;...

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:

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, HWND hWnd, UINT wMsgFilterMin, UINT wMsgFilterMax );

// // // //

adresa structurii cu mesajul identificatorul ferestrei primul mesaj al doilea mesaj

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 message wParam lParam time pt

DWORD ? ; identificatorul ferestrei căreia îi este destinat mesajul DWORD ? ; identificatorul mesajului DWORD ? ; parametru dependent de mesaj DWORD ? ; parametru dependent de mesaj DWORD ? ; momentul inserării mesajului în coada de mesaje 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 push 0 push NULL push offset msg call GetMessage or eax,eax

; ; ; ; ;

wMsgFilterMax wMsgFilterMin 0 - toate ferestrele lpMsg returneaza FALSE pentru WM_QUIT

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 procesului );

// codul de ieşire al

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, UINT Msg, WPARAM wParam,

// identificatorul ferestrei // mesajul // primul parametru al mesajului

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ă:

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 ferestrei NumeApp aplicatiei MesajDespre Mesaj1

db

'Programare sub Windows',0

;Numele

db

'Programare in Win32 ASM',0

;Numele

db db

'Paul Pocatilu, 1999',0 'Mouse click',0

.data? wcex WNDCLASSEX msg MSG hwnd dd ? hinst dd ? hmeniu dd ?

;var de tip structura clasa fereastra ;Var de tip MSG ;identificatorul de fereastra ;identificatorul instantei curente ;identificatorul de meniu

.code start: ;obtinerea hInstance

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 mov mov mov mov

wcex.hCursor,eax wcex.hbrBackground,COLOR_WINDOW+1 wcex.lpszMenuName,0 wcex.lpszClassName,offset NumeFer 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 push push push push push push

0 hinst hmeniu 0 CW_USEDEFAULT CW_USEDEFAULT CW_USEDEFAULT

; ; ; ; ; ; ;

lpParam hInstance identificator meniu identificator parinte inaltime latime y

45 push CW_USEDEFAULT push WS_OVERLAPPEDWINDOW push offset NumeApp push offset NumeFer push WS_EX_OVERLAPPEDWINDOW call CreateWindowEx mov hwnd,eax

; ; ; ; ;

x stilul titlul ferestrei numele clasei stilul extins

;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 push 0 push NULL push offset msg call GetMessage or eax,eax jz iesire

; ; ; ; ;

wMsgFilterMax wMsgFilterMin 0 - toate ferestrele lpMsg returneaza FALSE pentru WM_QUIT

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

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

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 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. poziţia

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

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

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 Microprocesoareaplicaţii, Editura Militară, Bucureşti, 1982 [LUPU82] Cristian Lupu, Vlad Ţepelea, Emil Purice Microprocesoare -

50

[MARI92]

[MASM78]

[MASM86]

[MAST95] [MATS76] [MCBE87] [MORG84] [MORRIS] [MUNT76] [MURR88] [MUSC96] [NORT89] [PETZ98] [PRET75]

[RICH94]

[RODE96]

[ROŞC77]

[RUNN88]

Aplicaţii, Editura Militară, Bucureşti, 1982 Marian Gheorghe, Badica Costin, Pătrăşcoiu Octavian Limbaje de asamblare Macro 11 – Intel 8086/88, Îndrumar de Laborator, Lito Universitatea Craiova, 1992 ***** MCS-86tm Macro Assembly language reference manual (manual order number 9806640-02), Intel Corporation, Santa Clara, 1978 ***** MStm - DOS 3.1 Macro ASSEMBLER Manual User's guide Masm Reference Manual, NEC Corporation 1986, Printed in Japan * * * , ASSEMBLY Language Master Class, Wrox Press Ltd., Birmingham, 1995 Y. Matsumoto AA: Assembly Automation 1302XC 13O1XC Robert W. Mc.Beth, J. Robert Ferguson IBM ASSEMBLER, John Wiley & Sons, New York, 1987 Morgan Cristopher L., Assembly Language Routines for the IBM PC & T, New York, Waite Group's, 1984 A.J. T. Davie, R. Morrison Recursive descent compiling, University of St. Andrews, Scotland E. Munteanu, V. Costea, N. Mitrov Programare in limbajul de asamblare ASSIRIS, Editura Tehnică, Bucureşti, 1976 Assembly Language Programming under OS/2, Osborne Mc Gran Hill Berkley, 1988 Gheorghe Muscă Programare în limbaj de asamblare, Editura Teora, Bucureşti, 1996 Peter Norton, John Socha, Peter Norton's Assembly Language Book for the IBM PC, Brady – New York, 1989 Petzold, Charles, Yao, Paul Programare în Windows 95, Editura Teora, Bucureşti, 1998 T. W. Prett Programming languages design and implementation, Prentice Hall Inc. Englewood Cliffs NJ, 1975 Richard P. Paul, SPARC Architecture Assembly Language Programming & C., Prentice Hall, Englewood Cliffs, New Jersey, 1994 Liviu Rodean, Optimizarea programelor elaborate în limbaje de asamblare, Lucrare de licenţă, ASE - Bucureşti, 1996 V. Roşca, C. Apostol, I. Ivan, I. Roşca, Limbaje de programare, Limbajul ASSIRIS, vol 1, 2, Lito ASE, Bucureşti, 1977 Wiliam C. Runnion, Structured Programming in Assembly

51

[SACH93] [SALE95]

[SANC90]

[SANC94] [SANC95]

[SCAN83]

[SCAN87]

[SCAN88] [SCHA93] [SCHM95] [SENC93] [SERA87]

[SERB85]

[SOCE75]

[SOMN92] [STRU92]

Language for IBM PC, PWS - Kent Publishing Company, Boston, 1988 Holger SCHAKEL, Programmer en Assembleur sur PC, Edition Micro Application, Paris, 1993 Cristian Salescu, Implementarea mecanismelor de recursivitate în limbaje de asamblare – Lucrare de licenţă ASE, Bucureşti, 1995 Julio Sanchez, Assembly Language tools and techniques for the IBM Micro computers, Prentice Hall Englewood Cliffs, New Jersey, 1990 Julio Sanchez, Maria P. Canton, PC Programming Hand book Mc Gron Hill Inc, New York, 1994 Julio Sanchez, Maria P. Canton, Numerical Programming the 387, 486 and PentiumTM, Mc Graw Hill, Inc., New York, 1995 Leo J Scanlon, IBM PC & XT Assembly Language A guide for programmers, Brady Communications Company Inc., New York, 1983 Leo J Scanlon, Assembly Language Subroutines for MSDOS Computers, Blue Ridge Summit PA TAB Boccks, 1987 Leo J. Scanlon, 8086/8088/80286, Assembly Language A Brady Book, New York, 1988 Holger Schakel Programmer in assembleur sur PC, Edition MicroAplication, Paris, 1993 Michael L. Schmit, Pentiumtm Processor Optimization tools AP Professional, New York, 1995 Sen-Cuo Ro, Shean Chuen Hen i386/i486 Advanced Programming, Van Nostrand Reinhold, New York, 1993 Luca Dan Şerbănaţi, Limbaje de asamblare şi asambloare în Limbaje de programare şi compilator, Editura Academiei, Bucureşti, 1987 Luca Dan Şerbănaţi, Valentin Cristea, Claudiu Popescu, Limbajul MACRO-11, Îndrumar de Laborator, Lito IPB, Bucureşti, 1985 A. Soceneantu, G. Gavrilescu, T. Ilin Limbaje de asamblare pentru calculatoarele electronice numerice. Asambloare, Editura Facla, Timişoara, 1975 Dan Somnea, Vlăduţ Teodor, Programarea în Assembler, Editura Tehnică, Bucureşti, 1992 Crişan Strugaru, Mircea Popa, Microprocesoare pe 16 biţi, Editura TM, Timişoara, 1992

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"

Related Documents