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.