ABONAMENTE VIDEO REDACȚIA
RO
EN
NOU
Numărul 150
Numărul 149 Numărul 148 Numărul 147 Numărul 146 Numărul 145 Numărul 144 Numărul 143 Numărul 142 Numărul 141 Numărul 140 Numărul 139 Numărul 138 Numărul 137 Numărul 136 Numărul 135 Numărul 134 Numărul 133 Numărul 132 Numărul 131 Numărul 130 Numărul 129 Numărul 128 Numărul 127 Numărul 126 Numărul 125 Numărul 124 Numărul 123 Numărul 122 Numărul 121 Numărul 120 Numărul 119 Numărul 118 Numărul 117 Numărul 116 Numărul 115 Numărul 114 Numărul 113 Numărul 112 Numărul 111 Numărul 110 Numărul 109 Numărul 108 Numărul 107 Numărul 106 Numărul 105 Numărul 104 Numărul 103 Numărul 102 Numărul 101 Numărul 100 Numărul 99 Numărul 98 Numărul 97 Numărul 96 Numărul 95 Numărul 94 Numărul 93 Numărul 92 Numărul 91 Numărul 90 Numărul 89 Numărul 88 Numărul 87 Numărul 86 Numărul 85 Numărul 84 Numărul 83 Numărul 82 Numărul 81 Numărul 80 Numărul 79 Numărul 78 Numărul 77 Numărul 76 Numărul 75 Numărul 74 Numărul 73 Numărul 72 Numărul 71 Numărul 70 Numărul 69 Numărul 68 Numărul 67 Numărul 66 Numărul 65 Numărul 64 Numărul 63 Numărul 62 Numărul 61 Numărul 60 Numărul 59 Numărul 58 Numărul 57 Numărul 56 Numărul 55 Numărul 54 Numărul 53 Numărul 52 Numărul 51 Numărul 50 Numărul 49 Numărul 48 Numărul 47 Numărul 46 Numărul 45 Numărul 44 Numărul 43 Numărul 42 Numărul 41 Numărul 40 Numărul 39 Numărul 38 Numărul 37 Numărul 36 Numărul 35 Numărul 34 Numărul 33 Numărul 32 Numărul 31 Numărul 30 Numărul 29 Numărul 28 Numărul 27 Numărul 26 Numărul 25 Numărul 24 Numărul 23 Numărul 22 Numărul 21 Numărul 20 Numărul 19 Numărul 18 Numărul 17 Numărul 16 Numărul 15 Numărul 14 Numărul 13 Numărul 12 Numărul 11 Numărul 10 Numărul 9 Numărul 8 Numărul 7 Numărul 6 Numărul 5 Numărul 4 Numărul 3 Numărul 2 Numărul 1
×
▼ LISTĂ EDIȚII ▼
Numărul 62
Abonament PDF

Rutina software citește-modifică-scrie în sisteme multicore

Flaviu Nistor
Hardware Development Engineer @ Continental Sibiu



PROGRAMARE

În cartea intitulată "Expert C Programming Deep Secrets", Peter Van Der Linde descrie o serie de greșeli și probleme cauzate de folosirea limbajului de programare ANSI C fără o înțelegere deplină a acestuia. Autorul descrie felul în care au fost descoperite diverse probleme și rezolvarea lor, atenția lui orientându-se către probleme generate de folosirea greșită a compilatorului sau a unor erori de sintaxă ANSI C.

În acest articol, voi descrie un astfel de scenariu, dar axându-mă asupra părții hardware a arhitecturii unui microcontroler pe care are loc execuția codului sursă. Problemele care pot să apară sunt cauzate nu de sintaxa folosită pentru a scrie rutina software, ci de modul în care secvența software folosește resursa hardware în sistemele multicore.

Astfel de probleme pot fi distructive pentru felul în care are loc execuția codului și sunt extrem de greu de depistat/corectat, mai ales dacă înțelegerea asupra arhitecturii hardware nu este deplină. În aceste cazuri particulare conținutul acestui articol poate fi considerat drept o sumă de "bune practici".

În programarea embedded, metoda software citește-modifică-scrie este o practică uzuală. În procesul de accesare a unui registru la nivel de bit, pentru a seta sau șterge un anumit bit, orice programator se va gândi în primul rând la varianta software prin care inițial va citi registrul, va aplica o mască, iar la final va rescrie registrul cu nouă valoare.

