14.3.3. Igiene del filesystem¶
La memoria flash e SD di una cam in produzione si riempiono di file che nessun operatore andrà a ripulire a mano. Due decisioni riguardanti questa memoria accompagnano il prodotto per tutta la sua vita: quale supporto contiene quale tipo di dati e come sono strutturate le directory affinché le operazioni sui file continuino a funzionare man mano che l’applicazione accumula record.
14.3.3.1. Dove vanno le cose¶
Il codice e gli asset risiedono nei moduli congelati e nel ROMFS che la build fissa al momento della spedizione. Lo stato dell’applicazione – tutto ciò che l’applicazione scrive a runtime, tutto ciò che cresce, tutto ciò che cambia tra un avvio e l’altro – deve risiedere da qualche altra parte. La cam espone due supporti scrivibili a questo scopo:
Flash interna in
/flash: un piccolo filesystem scrivibile montato prima che venga eseguito qualsiasi codice applicativo. Il posto giusto per piccoli record di dimensione fissa che sopravvivono ai riavvii: la configurazione che l’applicazione aggiorna a runtime, l’ultima calibrazione nota, un contatore progressivo, un file marcatore di una riga che dice «questa cam è stata provisionata.» Cicli di scrittura limitati – la flash interna moderna tollera da migliaia a decine di migliaia di scritture per settore, non milioni, quindi le scritture devono essere poco frequenti, non per ogni frame.Scheda SD in
/sdcard: un filesystem scrivibile più grande montato quando è presente una scheda. Il posto giusto per file voluminosi e di dimensione variabile: acquisizioni di immagini e video, file di log, dati per il fine-tuning del modello, qualsiasi cosa che possa crescere fino a megabyte o gigabyte. Maggiore capacità di scrittura rispetto alla flash interna ma comunque finita; rimovibile, sostituibile, e il supporto che più probabilmente sparisce mentre l’applicazione è nel mezzo di una scrittura.
La risposta giusta su dove scrivere qualcosa è quasi sempre «flash per i piccoli record fissi, SD per tutto il resto.» I due non sono intercambiabili: un’applicazione che scarabocchia il proprio file di log progressivo su /flash esaurirà la resistenza alle scritture della flash in un’installazione in cui sarebbe andato tutto bene su SD.
14.3.3.2. Tratta entrambi come soggetti a guasti¶
/flash e /sdcard possono entrambi guastarsi. La scheda SD può essere espulsa, la flash può danneggiarsi a causa di un’interruzione di corrente nel mezzo di una scrittura, entrambe possono esaurire lo spazio, e qualsiasi operazione su una delle due può sollevare un OSError per motivi che l’applicazione non avrà la possibilità di diagnosticare sul campo.
Due pattern fanno sì che l’applicazione sopravviva a tutto questo:
Avvolgi i mount e le operazioni in blocchi try. Ogni
open(),os.listdir(),os.rename()su percorsi di dati utente è potenzialmente soggetto a guasti. CatturaOSError, registralo nel log e ricorri a un’alternativa definita – scrivi su/flashse/sdcardnon c’è, salta l’operazione se nessuno dei due è disponibile.Scritture atomiche per i file che devono sopravvivere a un’interruzione di corrente. Scrivi su un percorso temporaneo, chiudi l’handle, poi usa
os.rename()sul nome attivo. O il rename è riuscito e il file è la nuova versione, oppure non è riuscito e il file è la vecchia versione. Non esiste un terzo stato in cui il file è scritto a metà:import os def write_config_atomic(path, contents): tmp = path + '.tmp' with open(tmp, 'w') as f: f.write(contents) f.flush() os.rename(tmp, path)
Il pattern funziona sia su flash che su SD. Non funziona per file abbastanza grandi da far sì che il file temporaneo consumi lo spazio libero del filesystem; riservalo ai piccoli record.
14.3.3.3. La trappola della directory lenta¶
Il VFS di MicroPython non indicizza il contenuto delle directory come fa un filesystem desktop. os.listdir() e os.stat() scorrono linearmente la tabella dei file sottostante. Una directory con un centinaio di file va bene; una directory con diecimila file è inutilizzabilmente lenta, con ogni os.listdir() che richiede secondi e ogni open() che deve verificare la tabella durante il suo percorso.
Le applicazioni che scrivono log o acquisizioni su disco incontrano questo problema più rapidamente. Uno schema ingenuo come /sdcard/logs/<timestamp>.log che apre un nuovo file al minuto riempie la directory logs/ con mezzo milione di file in un anno di installazione. Molto prima di allora l’applicazione inizia a mancare il proprio frame rate perché ogni apertura di file richiede più tempo di un intervallo tra frame.
Il pattern giusto è distribuire i file su un albero di sottodirectory datate in modo che nessuna singola directory contenga mai più di qualche centinaio di voci:
import os
import time
LOG_ROOT = '/sdcard/logs'
def log_path(now=None):
if now is None:
now = time.localtime()
year, month, day, hour = now[0], now[1], now[2], now[3]
directory = '{}/{:04d}/{:02d}/{:02d}'.format(
LOG_ROOT, year, month, day)
_makedirs(directory)
return '{}/{:02d}.log'.format(directory, hour)
def _makedirs(path):
# os.makedirs equivalent -- create each level if missing
parts = path.split('/')
for i in range(2, len(parts) + 1):
sub = '/'.join(parts[:i])
try:
os.mkdir(sub)
except OSError:
pass
Un anno di logging con un file all’ora è ora distribuito su 365 directory giornaliere, ciascuna contenente al massimo 24 file; os.listdir() su una qualsiasi singola directory rimane economico, e il ciclo dei frame dell’applicazione non si blocca sulle operazioni sui file man mano che l’installazione invecchia.
Lo stesso principio si applica alle acquisizioni di immagini, alle tracce dei sensori, o a qualsiasi altra cosa per cui l’applicazione scrive un file per evento. Se la frequenza degli eventi è alta, l’albero deve essere più profondo (anno/mese/giorno/ora, oppure anno/mese/giorno/ora/minuto) in modo che ogni directory foglia rimanga piccola. Se la frequenza degli eventi è bassa, un albero anno/mese è sufficiente.
14.3.3.4. Percorsi per dispositivo¶
In un parco di più di una cam, i file di log devono identificare da quale unità fisica provengono. machine.unique_id() restituisce un identificatore hardware impresso nella cam in fabbrica; è lo stesso valore tra un riavvio e l’altro, tra gli aggiornamenti del firmware, e tra le sostituzioni della scheda SD. Inseriscilo nel percorso del log o nei record del log e un operatore che guarda una pila di schede SD o un log centralizzato può capire quale sia quale:
import binascii
import machine
UNIT_ID = binascii.hexlify(machine.unique_id()).decode()
LOG_ROOT = '/sdcard/logs/' + UNIT_ID
Combinato con il pattern delle sottodirectory datate, il layout diventa /sdcard/logs/<unit-id>/2026/06/09/14.log – un’ora di record di una singola unità, in una directory abbastanza superficiale da poter essere scorsa, su un percorso che identifica l’unità sul filesystem stesso.
14.3.3.5. Mettere tutto insieme¶
La memoria scrivibile di una cam in produzione assomiglia all’incirca a questa:
/flash– configurazione, calibrazione, un marcatore di provisioning. Scritta raramente, letta spesso. Pattern di rename atomico per ogni file la cui perdita comprometterebbe il prossimo avvio./sdcard/logs/<unit-id>/<year>/<month>/<day>/<hour>.log– il log operativo. Scritto continuamente, ruotato dal percorso, mai scritto attraverso una directory con migliaia di elementi simili./sdcard/captures/<unit-id>/<year>/<month>/<day>/– le acquisizioni di immagini o video che l’applicazione effettua. Stessa forma dell’albero, stesso motivo.
Quel layout costa all’applicazione circa venti righe di codice e la salva dai modi di fallimento che mettono fuori uso la cam mesi dopo l’inizio di un’installazione.