Gestione della Memoria nella .NET Compact Framework e in Windows Mobile (Parte 1 – Memoria Virtuale su Windows CE)
Dopo aver descritto rapidamente lo scenario in cui operano gli Sviluppatori per Windows Mobile nel precedente post, possiamo cominciare a parlare di come la memoria sia gestita nei sistemi operativi basati su Windows CE 5.0. E specifico 5.0 perchè in Windows Embedded CE 6.0 la gestione della memoria è (finalmente!!) totalmente cambiata e non esistono più le limitazioni di cui parleremo. Tra l’altro, si tratta in parte delle stesse limitazioni descritte per Windows CE 4.2 da Doug Boling nell’articolo MSDN Windows CE .NET Advanced Memory Management, nonostante l’articolo risalga ben al 2002! Per fortuna alcuni miglioramenti sono stati introdotti a partire da Windows Mobile 6 e specialmente in 6.1 (e quindi anche nel 6.5), grazie ai quali non solo le applicazioni hanno più memoria virtuale a loro disposizione, ma anche l’intero sistema operativo risulta più stabile.
Non vorrei ripetere qui quello che è possibile trovare in documentazione o in vari blog: vorrei invece mostrare visivamente la teoria, poichè solo guardando dati del genere ci si rende conto di quanto debba essere bravo uno Sviluppatore per Windows Mobile! Si tratta dell’output di un tool sviluppato anni fa da Symbol (poi acquisita da Motorola), che permetteva al costruttore di capire come il processo device.exe fosse responsabile o meno di vari problemi legati alla memoria. Perchè? Perchè (a) device.exe è il processo che carica le DLL relative ai driver nel proprio Address Space; (b) se non è stata già caricata da altri processi, la DLL andrà ad occupare la porzione di memoria virtuale al di sotto del “DLL Load Point”; (c) ciascuna DLL può avere delle dipendenze, anch’esse da caricare; (d) ciascuna DLL può creare il proprio heap per immagazzinare dati.
Il risultato, su un Windows Mobile 5.0, era qualcosa del genere (ho oscurato quelli che potevano essere i dati sensibili della software house di cui ho gestito la richiesta di supporto):
Anzitutto, cosa intendeva Symbol\Motorola per “Code” (blu) e “Data” (verde):
- “Code”: sono le DLLs della *RAM* che vengono caricate o mappate all’interno del process-slot. Partono dal limite superiore dello slot di 32MB e scendono verso il basso. Se più processi usano la stessa DLL, il secondo la mappa allo stesso indirizzo in cui il primo l’ha caricata.
- “Data”: è il codice compilato dell’eseguibile + Heap(s) + Stack. Parte dal basso e cresce.
- la riga verticale rossa rappresenta il “DLL Load Point”, ovvero l’indirizzo cui sarà caricata una DLL che non sia stata caricata da nessun altro processo
Quella figura rappresenta la situazione in cui si trovano i process slot – quindi non parliamo di tutta la memoria virtuale (in particolare non mostra il contenuto della Large Memory Area):
Perchè ho specificato DLLs della *RAM*? Perchè quelle poste dall’OEM nella ROM (= firmware) vengono eseguite direttamente lì, senza che il processo ne carichi “il compilato” nel proprio Address Space (XIP: “eXecuted In Place”, nello Slot 1).
Quella figura mostra anche il fatto che la parte verde (code+heap+stack) può superare il DLL Load Point. In effetti, i problemi legati alla mancanza di memoria virtuale disponibile sono in genere di 2 tipi:
- un processo tenta di caricare una DLL che non sia stata caricata da nessun altro, tentando di spostare il DLL Load Point in una zona occupata già da code+heap+stack
- un processo tenta di allocare memoria (code+heap+stack) ma il process slot ha ormai saturato anche le sezioni libere lasciati dalle DLL
Ecco anche perchè in generale uno dei consigli per evitare problemi di memoria è sempre stato quello di caricare allo startup dell’applicazione, anche manualmente attraverso una chiamata a LoadLibrary, tutte le DLL usate dall’applicazione stessa. Un altro esempio visuale a tal proposito è il seguente (il processo il cui nome è oscurato è l’applicazione NETCF dello Sviluppatore con cui stavo lavorando nell’ambito di una Richiesta di Supporto Tecnico):
Parleremo in seguito in dettaglio delle particolarità della NETCF, ma vale la pena a questo punto notare un dettaglio: a parte le DLL della runtime vera e propria (mscoree*.dll, netcfagl*.dll), qualsiasi altro assembly non spreca address space nel process slot, ma viene caricato nella Large Memory Area. Anzi, se viene usata la versione della runtime inserita nella ROM dall’OEM, anche le DLL della runtime non intaccano la memoria virtuale dell’address space. Ovviamente la cosa è diversa nel caso in cui l’applicazione esegua il P/Invoke verso DLL native: queste saranno caricate nell’address space del processo.
Inoltre, nell’immagine che mostra tutti i processi in azione è possibile notare nel limite superiore di tutti gli address space una porzione di memoria virtuale “blu” uguale per tutti i processi. Si tratta di memoria bloccata dal sistema operativo di dimensione pari alla somma degli ESEGUIBILI (i semplici file .EXE) attivi in un determinato istante. Quindi gli EXE monolitici di notevoli dimensioni (ad esempio perchè racchiudono risorse) sono sconsigliati su Windows CE! Ed in generale mi è capitato di vedere “grandi” EXE nelle applicazioni NETCF…
Grazie a quelle figure è anche facile capire come mai l’intera stabilità del sistema sia funzione di tutti i processi attivi, ed in particolare è facile intuire che molto spesso DEVICE.EXE sia fonte di grattacapi! Pensate a quei dispositivi basati su Windows Mobile che abbiano lo stack Radio (i.e. il telefono), Bluetooth, WiFi, Camera, barcode-scanner, etc.. ciascuno di questi driver è una DLL che device.exe deve caricare (linea blu), ed inoltre ciascuna può creare la propria heap e il proprio stack (linea verde). Alcuni OEM hanno dato la possibilità agli Sviluppatori di disattivare programmaticamente alcuni driver (ovvero ridurre le DLL in device.exe), ma ovviamente non si può dare per scontato ad esempio che l’utente non riavvii manualmente quella funzionalità a posteriori (o lo faccia un’altra applicazione…).
Quali soluzioni sono state cercate per contrastare il dominio di device.exe? In molti casi, le DLL dei driver venivano caricate da SERVICES.EXE, che è l’host process che mantiene le DLL dei servizi su Windows CE. Ma molto spesso non era sufficiente… La novità introdotta in Windows Mobile 6.1 è che le DLL NATIVE con dimensione >64KB vengono generalmente caricate nei cosiddetti Slot 60 e 61, che fanno parte della Large Memory Area. Un’altra miglioria di Windows Mobile 6.1 è stata l’aver dedicato un altro slot (slot 59) agli stack dei driver (parte della linea verde di device.exe). Certo, questo significa che i Memory-Mapped File hanno meno spazio a disposizione (e mi è capitato proprio di gestire una richiesta esattamente a questo proposito, da parte di una software house che stava sviluppando un software di navigazione GPS che non riusciva più a caricare alcuni file delle mappe in WinMo6.1), ma in generale l’intero sistema operativo gode di una stabilità che prima non aveva…
Per concludere, il tool che ho menzionato era stato sviluppato da Symbol e non credo sia disponibile pubblicamente. Ma un tool simile è stato recentemente messo pubblicato su CodePlex (codice sorgente incluso!) tramite l’articolo Visualizing the Windows Mobile Virtual Memory Monster. Il termine “Virtual Memory Monster” fu coniato anni fa da Reed Robison… (parte 1 e parte 2). Mi è già capitato di usarlo in alcune richieste e lo consiglio fortemente!
Spero che grazie a questo approccio visuale alcuni dubbi siano stati ora chiariti!
A presto!
~raffaele
Raffaele Limosani
Senior Support Engineer
Windows Mobile & Embedded Developer Support
My Blog around Mobile Development