Gestionarea memoriei¶
Spre deosebire de limbaje de programare precum C/C++, MicroPython ascunde detaliile gestionarii memoriei fata de dezvoltator, prin suport pentru gestionarea automata a memoriei. Gestionarea automata a memoriei este o tehnica folosita de sistemele de operare sau de aplicatii pentru a administra automat alocarea si dezalocarea memoriei. Aceasta elimina probleme precum uitarea de a elibera memoria alocata unui obiect. Gestionarea automata a memoriei evita, de asemenea, problema critica a folosirii memoriei care a fost deja eliberata. Gestionarea automata a memoriei ia multe forme, una dintre ele fiind colectarea gunoiului (garbage collection, GC).
Colectorul de gunoi (garbage collector) are de obicei doua responsabilitati;
Aloca obiecte noi in memoria disponibila.
Elibereaza memoria neutilizata.
Exista numeroase algoritmi GC, dar MicroPython foloseste politica Mark and Sweep pentru gestionarea memoriei. Acest algoritm are o faza de marcare (mark) care parcurge heap-ul marcand toate obiectele vii, in timp ce faza de maturare (sweep) trece prin heap recuperand toate obiectele nemarcate.
Functionalitatea de colectare a gunoiului in MicroPython este disponibila prin modulul incorporat gc:
>>> x = 5
>>> x
5
>>> import gc
>>> gc.enable()
>>> gc.mem_alloc()
1312
>>> gc.mem_free()
2071392
>>> gc.collect()
19
>>> gc.disable()
>>>
Chiar si atunci cand este apelat gc.disable(), colectarea poate fi declansata cu gc.collect().
Memoria MicroPython din codul C¶
Cunoasterea colectorului de gunoi este necesara atunci cand se scrie cod C care aloca memorie din „heap-ul Python” (adica functiile m_malloc(), m_malloc0(), m_free() etc).
Faza de marcare a colectorului de gunoi cauta pointeri vii catre memoria heap pornind de la urmatoarele radacini:
Stiva runtime-ului Python principal (sau a REPL-ului).
Stivele fiecarui „fir Python”, pentru porturile care implementeaza fire Python peste firele sau task-urile native ale sistemului de operare.
„Pointerii radacina” definiti in codul C folosind macrocomanda
MP_REGISTER_ROOT_POINTER. Acestia sunt modalitatea recomandata de a avea pointeri cu domeniu static catre heap-ul Python.Alocarile urmarite facute cu functiile
m_tracked_calloc(),m_tracked_reallocsim_tracked_free(). Aceste functii speciale permit alocarea unui bloc de memorie care este intotdeauna considerat viu de catre colectorul de gunoi. Similar alocarii de memorie in C, aceasta memorie este eliberata doar prin apelaream_tracked_free()sau printr-o resetare soft. Exista un consum mic de memorie si un cost suplimentar de executie pentru fiecare alocare urmarita. Aceasta facilitate nu este activata implicit pe toate porturile.
Colectorul de gunoi scaneaza apoi recursiv si marcheaza toata memoria indicata de pointerii radacina, pana cand toate adresele sunt epuizate. Acest lucru este suficient pentru a gasi toate obiectele Python care sunt inca in uz de catre runtime-ul MicroPython.
Totusi, urmatoarea memorie nu va fi scanata de colectorul de gunoi si ar putea fi eliberata prematur:
Variabile C statice sau globale care contin pointeri catre memoria heap.
Pointeri care nu indica spre „capul” unui tampon (buffer) alocat (adica spre adresa exacta returnata de
m_malloc()), ci spre o adresa din interiorul tamponului alocat (de exemplu, un pointer catre o structura imbricata). Din motive de performanta, colectorul de gunoi nu marcheaza tamponul inconjurator in aceste cazuri.Stiva oricarui fir sau task RTOS care nu ruleaza cod Python sau nu este inregistrat manual ca „fir Python” (pentru porturile care suporta fire sau task-uri native).
Modalitati de a evita utilizarea dupa eliberare (use-after-free) in aceste scenarii:
Folositi API-ul de alocare urmarita
m_tracked_calloc(),m_tracked_realloc()sim_tracked_free().Inregistrati un pointer radacina (vezi mai sus), in loc sa stocati un pointer intr-o variabila statica.
Restructurati codul, de exemplu prin crearea unui API in care codul Python initializeaza un obiect Python de tip singleton (implementat in C) care detine toti pointerii relevanti, in loc sa ii pastreze in variabile statice.
Notă
Resetare software curata intotdeauna heap-ul Python si elibereaza toata memoria. Este important sa nu pastrati niciun pointer catre heap dupa o resetare soft, deoarece acestia vor deveni pointeri suspendati (dangling) catre memorie eliberata.
Unele porturi suporta de asemenea un „heap C” (vezi Alocarea dinamică a memoriei în C), caz in care puteti aloca memorie care va ramane valida peste resetarea soft prin apelarea functiilor C standard malloc etc.
Modelul de obiect¶
Toate obiectele MicroPython sunt referite prin tipul de date mp_obj_t. Acesta are de obicei dimensiunea unui cuvant (adica aceeasi dimensiune ca un pointer pe arhitectura tinta) si poate fi de regula pe 32 de biti (STM32, RP2, nRF, Unix x86) sau pe 64 de biti (Unix x64). Poate fi de asemenea mai mare decat dimensiunea unui cuvant pentru anumite reprezentari de obiecte; de exemplu OBJ_REPR_D are un mp_obj_t de dimensiunea 64 de biti pe o arhitectura pe 32 de biti.
Un mp_obj_t reprezinta un obiect MicroPython, de exemplu un intreg, un float, un tip, un dict sau o instanta de clasa. Unele obiecte, precum valorile booleene si intregii mici, isi au valoarea stocata direct in valoarea mp_obj_t si nu necesita memorie suplimentara. Alte obiecte isi au valoarea stocata in alta parte a memoriei (de exemplu pe heap-ul gestionat prin colectarea gunoiului), iar mp_obj_t al lor contine un pointer catre acea memorie. O parte din mp_obj_t este eticheta (tag) care indica ce tip de obiect este.
Vezi py/mpconfig.h pentru detaliile specifice ale reprezentarilor disponibile.
Etichetarea pointerilor (pointer tagging)
Deoarece pointerii sunt aliniati la cuvant, atunci cand sunt stocati intr-un mp_obj_t bitii inferiori ai acestui descriptor de obiect vor fi zero. De exemplu, pe o arhitectura pe 32 de biti cei mai putin semnificativi 2 biti vor fi zero:
********|********|********|******00
Acesti biti sunt rezervati pentru stocarea unei etichete (tag). Eticheta stocheaza informatii suplimentare, ca alternativa la introducerea unui nou camp care sa stocheze acele informatii in obiect, ceea ce ar putea fi ineficient. In MicroPython eticheta indica daca avem de-a face cu un intreg mic, un sir de caractere internat (mic) sau un obiect concret, fiecaruia dintre acestea aplicandu-i-se o semantica diferita.
Pentru intregii mici, maparea este aceasta:
********|********|********|*******1
Unde asteriscurile contin valoarea intreaga propriu-zisa. Pentru un sir internat sau un obiect imediat (de exemplu True) dispunerea valorii mp_obj_t este, respectiv:
********|********|********|*****010
********|********|********|*****110
In timp ce un obiect concret care nu este niciunul dintre cele de mai sus ia forma:
********|********|********|******00
Stelutele de aici corespund adresei obiectului concret in memorie.
Alocarea obiectelor¶
Valoarea unui intreg mic este stocata direct in mp_obj_t si va fi alocata pe loc (in-place), nu pe heap sau in alta parte. Ca atare, crearea intregilor mici nu afecteaza heap-ul. Similar pentru sirurile internate care isi au deja datele textuale stocate in alta parte si pentru valorile imediate precum None, False si True.
Tot ceea ce este un obiect concret este alocat pe heap, iar structura sa de obiect este astfel incat un camp este rezervat in antetul obiectului pentru a stoca tipul obiectului.
+++++++++++
+ +
+ type + object header
+ +
+++++++++++
+ + object items
+ +
+ +
+++++++++++
Cea mai mica unitate de alocare a heap-ului este un bloc, care are dimensiunea de patru cuvinte masina (16 octeti pe o masina pe 32 de biti, 32 de octeti pe o masina pe 64 de biti). O alta structura, de asemenea alocata pe heap, urmareste alocarea obiectelor in fiecare bloc. Aceasta structura se numeste bitmap.
Bitmap-ul urmareste daca un bloc este „liber” sau „in uz” si foloseste doi biti pentru a urmari aceasta stare pentru fiecare bloc.
Colectorul de gunoi mark-sweep gestioneaza obiectele alocate pe heap si utilizeaza de asemenea bitmap-ul pentru a marca obiectele care sunt inca in uz. Vezi py/gc.c pentru implementarea completa a acestor detalii.
Alocare: dispunerea heap-ului
Heap-ul este aranjat astfel incat este alcatuit din blocuri grupate in pool-uri. Un bloc poate avea diferite proprietati:
ATB(allocation table byte): Daca este setat, atunci blocul este un bloc normal
FREE: Bloc liber
HEAD: Capul unui lant de blocuri
TAIL: In coada unui lant de blocuri
MARK : Bloc-cap marcat
FTB(finaliser table byte): Daca este setat, atunci blocul are un finalizator