O persoană care folosește limbajul de programare C va folosi cel mai probabil una din cele doua linii de cod pentru a seta sau șterge un bit specific dintr-un registru.

Registru |= 0x0002 //seteaza bitul 1
Registru &= 0xFFFC //sterge bitul 0 și 1

Chiar dacă în sintaxa C avem o linie de cod, după compilare vom obține o serie de instrucțiuni ASM care, prin execuția lor vor seta sau șterge bitul dorit.

Fără a intra în detalii de limbaj ASM, ne putem imagina cum ar arăta într-un mod abstract următoarea linie de cod C.

Registru |= 0x0002 //setează bitul 1

Aceasta ar însemna:

  1. Citește valoarea Registru (într-un registru temporar de lucru al core-ului, de ex. R0).
  2. Aplică masca folosită pentru setarea bitului unu, valorii salvate în R0.
  3. Scrie noua valoare conținută de R0 în registrul Registru.

Aceștia sunt pașii pentru a realiza rutina software de citire-modificare-scriere. Acest mecanism software nu trebuie confundat cu mecanismul hardware de citire-modificare-scriere care se realizează automat în sistemele care au implementat ECC și în care o scriere se face cu o dimensiune mai mică decât lățimea busului de date. Aceasta este un alt subiect care însă nu intră în scopul acestui articol.

Într-un sistem cu un singur core nu sunt multe lucruri de adăugat despre această rutină software folosită pentru setarea sau ștergerea unui anumit bit, parte a unui registru. Mecanismul de citire-modificare-scriere funcționează fără să creeze probleme, după cum este de așteptat.

Vom discuta în continuare despre același mecanism software de citire-modificare-scriere folosit într-un sistem multi core în care se execută rutine de cod în paralel pe două sau mai multe core-uri care folosesc aceleași resurse (periferice).

Pentru a prezenta lucrurile într-un mod cât mai clar voi descrie un scenariu real. Modulul DMA este implementat uzual în arhitectura unui microcontroler, el fiind utilizat pentru transferuri de date cu scopul de a înlătura aceasta sarcină "din grija" procesorului. Din acest motiv este foarte probabil ca modulul DMA să fie folosit de toate core-urile din cadrul arhitecturii. Modulul are mai multe canale care pot fi valorificate simultan pentru a face transferuri de date, fiecare canal având câte un bit de activare. Biții de activare sunt parte a unui registru care e alcătuit din biții aferenți tuturor canalelor. Pentru a activa sau opri un singur canal este nevoie de setarea sau ștergerea unui singur bit.

Din punct de vedere software, rutinele care se execută în paralel pe două sau mai multe core-uri sunt independente și pot fi scrise de persoane diferite, având cerințe diferite.

În continuare, vom vedea ce se poate întâmpla dacă folosim rutina clasică software citire-modificare-scriere. Presupunem că unul din core-uri folosește canalul DMA 4 [CH4], iar cel de-al doilea core canalul DMA 6 [CH6].

Dacă ambele core-uri doresc să activeze canalele DMA aferente, codul va arăta ca în tabelul următor:

Numărul core-ului Core-ul unu Core-ul doi
Ce se execută? DMAreg = 0x0010 DMAreg = 0x0040


Fiecare core va executa propriul cod. Astfel, în ochii fiecărui programator codul scris de el trebuie să funcționeze corect. Dacă facem abstracție de la faptul că vorbim despre un sistem multicore care împarte aceleași resurse, folosirea rutinei software citește-modifica-scrie nu poate crea probleme. În realitate, nu putem ignora faptul că execuția codului are loc într-un sistem multicore.

Dacă analizăm care poate fi problema, printr-o simplă abstractizare a instrucțiunilor C (într-o rutină generică) ne vom putea imagina următoarea secvență care rulează pe cele două core-uri (nota: fiecare core are propriul registru de lucru R0):

Ce se execută? Core-ul 1 Core-ul 2
PASUL 1 Citește DMAreg în R0 Citește din zona perifericelor
PASUL 2 Aplică masca valorii din R0 Verifică un bit oarecare
PASUL 3 Scrie valoarea din R0 în DMAreg (activare CH4) Fă un salt
PASUL 4 Citește din RAM Citește DMAreg în R0
PASUL 5 Adună x la valoarea citită Aplică masca valorii din R0
PASUL 6 Scrie în RAM noua valoare Scrie valoarea din R0 în DMAreg (activare CH6)


