Cum funcționează un procesor? Legătura strânsă cu memoria.

Limbaj de asamblare

Încep cu o comparație empirică…dacă am încerca să asociem părți ale corpului uman cu componentele unui calculator, oare ce-am obține? Simplificat:

  • creierul ar fi probabil procesorul și memoria
  • gura și corzile vocale…placa de rețea, placa de sunet
  • urechile, microfonul (ambele sunt periferice de intrare)
  • mâinile și picioarele, poate periferice clasice(mouse, tastatură etc.)

Dacă continuăm alegoria și mergem cu presupunerea rezonabilă că un creier e poate cea mai utilă parte la un om,  după logica de mai sus, e probabil ca memoria și unitatea de procesare a unui calculator să fie componentele sale cheie – surprinzător, avem dreptate.

Astfel, în acest articol, voi încerca să aduc puțină lumină asupra ideilor ce stau în spatele unităților de procesare, cum au apărut, direcția de dezvoltare ș.a. Și nu va fi locul unde vom discuta despre procesoare Intel sau AMD. Sunt enorm de complicate, dar măcar vom atinge principiile de funcționare.

 

1. Privire generală asupra abstractizărilor

Să începem a gândi low – level. Pentru a ne face o idee asupra noțiunii de low-level să ne gândim la Notepad. Pare simplu și super basic, dar pentru noi el se va afla în ceruri(ca nivel de abstractizare). Aceste abstractizări sunt foarte utile, tocmai fiindcă surprind o generalitate grozavă. Oare ce se află sub notepad? Cu siguranță se află sistemul de operare – o altă abstractizare pe care utilizatorul o resimte sub forma unor apeluri sistem, un sistem de fișiere, toate animate de niște procese ce merg în tandem creându-ți impresia de simplitate în acel ecran plin cu pictograme…Interesant, ținând cont că pentru a funcționa corect un  S.O. modern are minim 80-100 de procese ce merg aparent simultan. Ne întrebăm atunci ce e sub sistemul de operare. La acest nivel noțiunea de interfață cu utilizatorul sau cod lizibil nu mai are sens, fiindcă am ieșit din universul software. Suntem la nivelul componentelor, iar aici doar arhitectura hardware ne mai scoate din impas. Chiar și la acest nivel, am putea să reducem totul la porți logice și electronică, iar toate acestea la componentele fundamentale…tranzistori, condensatori, rezistori și din când în când bobine…și mai jos e doar ELECTRONUL, cu mărimile ce ni-s dragi(I și V), introduse de dragul explicării sale. Tensiunea din urmă(V) e importantă fiindcă dacă-i mai multă sau mai puțină vom zice că-i fie 0(nimic), fie ceva(1) și iacă-tă intuim că sistemul ăsta cu 0 și 1 o fi destul de bun.

În articol, vom fi la nivelul arhitecturii hardware. Imaginile cu diagrame sunt preluate din cursul Arhitectura Calculatoarelor,  UT Cluj, facultatea de Automatică și Calculatoare.

 

2. Primul procesor

Înainte de procesor vorbim de codul mașină, fiindcă vreau să pun totul în legătură cu ceea ce mai știm. Voi presupune că ați mai scris C și ați mai compilat din Bash sau CMD. În fine, și dacă nu, faci ceva de genul:

-o vine de la obiect, fiindcă asta se obține la finalul procesului de compilare. Acesta e de fapt codul mașină(ce a trecut prin compilator și asamblor). Când scriem ./hello un Linker atașează librăriile suplimentare(alt cod mașină din altă parte) și în final, executabilul rulează totul ca pe un întreb. Programul în execuție nu e altceva decât secvențe de biți, ce sunt stocate în memorie sub formă de instrucțiuni.

Ca o mică paranteză, prin compilare înțelegem compilator + asamblor, deși e incomplet spus, numai că s-a încetățenit totul. De curiozitate, pentru a vedea rezultatul compilării, scrieți:

Acel my_asm_output.s va arăta și va conține cod de asamblare.

Limbaj de asamblare

 

Despre regiștri

În imaginea de mai sus puteți vedea niște instrucțiuni de tipul movl și addl, ce mută, respectiv adună informația. Mai sunt câteva lucruri de tipul eax, edx, rsi ș.a. Aceștia sunt regiștri. Nu voi intra în detalii, căci e o întreagă poveste în spatele lor, iar arhitectura Intel 8080 a devenit standardul de facto în explicarea și înțelegerea rolului lor. În esență sunt memorii foarte rapide, cele mai rapide de fapt, din interiorul procesorului. Ele sunt buffere de 32 sau 64 de biți, strâns legate de arhitectura procesorului.

