Procesor Java în Baza Arhitecturii Risc
INTODUCERE 5
1. NECESITATEA UTILIZĂRII ȘI PARTICULARITĂȚILE DE FUNCȚIONARE A JAVA VIRTUAL MACHINE……………………………..7
1.1. Noțiuni generale. 7
1.2. Destinația Java Virtual Machine 7
1.3. Conceptele limbajului de programare JAVA 8
1.4. Lansarea mașinii virtuale 16
2. REALIZĂRI ÎN DOMENIU 63
2.1. Coprocesorul JStar 63
2.2. Nucleul JAVA – Espresso 64
2.3. Procesorul JAVA – MOON 65
2.4. Generalizare 66
3. MODELELE ARHITECTURALE ALE PROCESORULUI JAVA 69
3.1. Generalități 69
3.2. Funcțiile JVM 70
3.3. Organizarea structurală și algoritmul de funcționare a
procesorului JAVA 72
4. STRUCTURA ȘI MODUL DE FUNCȚIONARE A
PROCESORULUI 77
4.1. Caracteristici generale 77
4.2. Structura generală internă a procesorului 78
4.3. Unitatea de registre 79
4.4. Unitatea de interfațare cu magistrala……………………………………….80
4.5. Stiva 81
4.6. Unitatea de execuție a intrucțiunilor 81
4.7. Unitatea de execuție a instrucțiunilor în virgulă flotantă 82
4.8. Unitatea de control 82
4.9. Unitatea de control a microprogramelor 83
4.10. Unitatea de dirijare cu thread-urile 84
5. CALCULUL PARAMETIRLOR ECONOMICI 86
5.1. Introducere 86
5.2. Etapele de proiectare 87
5.3. Estimarea cheltuielilor materiale 89
5.4. Calculul remunerării muncii 90
5.5. Calculul defalcărilor în fondul social de asigurare 91
5.6. Estimarea cheltuielilor pentru arenda calculatorului 91
5.7. Estimarea cheltuielilor pentru regie 92
5.8. Estimarea cheltuielilor totale pentru elaborare 92
5.9. Calculul efectului economic 93
6.PROTECȚIA MUNCII 96
6.1. Introdcere 96
6.2. Analiza condițiilor de muncă 97
6.3. Cerințele ergonomice privitor locul de muncă 98
6.4. Calcularea iluminatului natural 99
6.5. Securitatea antiincendiară în sălile de calcul 101
CONCLUZII 104
BIBLIOGRAFIE 105
=== CAPIT1 ===
1. Necesitatea utilizării și particularitățile de funcționare a Java Virtual Machine
1.1. Noțiuni generale
JAVA este un limbaj de programare concurent, orientat pe obiect. Sintaxa sa e similara cu cea a limbajului C, C++ însă concomitent exclude unele particularități care fac limbajele C și C++ complexe și confuze. Limbajul JAVA a fost conceput pentru elaborarea softului pentru rețele, astfel asigură lucrul concomitent cu mai multe calculatoare și securitatea înaltă a datelor. Aceasta se demonstrează prin faptul că codul compilat circulă prin rețea, comunică cu calculatoarele și asigură clienții că nu există nici un pericol de lansare.
Popularitatea tehnologiei World Wide Web face ca limbajul JAVA să fie și mai interesant. Posibilitățile de a realiza o pagină web cu ajutorul limbajului HTML sînt destul de limitate, deoarece în prezent e necesar de a reflecta tot mai multă și mai multă informație. Deci se ajunge la întrebarea: cum de extins aceste posibilități.
Această problemă a fost rezolvată de către firma Sun implementînd posibilitatea de utilizare combinată a limbajului JAVA și HTML. Astfel codul programului este copiat concomitent cu codul paginii. Înainte de a fi acceptate de către browser programele verifică dacă nu există nici un pericol de lansare și securitatea datelor. Asemenea paginilor HTML, programele compilate sînt independente de rețea și de calculatorul gazdă.
1.2. Destinația Java Virtual Machine
Java Virtual Machine (JVM) este baza ce unește platformele JAVA și JAVA 2. Ea este componenta tehnologiei care duce răspunderea de independența hard și soft. Ea are două funcții de bază: executarea codului și protecția datelor. JVM este o mașină abstractă. Asemenea unei mașini reale, JVM are un set de instrucțiuni bine definit și dirijează cu memoria în timpul rulării.
Primul prototip a JVM a fost implementat de către Sun Microsystems, Inc., care emula setul de instrucții a unui robot de asamblare. În prezent implementarea JVM a firmei Sun, componentă a produselor JavaTM 2 SDK și JavaTM 2 Runtime Enviroment, emulează o Java virtual machine pentru Win32 și Solaris foarte complexă și sofisticată. Totuși JVM nu include unele particularități hard precum și soft. Spre exemplu, ea nu este inerent întreruptă, astfel întreruperile trebuie prevăzute de către program. Aceasta s-a realizat și reieșind din faptul că programele trebuie să fie independente de calculatorul pe care se lansează precum și de sistemul de operare. Emularea JVM în cadrul procesorului fizic este reprezentată în figura 1.1.
JVM este independentă total de limbajul de programare JAVA. Legătura între ele este efectuată prin fișiere ce conțin bytecodul programului, așa numitele fișiere CLASS. Un fișier class conține instrucțiunile JVM (sau bytecodul), tabelul de simboluri precum și altă informație suplimentară.
Pentru securitate, JVM impune un format strict a codului în fișierul class. Totuși unele limbaje ce au funcționalitatea de crea fișiere de tip class, pot fi de asemenea primite (executate) de către JVM. Unele limbaje au implementat această posibilitate pentru a asigura portabilitatea programelor pentru diferite platforme, astfel folosind JVM ca o mașină portabilă a programelor.
1.3. Conceptele limbajului de programare JAVA
Codul unic. Programele scrise în limbajul JAVA utilizează caracterele Unicod pentru codificare, versiunea 2.1, accesibil pe adresa http://www.unicode.org. Toate elementele de intrare a unui program JAVA sînt formate în exclusivitate din caractere ASCII, excepție fac numai comentariile și datele de tip string.
Identificatorii. Identificatorii pot fi de o lungime nelimitată incluzînd litere și numere, prima din care trebuie să fie literă. Literele sînt accesibile în așa mod ca fiecare programator poate utiliza identificatori în limba sa natală. Caracterele sînt verificate cu ajutorul metodelor Character.isJavaLetter și Character.isJavaLetterOrDigit.
Tipuri și valori. Limbajul Java are o evidență strictă a tipurilor, ceea ce însemnă că fiecare variabilă și fiecare expresie are un tip care e recunoscut în procesul de compilare. Tipurile limitează valorile pe care le poate căpăta o variabilă sau pe care le poate produce o expresie, limitează operațiile suportate de acele valori și determină însemnătatea acelei operații. Strictețea tipurilor ajută la detectarea erorilor la stadia de compilare a programului.
Toate tipurile sînt divizate în două categorii: tipuri primitive și referențiale. Este de asemenea tipul null, destinat pentru expresiile ce nu au valoare, care nu are nume. Referința nulă este o valoare valabilă numai pentru expresii de tip null și poate totdeauna fi convertită în alte tipuri referențiale. Tipul primitiv este un tip care este predefinit de către limbajul JAVA. Valorile primitive nu-și partajează starea cu alte valori primitive. Tipurile primitive sînt de tip boolean și numerice. Tipurile numerice sînt întregi și cu virgulă flotantă. Tipurile întregi sînt: byte, short, int și long, a căror valori sînt de 8, 16, 32 și 64 biți. Tipurile cu virgulă flotantă sînt float și double, care sînt conceptual asociate cu 32 biți – precizie simplă și 64 biți – precizie dublă IEEE 754. Tipurile de date suportate de JVM sînt reprezentate în figura 1.2.
Operatorii pentru valori întregi. Limbajul de programare JAVA include un număr de operatori ce operează cu valori întregi pentru operații logice, aritmetice, de incrementare și decrementare, logice bit cu bit, de deplasare.
Operatorii întregi predefiniți nu indică depășirea pozitivă cît nici cea negativă. Unicii operatori ce generează excepții sînt împărțirea pentru numere întregi și determinarea restului de la împărțire, care produc o excepție de împărțire la zero.
Tipuri în virgulă mobilă, setul de valori. Standardul IEEE 754 include nu numai operarea cu numere pozitive și negative cu semn, dar de asemenea și pozitiv și negativ zero, infinit pozitiv și negativ și o valoare specială Not-a-Number (NaN). Valoarea NaN este folosită pentru a reprezenta rezultatul unei operații greșite spre exemplu de împărțirea 0 la 0.
Fiecare implementare a limbajului JAVA necesită să suporte două seturi standard de valori in virgulă mobilă, cu precizie simplă și dublă. În plus, limbajul JAVA poate să suporte unul sau altul sau chiar ambele seturi de valori pentru virgulă flotantă cu exponentă extinsă, float-extended-exponent, double-extended-exponent.
Valorile finite diferite de zero în virgulă flotantă pot fi exprimate în forma s*m*2(e-N+1), unde s este +1 sau -1, m este un număr întreg pozitiv mai mic decît 2N, și e este un număr întreg între Emin=-(2K-1-2) și Emax=2K-1-1, inclusiv și respectiv N și K sînt parametri ce depind de setul de valori. Valorile limită pentru parametrii N și K, pentru două seturi de valori reale obligatorii și pentru două seturi de valori reale opționale, sînt indicate în tabelul 1.
Tabelul 1.1
Valorile limită pentru parametrii N și K
La comparare, valorile zero pozitiv și zero negativ sînt egale, astfel rezultatul expresiei 0.0 == -0.0 este adevărat și rezultatul 0.0 > -0.0 este fals. Dar alte operații diferențiază zero pozitiv și cel negativ, spre exemplu 1.0/0.0 are valoarea infinit pozitiv și 1.0/-0.0 are valoarea infinit negativ. Operațiile logice asupra numerelor de tip NaN au tot timpul valoare falsă, deoarece NaN nu este număr.
Operatorii pentru valori în virgulă flotantă. Limbajul de programare JAVA include in număr de operanzi pentru numerele reprezentate în virgulă mobilă, care includ operații logice, aritmetice, de transformare, de incrementare și decrementare. Dacă cel puțin un operator a unei operații este de tip real, atunci operația este de tip real chiar dacă alt operator este de tip întreg. Valorile întoarse de operatorii în virgulă mobilă de asemenea sînt specificate de standardul IEEE 754. În particular, limbajul de programare JAVA necesită suportarea numerelor denormalizate în virgulă mobilă ce fac parte din acest standard. Operatorii asupra numerelor reale pot produce de asemenea excepții. O operație de depășire superioară va avea valoarea infinit pozitiv sau negativ, depășirea inferioară va avea valoarea zero pozitiv sau negativ ori răspuns denormalizat și operația care nu e descrisă matematic va avea rezultatul NaN. Dacă măcar unul din operanzi pentru toate operațiile numerice (cu excepția cele de comparație) este NaN, atunci rezultatul operației v-a fi NaN de asemenea.
Tipuri de referință, obiect și valori de referință. Există trei diferite tipuri de referință: tipul clasă (class type), interfață (interface type) și șir (array type). Un obiect este o clasă sau un șir creat dinamic. Valorile de tip referință sînt pointeri la aceste obiecte și o referință specială nulă, care indică că referința nu indică la nici un obiect. Un obiect este creat la început, apoi pe parcurs este distrus daca nu sînt referințe la el. Obiectele nu pot fi corectate sau eliminate prin directive explicite a limbajului.
Pot exista mai multe referințe la același obiect. Cele mai multe obiecte au stări memorizate în cîmpuri ale obiectelor care sînt instanțe ale claselor ori în variabile care sînt componente ale unui șir.
Fiecărui obiect îi este asociată o blocare care este utilizată de metodele de sincronizare pentru a efectua controlul asupra accesului concurent de către mai multe thread-uri. Tipurile referință formează o ierarhie. Fiecare tip clasă este o subclasă a altui tip clasă, excepție face clasa Object, care este clasă superioară pentru toate clasele și șirurile. Toate obiectele, inclusiv șirurile, suportă metodele clasei Object. Toate tipurile clasă și șir moștenesc metodele clasei Obiect.
Variabile. O variabilă este o locație de memorie. Ea are un tip asociat, uneori numit tip de compilare, care este de tip primitivă sau referință. O variabila de tip primitivă tot timpul are o valoare strictă de tip primitivă și nici într-un caz nu alta, pe cînd o variabilă de tip referință poate avea valoarea null care indică cu nu are referință la nici un obiect sau poate conține o referință la un obiect. Compatibilitatea valorii variabilei cu tipul ei este asigurată, deoarece în perioada de compilare se face verificarea se efectuează verificarea la corespundere a tipului variabilei cu valoarea atribuită ei. Există șapte tipuri de variabile: clasă, instanță, șir, parametru a metodelor, parametru a constructorilor, parametru a excepției, locale.
Conversia și promoția. Conversia din tipul S în tipul T permite unei expresii de tip S de a fi tratată în timpul compilării drept expresie de tipul T. Limbajul de programare JAVA include șase tipuri generale de conversii: conversia identică; conversia primitivă largă; conversia primitivă îngustă; conversia referențială largă; conversia referențială îngustă; conversia string.
Conversia primitivă largă. Conversiile în baza tipurilor primitive enumerate mai jos sînt numite conversii primitive "largi":
byte – short, int, long, float, double; int – long, float, double;
short – int, long, float, double; long – float, double;
char – int, long, float, double; float – double.
Conversia largă nu pierde informația despre semnul și mărimea valorii numerice.
Conversia primitivă îngustă. Conversiile în baza tipurilor primitive enumerate mai jos sînt numite conversii primitive "înguste":
byte – char; long – byte, short, char, int;
char – byte, short; float – byte, short, char, int, long;
short – byte, char; double – byte, short, char, int, long, float.
int – byte, short, char;
Conversia îngustă poate duce la pierderea informației despre semnul și mărimea valorii numerice, de exemplu transformînd valoarea de tip int 32763 în valoare de tip byte vom obține -5. Conversia îngustă poate duce la pierderea preciziei. Transformarea îngustă din double în float are loc conform standardului IEEE 754, prin rotungire.
Conversia referențială largă. Conversia referențială largă niciodată nu necesită o acțiune specială în timpul rulării, iată de ce niciodată nu generează excepții.
Conversia referențială îngustă. Conversiile următoare sînt numite conversii referențiale înguste: dintr-un oarecare tip clasă S în alt tip clasă T, cu condiția că S este o clasă superioară lui T; dintr-un oarecare tip clasă S într-un tip interfață K, cu condiția că S nu este final și nu implementează pe K; din tip obiect în tip șir; din tip obiect în tip interfață; dintr-un oarecare tip interfață J în careva tip clasă T, care nu este final; dintr-un oarecare tip interfață J în careva tip clasă T, care este final, cu condiția că T implementează J; dintr-un oarecare tip interfață J în careva tip interfață K, cu condiția că J nu este o subinterfață a lui K și nu este nici o metodă m declarată și în J și în K cu același nume dar de tipuri diferite; din careva tip șir SC[] în alt tip șir TC[], cu condiția că SC și TC sînt tipuri referință și există un tip permis de conversie îngustă din SC în TC.
Astfel de conversii necesită o verificare în timpul rulării pentru a determina dacă este posibilă sau nu conversia din tipul sursă în tipul destinație.
Conversia setului de valori. Conversia setului de valori este procesul de mapare a unei valori în virgulă mobilă dintr-un set de valori în altul fără ai schimba tipul.
Conversia de atribuire. Conversia de atribuire are loc cînd valoarea unei expresii este atribuită unei variabile, tipul expresiei trebuie sa fie convertit în tipul variabilei. Contextul repartizat permite folosirea conversiei identice, conversiei primitive largi ori a conversiei referențiale largi.
Conversia de invocare a metodei. Conversia de invocare a metodei este aplicată pentru fiecare valoare argument la apelarea metodei sau constructorului respectiv: tipul expresiei argument trebuie să fie convertită în tipul parametrului corespunzător. Contextul metodei invocate permite folosirea conversiei identice, conversiei primitive largi ori a conversiei referențiale largi. Dacă tipul unei expresii argument este float sau double, atunci conversia setului de valori este aplicată după conversia tipului.
Conversia distribuită. Conversia distribuită este mai “puternică” decît conversia metodei invocate și repartizate, tipul expresiei operandului trebuie să fie convertită în tipul bine determinat al operatorului destinație. Contextul conversiei distribuite permite utilizarea conversiei identice, conversiei primitive largi, conversiei primitive înguste, a conversiei referențiale largi și înguste.
Promovarea numerică. Promovarea numerică este folosită la convertirea operanzilor unui operator numeric într-un tip comun pentru ca să fie posibilă efectuarea operației. Există două tipuri de promoție numerică: unară și binară.
După conversia tipurilor este aplicată conversia setului de valori pentru fiecare operand.
Nume simple și calificate. Numele sînt folosite pentru a referi entitățile definite în program. O entitate declarată este un pachet, tip, componentă a tipului, parametru sau o variabilă locală. Un nume simplu este un identificator simplu. Numele calificate permit accesul la membrii pachetelor și tipurilor referință. Numele calificat este alcătuit din nume și identificator separați prin punct.
Pachete. Un pachet este alcătuit dintr-un număr de biblioteci de compilare și are un nume ierarhic. Pachetele sînt dezvoltate independent și fiecare pachet are setul de nume propriu, care permite de a depista din timp conflictele de nume. Orice implementare a JVM determină cum pachetele, bibliotecile de compilare și subpachetele sînt create și memorizate, care nume a pachetelor de nivel înalt sînt în sfera compilării particulare și care pachete sînt accesibile. Pachetele pot fi memorizate în fișierul de sistemă local, în fișierul de sistem distribuit, ori în unele forme a bazelor de date. Numele componentei pachetului ori numele clasei poate conține caractere care nu pot legal apărea în fișierele de sistemă a calculatorului gazdă. Implementarea JVM trebuie să suporte cel puțin un pachet fără nume, ea poate suporta mai mult de un pachet dar asta nu este necesar de a fi anume așa. Care biblioteci de compilare sînt în fiecare pachet fără nume este determinat de sistema gazdă. Pachetele fără nume sînt numai din conveniență cînd sînt elaborate aplicații mici sau temporare ori cînd numai se începe elaborarea.
Membrii pachetelor. Pachetele și tipurile referință au membri. Membrii unui pachet sînt subpachetele, toate clasele și tipurile referință declarate în toate bibliotecile de compilare a pachetului. Membrii tipului referință sînt cîmpurile, metodele, clasele și interfețele de bază. În general, subpachetele unui pachet sînt determinate de sistema gazdă. Totuși, standardul pachet java are totdeauna specificul ei. Nu există doi membri diferiți a aceluiași pachet care pot avea același nume simplu, pe cînd membrii diferitor pachete pot avea aceleași nume simple.
Excepțiile. Cînd un program încalcă semantica limbajului de programare JAVA, JVM generează programului o excepție ce indică această eroare. Un exemplu de încălcare este încercarea de a accesa elemente în afara unui șir. Limbajul de programare JAVA specifică faptul că o excepție v-a fi generată cînd constrîngerea semanticii este încălcată și va cauza un transfer din locul apariției excepției într-un loc care poate fi definit de programator și care deservește excpția dată. Metoda de invocare este numită abruptă dacă în cazul apariției unei excepții punctul de control al programului este redirecționat în afara procedurii în care a apărut excepția. În limbajul JAVA mecanismul de excepții este integrat cu modelul de sincronizare, astfel deservirea excepțiilor se face sincronizat.
Cauzele excepțiilor. O excepție este generată din una din trei cauze:
1. Cînd JVM detectează o eroare de execuție. Aceste excepții sînt generate în anumite puncte bine determinate ale programului. Ele pot fi de genul: acces la elemente din afara șirului, erori de încărcare sau unire a programelor, cînd e depășită limita de acces la oarecare resurse, spre exemplu cînd e folosită prea multă memorie.
2. A fost executată o instrucțiune de generare a excepției.
3. A apărut o excepție asincronă deoarece:
– a fost invocată metoda de stopare a execuției thread-ului sau grupei în întregime;
– a apărut o eroare internă în implementarea JVM.
Prelucrarea excepțiilor. Cînd este generată o excepție, controlul este transferat de la codul unde a fost generată excepția la cea mai apropiată instrucțiune catch a declarației try, care prelucrează excepția. Excepții asincrone sînt acelea care pot apărea în orice punct a execuției programului. Excepțiile asincrone sînt rare și sînt cauzate de:
– invocarea metodei de stopare a clasei Thread sau ThreadGroup;
– o eroare internă în implementarea JVM.
Limbajul de programare JAVA permite un timp mic, limitat dar și important pentru a efectua careva operațiuni înaintea apariției unei excepții asincrone. Această pauză permite de a lansa un cod optimizat care permite de a redirecționa excepția aceasta la un punct al programului unde ea va fi prelucrată.
1.4. Lansarea mașinii virtuale
JVM începe execuția prin invocarea metodei main a cărorva clase specificate, transmițîndu-le numai un singur argument care este un șir de tip string. Aceasta face ca clasa specificată să fie încărcată, efectuată legătura cu alte tipuri pe care le utilizează și inițializată.
JVM utilizează ClassLoader care efectuează încărcarea fișierelor clasă pentru a fi executate. După ce clasa e încărcată, ca să fie posibilă invocarea metodei main ea la început trebuie să fie inițializată și tipul trebuie să fie linkată înainte de a fi folosit. Linkarea prevede verificarea, pregătirea și rezoluția.
Verificarea urmărește corectitudinea semanticii JVM. Pregătirea încadrează alocarea memoriei pentru cîmpurile și metodele statice care sînt folosite internal de către JVM, așa ca tabelele metodelor. Rezoluția este procesul de verificare a referințelor simbolice din clasa program cu alte clase și interfețe, prin încărcarea altor clase și referințe și verificarea dacă referințele sînt corecte.
Încărcarea. Încărcarea se referă la procesul de găsire a reprezentării binare a tipului clasă sau interfață, poate prin prelucrarea lui operativă, dar mai probabil că prin restabilirea reprezentării lui binare prelucrată din codul sursă a programului de către compilator. Apoi din acest cod este construit obiectul clasă sau interfață. Formatul binar al clasei sau interfeței este de obicei un fișier cu extensia class, numit fișierul format al clasei (class file format).
Linkarea (verificarea, prepararea și rezoluția). Linkarea este procesul de pregătire a formei binare a clasei pentru a putea fi executată în viitor. Etapa precedentă linkării este încărcarea clasei sau interfeței. Linkarea este alcătuită din trei activități: verificarea, prepararea și rezoluția.
Inițializarea. Inițializarea unei clase consistă din executarea inițializatorilor ei statici și a inițializatorilor pentru cîmpurile statice declarate în clasă. Inițializarea unei interfețe consistă din executarea inițializatorilor pentru cîmpurile declarate în interfață. Înainte de a inițializa o clasă sau o interfață, trebuiesc inițializate superclasele lor directe, iar interfețele implementate de către clasă nu e necesar de le inițializat. Similar și pentru interfețe e necesar de a inițializa superinterfețele lor directe. Invocarea a careva metode din bibliotecile clasei de asemenea necesită inițializarea clasei sau interfeței.
Succesiunea evenimentelor de încărcare, linkare și inițializare sînt prezentate în figura 1.3.
Finalizarea lucrului JVM. JVM termină toate activitățile și stopează lucrul din următoarele două cauze:
– toate thread-urile care nu sînt de bază sînt terminate;
– careva thread a invocat metoda exit a clasei Runtime sau System și instrucțiunea de ieșire este permisă de către managerul securității.
Programul poate specifica pentru toți finalizatorii care n-au fost automat invocați să fie lansați înainte ca mașina virtuală să-și termine lucrul. Aceasta se face prin invocarea metodei runFinalizersOnExit a clasei System cu argumentul true. Odată rulați, finalizatorii sînt activați, ei pot fi dezactivați prin invocarea runFinalizersOnExit cu argumentul false.
Expresii FP-strict. Dacă tipul unei expresii este float sau double, atunci apare întrebarea la care set de valori expresia poate fi alipită. Aceasta este dirijat de către regulile de conversie a seturilor de valori, aceste reguli respectiv depind de faptul dacă expresia dată este FP-strict sau nu.
Fiecare expresie constantă de compilare este FP-strict. O expresie este considerată a fi FP-strict dacă ea suportă modificatorii strictfp. Urmează că o expresie nu este FP-strict dacă și numai dacă, ea nu este o expresie constantă de compilare și ea nu apare în declarații care au modificatori strictfp.
1.5. Thread-urile
JVM permite executarea în paralel a mai multor thread-uri. Aceste thread-uri independent execută codul care operează cu valori și obiecte rezidente în memoria principală partajată. Thread-urile pot fi suportate avînd mai multe procesoare (paralelism real), de un procesor prin partajarea timpului sau de mai multe procesoare prin partajarea timpului.
Pentru sincronizarea thread-urilor JAVA folosește monitoarele, mecanism care permite unui thread într-un anumit moment de timp să execute o porțiune de cod care nu este ocupată de alte thread-uri. Comportarea monitoarelor este explicată prin termenii de blocare. Există o blocare asociată fiecărui obiect.
Fiecare thread are memoria sa de lucru în care el își poate păstra copiile valorilor variabilelor din memoria principală care sînt partajate de mai multe thread-uri. Pentru a accesa o variabilă partajată thread-ul la început trebuie să obțină blocarea ei. Aceasta înseamnă că valoarea variabilei din memoria principală va fi copiată în memoria de lucru. Prin deblocarea variabilei, thread-ul garantează că valoarea variabilei ce se conține în memoria locală va fi scrisă înapoi în memoria principală.
Interacțiunea dintre thread și memoria principală și astfel dintre thread-uri poate fi explicată prin acțiuni de nivel jos. Există reguli despre ordinea în care aceste acțiuni pot apărea.
1.6. Structura JVM
Din descrierea limbajului JAVA putem trage concluzia despre construcția de bază și principiile de funcționare de bază a JVM. În cele ce urmează vom face o analiză amănunțită a JVM realizate soft pentru a acumula suficiente cunoștințe pentru proiectarea unui procesor fizic ce ar îndeplini toate funcțiile JVM. În figura 1.4 este redată structura JVM.
Fișierul format al clasei (Class File Format). Codul compilat care urmează a fi executat de către mașina virtuală java (JVM) este reprezentat utilizînd un format binar independent de hard și soft, tipical alocat într-un fișier numit fișierul format al clasei. Acest fișier definește precis reprezentarea clasei sau interfeței, incluzînd toate detaliile necesare.
Tipuri și valori primitive. Tipurile de date primitive suportate de către JVM sînt asemenea tipurilor suportate de JAVA cu excepția tipului boolean care e transformat în valori de tip întreg și tipul returnAddress care nu este prezent numai la JVM. Tipul returnAddress este folosit de către instrucțiunile jsr, ret și jsr_w ale JVM. Valorile tipului returnAddress sînt pointeri pentru opcodurile instrucțiunilor JVM. Valorile acestui tip nu pot fi accesate și respectiv modificate de către programele scrise în limbajul JAVA. JVM suportă lucrul cu direct cu șiruri de tip boolean. Instrucțiunea newarray permite crearea șirurilor de tip boolean. Aceste șiruri sînt accesate și modificate utilizînd instrucțiunile baload și bastore.
Tipurile și valorile referință. Există trei feluri de tipuri referință: clasă, șir și interfață. Valorile lor sînt referințe la instanțe create dinamic a claselor, șirurilor. O valoare referință poate de asemenea fi și o referință nulă, care nu indică la nici un obiect, notată prin null.
Partajarea memoriei. JVM definește diferite zone de memorie care sînt utilizate în timpul rulării programelor. Unele din aceste zone de memorie sînt create la lansarea JVM și sînt distruse numai după stoparea mașinii virtuale. Alte zone de memorie sînt pentru fiecare thread în parte. Astfel de zone sînt inițiate la crearea thread-ului și sînt distruse odată cu thread-ul.
Registrul pc. JVM suportă executarea a mai multor thread-uri concomitent. Fiecare thread are registrul său propriu pc (program counter). În careva punct, fiecare thread execută codul unei singure metode, care este metoda curentă pentru acel thread. Dacă acea metodă nu este proprie, registrul pc conține adresa instrucției JVM care se execută la moment. Dacă metoda care se execută la moment este proprie thread-ului, atunci registrul pc a mașinei virtuale nu este definit.
Stivele JVM. Fiecare thread a JVM are stiva lui proprie, care a fost creată în același moment cu thread-ul. Stivele JVM sînt echivalente cu cele a altor limbaje convenționale precum este C, ea păstrează variabilele locale și rezultatele parțiale și respectiv ajută la invocarea metodelor și reîntoarcerea din ele. Memoria alocată pentru stiva mașinii virtuale nu e necesar să fie continuă.
Stiva respectiv poate fi de o mărime constantă sau poate avea o mărime variabilă, în dependență de evaluarea execuției programului. Dacă stiva e de o mărime fixă, atunci mărimea memoriei alocate fiecărei stive în parte este calculată la crearea ei. JVM de asemenea poate permite programatorului de a indica mărimea stive, precum de altfel și se face în cazul modificării dinamice a mărimii stackului, există limitele minimă și maximă.
Dacă programul necesită o stivă mai mare atunci JVM generează eroarea StackOverflowError.
Dacă stiva poate fi extinsă dinamic și este făcută o încercare de extindere dar nu este suficientă memorie, sau e insuficientă memorie pentru a crea o stivă nouă, atunci este generată eroarea OutOfMemoryError.
Heap-ul. JVM are o zonă de memorie la care au acces toate thread-urile numită heap. Heap-ul este o zonă a memoriei din care este alocată memorie pentru toate instanțele claselor și șirurilor.
Heap-ul este creat la lansarea JVM. Dirijarea memoriei este efectuată de o sistemă automată numită "colector de gunoi". Modul de dirijare cu memoria poate fi ales în dependență de cerințele sistemei. Heap-ul poate fi de mărime fixă sau variabilă, în dependență de necesitățile programului. Memoria pentru anten nu e necesar de a fi continuă.
JVM poate permite programatorului sau utilizatorului controlul asupra mărimii inițiale a antetului, sau în cazul antetului variabil – controlul asupra limitelor minime și maxime.
Zona metodelor. JVM are o zonă a memoriei care este partajată tuturor thread-urilor. Aici sînt memorizate așa structuri ca datele cîmpurilor și metodelor, constantele de inițializare, codul pentru metode și constructori, incluzînd metodele speciale folosite la inițializarea claselor și instanțelor. Zona metodelor este creată la lansarea JVM. Zona metodelor poate fi de o mărime fixă sau variabilă. Memoria pentru zona metodelor nu e necesar de a fi continuă.
Stivele proprii a metodelor. JVM poate folosi stiva convențională, numită "stiva C", pentru a întreține metodele proprii, metode scrise în alte limbaje diferite de JAVA. Stiva proprie a metodei poate de asemenea fi folosită de către implementare în calitate de interpretator a setului de instrucțiuni a JVM într-un limbaj ca C. Implementările JVM care nu pot încărca metodele native și care nu se pot bizui pe stiva convențională, nu au necesitatea de a defini stiva proprie a metodei.
Cadre. Un cadru este folosit pentru a păstra datele și rezultatele parțiale, în așa mod precum s-ar efectua legătura dinamică, valorile întoarse de către metode și expedierea excepțiilor.
Un cadru nou este creat de fiecare dată cînd este invocată o metodă. Un cadru este distrus cînd invocarea metodei ea sfîrșit, chiar dacă sfîrșitul este normal sau abrupt. Cadrele sînt alocate din stivele thread-urilor care le creează. Fiecare cadru are șirul său de variabile locale, operatorul de stivă propriu și o referință la tabela de constante a clasei metodei curente.
Mărimea șirului de variabile locale și a operatorului stivei sînt determinate în timpul compilării și este furnizat împreună cu codul metodei. Astfel mărimea cadrului depinde numai de implementarea JVM și memoria pentru această structură poate fi alocată simultan cu invocarea metodei.
Variabile locale. Fiecare cadru conține un șir de variabile cunoscute ca variabilele ei locale. O singură variabilă locală poate avea o valoare de tip boolean, byte, char, short, int, float, referință sau returnAddress. O pereche de variabile locale pot avea valori de tipul long sau double.
JVM folosește variabilele locale pentru a transmite parametrii la invocarea metodei. În cazul invocării metodei, variabila locală 0 este tot timpul folosită pentru a transmite referința obiectul care a invocat metoda dată. Alți parametri sînt consecutiv transmiși în variabilele locale începînd cu variabila 1.
Operatorii stivei. Fiecare cadru conține o stivă LIFO, cunoscută ca operator de stivă. Mărimea operatorului de stivă este determinată în timpul compilării și este memorizată în fișierul clasă împreună cu codul metodei.
La crearea cadrului operatorul stivei este gol. Unele instrucțiuni JVM introduc valorile variabilelor locale ori a cîmpurilor în stivă, alte instrucțiuni extrag valorile din stivă, efectuează operații asupra lor și pun rezultatele înapoi în stivă. Operatorul stivei este de asemenea folosit pentru a transmite parametri metodelor și de a primi rezultatele.
De exemplu, instrucțiunea iadd adună două numere întregi. Ea necesită ca operanzii care trebuie prelucrați să fie primele două variabile din operatorul stivei. Ele sînt adunate și rezultatul este pus în stivă. Operatorul stivei poate include valori de orice tip.
Linkarea dinamică. Fiecare cadru conține o referință la tabela de constante pentru tipurile metodei curente pentru a suporta linkarea dinamică a codului metodei. Codul metodei, din fișierul clasă, se referă la invocarea metodei și accesarea variabilelor prin referințe simbolice. Linkarea dinamică transformă aceste referințe simbolice în referințe concrete ale metodei, încarcă clasele necesare pentru a verifica legăturile.
Terminarea normală a invocării metodei. O metodă este terminată normal dacă pe parcursul executării ei nu au apărut excepții și erori. În cazul terminării normale a metodei, poate fi întoarsă o valoare în metoda care a efectuat invocarea. Cadrul curent este folosit în acest caz pentru a restabili starea metodei invocatoare, incluzînd variabilele sale locale și operatorul stivei.
Terminarea abruptă a invocării metodei. Invocarea metodei se termină abrupt dacă pe parcursul executării codului metodei a fost generată e excepție care nu e prelucrată de către metoda dată. Metodele ce se termină abrupt niciodată nu întorc nici o valoare.
Aritmetica în virgulă mobilă. JVM încorporează setul de reguli definite în standardul IEEE 754. Însă există unele diferențe care sînt descrise în continuare.
Operațiile în virgulă mobilă implementate în JVM nu generează excepții, chiar dacă avem divizarea la zero. De asemenea nu sînt generate excepții la operațiile logice, de exemplu compararea unui număr cu o valoare NaN.
Toate operațiile de rotungire în JVM utilizează metoda rotungirii la cea mai apropiată valoare. La transformarea unui număr real în număr întreg este folosită rotungirea spre zero. JVM nu suportă formatele de numere extinse ce sînt definite în IEEE 754. Tipurile fload-extended-exponent și double-extended-exponent suportă numai exponente extinse dar nu și mantise după cum e prevăzut în standard.
Metode de inițializare. La nivelul JVM, fiecare constructor apare ca o instanță a metodei de inițializare care are un nume special <init>. Acest nume îi este atribuit de către compilator. Deoarece numele <init> nu este un identificator, el nu poate fi folosit direct din limbajul de programare JAVA. Metoda de inițializare a unei noi instanțe poate fi invocată numai în limitele JVM de către funcții speciale și ea este valabilă numai pentru instanțe a claselor neinițializate.
O clasă sau o interfață are cel mult o metodă de inițializare și este inițializată prin invocarea acestei metode. Metoda de inițializare este statică și nunecesită argumenți. Metoda de inițializare a clasei are numele de <clinit>, care îi este atribuit de către compilator.
Excepții. La apariția unei excepții toate procesele sînt stopate abrupt și este efectuat transferul la punctul de deservire a acestei excepții descris de către clauza catch. Dacă nu există e deservire pentru excepția apărută atunci se execută stoparea thread-ului, nu înainte de a îndeplini clauza finally dacă ea există. Căutarea codului de deservire a excepției este efectuată într-o ordine prestabilită, conform unui tabel alocat în memoria locală a fiecărei metode.
Sumarul setului de instrucțiuni. Instrucțiunile JVM constau din opcodul de un octet ce specifică operația ce trebuie îndeplinită, urmată de zero sau mai mulți operanzi, care indica argumenții sau datele ce sînt folosite la îndeplinirea operației. Multe instrucții nu au operanzi și sînt alcătuite numai din opcod.
Ciclul intern de lucru al JVM, cu excluderea excepțiilor, este următorul:
do{
fetch an opcode;
if (operands) fetch operands;
execute the action for the opcode;
}while(there is more to do);
Numărul și mărimea operanzilor este determinată de către opcod. Dacă operandul are mărime mai mare decît un octet, atunci el este citit ca doi operanzi, apoi este concatenat, de exemplu: (byte1<<8)|byte2.
Toate bytecode-urile sînt de mărimea unui octet, există numai două excepții de funcții care au operanzi de lungimea a patru octeți. Decizia de a limita mărimea opcod-urilor la un octet a fost făcută pentru a face codul cît mai compact și de a limita mărimea setului de instrucțiuni. Cuvintele nepoziționate mai mari de un octet indică că în timpul îndeplinirii datele vor fi concatenate din mai mulți octeți.
Tipurile și JVM. Pentru majoritatea instrucțiunilor, tipul este reprezentat printr-o literă în mnemonica opcodului: i – int, l – long, s – short, b – byte, c – char, f – float, d – double și a pentru referință. Dacă orice instrucțiune ar fi lucrat cu orice tipuri de date atunci, aceasta ar duce la mărirea setului de instrucțiuni și respectiv un octet nu ar fi suficient de le reprezentat pe toate. În loc, mașina virtuală, impune un set redus de instrucțiuni, care operează cu operanzi de numai un tip anumit, iar la necesitate există instrucțiuni de conversie a tipului datelor.
Tabelul de mai jos redă setul de instrucțiuni și tipurile suportate de către ele. Se poate ușor de observat că majoritatea instrucțiunilor nu suportă tipul byte și char. Aceasta e din cauza că la stadia de compilare operațiile asupra numerelor de tip byte și char sînt înlocuite cu operații asupra numerelor de tip int cu transformarea respectivă a tipului.
Tabelul 1.2
Setul de instrucțiuni și tipurile suportate de către ele
Maparea dintre tipurile actuale a JVM și cele de prelucrare este redată în următorul tabel:
Tabelul 1.3
Maparea dintre tipurile actuale a JVM și cele de prelucrare
Oarecare instrucțiuni a JVM așa ca pop și swap, operează cu stiva fără a lua în considerație tipul valorii, totuși, aceste instrucțiuni sînt constrînse să utilizeze numai valori din categoria tipurilor de prelucrare.
Instrucții aritmetice. Instrucțiunile aritmetice evaluează un rezultat care de obicei este în funcție de două valori a operatorului stivei și pune rezultatul înapoi în stivă. Există două tipuri principale de instrucțiuni aritmetice: care operează cu operanzi de tip întreg și care operează cu operanzi de tip real.
JVM nu indică depășirea la efectuarea operațiilor asupra numerelor întregi. Unicele operații care pot genera o excepție sînt div și rem, care generează excepție în caz că împărțitorul este egal cu zero.
Instrucțiunile ce operează cu numere reale de asemenea nu generează excepții de depășire, dacă valoarea e prea mare pentru a fi reprezentată ea trece în plus infinit, dacă e prea mică ea trece în minus infinit. Pentru toate operațiile aritmetice ce au măcar unul din operanzi egal cu NaN, rezultatul v-a fi de asemenea NaN.
Instrucțiunile de conversie a tipurilor. JVM suportă direct conversia numerică largă dintre următoarele tipuri:
int – long, float, double.
long – float, double.
float – double.
Instrucțiunile de conversie numerică largă sînt: i2l, i2f, i2d, l2f, l2d și f2d. Acest tip de conversie nu duce la pierderea informației sau la rotungirea rezultatelor. Însă uneori conversia din int sau long în float, sau din long în double poate duce la pierderea preciziei, aceasta se întîmplă din cauza pierderii celor mai puțin semnificativi biți, iar rezultatul obținut este numărul întreg corect rotungit. În pofida faptului că poate fi pierdută precizia, conversia numerică largă niciodată nu duce la generarea excepțiilor.
JVM de asemenea suportă conversia numerică îngustă care permite următoarele transformări:
int – byte, short, char.
long – int.
float – int, long.
double – int, long, float.
Instrucțiunile de conversie numerică îngustă sînt: i2b, i2c, i2s, l2i, f2i, f2l, d2i, d2l și d2d. Rezultatul conversiei numerice înguste poate fi diferit de valoarea inițială prin semn, valoare, precizie. Efectuarea unei astfel de conversii constă în simpla trunchiere a valorii inițiale pînă la mărimea ce poate fi reprezentată de către tipul destinație.
Sincronizarea. Sincronizarea de către JVM este efectuată prin intermediul monitoarelor. Sincronizarea secvențelor de instrucțiuni este de obicei efectuată prin codificarea blocurilor sincronizate a limbajului de programare JAVA. Instrucțiunile pentru sincronizare sînt: monitorenter și monitorexit. Pentru a efectua o sincronizare sigură, JVM trebuie să coopereze cu compilatorul limbajului JAVA. Compilatorul trebuie să asigure că pentru orice instrucțiune monitorenter să fie numaidecît o instrucțiune monitorexit, indiferent dacă metoda este terminată corect sau abrupt.
Bibliotecile clasă. JVM trebuie să permită suportarea suficientă a implementării bibliotecilor clasă a platformelor asociate. Careva din clasele acestor biblioteci nu pot fi implementate numai decît în cooperare cu JVM. Clasele care posibil pot necesită suport special din partea JVM sînt cele ce suportă:
Reflecția. Asemenea claselor din pachetul java.lang.reflect și clasa Class.
Încărcarea sau crearea unei clase sau interfețe. Cel mai potrivit exemplu este clasa ClassLoader.
Linkarea și inițializarea a unei clase sau interfețe. Drept exemplu poate servi de asemenea clasa ClassLoader.
Securitatea – așa ca clasele în pachetul java.security și alte clase ca SecurityManager.
Multithread – drept exemplu clasa Thread.
Referințe slabe – astfel de clase ca acele din pachetul java.lang.ref.
1.7. Structura fișierului clasă (ClassFile)
Fișierul clasă are o structură strictă și anume:
ClassFile{
u4 magic;
u2 minor_version;
u2 majir_version;
u2 constant_pool_count;
cp_info constant_pool[constant_pool_count-1];
u2 access_flags;
u2 this_class;
u2 super_class;
u2 interfaces_count;
u2 interfaces[interfaces_count];
u2 fields_count;
field_info fields[fields_count];
u2 methods_count;
method_info methods[methods_count];
u2 attributes_count;
attribute_info attributes[attributes_count];
}
Cîmpurile în structura fișierului clasă sînt următoarele:
magic – acest cîmp indică numărul magic care identifică fișierul clasă, el are valoarea 0xCAFEBABE.
minor_version, major_version – aceste cîmpuri indică versiunile minime și maxime a fișierului dat. Dacă versiunea fișierelor pe care le poate interpreta JVM se află între limitele minimă și maximă, atunci acest fișier clasă poate fi interpretat, altfel – nu.
constant_pool_count – acest cîmp indică numărul de elemente în tabela de constante plus unu. Indicele tabelei de constante este corect dacă el se află în limitele dintre zero si constant_pool_count.
constant_pool[] – acest cîmp este un tabel de structuri care reprezintă diferite constante, nume ale claselor și interfețelor, numele cîmpurilor și alte constante care sînt referite în interiorul clasei date și în subclasele ei. Formatul fiecărui cîmp al tabelului este indicat prin primul octet numit "tag".
acces_flag – valoarea acestui cîmp este o mască a fanioanelor folosite pentru a determina nivelul de acces la cîmpurile clasei sau interfeței date.
Interpretarea fanioanelor:
Tabelul 1.4
Interpretarea fanioanelor
Clasa se deosebește de interfață prin starea fanionului ACC_INTERFACE. Dacă acest fanion este setat, atunci obiectul dat este interfață și numaidecît trebuie să fie setate fanioanele ACC_PUBLIC și ACC_ABSTRACT. Există de asemenea o serie de fanioane care nu sînt utilizate, ele sînt rezervate pentru utilizare în viitor, ele de obicei sînt setate în zero de către compilator și de JVM sînt ignorate.
this_class – valoarea acestui cîmp trebuie să fie un indice a tabelului de constante. Elementul din poziția corespunzătoare valorii this_class trebuei să fie o structură, CONSTANT_Class_info ce reprezintă clasa sau interfața definită de către acest fișier.
super_class – pentru o clasă, valoarea cîmpului super_class trebuie să fie zero sau un indice valid în tabela de constante. Dacă valoarea cîmpului nu este zero, atunci elementul din tabelul de constante trebuie să fie o structură CONSTANT_Class_info care reprezintă superclasa directă a clasei definite de acest fișier clasă. O superclasă niciodată nu poate fi finală. Dacă valoarea acestui cîmp este zero, aceasta înseamnă că clasa dată este clasa Object, deoarece numai ea nu are superclasă.
Pentru o interfață, valoarea cîmpului super_class trebuie tot timpul să fie un indice valid a tabelului de constante și respectiv elementul cu acest indice reprezintă o structură CONSTANT_Class_info ce reprezintă clasa Object.
interface_count – această valoare indică numărul superinterfețelor directe acestei clase sau interfețe.
interfaces[] – orice valoare din acest șir trebuie să fie un indice valid a tabelului de constante. Orice element corespunzător din acest tabel trebuie să fie o structură CONSTANT_Class_info ce reprezintă o interfață care este superinterfață directă a acestei clase sau interfețe.
fields_count – această valoare indică numărul de structuri field_info în tabela cîmpurilor.
fields[] – orice valoare a tabelului fields[] trebuie să fie o structură field_info ce conține descrierea completă a cîmpului în această interfață sau clasă. În acest tabel sînt incluse numai acele cîmpuri care sînt definite nemijlocit dar nu căpătate prin moștenire.
methods_count – această valoare indică numărul de structuri method_info în tabelul metodelor.
methods[] – orice valoare în tabela metodelor trebuie să fie o structură method_info ce conține descrierea completă a metodelor din această clasă sau interfață. Dacă metoda nu este nativă sau abstractă, atunci sînt incluse de asemenea instrucțiunile JVM ce implementează această metodă.
Structurile method_info reprezintă toate metodele declarate de către această clasă sau interfață. Acest tabel nu include metodele moștenite.
attributes_count – această valoare indică numărul de atribute în tabela de atribute a clasei respective.
attributes[] – orice valoare a tabelului de atribute trebuie să fie o structură attribute. Atributele definite de această specificare ca fiind incluse în tabela de atribute a structurii ClassFile sînt atributele SourceFile și atributele Deprecated.
Implementarea JVM ignorează unele sau toate atributele din tabela de atribute, care nu sînt recunoscute ca fiind valide.
Reprezentarea internă a numelor calificate depline a claselor și interfețelor. Numele claselor și interfețelor care apar în structura fișierului clasă sînt reprezentate în forma calificată deplină. Astfel de nume sînt reprezentate ca structuri CONSTANT_Utf8_info. Numele claselor și interfețelor sînt referențiate din structurile CONSTANT_NameAndType_info care au astfel de nume în descrierea lor și din toate structurile CONSTANT_Class_info.
Descriptorii. Descriptorul reprezintă tipul unui cîmp sau a unei metode. Descriptorii sînt specificați folosind un set de reguli care descriu cum secvențele de caractere pot forma descriptori corecți sintactic de diferite tipuri. Ultimul simbol este reprezentat în formă semigrasă iar restul simbolurilor în formă italică. Orice definire se începe din rînd nou. De exemplu:
FieldType:
BaseType
ObjectType
ArrayType
definește că FieldType poate reprezenta BaseType, sau ObjectType, sau ArrayType. Un simbol intermediar care este urmat de un asterisc reprezintă zero sau mai multe valori diferite posibile produse de acel simbol, adăugate fără spațiu. De exemplu:
MethodDescription:
( ParameterDescriptor* ) ReturnDescriptor
definește că MethodDescriptor este urmat de o paranteză de stînga, apoi urmează zero sau mai multe valori ParameterDescriptor, urmate de o paranteză de dreapta, urmată de ReturnDescriptor.
Descriptorii cîmpurilor. Descriptorul cîmpului reprezintă tipul unei clase, instanțe sau a unei variabile locale. El este o serie de caractere generate în conformitate cu gramatica:
FieldDescriptor:
FieldType
ComponentType:
FieldType
FieldType:
BaseType
ObjectType
ArrayType
BaseType:
B
C
D
F
I
J
S
Z
ObjectType:
L <classname>;
ArrayType:
[ComponentType
Caracterele din BaseType, L, ObjectType și [ din ArrayType sînt toate caractere ASCII. <classname> reprezintă numele deplin calificat a clasei sau interfeței.
Interpretarea tipurilor cîmpului este următoarea:
Tabelul 1.5
Interpretarea tipurilor cîmpului
De exemplu: descriptorul unei variabile instanțe de tip int este reprezentată simplu numai prin I. Descriptorul a unei variabile instanțe de tip Object este Ljava/lang/Object;. E de menționat că este folosită forma internală a numelui deplin complet pentru clasa Object. Descriptorul unei variabile instanțe care este un șir multidimensională dublă, double d[][][]; este D.
Descriptorii metodelor. Descriptorul metodei reprezintă parametrii care i se transmit metodei și rezultatul care îl întoarce ea:
MethodDescriptor:
( ParameterDescriptor* ) ReturnDescriptor
Descriptorul parametrilor reprezintă marametrii transmiși:
ParameterDescriptor:
FieldType
Descriptorul RetunDescriptor reprezintă tipul valorii întoarse de către funcție. El este o serie de caractere generate conform gramaticii:
ReturnedDescriptor:
V
Caracterul V reprezintă că tipul valorii întoarse de către metodă este void. Un descriptor de metodă este validat dacă lungimea parametrilor lui este mai mică sau egală cu 255. Lungimea totală este calculată prin sumarea contribuției fiecăriu parametru, unde un parametru de tip long sau double contribuie cu 2 unități la lungime, iar restul parametrilor cu o unitate.
De exemplu, descriptorul metodei
Object mymethod(int i, double d, Thread t)
este (IDLjava/lang/Thread;)Ljava/lang/Object;
Descriptorii metodelor sînt asemenea, nu are importanță că e metodă a clasei sau a interfeței. Referința la metoda de instanțiere a clasei nu este redată în secția de descriere a metodelor, ea este transmisă implicit de către instrucțiunile de invocare a metodei a JVM.
Tabela de constante. Instrucțiunile de lucru cu clasele sau instanțele au referințe la tabela de constante. Orice element a acestui tabel are următorul format general:
cp_info{
u1 tag;
u1 info[];
}
Orice element a tabelului trebuie să înceapă cu o etichetă de un octet care indică tipul elementului cp_info. Orice etichetă trebuie să fie urmată de doi sau mai mulți octeți ce poartă informația despre constante. Formatul informației adiționale variază în dependență de valoarea etichetei.
Etichetele valide și valorile lor sînt:
Tabelul 1.6
Etichetele valide și valorile lor
Structura CONSTANT_Class_info. Structura CONSTANT_Class_info este utilizată pentru a reprezenta o clasă sau o interfață:
CONSTANT_Class_info{
u1 tag;
u2 name_index;
}
unde
tag – etichetă ce are valoarea CONSTANT_Class(7).
name_index – valoarea acestui cîmp trebuie să fie un indice a tabelului de constante. Elementul indicat de acest indice trebuie să fie o structură CONSTANT_Utf8_info ce reprezintă numele calificat deplin a clasei sau interfeței.
Deoarece șirurile sînt obiecte, opcodurile anewarray și multianewarray pot referi clasele de tip șir prin intermediul structurii CONSTANT_Class_info din tabela de constante. Pentru astfel de clase șir, numele clasei este descriptor de tip șir. De exemplu, numele clasei ce reprezintă un șir bidimensional int [][] este [[I. Numele clasei ce reprezintă un șir de tip Thread: Thread[] este [Ljava/lang/Thread;
Structurile CONSTANT_Fieldref_info, CONSTANT_Methodref_info și CONSTANT_InterfaceMethodref_info. Metodele cîmpurilor, metodelor și a interfețelor sînt reprezentate de structuri similare:
CONSTANT_Fieldref_info{
u1 tag;
u2 class_index;
u2 name_and_type_index;
}
CONSTANT_Methodref_info{
u1 tag;
u2 class_index;
u2 name_and_type_index;
}
CONSTANT_InterfaceMethodref_info{
u1 tag;
u2 class_index;
u2 name_and_type_index;
}
Elementele acestor structuri sînt:
tag – elementul structurii CONSTANT_Fieldref_info are valoarea CONSTANT_Fieldref(9); elementul structurii CONSTANT_Methodref_info are valoarea CONSTANT_Methodref(10); elementul structurii CONSTANT_InterfaceMethodref_info are valoarea CONSTANT_InterfaceMethodref(11).
class_index – valoarea acestui element trebuie să fie un indice valid a tabelei constantelor. Elementul indicat de către acest indice trebuie să fie o structură de tipul CONSTANT_Class_info ce reprezintă tipul clasei sau interfeței ce conține declarația metodei sau cîmpului. El poate fi de tip clasă sau interfață.
name_and_type_inex – valoarea acestui element trebuie să fie un indice valid a tabelei constantelor. Elementul indicat de către acest indice trebuie să fie o structură de tipul CONSTANT_NameAndType_info. Acest element indică numele și descriptorul cîmpului sau metodei. Dacă numele metodei a structurii CONSTANT_Methodref_info începe cu simbolul "<", atunci acest nume este un nume special <init>, ce reprezintă o metodă instanță de inițializare. O astfel de metodă trebuie să întoarcă o valoare de tip void.
Cîmpuri. Orice cîmp este descris de o structură field_info. Nu există cîmpuri în aceeași clasă ce pot avea același nume și descriptor. Formatul acestei structuri este:
field_info{
u2 acces_flags;
u2 name_index;
u2 descriptor_index;
u2 attributes_count;
attribute_info attributes[attributes_count];
}
Elementele structurii field_info sînt următoarele:
access_flags – Valoarea acestui cîmp reprezintă o mască de fanioane destinată pentru permisiunea de acces la proprietățile acestui cîmp. Interpretarea fiecărui fanion dacă este setat este redată în tabela de mai jos.
Tabelul 1.6
Interpretarea fanioanelor setate
name_index – această valoare trebuie să fie un indice valid al tabelului de constante. Elementul indicat de name_inedx trebuie să fie o structură CONSTANT_Utf8_info care trebuie să reprezinte numele cîmpului în formă simplă, care este ca un identificator a limbajului JAVA.
descriptor_index – această valoare trebuie să fie un indice valid al tabelului de constante. Elementul indicat de acest indice trebuie să fie o structură CONSTANT_Utf8_info care trebuie să reprezinte descriptorul cîmpului.
attributes_count – această valoare indică numărul de atribute a acestui cîmp.
attributes[] – orice valoare a tabelului de atribute trebuie să fie o structură de atribute. Un cîmp poate avea mai multe atribute asociate lui. Atributele aparente în tabela de atribute sînt: ConstantValue, Syntetic și Deprecated.
Implementarea JVM trebuie să recunoască și corect să citească atributele ConstantValue găsite în tabela de atribute a structurii field_info. Toate atributele care nu sînt recunoscute sînt ignorate. Atributele care nu au fost menționate nu invocă erori la decodificarea fișierului format al clasei, ele sînt pentru a introduce informație suplimentară.
Metode. Orice metodă, inclusiv metodele de inițializare a instanțelor, de inițializare a claselor și interfețelor, sînt descrise de structura method_info. Nu există două metode în același fișier clasă ca să aibă același nume și descriptor. Structura este următoarea:
method_info{
u2 access_flags;
u2 name_index;
u2 descriptor_index;
u2 attributes_count;
attribute_info attributes[attributes_count];
}
Elementele structurii method_info sînt următoarele:
access_flags – valoarea acestui cîmp reprezintă o mască de fanioane destinată pentru permisiunea de acces la proprietățile acestei metode. Interpretarea fiecărui fanion dacă este setat este redată în tabela de mai jos.
Tabelul 1.7
Interpretarea fiecărui fanion setat
Metodele de inițializare a claselor și instanțelor sînt chemate implicit de către JVM, valorile fanioanelor de acces sînt ignorate toate în afară de fanionul ACC_STRICT. Restul octeților ce nu au fost menționați sînt rezervați pentru a fi utilizați în viitor. Valorile lor trebuie setate în zero și ignorate de către JVM.
name_index – această valoare trebuie să fie un indice valid al tabelului de constante. Elementul indicat de acest indice trebuie să fie o structură CONSTANT_Utf8_info care reprezintă unul din numele speciale a metodei <init> sau <clinit>, sau numele metodei memorizat în formă simplă.
descriptor_index – această valoare trebuie să fie un indice valid al tabelului de constante. Elementul indicat de acest indice trebuie să fie o structură CONSTANT_Utf8_info care reprezintă descriptorul metodei.
attributes_count – această valoare indică numărul de atribute a acestei metode.
attributes[] – orice valoare a tabelului de atribute trebuie să fie o structură de atribute. O metodă poate avea mai multe atribute asociate ei. Atributele aparente în tabela de atribute sînt: Code, Exceptions, Synthetic și Deprecated.
Implementarea JVM trebuie să recunoască și corect să citească atributele Code și Exceptions găsite în tabela de atribute a structurii method_info. Toate atributele care nu sînt recunoscute sînt ignorate. Atributele care nu au fost menționate nu invocă erori la decodificarea fișierului format al clasei, ele sînt pentru a introduce informație suplimentară.
Atribute. Atributele sînt folosite în structurile ClassFile, field_info, method_info și Code_attribute a fișierului format al clasei. Toate atributele au următorul format general:
attribute_info{
u2 attribute_name_index;
u4 attribute_length;
u1 info[attribute_length];
}
Pentru toate atributele, cîmpul attribute_name_index trebuie să fie indice de mărimea a doi octeți, fără semn, a tabelei de constante. Elementul indicat de acest indice trebuie să fie o structură CONSTANT_Utf8_info ce reprezintă numele atributului. Valoarea elementului attribute_length indică lungimea în octeți a întregii structuri. Lungimea nu include primii șase octeți care sînt ocupați de attribute_name_index și de attribute_length.
Implementarea JVM trebuie să recunoască și corect să citească atributele Code, ConstantValue și Exceptions găsite în tabela de atribute a structurii method_info. Atributele InnerClasses și Synthetic trebuie să fie recunoscute și citite cu succes din fișierul sursă deoarece sînt necesare pentru bibliotecile de bază a platformelor Java și Java 2.
Definirea și denumirea atributelor noi. Compilatoarele pot defini și emite fișiere clasă ce conțin atribute noi în tabela de atribute. Implementarea JVM are posibilitatea de a recunoaște atributele noi, de a le citi și utiliza. Însă atributele noi definite nu trebuie să modifice sensul sau chiar structura fișierului clasă. JVM trebuie să ignoreze toate atributele necunoscute ce cauzează erori. Este cert faptul că în caz de insuficiență a atributelor clasa respectivă nu v-a fi interpretată corect.
Atributul ConstantValue. Atributul ConstantValue este de o lungime fixă și e utilizat în tabela de atribute. El reprezintă valoarea unui cîmp constant care trebuie să fie static, de aici rezultă că, fanionul ACC_STATIC trebuie să fie setat. Pentru o structură field_info nu pot fi mai mult de un astfel de atribut în tabela de atribute. Valoarea atributului ConstantValue este atribuită cîmpului la etapa de inițializare.
Dacă structura field_info reprezintă un cîmp nestatic care are în același timp și atributul ConstantValue, atunci acest atribut trebuie ignorat.
Atributul ConstantValue are următorul format:
ConstantValue_attribute{
u2 attribute_name_index;
u4 attribute_length;
u2 constantvalue_index;
}
Elementele structurii ConstantValue_attribute sînt următoarele:
attribute_name_index – această valoare trebuie să fie un indice valid al tabelului de constante. Elementul indicat de acest indice trebuie să fie o structură CONSTANT_Utf8_info ce reprezintă șirul de caractere "ConstantValue".
attribute_length – această valoare trebuie să fie egală cu 2.
constantvalue_index – această valoare trebuie să fie un indice valid al tabelului de constante. Elementul indicat de acest indice reprezintă valoarea constantă a acestui atribut și trebuie să fie de o structură asemenea structurii cîmpului, după cum e reprezentat mai jos.
Tabelul 1.8
Tabelul constantelor
Atributul Code. Atributul Code este de mărime variabilă folosit în tabela de stribute a structurii method_info. Acest atribut conține instrucțiuni a JVM și informație auxiliară pentru o singură metodă, metodă de inițializare a instanței sau metodă de inițializare a unei clase sau a unei interfețe. Dacă metoda este sau nativă sau abstractă, atunci structura sa method_info nu trebuie să conțină atributul Code, în caz contrar, trebuie să fie exact numai un atribut Code.
Atributul Code are următorul format:
Code_attribute{
u2 attribute_name_index;
u4 attribute_length;
u2 max_stack;
u2 max_locals;
u4 code_length;
u1 code[code_length];
u2 exception_table_length;
{ u2 start_pc;
u2 end_pc;
u2 handler_pc;
u2 catch_type;
} exception_table[exception_table_length];
u2 attributes_count;
attribute_info attributes[attributes_count];
}
Elementele structurii Code_attribute sînt următoarele:
attribute_name_index – această valoare trebuie să fie un indice valid al tabelului de constante. Elementul indicat de acest indice trebuie să fie o structură CONSTANT_Utf8_info ce reprezintă șirul de caractere "Code".
attribute_length – această valoare indică lungimea în octeți a atributelor, excluzînd primii șase octeți.
max_stack – această valoare indică mărimea maximă a stivei (adîncimea) pe parcursul executării metodei.
max_locals – valoarea max_locals indică numărul de variabile locale în șirul de variabile locale care a fost creată la invocarea metodei date, inclusiv și variabilele care sînt utilizate pentru transmiterea parametrilor.
code_length – valoarea code_length indică mărimea codului metodei respective în octeți. Această mărime numaidecît trebuie să fie mai mare decît zero, codul metodei nu poate fi gol.
code[] – șirul code conține codul ce implementează metoda curentă.
exception_table_length – această valoare indică numărul de elemente a tabelei exception_table.
exception_table[] – orice element a acestui tabel descrie o subrutină de prelucrare a întreruperii ce e plasată în codul metodei. Ordinea de poziționare în acest tabel este importantă. Orice element al acestui tabel are structura:
start_pc, end_pc – aceste două valori indică pozițiile unde începe și respectiv unde se termină rutina de deservire a excepției în codul metodei. Deservirea excepției trebuie să fie activă atîta timp cît registrul PC este în intervalul [start_pc, end_pc).
handler_pc – valoarea handler_pc indică începutul rutinei de deservire a excepției.
catch_type – dacă această valoare nu este egală cu zero, atunci ea trebuie să fie un indice valid al tabelului de constante. Elementul ce se află la poziția indicată de acest indice trebuie să fie o structură CONSTANT_Class_info ce reprezintă clasa excepției pe care această rutină de prelucrare a excepției o deservește. Aceasta trebuei să fie clasa Throwable sau oarecare din subclasele ei. Dacă valoarea elementului catch_type este egală cu zero atunci această rutină de deservire a întreruperii va fi invocată pentru orice tip de excepții.
attributes_count – această valoare indică numărul de atribute a atributului Code.
attributes[] – orice valoare a tabelei de atribute trebuie să fie o structură de atribute. Atributul Code poate avea mai multe atribute opționale asociate cu ea. Actual sînt definite și utilizate două atribute, LineNumberTable și LocalVariableTable care conțin informație suplimentară. Atributele greșite sau nedefinite sînt ignorate de către JVM.
Atributul Exceptions. Atributul Exceptions este de mărime variabilă și e utilizat în tabela de atribute a structurii method_info. Acest atribut indică ce excepții poate genera metoda. Poate exista numai un singur atribut Exceptions în fiecare structură method_info.
Formatul atributului exceptions este următorul:
Exceptions_attribute{
u2 attribute_name_index;
u4 attribute_length;
u2 number_of_exceptions;
u2 exception_index_table[number_of_exceptions];
}
Elementele structurii sînt următoarele:
attribute_name_index – această valoare trebuie să fie un indice valid al tabelului de constante. Elementul indicat de acest indice trebuie să fie o structură CONSTANT_Utf8_info ce reprezintă șirul de caractere "Exceptions".
attribute_length – această valoare indică lungimea în octeți a atributelor, excluzînd primii șase octeți.
number_of_exceptions – această valoare indică numărul de elemente a tabelului exception_index_table.
exception_index_table[] – această valoare trebuie să fie un indice valid al tabelului de constante. Elementul indicat de acest indice trebuie să fie o structură CONSTANT_Utf8_info ce reprezintă tipul clasei excepției pe care această metodă o va genera.
O metodă va genera o excepție dacă măcar unul din următoarele criterii va fi întîlnit:
– excepția este o instanță a clasei RuntimeException sau a unei subclase a ei;
– excepția este o instanță a clasei Error sau a unei subclase a ei;
– excepția este o instanță a claselor de excepții specificate în tabelul exception_index_table care a fost anterior declarat, sau o instanță a unei subclase a lor.
Atributul InnerClasses. Atributul InnerClasses este de mărime variabilă și e utilizat în tabela de atribute a structurii ClassFile. Dacă tabela de constante a clasei sau interfeței se referă la careva clasă sau interfață ce nu face parte din pachetul curent atunci, structura sa ClassFile trebuei să conțină exact un atribut InnerClasses în tabela sa de atribute.
Formatul atributului InnerClasses este următorul:
InnerClasses_attribute{
u2 attribute_name_index;
u4 attribute_length;
u2 number_of_classes;
{ u2 inner_class_info_index;
u2 outer_class_info_index;
u2 inner_name_index;
u2 inner_class_access_flags;
} classes[number_of_classes];
}
Elementele structurii sînt următoarele:
attribute_name_index – această valoare trebuie să fie un indice valid al tabelului de constante. Elementul indicat de acest indice trebuie să fie o structură CONSTANT_Utf8_info ce reprezintă șirul de caractere "InnerClasses".
attribute_length – această valoare indică lungimea în octeți a atributelor, excluzînd primii șase octeți.
number_of_classes – această valoare indică numărul de elemente a șirului claselor.
classes[] – orice element a tabelului de constante de tipul CONSTANT_Class_info care reprezintă o clasă ori o interfață C, care nu sînt membri a pachetului curent, trebuie să aibă exact un element corespunzător în șirul claselor. Dacă o clasă are membri alte clase sau interfețe atunci tabela de constante a sa trebuie să conțină referințe la toate aceste obiecte.
Orice element din șirul claselor conține următoarele patru elemente:
inner_class_info_index – această valoare trebuie să fie zero sau un indice a tabelului de constante. Elementul corespunzător din tabela de constante trebuie să fie o structură CONSTANT_Class_info ce reprezintă C. Restul elementelor din șirul claselor conțin informație despre C.
outer_class_info_index – dacă C nu este membru atunci valoarea outer_class_info_index trebuie să fie zero. Altfel, această valoare trebuie să fie un indice a tabelei de constante care indică o structură CONSTANT_Class_info care reprezintă clasa sau interfața a cărui membru este C.
inner_name_index – dacă C este anonimă atunci valoarea inner_name_index trebuie să fie zero. Altfel, această valoare trebuie să fie un indice a tabelei de constante care indică o structură CONSTANT_Utf8_info care reprezintă numele original simplu a lui C după cum e descris în codul sursă din care a fost compilată clasa respectivă.
inner_class_acces_flags – Această valoare este o mască de fanioane ce codifică permisiunea de acces la proprietățile clasei sau interfeței C, după cum e declarată în codul sursă. De asemenea această informație poate servi pentru a restabili originalul informației în caz că codul sursă nu e accesibil. Fanioanele sînt următoarele:
Tabelul 1.9
Tabelul fanioanelor
Restul biților care nu au fost menționați în tabel sînt rezervați pentru a fi folosiți în viitor.
Atributul Synthetic. Atributul Synthetic este de mărime fixă și e prezent în structurile field_info, method_info și în tabela de atribute a fișierului format al clasei. Orice membru al clasei care nu e inclus în codul sursă trebuie să fie marcat cu atributul Synthetic.
Formatul acestui atribut este următorul:
Synthetic_attribute{
u2 attribute_name_index;
u4 attribute_length;
}
Elementele structurii sînt:
attribute_name_index – această valoare trebuie să fie un indice valid al tabelului de constante. Elementul indicat de acest indice trebuie să fie o structură CONSTANT_Utf8_info ce reprezintă șirul de caractere "Synthetic".
attribute_length – valoarea dată indică lungimea atributului și este egală cu zero.
Atributul SourceFile. Atributul SourceFile este opțional și de mărime fixă. În tabela de atribute poate fi nu mai mult de un asemenea atribut.
Structura acestui atribut este următoarea:
SourceFile_attribute{
u2 attribute_name_index;
u4 attribute_length;
u2 sourcefile_index;
}
Elementele atributului SourceFile_attribute sînt:
attribute_name_index – această valoare trebuie să fie un indice valid al tabelului de constante. Elementul indicat de acest indice trebuie să fie o structură CONSTANT_Utf8_info ce reprezintă șirul de caractere "SourceFile".
attribute_length – acest element indică lungimea atributului și trebuie să fie egal cu 2.
sourcefile_index – această valoare trebuie să fie un indice valid al tabelului de constante. Elementul indicat de acest indice trebuie să fie o structură CONSTANT_Utf8_info ce reprezintă un șir de caractere care indică numele fișierului sursă. Numele este păstrat în formă simplă, adică nu conține calea absolută sau nici măcar directoriu.
Atributul LineNumberTable. Atributul SourceFile este opțional. El poate fi folosit de către debugger pentru a determina care instrucțiuni JVM aparțin unei anumite linii din fișierul sursă. Dacă atributele LineNumberTable sînt prezente în tabela de atribute a atributului Code, atunci ele trebuie să urmeze o ordine strictă asemenea succesiunii de linii din fișierul sursă.
Structura acestui atribut este următoarea:
LineNUmberTable_attribute{
u2 attribute_name_index;
u4 attribute_length;
u2 line_number_table_length;
{ u2 start_pc;
u2 line_number;
} line_number_table[line_number_table_length];
}
Elementele atributului sînt următoarele:
attribute_name_index – această valoare trebuie să fie un indice valid al tabelului de constante. Elementul indicat de acest indice trebuie să fie o structură CONSTANT_Utf8_info ce reprezintă șirul de caractere "LineNumberTable".
attribute_length – acest element indică lungimea atributului excluzînd primii șase biți.
line_number_table_length – Această valoare indică numărul de elemente în șirul line_number_table.
line_number_table[] – orice element din acest șir indică relațiile de legătură dintre liniile fișierului sursă și fișierului cod și e alcătuit din două elemente:
start_pc – această valoare este un indice a șirului de cod care indică instrucțiunea cu care începe o linie anumită din fișierul sursă.
line_number – această valoare indică numărul liniei din fișierul sursă.
Atributul LocalVariableTable. Atributul LocalVariableTable este opțional, de lungime variabilă și e parte componentă a atributului Code. El poate fi folosit de către debugger pentru a determina valoarea unei variabile locale anumite pe parcursul execuției metodei. În tabela de atribute pot fi exista mai multe atribute LocalVariableTable, ordinea lor nu este restricționată. Însă nu pot exista două atribute ce se referă la una și aceeași variabilă.
Atributul respectiv are următorul format:
LocalVariableTable_attribute{
u2 attribute_name_index;
u4 attribute_length;
u2 local_variable_table_length;
{ u2 start_pc;
u2 length;
u2 name_index;
u2 descriptor_index;
u2 index;
}local_variable_table[local_variable_table_length];
}
Elementele acestei structuri sînt:
attribute_name_index – această valoare trebuie să fie un indice valid al tabelului de constante. Elementul indicat de acest indice trebuie să fie o structură CONSTANT_Utf8_info ce reprezintă șirul de caractere "LocalVariableTable".
attribute_length – acest element indică lungimea atributului excluzînd primii șase biți.
line_number_table_length – această valoare indică numărul de elemente în șirul local_variable_table.
local_variable_table[] – orice element din acest șir indică intervalul de cod în care este folosită variabila locală respectivă. De asemenea indică indicele cadrului în care poate fi găsită variabila. Orice înregistrare trebuie să conțină următoarele elemente:
start_pc, length – valorile respective indică intervalul în care variabilele locale sînt utilizate, adică [start_pc, start_pc+length]. Valoarea start_pc trebuei să fie un indice a șirului codului a atributului Code respectiv și trebuei să indice un opcod sau o instrucțiune. Iar valoarea start_pc+length trebuei să fie un indice a șirului codului sau primul indice ce indică un opcod sau o instrucțiune dincolo de sfîrșitul șirului de cod respectiv.
name_index, descriptor_index – valoarea elementului name_index trebuie să fie un indice a tabelei de constante. Înregistrarea respectivă trebuie să conțină o structură CONSTANT_Utf8_info ce reprezintă numele variabilei locale în formă simplă.
Valoarea elementului descriptor_index trebuie să fie un indice a tabelei de constante. Înregistrarea respectivă trebuie să conțină o structură CONSTANT_Utf8_info ce reprezintă descriptorul cîmpului ce codifică tipul variabilei locale.
index – această valoare indică poziția variabilei locale în șirul de variabile locale a cadrului curent. Dacă variabile este de tip long sau double ea ocupă pozițiile index și index+1.
Atributul Deprecated. Atributul Deprecated este opțional și de mărime fixă. Este prezent în fișierul format al clasei și în structurile field_info și method_info. Acest atribut se utilizează pentru a marca clasa, interfața, metoda sau cîmpul ca fiind rebutat, prezența lui nu modifică semantica clasei sau interfeței.
Atributul are următorul format:
Deprecated_attribute{
u2 attribute_name_index;
u4 attribute_length;
}
Elementele acestei structuri sînt:
attribute_name_index – această valoare trebuie să fie un indice valid al tabelului de constante. Elementul indicat de acest indice trebuie să fie o structură CONSTANT_Utf8_info ce reprezintă șirul de caractere "Deprecated".
attribute_length – acest valoare trebuie să fie egală cu zero.
1.8. Limitările codului JVM
Codul JVM ce descrie o metodă, o metodă de inițializare a unei instanțe, sau o metodă de inițializare a unei clase sau unei instanțe este păstrat în șirul codului a atributului Code a structurii method_info a fișierului format al clasei.
Limitările statice. Limitările statice indică modul de prezentare a instrucțiunilor în șirul codului și ce operanzi trebuie să conțină. JVM impune următoarele constrîngeri statice:
Șirul codului nu poate fi gol, adică elementul code_length nu poate fi egal cu zero.
Lungimea codului trebuie să fie mai mică decît 65536.
Indicele opcodului primei instrucțiuni trebuie să înceapă de la 0.
Șirul codului poate conține numai instrucțiuni predefinite și documentate.
Pentru orice instrucțiune din șirul instrucțiunilor, cu excepția ultimei, indicele opcodului următoarei instrucțiuni trebuie să fie egal cu indicele instrucțiunii curente plus lungimea ei, inclusiv toți operanzii. Instrucțiunile lungi sînt tratate în mod special de către JVM utilizînd opcoduri speciale ce indică prelungirea instrucțiunii lungi. Niciodată octeții unei instrucțiuni lungi nu pot fi interpretați sau accesați independent.
Ultimul octet al ultimei instrucțiuni trebuie să aibă indicele code_length-1.
Limitările statice asupra operanzilor instrucțiunilor sînt următoarele:
Eticheta de salt pentru instrucțiunile de ramificare și salt (jsr, jsr_w, goto, goto_w, ifeq, ifne, ifle, iflt, ifge, ifgt, ifnull, ifnonnull, if_icmpeq, if_icmpne, if_icmple, if_icmplt, if_icmpge, if_icmpgt, if_acmpeq, if_acmpne) trebuie să fie în limitele metodei curente.
Instrucțiunea multianewarray trebuie folosită numai pentru crearea șirurilor cu lungimea nu mai mică decît operandul de dimensiune. Operandul de dimensiune a oricărui șir trebuie să fie diferit de zero.
Operandul atype a oricărei instrucțiuni newarray trebuie să conțină una din valorile: T_BOOLEAN(4), T_CHAR(5), T_FLOAT(6), T_DOUBLE(7), T_BYTE(8), T_SHORT(9), T_INT(11) sau T_LONG(11).
Limitări structurale. Limitările de structură specifică limitările relațiilor dintre funcțiile JVM. Ele sînt următoarele:
Orice instrucție trebuie să fie executată numai cu tipul și numărul de parametri corespunzători din stivă sau din șirul de variabile locale. Instrucțiunile ce sînt destinate operării cu valori de tip int de asemenea pot opera și cu valori de tip boolean, byte, char și short.
Nici într-un punct al programului pe parcursul execuției nu este posibil ca octeții unei variabile locale de tip long sau double să fie inversați, să fie despărțiți sau să fie prelucrați fiecare în parte. Nici o variabilă locală nu poate fi accesată pînă nu îi este atribuită o valoare. Mărimile stivei locale pe parcursul execuției sînt fixate și nu pot fi modificate.
Orice metodă de inițializare a instanței, cu excepția celei ce derivă de la clasa Object, trebuie să apeleze altă metodă de inițializare a instanței a acesteia sau a superclasei sale. Totuși, cîmpurile instanță ale acesteia care sînt declarate în clasa curentă pot fi stabilite înainte de a apela careva metodă de inițializare.
Orice instrucțiune de revenire trebuie să coincidă cu tipul de întoarcere a metodei. Dacă metoda întoarce o valoare de tipul boolean, char, byte, short sau int, atunci poate fi utilizată numai instrucțiunea ireturn. Toate metodele de inițializare a instanțelor, metodele de inițializare a claselor și interfețelor, precum și metodele declarate ca void, trebuie să utilizeze instrucțiunea return. În restul cazurilor este utilizată una din următoarele instrucțiuni, în dependență de tipul valorii de întoarcere a metode și anume: freturn, lreturn, dreturn, areturn.
Dacă sînt utilizate instrucțiunile de acces la cîmpurile protectate a superclasei getfield sau putfield, sau sînt folosite instrucțiunile invokevirtual sau invokespecial pentru a accesa metodele protectate a superclasei, atunci tipul instanței clasei accesate trebuie să fie asemenea cu cel al clasei curente.
Tipul oricărei instanțe a clasei accesate de către instrucțiunea getfield sau modificate de instrucțiunea putfield trebuie să fie compatibile cu tipul clasei specificate în instrucție.
Tipul oricărei valori memorizate într-un șir de tip referință prin instrucțiunea aastore trebuie să fie compatibilă cu tipul componentelor șirului.
Orice instrucțiune athrow trebuie să genereze numai valori care sînt instanțe a clasei Throwable ori a subclaselor ei.
Instrucțiunea ce urmează orice instrucțiune jsr sau jsr_w poate fi întoarsă prin intermediul unei instrucțiuni ret.
Instrucțiunile jsr și jsr_w nu pot fi utilizate la chemarea unei subrutine dacă această subrutină este deja prezentă în secvența subrutinelor apelate.
Orice instanță a tipului returnAddress poate fi întoarsă, adică atribuită, numai o singură dată.
Limitările JVM. Fișierul clasă conține cîteva limitări implicite a JVM și anume:
Spațiul de memorie alocat unei clase sau unei instanțe este limitat pînă la 65535 înregistrări de către cîmpul constant_pool_count ce are lungimea de 2 octeți. Aceasta acționează ca o limită internă a complexității unei singure clase sau interfețe.
Mărimea codului pentru metodele nenative și neabstracte este limitat de către mărimea indicilor din tabela de excepții a atributului Code, a atributului LineNumberTable și LocalVariableTable. Numărul maxim de variabile locale, în șirul de variabile locale, creat la invocarea metodei, este limitat pînă la 65535 de către mărimea elementului max_locals a atributului Code.
Numărul cîmpurilor care pot fi declarate de către o clasă sau interfață este de 65535 și este limitat de elementul fields_count a structurii ClassFile. Numărul cîmpurilor este calculat fără a lua în considerație cîmpurile moștenite.
Numărul metodelor care pot fi declarate de către o clasă sau interfață este de 65535 și este limitat de elementul methods_count a structurii ClassFile. Numărul metodelor este calculat fără a lua în considerație metodele moștenite.
Numărul superclaselor directe a unei clase este de 65535 și este limitat de elementul interfaces_count a structurii ClassFile.
Mărimea stivei unui cadru este de 65535 și este limitată de cîmpul max_stack a structurii Code_atribute.
Numărul de variabile locale într-un cadru este de 65535 și este limitat de elementul max_locals a structurii Code_attribute și de metoda de indexare a variabilelor care utilizează indici de mărimea a doi octeți.
Lungimea maximă a unui șir este de 255, care e dictată de mărimea opcodului instrucțiunii multianewarray.
Numărul maxim de parametri al unei metode este de 255, e limitat de către descriptorul metodei.
Lungimea denumirilor de cîmpuri și metode, a cîmpurilor și descriptorilor de metode și a altor valori de tip string este de 65535 caractere și este limitată de elementul de lungimea a doi octeți a structurii CONSTANT_Utf8_info. E de menționat că mărimea e calculată în octeți pentru caractere codificate. Iar un caracter codificat ocupă pînă la 3 octeți, iată de ce șirurile de caractere sînt în realitate mai mici.
Verificarea fișierelor format al clasei. Cu toate că compilatorul firmei Sun pentru limbajul de programare JAVA produce numai fișiere care satisfac cerințele enumerate anterior, JVM nu este garantată că unele fișiere care trebuie să le încarce au fost generate anume de acel compilator sau sînt formate corect.
O altă problemă este cea de compatibilitate între versiuni. Fișierul poate fi compilat cu o versiune mai veche sau mai nouă de compilator. Astfel unele clase pot fi deja modificate, pot fi schimbate tipurile unor cîmpuri, poate fi schimbat tipul de acces la cîmpuri și poceduri, etc.
Anume din cauza acestor posibile erori, JVM trebuie să verifice de sine stătător dacă fișierul care urmează să-l încarce este valid sau nu. Astfel JVM verifică dacă orice fișier de tip clasă satisface toate condițiile la etapa de linkare.
Verificarea în timpul linkării mărește performanța interpretatorului. Verificarea lungă ce ar trebuie efectuată pentru a verifica orice instrucțiune poate fi eliminată. JVM poate presupune că această verificare a fost deja efectuată. De exemplu, pentru JVM deja vor fi clare următoarele lucruri:
– nu există depășiri a stivei;
– toate variabilele locale utilizate sînt corecte;
– argumenții instrucțiunilor JVM sînt verificați și nu sînt erori.
Verificatorul fișierului clasă este independent de compilator. El poate verifica orice cod generat de către compilatoarele firmei Sun și nu numai. Orice cod v-a fi validat, indiferent de cine este generat, numai dacă el satisface tuturor condițiilor.
Procesul de verificare. Procesul de verificare constă din patru pași:
Pasul 1. Cînd fișierul clasă este încărcat, la început se verifică formatul lui. Primii patru octeți trebuie să conțină numărul magic corect. Toate atributele recunoscute trebuie să fie de mărimea prestabilită. Fișierul nu trebuie să fie trunchiat sau să conțină informație redundantă la sfîrșit.
Pasul 2. Cînd fișierul clasă este linkat, verificatorul efectuează toate verificările care pot fi efectuate fără a verifica tabelul codului a atributului Code. Verificarea efectuată la acest pas include următoarele:
Verificarea dacă clasele finale nu sînt subclase și că metodele finale nu sînt suprascrise.
Verificarea dacă orice clasă, cu excepția clasei Object, are o superclasă.
Verificarea dacă tabela de constante corespunde fișierului respectiv.
Verificarea dacă toate referințele cîmpurilor și a metodelor în tabela de constante au nume corecte.
E de remarcat faptul că verificarea referințelor metodelor și cîmpurilor nu include și verificarea dacă o asemenea metodă sau cîmp există în clasa dată, este verificat numai dacă acest element este formatat corect. O verificare mai minuțioasă este efectuată în pașii 3 și 4.
Pasul 3. Pe parcursul linkării este verificată fiecare șir de cod a atributului Code pentru fiecare metodă în parte. Verificatorul garantează că în orice punct a programului următoarele sînt adevărate:
– stiva este de aceeași mărime și conține aceleași tipuri de variabile;
– nici o variabilă locală nu este accesată pînă nu îi este atribuită vre-o valoare de tip corespunzător cu al ei.
– metodele sînt invocate cu argumenții corespunzători;
– cîmpurilor li se atribuie valori numai de același tip;
– toate opcodurile au tipuri corespunzătoare în stivă și în șirul variabilelor locale.
Pasul 4. Din considerații de eficiență, careva teste care pot fi efectuate în principiu la pasul 3, au fost amînate pînă la prima invocare a metodei.
De exemplu, dacă o metodă invocă o altă metodă care întoarce o instanță a clasei A și acea instanță este atribuită numai unui cîmp de același tip, verificatorul nu-și face griji dacă clasa A există. Totuși, dacă ea este atribuită unui cîmp de tip B, atunci definirile ambelor cîmpuri trebuiesc încărcate pentru a verifica dacă A este o subclasă a lui B.
Pasul patru este un pas virtual, a cărui verificare este făcută de către instrucțiuni corespunzătoare a JVM. La început instrucțiunea este executată și ea efectuează încărcarea definirii tipurilor referențiate dacă încă nu e încărcată. Dacă metoda este invocată pentru prima dată atunci sînt efectuate următoarele:
– se verifică dacă o astfel de metodă sau cîmp există în clasă;
– se verifică dacă metoda sau cîmpul referențiat are descriptorul indicat;
– se verifică dacă metoda în execuție are acces la metoda sau cîmpul la care se referă.
JVM nu trebuie să verifice tipul operanzilor din stivă deoarece această verificare a fost efectuată la pasul 3. Erorile depistate la pasul 4 generează eroarea LinkageError.
Într-o implementare a JVM firma Sun, după ce verificarea a avut loc, orice instrucțiune a fost înlocuită cu o instrucțiune alternativă. Astfel, această instrucțiune alternativă indică că deja a fost verificată și nu mai este necesitatea de a mai fi verificată din nou. Astfel invocarea metodelor este mai rapidă.
Verificatorul de ByteCode. Verificatorul de ByteCode este pasul trei al procesului de verificare și este cel mai complex. Codul fiecărei metode este verificat independent. Pentru început, octeții care formează codul sînt puși conform secvenței instrucțiunilor și indicele începutului fiecărei instrucțiuni este pus într-un șir. Verificatorul atunci parcurge toate instrucțiunile a doua oară și le verifică. Pe parcursul acestui pas sînt verificați toți operanzii instrucțiunilor.
În cazul instrucțiunilor lungi, codul principal este considerat început de instrucțiune, iar opcodul ce indică modificarea operației de către instrucțiunea largă nu este considerat ca început de instrucțiune. Ramificările în mijlocul instrucțiunilor nu sînt permise.
Sînt verificate tipurile și metodele de acces a variabilelor locale. Toate referințele din tabela de constante trebuie să fie de tipul necesar. De exemplu, instrucțiunea ldc poate fi folosită numai cu date de tip int sau float.
Codul de asemenea nu se poate termina la mijlocul unei instrucțiuni.
Pentru fiecare procedură de deservire a excepției, indicii de început și de sfîrșit a codului trebuie să coincidă cu începutul unei instrucții și respectiv cu sfîrșitul altei instrucții.
Pentru fiecare instrucțiune a metodei verificatorul înregistrează componența stivei și a șirului de variabile locale înainte de a fi îndeplinită acea instrucțiune. Pentru stivă, verificatorul trebuie să știe mărimea stivei și tipul oricărui operand ce se conține în ea. Pentru fiecare variabilă locală, verificatorul trebuie să cunoască tipul ei sau că acea variabilă conține o valoare nedeterminată (de exemplu încă nu e inițializată).
La început este inițializat analizatorul de date. În acest moment este selectată prima instrucțiune pentru care stiva este goală, variabilele locale care reprezintă parametrii inițiali conțin valori de tipuri corespunzătoare cu cele indicate de descriptorul metodei. Celelalte variabile au valori nedeterminate. Pentru restul instrucțiunilor care nu au fost încă examinate, nu se cunoaște nimica despre stivă și variabilele locale.
În continuare este lansat analizatorul. Orice instrucțiune care nu e marcată că a fost verificată va fi analizată. Analizatorul execută următorul ciclu:
Este selectată instrucțiunea a cărui bit ce indică verificarea (în continuare "bit modificator") este setat. Dacă nu există așa instrucțiuni, atunci metoda e deja verificată, altfel bitul modificator este resetat.
Este modelată acțiunea instrucțiunii asupra stivei și a variabilelor locale prin următoarele:
Dacă instrucțiunea utilizează valori din stivă atunci este verificat dacă stiva conține suficiente valori și dacă tipul primei valori corespunde cu cerințele instrucțiunii.
Dacă instrucțiunea utilizează variabile locale atunci este verificat dacă tipul loc coincide cu cel necesar instrucțiunii.
Dacă instrucțiunea introduce valori în stivă atunci se verifică dacă este suficient spațiu pentru a fi introduse.
Dacă instrucțiunea modifică o variabilă locală, atunci se verifică dacă înregistrarea a avut loc cu succes.
Se determină instrucțiunea următoare care poate fi:
– următoarea instrucțiune dacă instrucțiunea curentă nu este de salt sau reîntoarcere (de exemplu return, goto, athrow);
– instrucțiunea care e etichetă de saltul condiționat sau necondiționat sau de ramificare;
– careva instrucțiuni de prelucrare a excepțiilor.
Starea determinată a stivei și a variabilelor locale este utilizată la verificarea instrucțiunii succesoare. În caz de generare a unei excepții, toate valorile din stivă sînt eliminate și este introdusă numai obiectul de tip excepție.
Dacă instrucțiunea succesoare n-a fost verificată, procedura se repetă de la început asemeni pașilor enumerați mai sus. Dacă următoarea instrucțiune deja a fost verificată atunci se efectuează numai îmbinarea stivei și a variabilelor locale și în caz de detectare a modificărilor este setat bitul modificator.
După aceasta este efectuată trecerea la punctul 1.
Îmbinarea a două stive este permisă numai dacă numărul de valori coincid. Tipurile valorilor din stivă trebuie de asemenea să fie identice, numai cu excepția că pot avea valori diferite. În acest caz valoarea ce diferă va conține referința la o instanță a primei superclase comune ambelor valori. O astfel de referință există, deoarece clasa Object este superclasă a tuturor claselor.
Pentru a îmbina două șiruri de variabile locale sînt verificate perechile respective de variabile. Dacă tipurile nu sînt identice, atunci verificatorul marchează această variabilă că conține o valoare neutilizabilă, cu excepția cazului cînd ambele variabile sînt de tip referință.
Dacă analizatorul termină verificarea metodei fără erori, atunci metoda a fost verificată cu succes de către pasul 3 a verificării fișierului clasă.
Valori de tip long și double. Valorile de tip long și double sînt tratate special de către procesul de verificare. Cînd o astfel de variabilă este memorizată într-o variabilă locală cu indicele n, atunci indicele n+1 este special marcat pentru a nu fi folosit pentru altă variabilă locală. Cînd o valoare de tip long sau double este memorizată într-o variabilă locală cu indicele n, atunci este verificată locația cu indicele n-1, dacă ea indică la o variabilă de tip long sau double atunci ea este marcată ca neutilizabilă.
Verificarea stivei pentru valori de tip long sau double este mai simplă. Astfel de valori sînt tratate ca una singură. De exemplu, pentru a verifica instrucțiunea dadd este suficient de a testa dacă primele două valori a stivei sînt de tip double. Pentru determinarea lungimii stivei astfel de valori au lungimea doi. Instrucțiunile de operare cu stive trebuie să trateze valorile de tip long și double ca una întreagă, adică indivizibile. Pentru astfel de valori trebuiesc utilizate instrucțiunile pop2 sau dup2.
Metoda de inițializare a instanței și obiectele noi create. Crearea unei instanțe a clasei este un proces alcătuit din mai mulți pași.
…
new myClass(i,j,k);
poate fi implementat în felul următor:
new #1 // Alocarea spațiului pentru myClass
dup // Dublarea obiectului în stivă
iload_1 // Push i
iload_2 // Push j
iload_3 // Push k
invokespecial #5 // Este invocat myClass.<init>
…
Această secvență de instrucțiuni pune obiectul nou creat în vîrful stivei. Metoda de inițializare a instanței pentru clasa myClass, vede noul obiect creat neinițializat ca argument al său în variabila locală cu indicele 0. Pînă a fi invocată o altă metodă de inițializare a instanței a clasei myClass sau a unei superclase directe ei, unica operație care poate fi efectuată de către această metodă asupra variabilei este atribuirea cîmpurilor declarate în myClass.
La efectuarea analizei asupra metodelor instanțe, verificatorul inițializează variabila locală 0 astfel ca să conțină un obiect al clasei curente sau, variabila 0 poate conține o valoare de tip special care indică un obiect neinițializat. Apoi este invocată metoda de inițializare corespunzătoare a acestui obiect, peste tot unde este întîlnit acest tip special a modelului de verificare, în stivă și în șirul de variabile locale, este înlocuit cu tipul clasei curente. Verificatorul respinge codul care utilizează obiectul nou înainte de a fi inițializat sau care inițializează obiectul mai mult decît odată.
Similar, un tip special este creat și introdus în stiva modelului de verificare ca rezultat al instrucțiunii new. Tipul special indică instrucțiunea cu care a fost creată instanța clasei. Cînd este invocată metoda de inițializare a instanței pentru acea instanță a clasei, toate ocurențele tipului special sînt înlocuite cu tipul instanței clasei intenționate.
Numărul instrucțiunii trebuie să fie memorizat ca parte componentă a tipului special, deoarece pot exista mai multe instanțe neinițializate a clasei în stivă în același moment de timp. De exemplu, secvența de instrucțiuni a JVM care implementează
new InputStream(new Foo(), new InputStream("foo"))
poate avea două instanțe neinițializate de tipul InputStream în stivă în același moment de timp.
O secvență corectă de instrucțiuni nu poate avea un obiect neinițializat în stivă sau în șirul de variabile locale pe parcursul reîntoarcerii după ramificare, sau într-o variabilă locală în cod protectat de către o procedură de prelucrare a excepției sau de către clauza finally.
Prelucrarea excepțiilor. Codul JVM produs de către compilatorul firmei Sun pentru limbajul de programare JAVA, generează proceduri de prelucrare a întreruperilor în următorul mod:
Unica posibilitate de a lansa o subrutină de prelucrare a întreruperii este de a genera o întrerupere. Este imposibil de a crea o ramificare în program sau de folosit instrucțiunea goto pentru a ajunge în codul unei subrutine de prelucrare a excepțiilor. Aceste restricții nu sînt impuse de către verificatorul fișierului clasă, deoarece ele nu amenință cu nimic JVM. Atîta timp cît criteriile de verificare sînt satisfăcute, verificatorul va continua să prelucreze codul programului.
Excepțiile și finaly. Pentru a fi siguri că o porțiune de program este executată independent de faptul dacă apar sau nu careva excepții, codul dorit trebuie inclus în clauza finally a instrucțiunii try. De exemplu, fie că avem următorul cod:
try{
startTimer();
startCalculation();
}finally{
stopTimer();
}
funcția stopTimer v-a fi invocată indiferent de faptul dacă pe parcursul îndeplinirii funcției startCalculation vor apărea erori sau nu.
Pentru a implementa construcția try-finally, compilatorul firmei Sun pentru limbajul de programare JAVA, utilizează posibilitățile dispozitivelor de prelucrare a întreruperilor precum și două funcții speciale: jsr (salt la subrutină) și ret (reîntoarcere din subrutină). Clauza finally este compilată ca o subrutină a metodei din care face parte, fiind asemănător unei subrutine de prelucrare a excepției. Cînd este invocată instrucțiunea jrs care duce la invocarea subrutinei ce trebuie executată, ea pune adresa de întoarcere în stivă ca o valoare de tip returnAddress, adică adresa imediat următoarei instrucțiuni ce trebuie executată. Codul subrutinei memorizează adresa de întoarcere într-o variabilă locală. La sfîrșitul subrutinei, instrucțiunea ret extrage adresa din variabila locală și efectuează transferul controlului la instrucțiunea ce corespunde acestei adrese.
Subrutina finally poate fi invocată prin cîteva metode. Dacă clauza try se termină normal, atunci subrutina finally este invocată cu ajutorul instrucțiunii jsr înainte de a fi îndeplinită următoarea instrucțiune. Instrucțiunile break sau continue, care cauzează întreruperea execuției clauzei try, invocă subrutina finally prin intermediul instrucțiunii jsr înainte de a ieși din corpul clauzei try. Dacă clauza try execută instrucțiunea return, atunci sînt efectuați următorii pași:
– dacă trebuie de întors careva valoare atunci ea este memorizată într-o variabilă locală;
– este executată instrucțiunea jsr pentru a trece la subrutina finally;
– la întoarcerea din clauza finally este restabilită valoarea de întoarcere din variabila locală.
Dacă este generată vreo excepție în cadrul clauzei try și ea este prinsă de către un dispozitiv de prelucrare a excepțiilor, atunci acest dispozitiv face următoarele:
– este înregistrată excepția într-o variabilă locală;
– este efectuat saltul la subrutina finally;
– la întoarcerea din subrutină excepția este regenerată.
Codul clauzei finally este o problemă specială pentru verificator. De obicei, dacă o variabilă poate fi distinsă prin mai multe căi și măcar odată ea o sa conțină o valoare de tip incompatibil, atunci această variabilă devine neutilizabilă. Totuși clauza finally poate fi invocată prin diferite metode, care duc la diferite circumstanțe:
– la invocarea de către un dispozitiv de prelucrare a excepției, oarecare variabilă locală poate conține excepția;
– la invocarea ei înainte de reîntaorcere oarecare variabilă locală poate avea valoarea ce trebuie întoarsă;
– la invocarea la sfîrșitul clauzei try, aceeași variabilă poate conține o valoare specială a funcției try.
Codul clauzei finally poate efectua de sine stătător verificarea, numai că după terminarea executării instrucțiunii ret, verificatorul va de pista că variabila locală care trebuia să conțină excepția sau valoarea ce trebuie întoarsă programului acuma au valori nedeterminate. Anume din această cauză verificarea la astfel de cod este specifică și anume:
fiecare instrucțiune memorizează într-o listă calea prin a ajuns la instrucțiunea jsr. Pentru cea mai mare parte a codului această listă e goală, iar pentru codul din interiorul subrutinei finally această listă are un element. În cazul multiplicării codului, dar aceasta se întâmplă foarte rar, această lungime poate fi mai mare decât 1. Pentru fiecare instrucțiune și pentru fiecare jsr necesar de a ajunge la această instrucțiune, un vector de tip bit memorizează toate variabilele care au fost accesate sau modificate de la începutul executării instrucțiunii jsr. Întrucât verificatorul știe subrutina din care instrucțiunea trebuie să fie întoarsă, el poate găsi toate instrucțiunile jsr care au chemat subrutina și poate uni starea stivei și a variabilelor locale la momentul întoarcerii. La unire este folosit un set special de valori a variabilelor locale. Pentru fiecare variabilă, a cărui bit indică că a fost modificată sau accesată de către subrutină, tipul rămâne neschimbat. Pentru restul variabilelor este folosit tipul ce îl aveau până la execuția instrucțiunii jsr.
=== CAPIT2 ===
2. Realizări în domeniu
2.1. Coprocesorul JStar
JStar este creat de către compania JEDI Technologies of Santa Clara. Acest JAVA MIPS coprocesor poate fi utilizat aproape cu orice procesor pe 32 și 64 biți. El este plasat între nucleul procesorului și cache-urile de date și instrucțiuni, după cum e reprezentat în figura 2.1. Destinația principală este de a îndeplini funcțiile JVM cînd este necesar de executat codurile programelor JAVA. Coprocesorul trece în starea de execuție numai în cazul în care sînt depistate instrucțiuni Java, altfel el se află în stare pasivă și toate instrucțiunile native procesorului principal sînt transferate lui fără a fi alterate. Comutarea de la codul nativ la codul Java este efectuată de către o instrucțiune specială care înlocuiește apelarea JVM realizate soft.
Nu toate JVM sînt elaborate în același mod. JStar poate fi configurat ca să fie compatibil numai cu 4 tipuri de implementări, restul implementărilor sînt prevăzute pentru viitor. Coprocesorul de asemenea necesită susținerea procesorului principal, ambele funcționează la aceeași frecvență. JStar este mai efectiv decît JVM realizate prin metoda soft deoarece orice bytecode este executat într-un singur tact, însă în comparație cu compilatorul JIT (just-in-time) care efectuează optimizarea dinamică a operațiilor, uneori nu e așa de rapid. Coprocesorul reduce mărimea JVM dar nu o exclude total. Concomitent există unele funcții a JVM care totuși sînt realizate soft funcționarea cărora, cu părere de rău, uneori este mai lentă decît cea a instrucțiunilor realizate de către procesor.
Coprocesorul JStar are avantajele: mărește performanța sistemului, consum mic de energie, construcție simplă, mic după mărime. Însă de asemenea are și dezavantaje: nu elimină complet realizarea soft a JVM, reducerea vitezei de lucru la utilizare memoriei da capacitate mare, conlucrarea intensă cu procesorul de bază, instrucțiunile de lungimea unui cuvînt sînt redirecționate procesorului principal.
2.2. Nucleul JAVA – Espresso
Un alt model de implementare hard a JVM este elaborat de către compania Aurora VLSI Inc., care a elaborat trei tipuri de circuite: procesor, accelerator și coprocesor. Toate trei arhitecturi sînt destinate executării directe a bytecode-urilor, astfel mărind viteza de execuție a programelor JAVA, adică ele reprezintă elaborarea prin metoda hard a Java Virtual Machine. Nucleul Espresso este superscalar și are arhitectură RISC. Caracteristicile de bază sînt:
– rata de executare e de 8 instrucțiuni pe ciclu (14 bytecode-uri pe ciclu)
– implementarea funcțiilor JAVA așa ca:
– accesul direct la zona de memorie constantă;
– verificarea legăturilor dintre șirurile de date;
– implementarea metodelor invoke/return;
– executarea majorității bytecode-urilor JAVA (14 bytecode-uri necesită interveni-re soft)
– 5 niveluri de pipeline în baza arhitecturii RISC
– unitate pentru executarea operațiilor în virgulă flotantă
– cache pentru instrucțiuni modificabile – 32-16K de bytecode-uri.
Nucleul Java Espresso este destinat utilizării în diferite tipuri de sisteme care execută programe JAVA. Schema de încapsulare a coprocesorului Espresso este redată în figura 2.2.
2.3. Procesorul JAVA – MOON
Procesorul MOON, a firmei Vulcan ASIC, este o implementare a JVM. Implementarea este o combinație dintre hard și soft, majoritatea opcode-urilor fiind implementate direct în hard. Elaboratorii acestui procesor afirmă că crearea procesorului JAVA a fost împiedicată de performanțele slabe ale JVM și de faptul, că pentru a implementa totalmente în hard JVM este foarte complex. Procesorul totuși dă dovadă de o viteză înaltă datorită implementării directe a majorității instrucțiunilor de bază direct în hard.
Pentru a efectua trecerea de la un thread la altul este necesar de a memoriza starea tread-ului în execuție. Astfel a fost utilizată o arhitectură specializată, bazată pe arhitectura RISC, care impune o stare internă foarte compactă care poate fi înregistrată foarte rapid. Este implementată posibilitatea execuției a două tipuri de instrucțiuni complexe: microcoduri și externe. Microcodurile sînt acel tip de instrucțiuni care necesită pentru execuția lor mai multe instrucțiuni de bază. Instrucțiunile externe sînt acelea care cauzează un salt la memoria ROM predefinită a procesorului.
JAVA nu permite accesul direct al memoriei. Toate referințele la memoria principală sînt efectuate prin redirecționarea lor prin zona de memorie constantă alocată fiecărui thread. Astfel magistrala de date internă a procesorului este fixă și e de 32 de biți, iar magistrala adresei este configurabilă precum este magistrala de date externă.
Prin implementarea procesorului MOON firma Vulcan a obținut o realizare a JVM combinată dintre hard și soft, care evident execută foarte rapid opcode-urile generate de către compilatoarele Java. Acesta este un procesor specializat care fiind utilizat în sisteme pentru scopuri generale nu ar da dovadă de performanță înaltă.
2.4. Generalizare
Din realizările prezentate reiese că realizarea hard a JVM dă dovadă de performanțe înalte chiar dacă funcționarea sa trebuie să fie asigurată cu susținere soft. Utilizarea tot mai pe larg a limbajului JAVA duce la mărirea necontenită a numărului de aplicații ce urmează a fi executate de către JVM. Însă realizarea soft a JVM necesită foarte multe resurse și este destul de lentă chiar și pentru cele mai performante calculatoare. De aceea realizarea unei arhitecturi hard JAVA a fost inevitabilă.
Din toate materialele care au fost studiate, inclusiv și știrile de ultimă oră din Internet, reiese că cercetări în domeniul elaborării unui procesor sau coprocesor JAVA sînt efectuate de către mai multe companii așa ca: Sun, Vulcan ASIC Ltd, Aurora VLSI Inc, Ajile Systems, Uplands Vasby ș.a. Însă nu este menționată nici o realizare concretă care ar fi produsă în masă și utilizată pe larg în sistemele de calcul. Fiecare arhitectură are avantajele și dezavantajele ei. Totodată apare întrebarea: Sub ce formă este mai eficient de realizat JVM, procesor, coprocesor sau JVM accelerator. Pe care din aceste căi se va merge în continuare deocamdată este greu de presupus, deoarece nu toate au fost realizate în practică, dar numai efectuînd o analiză a schemelor și mostrelor propuse nu putem determina precis acest lucru.
Toate implementările sînt bazate totalmente pe arhitectura JVM realizate soft, acest fapt este dictat de menținerea compatibilității cu toate versiunile JVM, precum și dovedește că Java Virtual Machine are o arhitectură performantă și de viitor. La baza tuturor procesoarelor realizate este arhitectura RISC și sînt orientate spre lucru cu stiva. În plus, fiecare realizare hard este susținută și de careva intervenții soft. Acest fapt respectiv duce la scăderea vitezei de execuție a programelor Java însă și este necesar, deoarece există un set de rutine ce nu pot fi implementate în hard așa ca rutina de încărcare, verificare, “colectare a gunoiului”. Implementarea hard a acestor rutine ar duce la o structură foarte complicată și o limitare între versiunile de compilatoare ceea ce nu este de dorit.
În continuare vom face o mică analiză a realizărilor descrise anterior unde vom accentua avantajele și neajunsurile lor.
Coprocesorul JStar. Este utilizat numai ca o punte de trecere a instrucțiunilor, el este plasat între nucleul procesorului de bază și cache-urile de date și de instrucțiuni. Orice instrucțiune este analizată de către coprocesor și în cazul cînd aceasta e o instrucțiune JAVA, este capturată, inclusiv și operanzii, executată, iar rezultatele printr-un multiplexor sînt trimise în continuare nucleului procesorului de bază. Avantajul unui asemenea coprocesor este că e foarte simplu și instrucțiunile sînt executate într-un singur tact. Neajunsul este că nu poate executa toate instrucțiunile JAVA deoarece există instrucțiuni care necesită suport soft și că nu exclude totalmente JVM. Mai bine spus el este ca o unitate de execuție parțială a instrucțiunilor.
Coprocesorul JAVA – Espresso. Compania Aurora VLSI Inc. a elaborat trei arhitecturi de realizare a JVM: procesor, coprocesor și JAVA accelerator. Vom analiza numai coprocesorul JAVA deoarece prezintă mai mult interes pentru această lucrare. Nucleurile Espresso pot fi integrate în diferite tipuri de circuite integrate ce sînt destinate rulării aplicațiilor JAVA. Comunică cu procesorul prin intermediul magistralei de sistemă. De aceea viteza de lucru depinde de nivelul priorității de acces a magistralei. Datele sînt păstrate în cache-ul procesorului principal ceea ce duce la mărirea traficului de date prin magistrală. Necesită de asemenea intervenirea soft la executarea unor instrucțiuni.
Procesorul MOON. Este un procesor RISC cu arhitectura orientată pe stivă. Majoritatea instrucțiunilor JAVA sînt implementate hard. Conține algoritmi de optimizare a execuției instrucțiunilor. Menține execuția instrucțiunilor simple și complexe. Din cele complexe fac parte microcodurile și instrucțiunile externe aflate în alte zone de memorie, spre exemplu ROM. Dezavantajul acestui procesor este că el are o orientare foarte îngustă. El dă dovadă de o execuție foarte rapidă a instrucțiunilor JAVA însă la utilizarea lui pentru scopuri generale el cedează altor procesoare. El are un domeniu îngust de utilizare și anume utilizarea în sistemele de calcul scopul principal al cărora este executarea opcodurilor JAVA sau a altor programe ce utilizează acest format de reprezentare.
Am considerat că cea mai reușită metodă de realizare hard a JVM este totuși implementarea unui procesor JAVA. Un astfel de procesor poate fi incorporat la necesitate în orice sistem de calcul mărindu-i astfel performanțele de execuție a programelor JAVA fără a impune sistemului un domeniu îngust de utilizare.
=== CAPIT3 ===
3. Modelele arhitecturale ale procesorului JAVA
3.1. Generalități
A fost deja argumentată tema aleasă în capitolele anterioare. În continuare vom face o generalizare a materialelor expuse. Studiind informația colectată, inclusiv structura și principiul de funcționare a mașinii virtuale JAVA, vom propune un model structural de realizare hard a JVM.
Din materialele selectate în mare măsură din Internet se poate ușor de observat că implementarea hard a JVM este o problemă foarte actuală abordată de către mai multe companii. Această tendință este dictată de utilizarea tot mai vastă a limbajului de programare JAVA, care este un limbaj orientat pe obiect și este folosit în mare măsură pentru crearea aplicațiilor pentru rețea. Securitatea de comunicare și astfel securitatea datelor nu este de asemenea la un nivel foarte înalt implementată în JAVA. Astfel acest limbaj s-a impus foarte mult în crearea aplicațiilor pentru rețea, în cea mai mare parte este utilizat în combinație cu limbajul HTML la crearea paginilor web dinamice, bazelor de date prin internet.
Unicul neajuns este că JVM care execută codul programului JAVA necesită foarte multe resurse și din această cauză lansarea aplicațiilor JAVA pe unele calculatoare nu atât de performante este destul de lentă. Aceasta se explică prin faptul că însăși JVM necesită memorie și resursele procesorului pentru a fi lansată, în afară de necesitățile pentru programul ca atare lansat.
Pentru a mări viteza de transmisie a aplicației prin rețea și respectiv a vitezei de execuție a programului, fiecare obiect al aplicației după compilare este plasat într-un fișier numit ClassFileFormat sau fișier format al clasei (referință !!!). Un astfel de fișier conține absolut toată informația despre obiectul al cărui nume îl are. Divizarea aplicației în astfel de fișiere permite de a lansa aplicația fără a avea tot codul în întregime. Astfel, la apelarea altei clase care încă nu este pe calculatorul gazdă, programul este pus în stare de așteptare până nu este copiat fișierul necesar cu descrierea clasei apelate. Utilizând o astfel de metodă de implementare, se poate de copiat numai clasele apelate, astfel micșorând traficul de date prin rețea.
3.2. Funcțiile JVM
Lansarea oricărui program JAVA începe cu inițializarea JVM. În continuare este lansată procedura main a clasei transmise drept parametru din linia de comandă. În funcțiile de bază a JVM intră:
– încărcarea obiectelor;
– alocarea memoriei;
– linkarea;
– verificarea;
– crearea și lichidarea instanțelor obiectelor;
– execuția bytecode-urilor;
– sincronizarea thread-urilor.
Deci, fișierul format al clasei, în afară de codul executabil mai conține informație suplimentară așa ca: versiunea compilatorului, clasa părinte, informație despre interfețe, spațiul necesar de memorie, mărimea stivei, informație despre câmpuri, despre metode (figura 3.1). În afară de aceasta observăm că în funcțiile JVM intră multe proceduri auxiliare ce țin de organizarea și prelucrarea datelor. Astfel, pentru a implementa totalmente funcțiile JVM realizate prin metoda soft în hard, s-ar ajunge la un echipament destul de complicat. Un astfel de echipament ar mări cu mult viteza de execuție a programelor JAVA și, în plus, nu ar necesita atât de multe resurse. O astfel de realizare se poate considera ideală. Însă există și alte probleme care nu trebuie deloc uitate așa ca compatibilitatea dintre versiunile programelor. În caz că dispunem de o versiune deja învechită de JVM realizat soft, este destul de ușor de o schimbat cu una mai nouă numai prin instalarea unui pachet nou de programe. Însă dacă vom avea un echipament (procesor, coprocesor sau accelerator) care efectuează îndeplinirea programelor JAVA și el nu mai e compatibil cu versiunile noi de programe, este cu mult mai greu și costisitor de-l înlocuit. Însă și această problemă poate fi parțial ocolită prin utilizarea, spre exemplu, a procesoarelor cu set de instrucțiuni modificabil.
O altă metodă de înlocuire a JVM este cea parțială. Aceasta înseamnă combinare dintre hard și soft. Se poate de implementat un dispozitiv care ar executa parțial funcțiile JVM. Această metodă ar rezolva problemele de compatibilitate între versiuni dar, în același timp, nu exclude totalmente realizarea soft, deci procesorul de bază v-a trebui în continuare să deservească JVM. Avantajul principal este că opcode-urile ar putea fi executate într-un singur tact ca fiind instrucțiuni de bază a dispozitivului. Aceasta ar mări cu mult viteza de execuție, deoarece pentru executarea unui opcode sînt necesare cel puțin 10 tacte a procesorului de bază.
Din aceste două metode, de către companiile ce au făcut cercetări în domeniu, a fost selectată metoda combinată, metoda de realizare totalmente hard nu este menționată. Realizările au fost descrise pe scurt în capitolul 2, de unde se observă că au fost atinse trei direcții de implementare hard a JVM, coprocesor, procesor și accelerator. Fiecare din aceste metode are avantajele și dezavantajele ei. În proiectul dar am selectat realizarea hard sub formă de procesor cu arhitectură RISC.
Realizarea procesorului JAVA impune executarea tuturor instrucțiunilor în baza setului de instrucțiuni JAVA, ceea ce nu întotdeauna este efectiv. Un astfel de procesor ar putea fi utilizat numai cu softul respectiv și în sisteme specializate. Folosirea unui procesor ar duce la mărirea vitezei de execuție a programelor JAVA și în același timp nu duce la specializarea îngustă a sistemului în întregime. De asemenea, procesorul poate fi dirijat totalmente sau parțial de către procesorul de bază, astfel se pot rezolva multe probleme de intercalare soft inclusiv și compatibilitatea.
3.3. Organizarea structurală și algoritmul de funcționare a procesorului JAVA
Vom face o analiză a posibilităților de realizare a procesorului JAVA (în continuare – procesorul) prin metodele menționate în punctul 2 al acestui capitol și anume: implementare totalmente hard și combinare dintre hard și soft.
Implementarea hard. Această metodă prevede funcționarea independentă a procesorului, adică mașina virtuală va fi exclusă complet. Aceasta poate fi efectuată prin două metode. Prima metodă prevede existența unui program de bază care ar prelucra toate task-urile destinate JVM și ar manipula cu resursele procesorului. Acest program ar fi asemenea unui sistem de operare care ar dirija cu toate procesele, din aceste considerente această metodă este absurdă. A doua metodă constă în realizarea totalmente hard a JVM, fără intervenție soft. Un astfel de procesor ar fi foarte complex ca structură și proces de funcționare. Ar necesita o comunicare intensă cu memoria și procesorul principal ceea ce poate duce la descreșterea productivității.
Implementarea combinată. În continuare vom cerceta posibilitățile realizării combinate a JVM, adică hard în combinație cu soft. Tendința unei astfel de realizări este de a implementa cât mai multe funcții ale JVM prin metoda hard, astfel eliberând resursele procesorului de bază care pot fi folosite pentru scopuri generale. Un reușit exemplu sînt procesoarele cu set de instrucțiuni modificabil, ideea de funcționare a cărora poate fi utilizată pentru acest procesor. Printr-o astfel de structură se poate de soluționat problemele de compatibilitate și de modificări a fișierelor format al clasei. În continuare vom enumera cerințele către procesor și funcțiile de bază ce urmează a fi implementate.
Pentru ca fișierele clasă să fie ușor accesate prin rețea, setul de instrucțiuni a JVM a fost limitat la lungimea de 8 biți. Deci există numai 256 de instrucțiuni ce au un format fix. Lista completă a instrucțiunilor este prezentată în anexa 1. Deci rezultă că procesorul trebuie să fie de structură RISC. Putem utiliza o unitate de procesare clasică de tip RISC. Există mai multe cauze ce impun utilizarea anume acestei arhitecturi și anume:
– setul restrâns de instrucțiuni – ceea ce și este impus de către opcode-urile JAVA;
– rapiditatea execuției instrucțiunilor (majoritatea instrucțiunilor RISC se ex-ecută într-un singur tact);
– formatul fix de codificare a instrucțiunilor, etc.
Arhitectura RISC prevede separarea cache-urilor de date și de instrucțiuni. Deci procesorul trebuie să includă două blocuri de cache separate, unul pentru date și altul pentru instrucțiuni.
De asemenea arhitectura procesorului trebuie să fie orientată pe stivă. Această metodă de organizare este frecvent utilizată în arhitecturile moderne și dă dovadă de performanță înaltă. În plus, anume această metodă este utilizată și de către JVM. O astfel de organizare prevede ca toate operațiile sânt efectuate cu vârful stivei nu cu ajutorul registrelor. Parametrii ce urmează a fi transmiși metodelor de asemenea sânt transmiși utilizând stiva.
Metoda de comunicare cu procesorul de bază este realizată standard, adică prin metoda întreruperilor. Structura model a unui astfel de procesor este reprezentată în figura 3.2.
Componentele de bază a procesorului sunt:
– unitatea de interfațare cu magistrala (Bus Interface Unit);
– două cache-uri separate, de date și de instrucțiuni (Instruction cache, Data cache);
– stiva (Stack);
– unitate pentru executarea operațiilor în virgulă flotantă (Float Point Unit);
– unitate de execuție a instrucțiunilor (Execution Unit);
– blocul de registre (Register File);
– unitatea de control (Control Unit);
– unitatea de dirijare cu thread-urile (Thread Sheduler Unit);
– unitatea de control a microprogramelor (Microprogram Control Unit).
Această structură este ca un prim model a procesorului, așa că nu este exclus faptul că unele blocuri vor fi modificate în viitor sau chiar vor fi excluse în caz că nu vor da dovadă de eficiență. De asemenea, unele blocuri standard nu sunt reprezentate pe schemă, însă acest fapt nu indică excluderea lor, ele nu au fost reprezentate deoarece pot fi utilizate ca unități de uz generală.
Descrierea mai amănunțită a componentelor procesorului va fi prezentată în punctul 3 al acestui capitol.
În baza algoritmului prezentat în figura 3.3 vom explica metoda de funcționare a procesorului.
În algoritmul prezentat în figura 3.3 am prezentat la general particularitățile de funcționare a procesorului. Pentru a avea un consum minim de energie, în caz că nu este necesară utilizarea procesorului, funcționarea sa este suspendată atâta timp cât nu sunt instrucțiuni ce ar trebuie să fie executate de către procesor. Decizia de a conecta sau nu procesorul este luată de către procesorul principal, deci funcționarea procesorului va fi suspendată până la apariția semnalului de demaraj (blocul 2). Următorul pas este resetarea (blocul 3), care prevede setarea tuturor părților componente ale procesorului în regimul de bază de funcționare, anularea valorilor tuturor registrelor, resetarea timer-ilor, etc. Primul pas după resetare este extragerea instrucțiunii (blocul 4) care este indicată de către registrul PC (Program Counter). În caz că a fost extras codul unei macroinstrucțiuni, verificarea este făcută în blocul 10, se efectuează respectiv trecerea la adresa de start a microprogramului ce reprezintă macroinstrucțiunea respectivă (blocul 13). Apoi este extrasă instrucțiunea ce urmează a fi îndeplinită (blocul 16, care este echivalent cu blocul 4). Instrucțiunea extrasă este în continuare decodificată (blocul 17) și sînt generate semnalele respective de comandă pentru celelalte componente. După care urmează procesul de execuție a instrucțiunii (blocul 18). Apoi este verificat sfârșitul microprogramului (blocul 19), în caz că mai există instrucțiuni se efectuează salt la blocul 14 și funcționarea în continuare este efectuată conform pașilor descriși anterior. În caz că e depistată instrucțiune de reîntoarcere din microprogram, atunci se face verificarea dacă mai există instrucțiuni în cache-ul de instrucțiuni. În caz că cache-ul nu e gol, atunci saltul este efectuat la blocul 4 a algoritmului, care indică repetarea întregului ciclu de funcționare a procesorului. În caz contrar este efectuată trecerea la blocul 2, ce indică deconectarea procesorului până la sosirea de noi instrucțiuni.
În cadrul algoritmului am reprezentat și blocuri de prelucrare și deservire a întreruperilor, care în caz real pot apărea nu exact în acele puncte de execuție a programului indicate în algoritm. Blocurile 5 și 14 de prelucrare a întreruperilor prevăd detectarea întreruperii, fie de la procesorul principal fie de la timerile procesorului, spre exemplu de la unitatea de gestionare cu thread-urile. Blocurile 6 și 15 efectuează verificarea apariției întreruperii. În caz că a fost depistată o întrerupere este efectuată prelucrarea întreruperii date. Modul de prelucrare depinde de tipul întreruperii. În caz general este efectuată înregistrarea stării procesorului (blocul 7), detectarea procesului următor căruia îi vor fi alocate resursele procesorului (blocul 8) și încărcarea stării procesului ce urmează de a fi executat. Determinarea procesului ce va fi încărcat se efectuează conform unui algoritm care ia în considerație prioritatea procesului și starea sa.
Unitatea de procesare preluând instrucțiunile de la controlor lucrează la capacitatea sa maximă ne cheltuind timp pentru așteptarea aducerii instrucțiunii din memoria sistem, care este cu mult mai lentă ca procesorul.
Toate instrucțiunile sunt executate în mod pipeline. Aceasta presupune execuția instrucțiunii curente în timp ce este adusă instrucțiunea următoare, procedeu care mărește productivitatea sistemului. Pipeline-ul pentru acest procesor include 6 nivele și anume:
– fetch;
– decode;
– fetch operands;
– concatenate operands;
– execute instruction;
– writeback.
Acești pași au fost stabiliți reieșind din descrierea principiului de funcționare a JVM. Putem grupa acești pași în patru etape generale: extragerea instrucțiunii/datelor, decodificarea instrucțiunii, execuția instrucțiunii și înregistrarea rezultatelor. Observăm că etapa de extragere a operanzilor este urmată de concatenarea lor. Acest pas este efectuat deoarece toate datele sunt pe 8 biți, iar datele de tip integer, floar, double au o mărime mai mare, iată de ce ele sunt citite pe octeți iar mai apoi sunt concatenate pentru a fi procesate. Pentru operanzi de lungimea unui octet acest pas este înlocuit cu operația NOP.
=== CAPIT4 ===
4. Structura și modul de funcționare a procesorului
4.1. Caracteristici generale
Procesorul JAVA descris în această lucrare are la bază structura unui procesor clasic în baza arhitecturii RISC orientat pe stivă. Setul de instrucțiuni sunt bytecode-urile Java reprezentate în anexa 1. Spre deosebire de procesoarele clasice mai sunt incluse două module funcționale, unitatea de control a microprogramelor și unitatea de dirijare cu thread-urile. Caracteristicile generale ale procesorului sunt:
– registrele interne și magistrala de date externă sunt pe 32 de biți;
– viteza mărită de lucru datorită principiilor arhitecturale specifice, cât și unei structuri interne bazată pe conceptul de suprapunere, care permite aducerea în avans, a instrucțiunilor, în timpul unor cicluri fără accese la magistrale;
– setul de bază de instrucțiuni specific, ce permite execuția unei instrucțiuni într-un singur tact de ceas;
– poate fi utilizat împreună cu o gamă largă de procesoare astfel mărind viteza de rulare a aplicațiilor JAVA și eliberând resursele procesorului de bază care pot fi utilizate în scopuri generale;
– dirijarea cu thread-urile este efectuată de o unitate specială, ce permite schimba-rea între thread-uri foarte rapid și eliberarea sistemului de operare de această funcție;
– include un controlor de microprograme care are o memorie de tip Flash și deservește aplicațiile JAVA (verificarea, încărcarea ș.a.) care de asemenea sporește viteza de execuție a programelor.
Această configurație a procesorului a fost propusă în baza literaturii studiate referitor principiului de funcționare a JVM. Este cert faptul că timpul de care am dispus este foarte mic pentru a studia foarte amănunțit această problemă care necesită cunoașterea atât a principiului de funcționare a JVM cât și metodologia de proiectare a procesoarelor. Din aceste considerente a fost făcută numai elaborarea schemei generale a procesorului cu unele recomandări referitor mărimii memoriei, stivei, etc. O estimare precisă a acestor mărimi poate fi făcută numai la elaborarea structurii complete a procesorului și prin simularea funcționării sale, spre exemplu utilizând mediul de programare Altera Max+Plus II, cu ajutorul limbajului VHDL.
4.2. Structura generală internă a procesorului
Procesorul elaborat este alcătuit din 6 unități funcționale ce lucrează dependent una față de alta. Fiecare unitate are semnalul său de sincronizare care derivă de la semnalul de ceas de bază. Unitățile de bază a procesorului au fost enumerate pe scurt în capitolul 3 și ele sunt:
– unitatea de interfațare cu magistrala (Bus Interface Unit), rolul principal este de a aduce anticipat instrucțiunile din memorie și de a transfera operanzii între unitatea de execuție și exteriorul procesorului;
– unitatea de execuție a operațiilor în virgulă mobilă (Float Point Unit), este destinată îndeplinirii rapide a operațiilor ce au drept operanzi valori în virgulă flotantă, adică operații strict-fp;
– unitatea de execuție a instrucțiunilor (Execution Unit), care are rolul execuției operațiilor aritmetice și logice, totodată participă și la operațiile de transfer;
– unitatea de prelucrarea a instrucțiunilor (Control Unit), are rolul de a decodifica instrucțiunile și de a genera semnalele necesare pentru a asigura execuția lor. De asemenea îndeplinește funcția de control a tuturor unităților funcționale care intră în structură procesorului;
– unitatea de dirijare cu thread-urile (Thread Sheduler Unit), este destinată repartizării resurselor procesorului microproceselor în execuție, răspunde de determinarea următorului proces ce urmează a fi executat. Are o memorie internă de 512K în care sunt stocați descriptorii proceselor în așteptare;
– unitatea de control a microprogramelor (Microprogram Control Unit), unitate ce dispune de o memorie de 256K în care sunt memorizate codurile microprogramelor de deservire precum și poate servi pentru păstrarea rezultatelor intermediare a execuției microprogramelor;
– blocul de registre (Register File), este destinat pentru indicarea stării procesorului și indicării rezultatelor îndeplinirii operațiilor. Conține un set mic de registre deoarece procesorul are o arhitectură orientată pe stivă.
În continuare vom descrie mai amănunțit blocurile principale, efectuând unele estimări privitor datele acumulate din materialele studiate. O determinare precisă a parametrilor este posibilă după o studiere mai amănunțită și simularea procesorului.
4.3. Unitatea de registre
Unitatea de registre, RF (Register File), are o structură standard și destinație generală. Unica deosebire este că numărul de registre este redus deoarece procesorul are o arhitectură orientată pe stive din care cauză registrele destinate păstrării operanzilor și datelor intermediare sunt excluse. Astfel registrele incluse sunt:
– registrul indicator de adresă;
– registrul de adresare a memoriei;
– registrul de instrucțiuni;
– registrul indicatorilor de condiții.
Registrul indicator de adresă, PC (Program Counter). Succesiunea de instrucțiuni ce trebuie executată de procesor este înscrisă în locații de adrese succesive. Conținutul acestui registru de adrese este automat mărit cu o unitate după ce instrucțiunea a fost citită, în acest fel pregătindu-se adresa pentru instrucțiunea următoare. De fapt, PC este un registru cu prescriere, existând posibilitatea de prescriere în PC în cazul existenței în succesiunea logică a programului a unei ramificații, care apare în urma unei operații de decizie între mai multe variante. Pentru inițializarea unui program trebuie de încărcat PC cu adresa de început corespunzătoare.
Registrul de adresare a memoriei. Acest registru tampon de adresare, denumit buffer de adresare, este conectat la magistrala (externă) de adresare a memoriei. Conținutul registrului PC este transferat în bufferul de ieșire care va aplica pe magistrala externă de adresare un cuvânt binar de lungimea registrului PC, care reprezintă adresa unei locații de memorie. Dar încărcarea bufferului de adresare se poate face nu numai de la PC, ci și de la alte elemente ale procesorului.
Registrul de instrucțiuni, IR (Instruction Register). După ce un cuvânt de instrucțiune este citit, o copie a acestui cuvânt va fi înscrisă în registrul de instrucțiuni. Registrul IR păstrează instrucțiunea pe durata executării acesteia. Odată copiată instrucțiunea în IR conținutul numărătorului de adrese este automat incrementat cu o unitate PC+1. Biții instrucțiunii se aplică decodificatorului de instrucțiuni care, apoi prin unitatea de control, va genera toate semnalele de control necesare execuției ei.
Registrul indicatorilor de condiții, FR (Flags Register). Biții acestui registru sunt înscriși la valoarea 1 în urma unor teste din timpul execuției operațiilor aritmetice și logice ale programului. Testarea unor biți din acest registru presupune modificarea ordinii normale de execuție a programului și execuția unei instrucțiuni de ramificare, care va face un salt prin încărcare registrului PC cu o anumită adresă.
4.4. Unitatea de interfațare cu magistrala
Unitatea de interfațare cu magistrala, BUI (Bus Interface Unit), efectuează legătura dintre procesor și exteriorul său (procesorul principal, memorie, etc.). De asemenea generează semnalele de comandă necesare pentru accesul la memorie și semnalele, ce reflectă starea internă a procesorului, preluate de la unitatea de execuție.
BUI efectuează aducerea în avans a instrucțiunilor, aceasta înseamnă că toate datele și instrucțiunile sînt citite în timpul ce unitatea de execuție este ocupată de prelucrarea instrucțiunilor deja citite. Acest fapt reduce practic la zero timpul de citire a instrucțiunilor și datelor.
Deoarece opcode-urile și datele sunt reprezentate pe 8 biți, în timpul unei singure citiri pot fi extrase mai multe cuvinte, astfel mărind și mai mult viteza de citire.
4.5. Stiva
Deoarece procesorul are o arhitectură orientată pe stivă, ea va fi utilizată la maximum pentru păstrarea operanzilor și rezultatului îndeplinirii operațiilor. Mai mult ca atât, toate transmiterea parametrilor spre și din metode este efectuată prin intermediul stivei. Adâncimea ei este limitată de câmpul max_stack a structurii Code_attribute și constituie 65535 de poziții.
4.6. Unitatea de execuție a intrucțiunilor
Unitatea de execuție a instrucțiunilor, EU (Execution Unit) este destinată execuției operațiilor codul cărora este recepționat de la unitatea de prelucrare. Ea poate executa toate instrucțiunile funcționale și participă ca intermediar la execuția instrucțiunilor de transfer. Semnalele de dirijare cu EU sunt generate de către unitatea de control. După execuția instrucțiunilor funcționale unitatea fixează indicatorii de condiții care pot fi citiți de către unitatea de prelucrare pentru luarea deciziilor ulterioare. Intrările EU sunt: codul operației executate, operanzii care participă la realizarea operației, ca ieșiri sunt: rezultatul operației și indicatorii de condiții Flags.
După cum a fost menționat procesorul are arhitectură orientată pe stivă, iată de ce în mod normal operanzii sunt transmiși EU prin utilizând stiva. Pentru a micșora numărul de adresări la stivă și astfel de a mări viteza de execuție a instrucțiunilor, a fost implementată o metodă de anticipare a utilizării operanzilor. Această metodă prevede studierea instrucțiunii ce urmează de a fi executată și în cazul în care același operand e plasat în stivă pentru a fi transmis EU, iar apoi de către EU e extras, atunci el este direct transmis EU.
De exemplu, în cazul instrucțiunii ADD vom avea următorul scenariu standard:
PUSH a ; plasam operandul a în stivă
PUSH b ; plasam operandul b în stivă
ADD ; operandul b este citit din stivă
; operandul a este citit din stivă
; rezultatul a+b este înregistrat în stivă
scenariul modificat va fi:
PUSH a ; plasam operandul a în stivă
PUSH b ; plasam operandul b la intrarea EU
ADD ; operandul b este citit din stivă
;
; rezultatul a+b este înregistrat în stivă
Deci o asemenea implementare va duce nemijlocit la micșorarea timpului de execuție a instrucțiunilor.
4.7. Unitatea de execuție a instrucțiunilor în virgulă flotantă
Unitatea de execuție a instrucțiunilor în virgulă flotantă, FPU (Float Poin Unit) este destinată îndeplinirii instrucțiunilor ce au operanzi de tip real. Limbajul Java și respectiv și JVM, respectă totalmente standardul pentru numere în virgulă flotantă IEEE 754, deci se poate de utilizat o unitate FPU standard ce este utilizată în orice procesor. Semnalele de comandă sunt generate de către unitatea de comandă și metoda de funcționare este asemenea EU. De asemenea, este valabilă metoda de aducere anticipată a operanzilor descrisă pentru EU.
4.8. Unitatea de control
Unitatea de control, CU (Control Unit) dirijază cu procesul de funcționare a tuturor unităților procesorului. Are rolul de a decodifica instrucțiunile și de a genera semnalele de control necesare execuției instrucțiunilor, care sunt extrase succesiv din cache-ul de instrucțiuni. În caz că este depistată instrucțiunea ce indică apelul la un microprogram, este citită în continuare următoarea instrucțiune ce indică care program trebuie anume executat și lista parametrilor de execuție. În dependență de aceste date sunt generate semnalele necesare pentru Unitatea de Control a Microprogramelor. Pe parcursul decodificării instrucțiunilor, unitatea de control preia de la EU indicatorii de condiții generați în urma execuției instrucțiunii precedente, informație folosită în cazul interpretării instrucțiunilor de salt și generării erorilor.
Dacă în timpul decodificării a fost depistat un apel la microprogram, atunci CU generează semnalele necesare pentru a trece procesorul într-un mod extins de execuție a instrucțiunilor și transmite MCU codul microprogramului așteptând până nu vor fi furnizate instrucțiunile corespunzătoare codului instrucțiunii extinse (vezi algoritmul de funcționare a procesorului).
Setul de bază de instrucțiuni a procesorului include 205 de comenzi, dintre care 14 din ele necesită intervenția soft parțială sau realizarea lor prin microprograme (o astfel de realizare e posibilă cu ajutorul Unității de Control a Microprogramelor). Formatul unei instrucțiuni este următorul:
Numărul de operanzi solicitați de către este determinat de tipul instrucțiunii și respectiv poate varia între 0 și 65535. Este limitat de către indicatorul parameter_count a fișierului clasă. Opcode-ul 186 nu este utilizat de către JVM din considerații istorice. Dar, pentru procesorul dar el este interpretat ca indicator a unei comenzi de apel a microprogramului. Instrucțiunea ce urmează opcodul 186 este interpretată de către CU drept comandă de apel a microprogramului. Acest procedeu a fost propus reieșind din materialele teoretice studiate, în caz de apariție a careva coliziuni de date el poate fi modificată în oricare altul sau în general poate fi schimbat conceptul de indicare a apelului microprogramelor.
4.9. Unitatea de control a microprogramelor
Unitatea de control a microprogramelor, MCU (Microprogram Control Unit) a fost introdusă în componența procesorului pentru a deservi un șir de rutine ce sunt destinate prelucrării informației citite din fișierul format al clasei. O altă necesitate a includerii unui asemenea bloc este cea de soluționare a problemei de compatibilitate dintre programele surse si interpretatorul JAVA. Aceasta este posibil deoarece microprogramele sunt înscrise într-o memorie de tip Flash, deci ele pot fi ușor reprogramate. Această memorie este limitată și este numai de 256Kb, această valoare este stabilită luând în considerație lungimea opcode-urilor și a operanzilor care e de un octet. Timpul de acces la memorie este foarte mic, deoarece ea este plasată pe cipul procesorului. În caz de insuficiență de memorie, de către MCU poate fi accesată și memoria RAM a sistemului în care în prealabil a fost înscris codul microprogramului solicitat.
Prin intermediul acestei noi posibilități a procesorului pot fi implementate așa metode ca verificarea, încărcarea în baza MCU, astfel sunt eliberate resursele procesorului principal. Metoda de funcționare este asemenea unui procesor cu set de instrucțiuni modificabil ce include un controlor ierarhic de instrucțiuni.
4.10. Unitatea de dirijare cu thread-urile
Unitatea de dirijare a thread-urilor, TSU (Thread Sheduler Unit) efectuează alocarea resurselor thread-urilor sau proceselor în șirul de așteptare. Această funcție, în mod normal, e realizată de către sistemul de operare. Astfel unitatea TSU exclude necesitatea unui sistem de operare, executând toate funcțiile necesare dirijării thread-urilor și anume: sincronizarea, partajarea resurselor, luarea deciziei care proces va fi următorul preluat în execuție. Pentru a face o analiză a proceselor în așteptare TSU necesită careva informație, pentru ce și a fost inclusă o memorie rapidă cu mărimea de 512Kb destinată păstrării descriptorilor proceselor în așteptare. Capacitatea a fost calculată luând în considerație numărul de thread-uri posibile ale unui proces și mărimea descriptorului procesului. În caz de insuficiență a memoriei locale poate fi adresată, de către TSU, memorie de bază a sistemului pentru a aloca datele. Algoritmul de păstrare și de schimb a datelor dintre memoria principală a sistemului și memoria locală este asemeni celui pentru cache. Timpul alocat fiecărui proces este determinat de către timerul de sistem a procesorului.
Includerea unei astfel de unități permite de a elibera partea realizată soft a JVM de funcția de sincronizare și micșorează considerabil comunicarea dintre procesorul principal și procesor mărind numărul de instrucțiuni soluționate local.
=== CONCL ===
CONCLUZII
Pe parcursul îndeplinirii acestui proiect de diplomă am efectuat cercetările necesare elaborării unui procesor JAVA. Din materialele colectate (în mare măsură alcătuiesc ultimele știri din Internet) am observat că această problemă este actuală și e abordată de către mai multe companii de producere a suportului hard așa ca Sun, JEDI Technologies, Aurora VLSI Inc., Vulcan ASIC. Însă toate procesele de elaborare sunt în proces de demarare și informația ce este prezentată este foarte la general (vezi capitolul 2, Realizări).
Din aceste considerente nu e exclus faptul că unele elemente de arhitectură pot fi deja elaborate și implementate, fie în careva realizări sau mostre de verificare și testare.
Cu toate acestea am efectuat studiul necesar elaborării unui procesor JAVA în baza arhitecturii RISC care a inclus următorii pași:
– studierea la generală a limbajului JAVA;
– studierea structurii și a principiului de funcționare a JVM;
– cercetarea realizărilor în domeniu;
– studierea principiului de funcționare a procesoarelor;
– elaborarea structurii generale a procesorului JAVA.
Studierea acestei probleme necesită mult mai mult timp și includerea a mai multor persoane. Din insuficiență de timp nu am ajuns până la procesul de simulare a procesorului, însă și fără aceasta este clar că implementarea unui asemenea procesor este rentabilă și eficientă. Incluzând un asemenea procesor într-un sistem de calcul obținem mărirea vitezei de execuție a aplicațiilor JAVA și astfel sunt eliberate resursele procesorului de bază care pot fi utilizate de către alte procese.
=== ECONOM ===
5. Calculul parametrilor economici
5.1. Introducere
În prezent toată lumea este unită în rețeaua globală Internet. Zilnic tot mai mulți oameni devin membrii ai acestei rețele. Ea se infiltrează tot mai adînc în toate domeniile activității omului. Internetul este utilizat pentru cele mai diverse scopuri și necesități ca de exemplu: pentru efectuarea operațiunilor de vînzare-cumpărare, comunicare, informare în masă, extragerea informației necesare. Nimeni în prezent nu poate determina cu exactitate cîte calculatoare sînt incluse în această rețeauă și cîți utilizatori se folosesc zilnic de serviciile ei.
Pentru asigurarea securității datelor și pentru crearea paginilor web este utilizat limbajul de programare JAVA elaborat de către compania Sun. Interpretator al acestui limbaj este aplicația specială elaborată de aceeași companie numită Java Virtual Machine (JVM), care este implementată soft și necesită foarte multe resurse a procesorului de bază. Scopul acestei cercetări este de a elabora schema generală a creării unui procesor care ar îndeplini funcțiile JVM și respectiv ar spori și viteza de execuție a aplicațiilor JAVA și ar duce la eliberarea resurselor procesorului de bază care ar putea fi folosite pentru prelucrarea altor task-uri.
Un astfel de procesor poate fi utilizat în orice sistem de calcul, în special este util de a fi încadrat în calculatoarele specializate destinate executării programelor scrise în JAVA. Însă nu este limitată utilizarea sa numai pentru limbajul JAVA ci de asemenea poate fi utilizat pentru execuția programelor scrise în diferite limbaje de programare cu condiția că compilatoarele respective susțin generarea fișierelor format al clasei (*.class) ce conțin bytecode-urile ce urmează a fi interpretate de către procesor.
Deci, se poate de spus că proiectarea și elaborarea procesoarelor JAVA este profitabilă din punct de vedere economic datorită pieței mari de desfacere și a cererii tot mai mult în creștere la sistemele și aplicațiilor la baza cărora stau principiile limbajului JAVA.
Procesorul JAVA elaborat în prezentul proiect tinde să impună o nouă concepție de realizare a Java Virtual Machine și astfel va fi mărită productivitatea sistemului în întregime.
Astfel de procesoare ar fi ideale pentru firmele ce doresc de a mări productivitatea sistemului de calcul pe cale diferită de mărirea frecvenței de lucru a procesorului sau mărirea memoriei disponibile ci prin partajarea lucrului. Deoarece este cert faptul că orice lucru ce este universal nu duce la cele mai performanțe înalte în toate domeniile de aplicare a sa.
Costurile de producere a astfel de procesoare o să fie minore din cauza simplității sale, însă respectiv o sa fie necesitatea adaptării sistemului în genere pentru conlucrarea cu acest tip de procesor.
Deci pentru companiile ce elaborează sisteme specializate sau care doresc și pot să-și permită de a lărgi gama de produse, este rentabilă folosirea unui astfel de procesor din toate punctele de vedere.
5.2. Etapele de proiectare
În continuare sunt prezentate etapele proiectării, de care a fost nevoie pentru realizarea acestui proiect:
Analiza sarcinii.
Selectarea și studierea literaturii.
Studierea limbajului JAVA.
Studierea structurii JVM realizate prin metoda soft.
Studierea arhitecturilor moderne de procesoare JAVA.
Elaborarea schemei de structură a procesorului.
În cele ce urmează vom calcula timpul necesar pentru îndeplinirea fiecărei etape.
Timpul necesar îndeplinirii lucrărilor se determină conform formulei:
Ti = ( Ai + 4Mi + Bi ) / 6
unde:
AI – timpul minim de îndeplinire.
BI – timpul maxim de îndeplinire.
MI – timpul cel mai probabil de îndeplinire.
În tabelul de mai jos sunt ilustrate etapele de proiectare și timpul cheltuit pentru îndeplinirea lor. Pentru fiecare etapă a fost definit un cod, care va fi ulterior folosit în tabelul 5.2 pentru identificarea etapei respective. Unitatea de timp a fost luată în zile.
Tabelul 5.1
Timpul necesar pentru îndeplinirea fiecărei lucrări
În continuare pe baza tabelului de mai sus este prezentat tabelul 5.2, care reprezintă graficul calendaristic de efectuare a lucrărilor respective. Fiecare etapă este identificată prin codul atribuit în tabelul 5.1. Pentru identificarea consultațiilor s-a folosit codul Cx, unde x- numărul consultației. În calitate de consultant a fost conducătorul diplomei.
Tabelul 5.2
Planul calendaristic de efectuare a lucrărilor
Unde prin D ca executant figurează diplomantul, iar prin C ca consultant figurează conducătorul diplomei.
5.3. Estimarea cheltuielilor materiale
În tabelul de mai jos sunt prezentate materialele de care a fost nevoie pentru elaborarea proiectului și suma de bani cheltuită pentru procurarea lor.
Tabelul 5.3
Cheltuieli materiale
5.4. Calculul remunerării muncii
Remunerarea muncii în orice proces de cercetare ocupă un loc de bază. De acest factor depind atît timpul de efectuare, cît și calitatea rezultatelor obținute.
Mărimea salariilor lunare pentru diplomant și conducătorul diplomei au fost stabilite de 260.00 lei și 600.00 lei respectiv. Mai jos pe baza datelor din tabelul 5.2 conform timpului de lucru vom calcula salariile respective pe toată durata cercetării.
S(A) = (25/22)* 600 = 681.82(lei) ;
S(B) = (70/22)* 260 = 827.27 (lei) ;
unde:
25, 70 – numărul de zile lucrate.
22 – numărul de zile lucrătoare într-o lună.
220, 530 lei – salariile de bază pe o lună.
Tabelul 5.4
Plata salariilor
Salariile respective sunt salarii lunare de bază. Salariile suplimentare vor constitui 12% din salariile de bază, în cazul proiectului dat ele vor reprezenta respectiv:
Tabelul 5.5
Salariul suplimentar
5.5. Calculul defalcărilor în fondul social de asigurare
Defalcările în fondul social de asigurare sunt calculate în procente de la salariu lunar și cel suplimentar și constituie 28% plus 1% defalcări în fondul de pensii. Defalcările în fondul social și fondul de pensii pentru lucrarea curentă vor constitui respectiv
Tabelul 5.6
Defalcările în fondul social de asigurare
5.6. Estimarea cheltuielilor pentru arenda calculatorului
În cheltuielile pentru cercetare au mai intrat cheltuielile legate de arenda calculatorului, fără care ar fi fost imposibil de realizat acest proiect. Conform etapelor de proiectare calculatorul a fost arendat pe o perioadă de 35 zile, timp în care s-au desfășurat următoarele activități:
– Colectarea informației – 16 zile;
– Studierea arhitecturelor de procesoare deja implementate – 10 zile;
– Elaborarea modelului procesorului – 9 zile;
În tabelul de mai jos se conțin datele despre cheltuielile efectuate pentru arenda calculatorului:
Tabelul 5.7
Cheltuieli pentru arenda calculatorului
5.7. Estimarea cheltuielilor de regie
Cheltuielile suplimentare includ următoarele cheltuieli: cheltuieli legate de gestionare, deservire, remunerarea muncii aparatului de conducere, cheltuieli legate de repararea încăperilor de producție, a inventarului, protecția muncii și a mediului ambiant ș.a.m.d.
Mărimea cheltuielilor suplimentare constituie 120-200% de la salariul de bază și cel suplimentar. În cazul proiectului dat cheltuielile suplimentare vor constitui respectiv:
Tabelul 5.8
Cheltuielile de regie
5.8. Estimarea cheltuielilor totale pentru elaborare
În final vom estima toate cheltuielile suportate pentru efectuarea studiului dat. Ele vor include cheltuielile prezentate în tabelul de mai jos:
Tabelul 5.9
Cheltuielile totale pentru elaborare
5.9. Calculul efectului economic
Pentru a estima efectul economic obținut în urma elaborării procesorului JAVA, se va calcula coeficientul de eficiență științifică și de eficiență tehnico – științifice după formulele:
eficiența științifică –
eficiența tehnico – științifică –
unde: Kf.p. – coeficientul factorului de pondere,
Kc.r. – coeficientul de realizare.
În tabelele de mai jos sunt prezentați coeficienții de realizare obținuți, care exprimă diferite caracteristici ale studiului prezent ca: noutatea și utilitatea cercetărilor efectuate, aplicabilitatea rezultatelor obținute, finalitatea proiectului, etc. Calculînd eficiența științifică și cea tehnico – științifică, și comparîndu-le cu unitatea (cu cît sunt mai aproape de unu cu atît eficiența este mai mare) vom putea trage concluzii finale din punct de vedere economic despre proiectul dat.
Tabelul 5.10
Eficiența științifică a cercetărilor efectuate
Tabelul 5.11
Eficiența tehnico-științifică a cercetărilor efectuate
Conform datelor din tabele de mai sus obținem:
Kș. = 0.5*0.5 + 0.35*0.6 + 0.15*0.6 = 0.45
Kt.ș. = 0.5*0.5 + 0.3*0.8 + 0.2*0.8 = 0.65
Deoarece utilitatea unei cercetări științifice la prima vedere este greu de estimat, totuși pe baza coeficienților calculați mai sus putem afirma că rezultatele obținute sunt de nivel mediu și reprezintă niște rezultate acceptabile avînd în vedere proiectul dat și suma mică de bani cheltuită pentru realizarea sa.
Scopul proiectului dat a fost mai mult demonstrarea avantajelor pe care le poate oferi utilizarea unui procesor JAVA. Avantajul principal din punct de vedere economic îl constituie posibilitatea utilizării sistemelor de calcul ce au un astfel de procesor pentru o gamă largă de probleme. O caracteristică, ce ar aduce substanțiale economii financiare firmelor care ar utiliza un astfel de procesor pentru sistemele sale.
Este evident, că implementarea în practică a procesorului ar necesita investiții substanțiale și mai mult timp pentru proiectarea variantei finale. Dar datorită domeniului mare de utilizare a procesoarelor aceste investiții ar putea fi recuperate în cel mai scurt timp.
=== INTRO ===
INTRODUCERE
Dezvoltarea tot mai rapidă a tehnicii de calcul a dus la infiltrarea calculatorului în majoritatea sferelor activității umane. Dacă numai cu aproximativ 30 de ani în urmă procesoare cu frecvența de lucru de nivelul zecilor de megaherți nu era de închipuit nici pentru cele mai bune calculatoare, apoi în prezent orice calculator personal funcționează în baza procesoarelor ce au frecvența de lucru aproximativ de 1GHz. Însă mărirea vitezei de funcționare a procesorului nu este unica și nici cea mai bună soluție de a obține performanțe înalte.
Există mai multe posibilități de a mări productivitatea unui sistem de calcul. Una din ele este divizarea operațiilor. Aceasta este dictată de faptul că orice echipament universal nu permite atingerea performanțelor înalte. Anume din aceste considerente și sînt efectuate procesoare, coprocesoare și microcontroloare specializate, structura cărora depinde de destinația lor. Drept exemplu este chiar coprocesorul matematic care este parte componentă a majorității procesoarelor și care răspunde de efectuarea operațiilor aritmetice. Aceste operații pot fi efectuate și de către procesorul principal însă ele necesită un timp mai îndelungat. Iar coprocesorul matematic are o structură specială ce permite efectuarea rapidă a acestui tip de operații.
De asemenea sînt cunoscute microcontroloarele specializate a firmelor Infineon, Atmel, Motorola care au o arhitectură specifică și un domeniu îngust de utilizare. Ele pot fi folosite în scopuri generale, însă în acest caz productivitatea lor scade considerabil.
Popularitatea tehnologiei World Wide Web a făcut ca limbajul JAVA să fie utilizat foarte pe larg. JAVA este un limbaj de programare concurent, orientat pe obiect. Sintaxa sa e similara cu cea a limbajului C, C++ însă concomitent exclude unele particularități care fac limbajele C și C++ complexe și confuze. Limbajul JAVA a fost conceput pentru elaborarea softului pentru rețele, astfel asigură lucrul concomitent cu mai multe calculatoare și securitatea înaltă a datelor. Codul compilat este executat de către o mașină virtuală creată în cadrul mașinii fizice numită Java Virtual Machine (JVM). Anume datorită acestui fapt codul programului este portabil pentru orice tip de sistem de operare. Responsabilitatea pentru compatibilitatea hard și soft o poartă JVM care respectiv diferă de la un tip de calculator la altul.
Pentru crearea unei mașini virtuale este necesar de ai aloca memorie și respectiv de ai acorda resursele necesare ale procesorului. În prezent datorită utilizării tot mai vaste a limbajului JAVA necesitatea de a utiliza JVM este tot mai frecventă, ceea ce necesită multe resurse ale procesorului principal. De exemplu, pentru procesoare pe 32 biți este necesar de a executa cel puțin 10 instrucțiuni a procesorului de bază pentru un Java bytecode. Astfel, rezolvînd problemele de compatibilitate, comunicare prin rețea, securitate a datelor, firma Sun (elaboratorul limbajului JAVA și a JVM) s-a ciocnit de problema cum de redus la minimum resursele necesare JVM.
Una din metodele de soluționare a acestei probleme este utilizarea unui procesor care ar îndeplini funcțiile JVM. Utilizarea unui astfel de procesor ar reduce utilizarea resurselor procesorului principal. Anume studierii problemei date este dedicată această lucrare. Și anume studiului principiului de funcționare a JVM realizate soft și elaborării structurii principiale de bază a procesorului care ar realiza funcțiile JVM, adică ar interpreta codul compilat al limbajului JAVA independent de procesorul de bază sau cu mici intervenții ale lui.
=== MUNCA ===
6. PROTECȚIA MUNCII
6.1. Introducere
Protecția muncii este un element component al procesului de producție și este destinată creării condițiilor de muncă favorabile și a protejării lucrătorilor de acțiunea factorilor de producție dăunători și periculoși.
Ridicarea eficienței muncii programatorilor, intensificarea ei, este legată de crearea condițiilor de muncă sănătoase și fără pericol.
Protecția muncii poate fi privită ca un sistem, ce creează în sfera productivă interacțiunea optimală, din punct de vedere al păstrării sănătății și capacității de muncă a oamenilor cu mijloacele tehnice și mediul înconjurător.
Protecția muncii se bazează pe realizările diferitor sfere de cunoștințe: igiena muncii, sanitaria de producere, fiziologia și psihologia muncii, estetica, sociologia și rezolvă probleme psihofiziologice, economice, tehnico-inginerești și sociale.
Condițiile de muncă fără pericol asigură creșterea capacității de muncă. De ele depind: productivitatea și calitatea muncii, eficiența folosirii resurselor de muncă.
Așadar, protecția muncii este un sistem de acte legislative, social-economice, organizatorice, tehnice și mijloace, ce asigură securitatea, păstrarea sănătății și capacității de muncă a lucrătorului în timpul muncii.
Lucrul studentului în calitate de programator și operator a calculatorului este legat de aflarea pe lunga durată a studentului la calculator.
Lucrul operatorului la calculator se caracterizează prin prezența mică a substanțelor și acțiunilor dăunătoare în mediul înconjurător, însă este legat de acțiunea factorilor psihofiziologici, de încordări mari a vederii, de aceea se înaintează cerințe mari față de iluminarea locului de muncă, de componența spectrală a iluminării și de ergonomia locului de muncă.
6.2. Analiza condițiilor de muncă
Pentru analiza condițiilor de muncă vom alcătui următoarea tabelă care reprezintă caracteristicile condițiilor sanitaro-igienice ale factorilor dăunători și periculoși.
Tabelul 6.1
Factorii de apreciere a condițiilor de muncă
Unde: n – ne periculos, p – periculos.
Analiza condițiilor de muncă a fost efectuată pentru sala de calculatoare. În tabelă se indica condițiile optimale microclimaterice, care sunt destinate creării condițiilor favorabile de muncă pentru personalul sălii de calculatoare.
In sala de calculatoare se practică iluminarea naturală laterală și iluminarea artificială generală.
După categoria pericolului de electrocutare, sala de calculatoare se referă la încăperile cu pericol ridicat, fiindcă e posibilă atingerea concomitentă a omului cu unirile la pământ a aparatajului electric.
In sala de calculatoare persistă zgomotul, generat de surse de zgomot de diferite tipuri: imprimantele, instalațiile de condiționare (aerodinamice), transformatoarele de tensiune etc. Datorită măsurilor luate (utilizarea barajelor acustice, planificarea rațională a încăperii) nivelul de zgomot la locul de muncă nu-l depășește pe cel admisibil.
Analiza condițiilor de muncă a demonstrat că în sala de calculatoare persistă toți factorii necesari pentru izbucnirea incendiului (materiale inflamabile, surse de aprindere etc.). De aceea în aceste săli sunt prevăzute mijloace primare de stingere a incendiului ca stingătoarele, sunt instalate semnalizatoare, care sunt destinate pentru transmiterea semnalului în cazul izbucnirii incendiului.
În rezultatul analizei condițiilor de muncă la calculator au fost scoși la iveală următorii factorii dăunători și periculoși: posibilitatea izbucnirii incendiului, încărcări psihofiziologice ș.a.m.d.
6.3. Cerințele ergonomice privitor locul de muncă
Ergonomia și estetica sunt bazele culturii de producere, îndreptate spre crearea unui mediu favorabil de lucru, care se obține prin organizarea corectă a lucrului, amenajarea locurilor de lucru, mediului de reorganizare estetică.
Pentru crearea condițiilor favorabile de muncă în laborator, e necesar de a ține cont de posibilitățile psihofizice a omului. Este știut, că de a aduce omul intr-o stare nervoasă este mult mai ușor, decât contrariul. Lucrătorul, aflându-se într-o stare de excitare nervoasă comite multe erori, lucrând la calculator.
Un mare rol îl joacă planificarea locului de lucru, care trebuie să corespundă cerințelor comodității, economiei timpului și a energiei, folosirea rațională a suprafeței de producere, respectarea regulilor protecției muncii. La locul de lucru a operatorului sunt utilizate: monitorul, tastatura, mouse-ul, echipament de transmitere a datelor (modem, telefon), imprimanta, scaner-ul, etc.
Instalarea mijloacelor tehnice și a fotoliului operatorului în zona de lucru trebuie să asigure accesul comod , posibilitatea de a ocupa și de a elibera rapid locul de muncă, de a exclude atingerea întâmplătoare a mijloacelor tehnice și de a asigura ținuta comodă pentru muncă . La organizarea locului de muncă e necesar să ținem cont de datele antropometrice ale omului. Monitorul e necesar de instalat pe masă sau pe un suport, în așa mod ca distanța să nu fie mai mare de 700 mm (optimal 450-500 mm). Monitorul după înălțime trebuie să fie instalat în așa mod, ca unghiul dintre normala centrului monitorului și linia orizontală a privirii să fie de 20 de grade.
La suprafața orizontală unghiului de observare a monitorului nu trebuie să depășească 60 de grade. Tastatura e necesar să fie instalată pe masă sau pe un suport, în așa fel ca înălțimea tastaturii față de podea să fie de 650-720 mm. În cazul dat în cabinetul de lucru, monitorul și tastatura sunt instalate pe o masă standard și se folosesc scaune cu înălțimea de 450 de mm, dar acestea sunt parametrii optimali pentru un operator cu înălțimea de 175 cm.
În timpul lucrului des apar așa situații când operatorul e nevoit să acționeze rapid. Pentru aceasta e necesar de a organiza un mediu rațional, care asigură protecția operatorului de la excitanții exteriori, care măresc oboseala și încordarea operatorului.
6.4. Calcularea iluminatului natural
Iluminarea suficientă a locurilor de muncă este o condiție principală a lucrului productiv și a stării normale a sănătății omului. Iluminarea locului de muncă poate fi naturală, artificială și combinată.
Iluminarea locurilor de muncă trebuie să corespundă următoarelor condiții:
– nivelul de iluminare a suprafeței de lucru trebuie să corespundă normelor igienice pentru lucrul de tipul dat;
– trebuie să fie asigurată omogenitatea și stabilitatea nivelului de iluminat în încăpere, lipsa contrastelor între iluminarea suprafeței de lucru și spațiului înconjurător;
– sursele de lumină și alte obiecte nu trebuie să genereze reflecții în câmpul de vedere;
– iluminatul artificial după spectrul său trebuie să se apropie de spectrul iluminatorului natural.
O mare importanță pentru asigurarea capacității înalte de muncă și a bunei dispoziții o are iluminarea optimă a locului de lucru. Iluminatorul poate fi natural (se folosește energia solară), artificial (cu ajutorul becurilor incandescente sau fluorescente) și suprapus.
Iluminatorul natural se datorează energiei solare. Regulamentul cere ca în încăperile de lucru să fie obligatoriu iluminatul natural care poate fi lateral (prin ferestre), de sus (prin felinare) și mixt. Iluminatorul natural poate fi ne uniform.
Calcularea iluminatorului natural constă în calcularea ariei totale a ferestrelor, felinarelor exprimată în m2 :
( 6.1 )
unde :
Sf – suprafața ferestrei la iluminatul lateral;
Sn – suprafața podelei în încăpere a 12 m2 ;
En – valoarea normală a calculării iluminatorului artificial (CIN);
En4 = En3*m*c, unde :
En3 – valoarea CIN; En3 = 2.0.
m – coeficientul zonei climatice; m = 0.9
c – coeficientul zonei solare; c = 0.95.
Ks – coeficientul suplimentar; Ks = 1.2
Nf – caracteristica de lumină a ferestrei la iluminatul natural; Nf = 7.5
Kcl – coeficientul care ține cont de întunecare a ferestrelor de alte clădiri;
Kcl = 1.
– coeficient complex de pătrundere a luminii, se determină după formula:
0 = 1*2*3*4*5, unde
1 – coeficient de trecere a luminii pentru materialele date; 1 = 0.8
2 – coeficient care ține cont de pierderi ale luminii la trecere prin ferestre;
2 = 0.75
3 – coeficient care ține cont de pierderi ale luminii în condiții de bază;
3 = 1.0
4 – coeficient care ține cont de pierderi ale luminii în intervalele de slăbire a luminii; 4 = 0.65
5 – coeficient care ține cont de pierderi de lumină în plasă de protecție a felinarelor: 5 = 0.5
0 = 0.8*0.1*0.75*0.65*0.9 = 0.04
r1 – coeficient care ține cont de majorarea CIN al iluminatorului natural, datorită luminii încăperii; r1 = 2.25
Rezultatul obținut este: Sf = 8.46 m2 , deci fereastra sau felinarele trebuie să aibă o suprafață nu mai mică de cea calculată și în cazul dat cel mai optim ar fi o suprafață cu aria de 9 m2 pentru a asigura iluminarea corespunzătoare a locului de muncă.
6.5. Securitatea antiincendiară în sălile de calcul
Pentru a analiza nivelul securității incendiare a locurilor de muncă, a zonelor de producție, a sălilor de calcul se folosește următoarea clasificare:
1. Clasificarea materialelor de construcție și construcțiilor după nivelul de inflamabilitate:
– ne inflamabile;
– greu inflamabile;
– inflamabile;
2. Clasificarea construcțiilor după nivelul rezistență la incendiu (limita nivelului de rezistența la incendiu – timpul în ore din momentul începerii incendiului până la momentul apariției crăpăturilor).
3. Clasificarea încăperilor după RCIE ("Regulile de Construcție a Instalațiilor Electrice"):
– cu pericol de explozie;
– cu pericol de inflamare.
Criteriile de apreciere:
– Conținutul de substanțe inflamabile;
– Regimul termic de prelucrare.
4. Clasificarea proceselor de producție după pericolul incendiar:
– cu pericol de explozie;
– cu pericol de explozie și inflamare;
– cu pericol de inflamare;
– fără pericol de inflamare;
Conform primei clasificări (clasificarea materialelor de construcție și construcțiilor după nivelul de inflamabilitate) sala de calcul este ne inflamabilă deoarece sînt prevăzute multe măsuri de prevenire a incendiului cum ar fi: sisteme de semnalizare, podele din metal, mese metalice, pereții în sala de calcul se acoperă cu substanțe ne arzătoare.
După clasificarea a doua (clasificarea construcțiilor după nivelul rezistență la incendiu), de obicei sălile de calcul se află în clădiri construite din beton armat. Care are o rezistență la incendiu mare – pereții în sala de calcul se acoperă cu substanțe ne arzătoare.
După clasificarea a treia (după Regulile de Construcție a Instalațiilor Electrice), luând în considerație conținutul mic de substanțe inflamabile și regimul termic de prelucrare, sălile de calcul pot fi caracterizate – cu pericol mic de inflamare.
Sălile de calcul după pericolul incendiar a proceselor de producție se referă la categoria celor cu pericol de inflamare ceea ce se explică prin faptul, că în încăpere se găsesc substanțe inflamabile: de obicei, aceste săli sînt echipate cu utilaj care conține masă plastică, care totuși arde. Trebuie însă de menționat, în ultimul timp masa plastică utilizată la fabricarea tehnicii de calcul are o astfel de componență chimică, care nu arde sau care se autostinge după primele secunde de ardere. În sala de calcul de obicei lipsesc așa atribute cum ar fi : covoare, obiecte din lemn, dulapuri cu cărți, etc. Reiese că, cu toate că pericolul de inflamare există, el este foarte mic.
Securitatea antiincendiară poate fi asigurată prin măsuri de profilaxie antiincendiară și prin respectarea regulilor de prevenire a incendiului.
Copyright Notice
© Licențiada.org respectă drepturile de proprietate intelectuală și așteaptă ca toți utilizatorii să facă același lucru. Dacă consideri că un conținut de pe site încalcă drepturile tale de autor, te rugăm să trimiți o notificare DMCA.
Acest articol: Procesor Java în Baza Arhitecturii Risc (ID: 148794)
Dacă considerați că acest conținut vă încalcă drepturile de autor, vă rugăm să depuneți o cerere pe pagina noastră Copyright Takedown.