Exemplul de mai sus funcționează, dar trebuie avut în vedere faptul că execuția pe cele două core-uri este asincronă și la un moment dat putem avea următorul scenariu:

Ce se execută? Core-ul 1 Core-ul 2 Valoare DMAreg
PASUL 1 Citește DMAreg în R0 (ex: 0x8000) Make a branch 0x8000
PASUL 2 Aplică masca valorii din R0 Citește DMAreg în R0 (!!! Se va citi vechea valoare 0x8000) 0x8000
PASUL 3 Scrie valoarea din R0 în DMAreg Aplică masca valorii din R0 0x8010
(activare CH4)
PASUL 4 Citește din RAM Scrie valoarea din R0 în DMAreg 0x8040
(activare CH6)


Acum problema este evidentă. Primul core va activa canalul CH4 al DMA în PASUL 3, dar imediat, în PASUL 4, al doilea core va activa CH6 și va dezactiva CH4. Ambele core-uri aplică masca de activare aceleiași valori inițiale ale registrul DMAreg, adică valoarea 0x8000. La finalul PASULUI 4, valoarea dorită a registrului DMAreg este 0x8050, dar în realitate este 0x8040.

Analizate separat cele două rutine software nu au erori din punct de vedere al limbajului de programare C sau al firului propriu de execuție, dar din cauza specificului arhitecturii pe care rulează, codul induce un bug greu de detectat.

Soluțiile, după cum vă puteți imagina există, unele din ele fiind hardware. Le voi descrie în continuare pe două dintre ele.

O prima soluție este folosirea semafoarelor hardware când scriem cod într-o situație similară cu cea descrisă. În acest fel, unul din core-uri va aștepta până când semafoarele vor indica faptul că registrul a cărei valoare trebuie modificată este liber (nu este folosit de un alt master). Semafoarele hardware sunt o implementare comună (aș putea spune standard) în sistemele multicore.

A doua soluție este una specifică unui anumit tip de implementare a modulului periferic. Din păcate, nu toate perifericele vor oferi acest mecanism. Unele implementări ale perifericelor oferă registre pentru setarea sau ștergerea unui anumit bit din cadrul altor registre într-o operație atomică. Aceasta înseamnă că prin intermediul unui registru SETEAZĂ/ȘTERGE putem scrie sau șterge biți într-un alt registru (de exemplu, un registru care conține biții de activare al unui canal DMA, a unei anumite surse de întrerupere sau biți de stare) fără să afectăm alți biți, și cel mai important, fără nevoia de a citi acel registru.

Din punct de vedere al execuției codului, aceasta înseamnă:

Core-ul 1 Core-ul 2 Valoarea DMAreg
Pasul 1 Scrie registrul SET Orice instrucțiune 0x8010
Pasul 2 Orice instrucțiune Scrie registrul SET 0x8050


O scriere pe același tact al acestui registru de către ambele core-uri nu este posibilă, deoarece este o resursă unică. Bazat pe prioritatea atribuită celor două core-uri, în urma unei arbitrări, doar unul din cele două core-uri va face scrierea în acel tact.

Cum cea de-a doua soluție nu este disponibilă în toate tipurile de implementări, ca regulă generală, în sistemele multicore ar trebui folosite semafoarele pentru a șterge sau scrie biți în cadrul unor registre folosite în rutine software care rulează pe mai multe core-uri. Este important de adăugat că în sistemele multicore, semafoarele pot fi emulate și software, în cazul în care implementarea hardware lipsește.

Registrele care sunt folosite de mai multe core-uri și pot fi scrise utilizând una din soluțiile prezentate anterior, fiind definite ca atare pe baza unei arhitecturi software pentru fiecare proiect în parte. Dacă un periferic este folosit de codul executat pe mai mult de un core (ex: DMA, SPI), este o bună practică pentru că modificarea conținutului registrelor să fie făcută folosind una din soluțiile hardware disponibile.

NUMĂRUL 149 - Development with AI

Sponsori

  • Accenture
  • BT Code Crafters
  • Accesa
  • Bosch
  • Betfair
  • MHP
  • BoatyardX
  • .msg systems
  • P3 group
  • Ing Hubs
  • Cognizant Softvision
  • Colors in projects