Majoritatea operațiilor elementare pe care un calculator le face, se realizează cu acești regiștri. Simpla adunare a două numere se poate face punând cele două numere în 2 regiștri. Regiștri sunt conectați la unitatea aritmetico-logică, ce va face adunarea propriu zisă, iar rezultatul poate fi pus în alt registru, de exemplu eax(acumulatorul).

Suntem acum, suficient de dotați să vedem poate cel mai simplu procesor.

 

2. Hello World-ul procesoarelor

Să schematizăm exemplul de mai sus și să presupunem că scriem o instrucțiune în limbaj de asamblare. Poate ceva de genul:

Instrucțiunea de mai sus e scrisă în RTL(Register Transfer Language, un alt standard).

  • $rd este registrul destinație
  • $rs registrul sursă
  • $rt registrul de transfer

Hello World-ul procesoarelor

În imaginea de mai sus, folosim pentru simplitate instrucțiuni pe 32 de biți. Opcode(Operation Code) e o secvență de biți specifică unei anumite clase de instrucțiuni, function e chiar add în cazul nostru, iar sa reprezintă shift arithmetic, ce e folosit în operațiile de shiftare(de ex. înmulțirea cu 2 se face shiftând la stânga cu o poziție). Numerele ce sunt deasupra ne arată câți biți alocăm pentru fiecare lucrușor – adunând biți obținem o instrucțiune pe 32 de biți.

Unde e ținută acea instrucțiune? În memorie, desigur, mai exact în segmentul de cod al acesteia. Și-atunci ajungem de unde am plecat. Instrucțiunea de mai sus e de fapt o “linie” de cod obiect ce obținem la finalul compilării…cool.

3. Aducerea instrucțiunilor

Am văzut mai sus cum arată o instrucțiune. Un program compilat e format din mii de instrucțiuni de acest gen(programele mici, ca de obicei poate-s milioane), toate încărcate în memorie și gata să fie rulate într-o milisecundă.

Fun fact!! Această viteză e dictată de timpul necesar schimbării stării logice la nivelul porților logice, adică a tranzistorilor – dacă tranzistorii sunt mici e vorba de picosecunde.

Memoria e organizată în octeți, deci 4 octeți ne fac o instrucțiune. Memoria este precum o stivă, are o bază, dar cel mai important, poate fi adresată. Asta înseamnă că dacă la intrare introducem adresa 8, ieșirea va conține datele de la acea adresă. Tocmai acest comportament ne permitem să rulăm instrucțiuni consecutive. Vom schimba puțin exemplul de mai sus și obținem:

Aducerea Instructiunilor

PC este un registru și poartă numele de Program Counter. El e necesar, fiindcă astfel nu știm ultima adresă la care am rămas. În timp ce memoria oferă la ieșire instrucțiuni pe 32 de biți, ca cele de mai sus, un sumator adună 4 octeți la contorul de program(PC) pentru a merge la următoarea instrucțiune.

 

Instrucțiunea de ramificare(branch)

Trebuie să înțelegem că orice comandă pe care procesorul o înțelege, undeva, în adâncul său, se regăsește în circuite logice interconectate(ce-s lucruri palpabile…fire microscopice, tranzistori ș.a.). În acest articol prezint numai câteva instrucțiuni fundamentale, că oricum se complică puțin lucrurile și nu îmi doresc a face din el un curs de facultate.

Revenim la instrucțiunea de ramificare…if-ul acestei lumi. Ce înseamnă el de fapt? Dacă o condiție e îndeplinită, sari atunci la o anumită instrucțiune, iar dacă nu continuăm la următoarea. În limbaj RTL:

O întrebare faină ce mi-aduc aminte că ne-a pus-o proful nostru a fost…ce-i cu <<2? <<2 înseamnă a shifta la stânga cu două poziții, deci o înmulțire cu 4. Răspunsul nu-l spun, dar vă dau un indiciu…are legătură cu memoria și modul ei de organizare, datorită instrucțiunilor.

În codul de mai sus folosim doar 2 regiștri, rs și rt. Instrucțiunea va fi de forma:

Instructiune de tip I

Prin adresă, înțelegem o adresă relativă, iar prin imediat, un număr. În instrucțiunea de ramificare, acel imediat e folosit pe post de offset în instrucțiuni.

Instrucțiune de ramificare

În instrucțiunea de ramificare, atunci când condiția e îndeplinită, la contorul de program adunăm acel offset de care vorbeam. Contorul de program e pe 32 de biți, dar imediatul nostru, din lipsă de loc este pe 16 biți. Tocmai de aceea, imediatul trebuie extins cu semn(poate vrem să adunăm sau să scădem, depinde), ca să forțeze 16 biți de 0 în față(sau de 1, în complement față de 2, inversând cei 16 biți și adunând 1), pentru a-l face un imediat pe 32 de biți. Toate acestea, pentru a-l putea aduna cu contorul de program. Procesul de adunare se va face desigur prin unități aritmetico-logice(ALU).

 

Instrucțiunile load și store word (de scriere în regiștri, respectiv memorie)

Două instrucțiuni sunt foarte fundamentale pentru un procesor:

  • să știe cum să stocheze în memorie date
  • să încarce date din memorie în regiștri săi

Schematizat, în limbaj RTL:

Load and Store word

Dacă mergeți înapoi la exemplul cu instrucțiunea de adunare, acolo foloseam rs, rt, rd și function. Apoi, am ajuns la instrucțiunea branch și am văzut că aveam nevoie de rs și rt, iar restul biților îi puteam aloca pentru o adresă sau un imediat. Totuși, ambele instrucțiuni ajung să folosească ALU, așa că trebuie un mecanism prin care să permitem doar unei variante să fie activă. Soluția este să folosim un multiplexor, o chestie care din două intrări permite ieșirea la una singura, în funcție de valorile unui semnal de control(în ex. 0 pentru prima, 1 pentru a doua).

 

4. Procesorul asamblat

Am discutat câteva instrucțiuni esențiale și am schematizat componentele. Rămâne să le punem cap la cap. Fără a prezenta și partea de control, în acest moment, avem ceva de genul:

Procesor simplificat

În schema de mai sus, putem vedea o componentă nouă, ALU Control, împreună cu semnalul ei de activare, ALUOp. ALU control este un soi de multiplexor, ce are comenzi la intrări pentru ALU. Cu ALUOp selectăm care comandă vrem să o dăm la ALU(adunare, scădere, înmulțire, shiftare logică etc.).

Față de schemele anterioare, aceasta conține și semnale de activare pentru fiecare componentă. Ele sunt gestionate de către o unitate separată din procesor, numită Unitatea de Control. Modelul de procesor prezentat mai sus poartă numele de MIPS cu ciclu unic și este printre pionierii procesoarelor moderne.

Ștefănescu Marian

Pasionat de științe exacte, drumeții.

2 Comments

  1. Anonymous   •  

    Te felicit Mihai pentru cunostintele tale in domeniul IT.Te rog sa-mi spui si mie cati biti are un procesor bun .Care este maximul?

    • Stefanescu Marian   •     Author

      Salutare! Mulțumesc în primul rând pentru apreciere…cu mențiunea că mă cheamă Marian ((-:

      Cât despre numărul de biți al unui procesor bun, întrebarea este cam ambiguă – metricile de performanță ale unui procesor sunt de pe obicei IPC, Dhrystone, Whetstone(mai îs multe altele). Nu prea vedem aceste metrici în specificații atunci când cumpărăm deși poate ar fi mai util, dar vedem frecvența care e o măsură grosieră a numărului de instrucțiuni dintr-o secundă.

      Acu ceva ani totuși, frecvența a ajuns la un punct critic de unde nu mai poate urca…așa că lumea a lăsat-o în pace și a pus pe chip mai multe nuclee de procesare(alte ALU’s, IF si multe altele) => numărul de cores e foarte important pentru performanță. De ex., un procesor Quad-Core, 1.7GHz e mult mai bun ca unul cu un singur core la 3GHz, în majoritatea situațiilor.

      Să revenim la biții din procesor…((-: …Nivele de memorie cache memorează biți, deci da…cu cât sunt mai multe, cu atât procesorul e mai performant. Procesoarele actuale vin cu 3 nivele de Cache dupa registri, iar cele mai noi au si al patrulea nivel…care-i un eDDRAM dacă nu mă înșel ce poate fi destul de mare (128 de MB si e de obicei accesabil și de către procesor și de către GPU).

      În final, poate te referi la lungimea instrucțiunilor; procesoarele moderne de acasă sunt pe 64 de biți lungime a instrucțiunilor, iar pe unele servere se pretează să folosim 128.

Lasa un comentariu