Muistinhallinta¶
Toisin kuin C/C++:n kaltaisissa ohjelmointikielissä, MicroPython piilottaa muistinhallinnan yksityiskohdat kehittäjältä tukemalla automaattista muistinhallintaa. Automaattinen muistinhallinta on tekniikka, jota käyttöjärjestelmät tai sovellukset käyttävät hallitakseen automaattisesti muistin varaamista ja vapauttamista. Tämä poistaa haasteita, kuten objektille varatun muistin vapauttamisen unohtamisen. Automaattinen muistinhallinta välttää myös kriittisen ongelman, jossa käytetään jo vapautettua muistia. Automaattinen muistinhallinta esiintyy monessa muodossa, joista yksi on roskienkeruu (GC).
Roskienkerääjällä on yleensä kaksi tehtävää:
Varata uusia objekteja käytettävissä olevaan muistiin.
Vapauttaa käyttämätön muisti.
GC-algoritmeja on monia, mutta MicroPython käyttää muistin hallintaan Mark and Sweep -menetelmää. Tässä algoritmissa on merkitsemisvaihe (mark), joka käy keon läpi merkiten kaikki elossa olevat objektit, kun taas pyyhkimisvaihe (sweep) käy keon läpi vapauttaen kaikki merkitsemättömät objektit.
Roskienkeruun toiminnallisuus on MicroPythonissa käytettävissä gc-sisäänrakennetun moduulin kautta:
>>> x = 5
>>> x
5
>>> import gc
>>> gc.enable()
>>> gc.mem_alloc()
1312
>>> gc.mem_free()
2071392
>>> gc.collect()
19
>>> gc.disable()
>>>
Vaikka gc.disable() olisi kutsuttu, keruu voidaan käynnistää komennolla gc.collect().
MicroPython-muisti C-koodista¶
Roskienkerääjä on otettava huomioon kirjoitettaessa C-koodia, joka varaa muistia ”Python-keosta” (eli funktiot m_malloc(), m_malloc0(), m_free() jne.).
Roskienkerääjän merkitsemisvaihe etsii eläviä osoittimia keon muistiin alkaen seuraavista juurista:
Pääasiallisen Python-ajoympäristön (tai REPL:n) pino.
Kunkin ”Python-säikeen” pinot niissä porteissa, jotka toteuttavat Python-säikeet alkuperäisten käyttöjärjestelmän säikeiden tai tehtävien päälle.
C-koodissa makrolla
MP_REGISTER_ROOT_POINTERmääritellyt ”juuriosoittimet”. Tämä on suositeltu tapa toteuttaa staattisesti rajattuja osoittimia Python-kekoon.Funktioilla
m_tracked_calloc(),m_tracked_reallocjam_tracked_free()tehdyt seuratut varaukset. Nämä erityisfunktiot mahdollistavat sellaisen muistilohkon varaamisen, jonka roskienkerääjä katsoo aina eläväksi. Kuten C:n muistinvarauksessa, tämä muisti vapautetaan vain kutsumallam_tracked_free()tai pehmeällä uudelleenkäynnistyksellä. Jokaiseen seurattuun varaukseen liittyy pieni muistin- ja ajonaikainen lisäkuorma. Tätä ominaisuutta ei ole oletuksena käytössä kaikissa porteissa.
Roskienkerääjä skannaa ja merkitsee sitten rekursiivisesti kaiken muistin, johon juuriosoittimet osoittavat, kunnes kaikki osoitteet on käyty läpi. Tämä riittää löytämään kaikki Python-objektit, jotka ovat yhä MicroPython-ajoympäristön käytössä.
Roskienkerääjä ei kuitenkaan skannaa seuraavaa muistia, ja se voitaisiin vapauttaa ennenaikaisesti:
Staattiset tai globaalit C-muuttujat, jotka sisältävät osoittimia keon muistiin.
Osoittimet, jotka eivät osoita varatun puskurin ”alkuun” (eli täsmälleen siihen osoitteeseen, jonka
m_malloc()palauttaa), vaan johonkin varatun puskurin sisällä olevaan osoitteeseen (esimerkiksi osoitin sisäkkäiseen struktiin). Suorituskykysyistä roskienkerääjä ei merkitse ympäröivää puskuria näissä tapauksissa.Sellaisen säikeen tai RTOS-tehtävän pino, joka ei suorita Python-koodia tai jota ei ole manuaalisesti rekisteröity ”Python-säikeeksi” (niissä porteissa, jotka tukevat alkuperäisiä säikeitä tai tehtäviä).
Tapoja välttää käyttö-vapautuksen-jälkeen (use-after-free) näissä tilanteissa:
Käytä seuratun varauksen ohjelmointirajapintaa
m_tracked_calloc(),m_tracked_realloc()jam_tracked_free().Rekisteröi juuriosoitin (katso yllä) sen sijaan, että tallentaisit osoittimen staattiseen muuttujaan.
Rakenna koodi uudelleen, esimerkiksi luomalla rajapinta, jossa Python-koodi alustaa singleton-Python-objektin (toteutettu C:ssä), joka pitää sisällään kaikki olennaiset osoittimet sen sijaan, että ne olisivat staattisissa muuttujissa.
Muista
Pehmeä nollaus tyhjentää aina Python-keon ja vapauttaa kaiken muistin. On tärkeää, ettei pidetä mitään osoittimia kekoon pehmeän uudelleenkäynnistyksen jälkeen, sillä niistä tulee roikkuvia osoittimia vapautettuun muistiin.
Jotkin portit tukevat myös ”C-kekoa” (katso C-dynaaminen muistinvaraus), jolloin voit varata muistia, joka pysyy voimassa pehmeän uudelleenkäynnistyksen yli kutsumalla C:n vakiofunktioita malloc jne.
Objektimalli¶
Kaikkiin MicroPython-objekteihin viitataan tietotyypillä mp_obj_t. Tämä on yleensä sananmittainen (eli samankokoinen kuin osoitin kohdearkkitehtuurissa), ja se voi olla tyypillisesti 32-bittinen (STM32, RP2, nRF, Unix x86) tai 64-bittinen (Unix x64). Se voi myös olla sanaa suurempi tiettyjen objektiesitysten kohdalla; esimerkiksi OBJ_REPR_D:llä on 64-bittinen mp_obj_t 32-bittisellä arkkitehtuurilla.
mp_obj_t edustaa MicroPython-objektia, esimerkiksi kokonaislukua, liukulukua, tyyppiä, sanakirjaa tai luokan instanssia. Joidenkin objektien, kuten totuusarvojen ja pienten kokonaislukujen, arvo on tallennettu suoraan mp_obj_t-arvoon eivätkä ne vaadi lisämuistia. Toisten objektien arvo on tallennettu muualle muistiin (esimerkiksi roskienkeruun alaiseen kekoon) ja niiden mp_obj_t sisältää osoittimen kyseiseen muistiin. Osa mp_obj_t-arvosta on tunniste (tag), joka kertoo, minkä tyyppinen objekti on kyseessä.
Katso py/mpconfig.h käytettävissä olevien esitysmuotojen yksityiskohdista.
Osoittimen tunnisteet (pointer tagging)
Koska osoittimet ovat sanatasattuja, mp_obj_t-arvoon tallennettuna tämän objektikahvan alimmat bitit ovat nollia. Esimerkiksi 32-bittisellä arkkitehtuurilla alimmat 2 bittiä ovat nollia:
********|********|********|******00
Nämä bitit on varattu tunnisteen tallentamista varten. Tunniste tallentaa lisätietoa sen sijaan, että objektiin lisättäisiin uusi kenttä kyseisen tiedon tallentamiseksi, mikä saattaisi olla tehotonta. MicroPythonissa tunniste kertoo, käsittelemmekö pientä kokonaislukua, sisäistettyä (pientä) merkkijonoa vai konkreettista objektia, ja kuhunkin näistä sovelletaan eri semantiikkaa.
Pienille kokonaisluvuille kuvaus on tämä:
********|********|********|*******1
Asteriskit sisältävät varsinaisen kokonaislukuarvon. Sisäistetyn merkkijonon tai välittömän objektin (esim. True) tapauksessa mp_obj_t-arvon asettelu on vastaavasti:
********|********|********|*****010
********|********|********|*****110
Kun taas konkreettinen objekti, joka ei ole mikään edellä mainituista, on muotoa:
********|********|********|******00
Tähdet vastaavat tässä konkreettisen objektin osoitetta muistissa.
Objektien varaaminen¶
Pienen kokonaisluvun arvo tallennetaan suoraan mp_obj_t-arvoon ja se varataan paikan päällä, ei keosta tai muualta. Näin ollen pienten kokonaislukujen luominen ei vaikuta kekoon. Samoin on sisäistettyjen merkkijonojen kohdalla, joiden tekstidata on jo tallennettu muualle, sekä välittömien arvojen kuten None, False ja True kohdalla.
Kaikki muu, joka on konkreettinen objekti, varataan keosta, ja sen objektirakenne on sellainen, että objektin otsakkeeseen on varattu kenttä objektin tyypin tallentamista varten.
+++++++++++
+ +
+ type + object header
+ +
+++++++++++
+ + object items
+ +
+ +
+++++++++++
Keon pienin varausyksikkö on lohko, jonka koko on neljä konesanaa (16 tavua 32-bittisellä koneella, 32 tavua 64-bittisellä koneella). Toinen myös keosta varattu rakenne seuraa objektien varaamista kussakin lohkossa. Tätä rakennetta kutsutaan bittikartaksi (bitmap).
Bittikartta seuraa, onko lohko ”vapaa” vai ”käytössä”, ja käyttää kahta bittiä tämän tilan seuraamiseen kunkin lohkon osalta.
Mark-sweep-roskienkerääjä hallitsee keosta varattuja objekteja ja hyödyntää bittikarttaa myös yhä käytössä olevien objektien merkitsemiseen. Katso näiden yksityiskohtien täysi toteutus tiedostosta py/gc.c.
Varaaminen: keon asettelu
Keko on järjestetty siten, että se koostuu lohkoista altaissa (pool). Lohkolla voi olla erilaisia ominaisuuksia:
ATB (allocation table byte): Jos asetettu, lohko on tavallinen lohko
FREE: Vapaa lohko
HEAD: Lohkoketjun pää
TAIL: Lohkoketjun hännässä
MARK : Merkitty pään lohko
FTB (finaliser table byte): Jos asetettu, lohkolla on viimeistelijä (finaliser)