Cum să utilizați cuvântul cheie volatil în c, avr, programare

Sunteți familiarizați cu următoarele situații în codul de sistem încorporat C sau C ++?

• Codul funcționează bine până la rezolvarea optimizărilor pentru compilator.






• Codul funcționează bine - până când întreruperile sunt activate.
• Drivere hardware de lucru.
• sarcinile RTOS funcționează bine în mod izolat - până când se generează o altă sarcină.

Dacă răspunsul este "da" la oricare dintre aceste întrebări, atunci cel mai probabil nu ați utilizat cuvântul cheie volatil. Dar nu ești singurul. Puțini programatori înțeleg cum să folosească volatile corect. Din păcate, multe cărți pe limbajul de programare C nu acordă prea multă atenție calificativului volatil.

Cuvântul cheie volatil al lui C este un calificativ care este adăugat la declarația de variabilă (un calificativ de tip variabil, similar cu calificatorul const). Acesta îi spune compilatorului că valoarea variabilei poate fi modificată în orice moment - fără niciun cod vizibil de cod care înconjoară variabila. Consecințele acestui lucru sunt destul de grave. Cu toate acestea, înainte de a face acest lucru, să analizăm sintaxa.

[Sintaxa cuvintelor cheie volatile]

Pentru a declara o variabilă ca volatilă, adăugați cuvântul cheie volatil înainte sau după tipul de date în definiția variabilei. De exemplu, următoarele sunt definiții echivalente ale variabilei foo ca un număr întreg cu proprietatea volatilă:

Același lucru este valabil și pentru indicatorii pentru variabilele volatile, în special acelea care sunt asociate cu registrele I / O mapate în memorie. Ambele declarații definesc în mod egal pReg ca un pointer la un întreg nesemnat pe 8 biți (octet) cu proprietatea volatilă:

Indicatorii volatili pentru datele non-volatile sunt foarte rare, dar merită menționat următorul sintax:

Și tocmai pentru a fi complet, dacă dintr-o dată aveți de fapt nevoie să obțineți un pointer volatil la o variabilă volatilă, atunci scrieți-l astfel:

În cele din urmă, dacă ați aplicat volatile unui struct sau unei uniuni, tot conținutul (toate câmpurile) structurii / uniunii va obține proprietățile volatile. Dacă nu aveți nevoie de acest comportament, ar trebui să atașați calificativul volatil la câmpurile individuale (membri) ale structurii / uniunii.

[Utilizarea corectă a cuvântului volatil]

Variabila trebuie declarată volatilă ori de câte ori este modificată în mod neașteptat. În practică, pot exista doar 3 tipuri de astfel de variabile:

1. Registrele de memorie ale procesorului sau ale dispozitivului său periferic (registre periferice mapate în memorie).
2. Variabile globale, modificate în codul rutinei de întrerupere a serviciului (ISR).
3. Variabilele globale, care sunt abordate de sarcini diferite într-o aplicație multi-threaded (de obicei, se referă la diferite fluxuri RTOS).

Să analizăm mai detaliat fiecare dintre aceste trei cazuri.

De obicei, un astfel de cod va duce la o eroare, de îndată ce veți activa optimizarea compilatorului, când cu optimizarea inclusă va fi generat un cod ca acesta:

Acum, codul de asamblare va fi astfel:







Se realizează comportamentul dorit al programului.

Problemele insidioase tind să apară cu registrele care au proprietăți speciale. De exemplu, unele dispozitive periferice conțin registre care sunt resetate la zero prin simpla citire a acestora. Un număr suplimentar de lecturi (sau un număr mai mic de lecturi) decât se aștepta, poate duce la rezultate complet imprevizibile.

Întrerupeți manipulatorii. Întreruperea rutinelor de servicii (ISR) determină deseori variabilele care sunt bifate în codul principal (care lucrează în afara întreruperii) sau în codul unei alte întreruperi. De exemplu, ISR-ul portului serial poate verifica fiecare simbol primit pentru apariția simbolului ETX (probabil indicând sfârșitul mesajului). Dacă se întâlnește un simbol ETX, ISR poate seta un steag global. O implementare incorectă poate fi următoarea:

Când este dezactivată optimizarea compilatorului (acest lucru este, de obicei, cazul când se depanează), acest cod poate funcționa. Totuși, aproape orice optimizator demn de încredere "rupe" acest cod. Problema este că compilatorul nu are nicio informație că variabila etx_rcvd poate fi modificată în ISR. Atâta timp cât compilatorul gândește așa, pentru el expresia! Ext_rcvd este întotdeauna adevărat, astfel încât bucla nu se va termina niciodată. Prin urmare, tot codul după corpul buclă de către optimizator poate fi pur și simplu șters de optimizator. Dacă aveți noroc, compilatorul va raporta acest lucru. Dacă nu sunteți norocos (sau nu ați învățat încă cum să acordați atenție mesajelor compilatorului), codul va fi rupt. Firește, defecțiunea va fi imediat încredințată "optimizatorului rău".

Soluția în această situație este de a declara variabila etx_rcvd ca fiind volatilă. Apoi toate problemele dvs. (sau cel puțin problemele asociate cu acest caz) vor dispărea.

Aplicații cu mai multe fire. În ciuda prezenței cozilor, a țevilor și a altor mecanisme de schimbare într-un mediu multietajat (RTOS), este încă o practică obișnuită să se facă schimb de informații între două fire utilizând memoria partajată (o variabilă globală). Chiar și atunci când adăugați un programator preemptiv la codul dvs., compilatorul încă nu are nicio ipoteză că switch-ul de context va funcționa și că se poate întâmpla. În acest caz, atunci când o altă sarcină modifică variabila globală globală, același lucru se poate întâmpla ca și în cazul procesorului de întrerupere, după cum sa discutat mai sus. Ca și în exemplul anterior, variabila partajată trebuie declarată ca fiind volatilă. De exemplu, aici pot apărea probleme în cod atunci când accesați variabila comună cntr din diferite sarcini:

Acest cod, probabil, nu va funcționa atunci când optimizarea este activată. Declarația ca volatilă va fi modul corect de a rezolva problema.

Accesul la registrele mapate de memorie (registru mapat în memorie sau MMR) se face folosind indicii. Iată un alt exemplu în care au existat probleme cu lipsa cuvântului cheie pentru codul C pentru microcontrolerul PIC. Codul trebuie să facă o permutare a conținutului între două registre periferice:

Unele compilatoare vă permit să declarați implicit toate variabilele ca fiind volatile. Nu dăruiți această ispită, pentru că în esență vă dezavantajează să gândiți (precum și dezactivarea optimizării). Acest lucru conduce, de asemenea, la un cod mai puțin optimizat.

De asemenea, refuzați ispita să dezactivați întotdeauna optimizatorul. Optimizatorii moderni sunt atât de buni încât este imposibil să ne amintim că optimizarea duce la erori. Mai degrabă, includerea optimizării vă permite să identificați toate erorile potențiale ale codului pe care altfel nu le-ați fi observat.

Dacă vi s-au dat niște corecții de remediere pentru fixare, executați prostii operația grep din textele codului sursă în căutarea cuvântului volatil. Dacă ieșirea de grep este goală, atunci exemplele de aici vor oferi un bun punct de pornire pentru depanare.

[Cum rămâne cu structurile? ]

Cum să folosiți volatile cu structuri și pointeri în structuri? Ar trebui să definim un indicator pentru o structură volatilă sau fiecare element de structură individuală să fie definit ca volatil sau non-volatil? Poate că pentru unele registre periferice nu trebuie să utilizați volatile, deoarece valoarea lor nu se schimbă niciodată, dar starea, masca de întrerupere, tamponul de recepție etc. pot să se schimbe și, prin urmare, ar trebui să fie definite ca volatile?

Unde ar trebui să plasez cuvântul cheie volatil în aceste definiții pentru a folosi corect structura cu indicatorul meu?

În acest exemplu, cea mai bună practică este de a defini următoarea structură cu registrele I / O MMR:







Articole similare

Trimiteți-le prietenilor: