Questo articolo offre una panoramica di alto livello dell'architettura della piattaforma Firefox OS, introducendo concetti chiave e spiegando come le sue componenti interagiscono a livello base.
Nota: Tieni presente che Firefox OS è ancora un prodotto in pre-release. L'architettura qui descritta non è necessariamente definitiva.
Terminologia
Seguono alcuni termini che vanno compresi prima di procedere oltre con la lettura della documentazione.
- B2G
- In breve Boot to Gecko.
- Boot to Gecko
- Il nome in codice del sistema operativo Firefox OS. Vedrai spesso questo termine riferito a Firefox OS, in quanto venne usato a lungo prima che il progetto avesse un nome ufficiale.
- Firefox OS
- Firefox OS è fondamentalmente Mozilla (e i partner OEM) con i rispettivi brand e servizi di supporto e il sistema Boot to Gecko, con l'obiettivo di crearne un prodotto finito.
- Gaia
-
L'interfaccia utente della piattaforma Firefox OS. Ogni oggetto rappresentato sullo schermo, dopo aver avviato Firefox OS, viene gestito dallo strato di Gaia. Gaia implementa la schermata di blocco, la schermata iniziale, e tutte le applicazioni standard che ci si aspetta in un moderno smartphone. Gaia è implementato interamente con HTML, CSS e JavaScript. Le sue uniche interfacce al sistema operativo sottostante sono attraverso le open Web API. implementate dallo strato di Gecko. Applicazioni di terze parti possono essere installate attraverso Gaia.
- Gecko
- È l’ambiente di esecuzione delle app di Firefox OS; cioè, lo strato che fornische tutto il supporto per gli standard HTML, CSS e JavaScript. Garantisce che tutte le API siano correttamente funzionanti su ogni sistema operativo supportato da Gecko. Questo significa che Gecko include, fra l'altro, uno stack di rete, uno grafico, un motore che realizza il layout del contenuto, una macchina virtuale per l’esecuzione di codice JavaScript e livelli di portabilità per differenti piattaforme.
- Gonk
- Gonk è il sistema operativo di basso livello della piattaforma Firefox OS, consiste di un Kernel Linux (derivato da quello sviluppato nel progetto Android Open Source Project (AOSP)) e di uno strato di astrazione dell'hardware nello spazio di indirizzamento utente (userspace HAL). Il kernel e numerose librerie userspace sono noti progetti open source: Linux, libusb, bluez e via dicendo. Alcune parti del HAL sono condivise con il progetto AOSP: il GPS, la fotocamera, e altri. Si potrebbe dire che Gonk è una distribuzione di Linux. Gonk è uno dei sistemi su cui viene portato Gecko; cioè, così come c'è un porting di Gecko per Gonk, c'è un porting di Gecko per Mac OS, Windows e Android. Poichè il progetto Firefox OS ha il pieno controllo su Gonk, si possono esporre interfacce a Gecko che non possono essere esposte da altri sistemi operativi. Per esempio, Gecko ha accesso diretto allo stack di telefonia e al frame buffer di Gonk, ma non ha il medesimo accesso in ogni altro sistema operativo.
- Jank
- Questo termine normalmente utilizzato nel mondo delle app mobili, si riferisce all'effetto di lentezza piuttosto che di inefficienza indotto da codice non ottimizzato che blocca l'aggiornamento dell'interfaccia utente causando crash o ritardi nella risposta. I nostri ingegneri lavorano su Gaia utilizzano varie tecniche per cercare di evitare questo effetto a tutti i costi.
Architettura d'insieme
Nella seguente immagine viene confrontata l'architettura di piattaforme proprietarie e quella di Firefox OS.
Firefox OS ha eliminato lo strato di API native fra il sistema operativo e gli strati applicativi. Questa soluzione integrata riduce il carico della piattaforma e semplifica la gestione della sicurezza senza sacrificare le prestazioni e una ricca esperienza utente.
- Gaia è l'insieme delle web app principali e dell'interfaccia utente di Firefox OS. È' scritto in HTML5, CSS e JavaScript. Espone un insieme di API per consentire al codice della UI di interagire con l'hardware del telefono e con le funzionalità di Gecko.
- Gecko è il motore web e lo strato di presentazione di Firefox OS. Rappresenta l'interfaccia fra i contenuti web e il dispositivo. Gecko fornisce il motore di parsing e rendering HTML5, un insieme di Web API sicure per accedere alle funzionalità hardware, un framework per la gestione della sicurezza, un sistema per la gestione degli aggiornamenti e altri servizi core.
- Gonk è il componente al livello del kernel nello stack di Firefox OS, è l'interfaccia fra Gecko e l'hardware del dispositivo. Gonk gestisce l'hardware sottostante e espone le funzionalità dell'hardware alle Web API implementate in Gecko. Gonk può essere visto come la “black box” che esegue il lavoro complesso e dettagliato dietro le scene, controllando il dispositivo mobile gestendo le richieste al livello hardware.
- Il dispositivo mobile è il telefono su cui viene eseguito Firefox OS. L'OEM (Original Equipment Manifacturer, l'azienda che ha costruito il dispositivo) è responsabile per la fornitura del dispositivo mobile.
Architettura specifica di Firefox OS
Procedura di boot di Firefox OS
Questa sezione descrive il processo di boot (avvio) dei dispositivi Firefox OS, quali componenti sono coinvolte e dove. Come introduzione, il flusso generale di boot del sistema va dal bootloader nello spazio di indirizzamente del kernel, a init nel codice nativo, a B2G e Gecko nello spazio utente e quindi finalmente alla system app, al window manager, alla homescreen app all'interno di Gecko. Le altre applicazioni vengono eseguite successivamente.
Il processo di bootstrap
Quando un dispositivo Firefox OS viene avviato, l'esecuzione inizia nel bootloader primario. Da li, il processo di caricamento del sistema operativo procede nella modalità tipica; una successione di bootloader di più alto livello caricano quello successivo. Alla fine d questo processo, l'esecuzione viene passata al kernel Linux.
Ci sono alcuni punti che vale la pena menzionare nel processo di boot:
- I bootloader generalmente visualizzano la prima immagina (splash screen) vista dall'utente durante l'avvio del dispositivo; tipicamente il logo del vendor.
- I bootloader implementano funzionalità per installare (flashare) nuove immagini sul dispositivo. Dispositivi differenti utilizzano protocolli differenti; la maggior parte dei telefoni utilizza il protocollo fastboot, il Samsung Galaxy S II invece utilizza il protocollo odin.
- Prima della conclusione del processo di boot, il firmware del modem viene caricato e portato in esecuzione sul processore del modem. La modalità con cui avviene è specifico per dispositivo e potrebbe anche essere codice proprietario.
Il kernel Linux
Il kernel Linux utilizzato da Gonk è veramente simile a quello originale da cui è derivato (basato sul progetto Android Open Source Project). Ci sono piccole modifiche apportate nel progetto AOSP che non sono ancora stete depositate. Inoltre, i produttori di hardware alcune volte modificano il kernel e depositano tali modifiche sulla base della loro pianificazione. In generale, tuttavia, il kernel è simile all'originale.
Il processo di avvio di Linux è ben documentato altrove in Internet, pertanto questo articolo non ne parlerà.
Il Kernel Linux gestirà le componenti hardware del dispositivo e i principali processi. Al termine della sequenza di avvio del Kernel verrà avviato init nello spazio di indirizzamento utente (userspace), come avviene nella maggior parte dei sistemi UNIX-like. Init eseguirà i processi definiti in init.rc
e di seguito quelli definiti in init.b2g.rc quali b2g
(Il processo principale di Firefox OS, che contiene Gecko stesso) e rild
(il processo che gestisce la componente hardware modem e radio del dispositivo) — vedi sotto per maggiori dettagli.
Dopo aver avviato init
, il kernel gestisce le chiamate di sistema dai processi userspace e interrrupt dai dispositivi hardware. Molte funzionalità hardware vengono esposte ai processi userspace via sysfs
. Ad esempio, il seguente è un estratto del codice che legge lo stato di carica della batteria in Gecko:
FILE *capacityFile = fopen("/sys/class/power_supply/battery/capacity", "r"); double capacity = dom::battery::kDefaultLevel * 100; if (capacityFile) { fscanf(capacityFile, "%lf", &capacity); fclose(capacityFile); }
Altro sul processo di Init
Sempre init
esegue il mount dei file system richiesti e l'avvio dei servizi di sistema. Dopo di che, rimane attivo come gestore di processi. Similmente a come avviene in altri sistemi UNIX-like. Esso interpreta script (i file init*.rc
) che contengono i comandi per avviare vari servizi. Il file init.rc
di Firefox OS è sostanzialmente quello orignale di Android per quel dispositivo modificato per includere quanto necessario ad avviare Firefox OS, e varia da dispositivo a dispositivo.
Un compito chiave gestito dal processo di init
è l'avvio del processo b2g
; il core del sistema opetativo Firefox OS.
Il codice in init.rc
per avviare b2g
è simile al seguente:
service b2g /system/bin/b2g.sh class main onrestart restart media
Nota: Esattamente quanto init.rc
differisce dalla versione di Android varia da dispositivo a dispositivo; alcune volte init.b2g.rc
è semplicemente aggiunto, altre volte le modifche sono più significative.
L'archituttura dei processi userspace
Ora diamo uno sguardo di alto livello di come le varie componenti di Firefox OS interagiscono fra loro. Il diagramma mostra i principali processi userspace di Firefox OS.
Nota: Tieni a mente che Firefox OS è in sviluppo attivo, questo diagramma può cambiare e potrebbe non essere estremamente accurato.
b2g
è il processo primario di sistema. VIene eseguito con privilegi molto alti; ha accesso alla maggior parte dei device hardware. b2g
comunica con il modem, scrive sul framebuffer dello schermo, interagisce col GPS, con le fotocamere e altre funzionalità hardware. Internamente B2G esegue lo strato di Gecko (implementato da libxul.so). Leggi l'articolo su Gecko per conoscere i dettagli di come funziona e come B2G comunica con lui.
b2g
Il processo b2g
avvia, di volta in volta, un numero di processi a bassa priorità chiamati content process. Si tratta di processi in cui vengono caricate applicazioni web e altri contenuti web. Comunicano con Gecko tramite IPDL, un sistema a passaggio di messaggi.
Il processo b2g
esegue libxul, che referenzia b2g/app/b2g.js
per le preferenze di default. Dalle preferenze viene letto il file HTML b2g/chrome/content/shell.html
, compilato all'interno del file omni.ja
. shell.html
include a sua volta il file b2g/chrome/content/shell.js
, il quale avvia la system
app di Gaia.
rild
Il processo rild
rappresenta l'interfaccia fra i servizi di telefonia di Firefox OS e l'hardware. rild
è il deamon che implementa il Radio Interface Layer (RIL). In parte è codice proprietario ed è implementato dal produttore stesso dell'hardware per comunicare con il modulo modem/radio. Codice cliente può interfacciarsi con rild
tramite un socket UNIX-domain su cui rild
è in ascolto. Viene avviato dallo script di init
, verosimilmente come segue:
service ril-daemon /system/bin/rild socket rild stream 660 root radio
rilproxy
In Firefox OS il processo rilproxy
è client di rild
e agisce da proxy fra rild
e b2g
. È sufficente dire che tale proxy è richiesto come dettaglio di implementazione. Potete trovate il codice di rilproxy
su GitHub.
mediaserver
Il processo mediaserver
controlla la riproduzione di audio e video. Gecko si interfaccia ad esso tramite un meccanismo di chiamata di procedura remota di Android (RPC, Remote Procedure Call). Alcuni dei formati media che Gecko può riprodurre (OGG Vorbis audio, OGG Theora video, e WebM video) vengono decodificati da Gecko stesso e inviati direttamente al processo mediaserver
. Altri formati vengono invece decodificati tramite libstagefright
, che è in grado di accedere a codec proprietari e encoder hardware.
Note: Il mediaserver
è un componente "temporaneo" di Firefox OS. Si prevede che verrà sostituito ma non prima della versione 2.0 di Firefox OS.
netd
Il processo netd
viene utilizzato per configurare le interfacce di rete.
wpa_supplicant
Il processo wpa_supplicant
è un servizio standard UNIX che gestisce la connettività con access point WIFI.
dbus-daemon
Il processo dbus-daemon implementa D-Bus, un sistema di comunicazione fra processi basato su bus di sistema a scambio di messaggi. Viene utilizzato da Firefox OS per la comunicazione Bluetooth.
Gecko
Gecko, come già accennato, implementa gli standard web (HTML, CSS, e JavaScript) e viene utilizzato per implementare qualsiasi cosa l'utente vede in Firefox OS e controlla tutte le interazioni con l'hardware del telefono. Le web app connettono l'HTML5 all'hardware attraverso l'utilizzo controllato e sicuro di Web API, implementate in Gecko. Le Web API offrono accesso a funzionalità dello strato hardware del telefono (quali la batteria o la vibrazione) e ai dati memorizzati o disponibili sul telefono (quali il calendario o i contatti). I contenuti web richiamano le Web API all'interno di HTML5.
Una app consiste in una collezione di contenuti web HTML5. Per costruire web app per dispositivi mobili Firefox OS gli sviluppatore semplicemente assemblano, pacchettizzano e distribuiscono questi contenuti web. A run time, i contenuti web vengono interpretati, compilati e renderizzati in un browser web. Per maggiori informazioni sulle App vai all'App Center.
Nota: Puoi trovare il codice sorgente di Gecko alla url https://dxr.mozilla.org. E 'un buon riferimento, ma con un numero limitato di repository. Oppure puoi utilizzare il tradizionale https://mxr.mozilla.org, che contiene più progetti Mozilla.
Diagramma architetturale di Gecko
- Security Framework: contiene
- Permission Manager: Gateway di accesso alle funzionalità delle Web API.
- Access Control List: Matrice di ruoli e permessi richiesti per accedere alle funzionalità delle Web API.
- Credential Validation: Authenticazione per applicazioni/utenti.
- Permissions Store: Insieme di privilegi richiesti per accedere alle funzionalità delle Web API.
- Web API: Insieme di API standard che espongono funzionalità hardware a contenuti web. Forniscono alle web app un'accesso sicuro e programmatico a catteristiche del sottostante strato hardware del dispositivo mobile, assieme ai dati che sono memorizzati, o sono disponibili, sul dispositivo.
- I/O: Interfaccia verso l'hardware e archivi di dati.
- Software Updates: Ottiene e installa aggiornamenti software di sistema e per le applicazioni di terze parti.
- Content Layout & Rendering: Il motore che analizza, interpreta ed esegue contenuti web e, con le informazioni di formattazione, mostra i contenuti formattati all'utente.
- b2g process: (Gecko) viene eseguito da un processo di sistema con privilegi elevati ed ha accesso all'hardware del telefono. Le applicazioni in esecuzione sono processi figli di b2g.
I file di Gecko relativi a Firefox OS
b2g/
La cartella b2g contiene principalmente le funzioni relative a Firefox OS.
b2g/chrome/content
Contiene i file Javascript che vengono eseguiti dalla system app.
b2g/chrome/content/shell.html
È il punto di ingresso di Gaia - il codice HTML per la system app. shell.html
carica settings.js e shell.js:.
<script type="application/javascript;version=1.8" src="chrome://browser/content/settings.js"> </script> <script type="application/javascript;version=1.8" src="chrome://browser/content/shell.js"> </script>
settings.js
contiene i parametri di default di sistema.
b2g/chrome/content/shell.js
shell.js
importa tutti i moduli necessari, registra i listener principali, imposta sendCustomEvent
e sendChromeEvent
per comunicare con Gaia e fornisce supporto per l'installazione delle webapp: dalle quote di indexedDB, alle funzionalità di RemoteDebugger, al supporto per le tastiere e ai tool di cattura delle schermate.
La funzionalità più importante di shell.js
è quella di avviare la system
app di Gaia e successivamente quello di delegare tutte le attività di gestione alla system
app stessa.
let systemAppFrame = document.createElementNS('https://www.w3.org/1999/xhtml', 'html:iframe'); ... container.appendChild(systemAppFrame);
b2g/app/b2g.js
Questo script contiene le impostazioni predefinite, quali about:config nel browser e le stesse pref.js di Gaia. Queste impostazioni possono essere modificate dalla app di Settings o sovrascritte da user.js negli script di build di Gaia.
dom/{API}
Le nuove API (post-b2g) saranno collocate nella cartella dom/
. Le vecchie API saranno collocate nella cartella dom/base, come ad esempio Navigator.cpp
.
dom/apps
.jsm
verranno caricati— .js
API quali webapp.js
installate
dom/apps/src/
Tutti i permessi sono definiti in PermissionsTable.jsm
dom/webidl
WebIDL è il linguaggio utilizzato per definire le web API. Dai una lettura a WebIDL_bindings per conoscere quali attribuiti sono supportati.
hal/gonk
Questa cartella contiene i file relativi ai livelli di porting di Gonk.
File generati
module/libpref/src/init/all.js
Contiene tutti i file di configurazione.
/system/b2g/ omni.ja and omni.js
Contiene i pacchetti di stile.
Eleborazione degli eventi di input
La maggior parte degli eventi di Gecko vengono innescati da azioni utente, quali la pressione di pulsanti, il tocco su un touch screen, e così via. Questi eventi vengono acquisiti da Gecko tramite l'interfaccia nsIAppShell implementata da Gonk che rappresenta il punto di ingresso principale per una applicazione di Gecko; cioè, il device driver della periferica di input richiamerà i metodi esposti dall'oggetto nsAppShell
che rappresenta il sottosistema di Gecko per inviare eventi all'interfaccia utente.
Per esempio:
void GeckoInputDispatcher::notifyKey(nsecs_t eventTime, int32_t deviceId, int32_t source, uint32_t policyFlags, int32_t action, int32_t flags, int32_t keyCode, int32_t scanCode, int32_t metaState, nsecs_t downTime) { UserInputData data; data.timeMs = nanosecsToMillisecs(eventTime); data.type = UserInputData::KEY_DATA; data.action = action; data.flags = flags; data.metaState = metaState; data.key.keyCode = keyCode; data.key.scanCode = scanCode; { MutexAutoLock lock(mQueueLock); mEventQueue.push(data); } gAppShell->NotifyNativeEvent(); }
Questi eventi provengono dal sistema input_event standard di Linux. Su questa struttura Firefox OS ha implementato uno strato di astrazione che fornisce alcune funzioni molto utili come il filtro degli eventi. Si può vedere il codice che crea eventi di input nel metodo EventHub :: GetEvents in widget/Gonk/libui/EventHub.cpp.
Una volta ricevuti da Gecko, gli eventi vengono inoltrati al DOM da nsAppShell:
static nsEventStatus sendKeyEventWithMsg(uint32_t keyCode, uint32_t msg, uint64_t timeMs, uint32_t flags) { nsKeyEvent event(true, msg, NULL); event.keyCode = keyCode; event.location = nsIDOMKeyEvent::DOM_KEY_LOCATION_MOBILE; event.time = timeMs; event.flags |= flags; return nsWindow::DispatchInputEvent(event); }
Dopo di che, gli eventi vengono consumati da Gecko stesso o vengono inoltrati alle applicazioni Web come eventi DOM per ulteriori elaborazioni.
Grafica
Al livello più basso, Gecko utilizza OpenGL ES 2.0 per disegnare un contesto OpenGL. Questo avviene in NSWindow implementata da Gonk, con codice simile a questo:
gNativeWindow = new android::FramebufferNativeWindow(); sGLContext = GLContextProvider::CreateForWindow(this);
La classe FramebufferNativeWindow
è quella nativa Android; vedi FramebufferNativeWindow.cpp
. Essa utilizza le API gralloc per accedere ai driver grafici per mappare i buffer dal framebuffer nella memoria.
Gecko usa i suoi Layers per comporre contesti sullo schermo. In sintesi accade quanto di seguito descritto:
- Gecko scrive regioni di pagine separate in buffer di memoria. Spesso questi buffer sono nella memoria di sistema; altre volte sono delle texture mappate nello spazio di indirizzamento di Gecko, il che significha che Gecko sta scrivendo direttamente nela memoria video. Questo avviene generalmente attraverso il metodo
BasicThebesLayer::PaintThebes()
. - Gecko quindi compone tutte le texture sullo schermo utilizzando comandi OpenGL. Questa composizione avviene in
ThebesLayerOGL::RenderTo()
.
I dettagli di come il Gecko gestisce il rendering dei contenuti web è oltre lo scopo di questo documento.
Hardware Abstraction Layer (HAL)
Lo strato di astrazione dell'hardware di Gecko è uno dei livelli di portabilità. Gestisce l'accesso alle interfacce di sistema di più basso livello su più piattaforme attraverso API C++, utilizzate dai livelli più alti di Gecko. Queste API sono implementate su base piattaforma all'interno del HAL di Gecko stesso. Lo strato di astrazione dell'hardware non viene esposto direttamente al codice JavaScript in Gecko - di questa interazione se ne occupano le Web API.
Diamo uno sguardo al processo da una prospettiva di più alto livello. Quando un utente utilizza una funzionalità del telefono (ad esempio effettua una chiamata, accede ad una rete wifi o si connette via Bluetooth), tutti gli strati dello stack tecnologico di Firefox OS vengono coinvolti per completare la richiesta. Le applicazioni e i contenuti web sottopongono le richieste per accedere al dispositivo attraverso chiamate alle Web API (direttamente all'interno di funzioni HTML5) implementate in Gecko. Gecko, a sua volta, sottopone le richieste a Gonk. Una singola richiesta di Gecko può innescare una serie complessa di operazioni, avviate e gestite da Gonk, sul dispositivo mobile.
Come lavora HAL
Consideriamo come esempio la API di Vibrazione. Definita in hal/Hal.h. In sintesi (semplificando la firma del metodo), abbiamo la seguente funzione:
void Vibrate(const nsTArray<uint32> &pattern);
Questa è la funzione chiamata dal codice di Gecko per abilitare la vibrazione del dispositivo in accordo con lo specifico pattern; una funzione corrispondente è disponibile per rimuovere la vibrazione. L'implementazione di Gonk di questo metodo è presente nel file hal/gonk/GonkHal.cpp:
void Vibrate(const nsTArray<uint32_t> &pattern) { EnsureVibratorThreadInitialized(); sVibratorRunnable->Vibrate(pattern); }
Viene inviata la richiesta per avviare la vibrazione sul dispositivo ad un altro thread, tramite l'istruzione VibratorRunnable::Run()
. Il ciclo principale di questo thread è simile al seguente:
while (!mShuttingDown) { if (mIndex < mPattern.Length()) { uint32_t duration = mPattern[mIndex]; if (mIndex % 2 == 0) { vibrator_on(duration); } mIndex++; mMonitor.Wait(PR_MillisecondsToInterval(duration)); } else { mMonitor.Wait(); } }
vibrator_on()
è la API definita nel HAL di Gonk che avvia il motore di vibrazione. Internamente questo metodo invia un messaggio all'apposito driver scrivendo su un oggetto del kernel attraverso sysfs
.
Implementazione delle API di Fallback del HAL
Le API HAL di Gecko sono sempre disponibili su tutte le piattaforme. Se Gecko viene portato su una nuova piattaforma che non espone il motore di vibrazione (ad esempio un desktop), viene comunque resa disponibile una API di fallback. Per il motore di vibrazione la API è implementata in hal/fallback/FallbackVibration.cpp.
void Vibrate(const nsTArray<uint32_t> &pattern) { }
Implementazione delle Sandbox
Poichè molti contenuti web vengono eseguiti in content process con privilegi bassi, non possiamo assumere che quei processi abbiano i privilegi adeguati (ad esempio) per avviare o spegnere il motore di vibrazione. Inoltre, vorremmo avere un posto unico in cui gestire eventuali situazioni di race condition. Nel HAL di Gecko questo viene gestito tramite una "sandbox". La sandbox agisce come un proxy, semplicemente inoltrando le richieste dei content process al "Gecko server". Le richieste vengono inoltrate utilizzando IPDL.
Per la vibrazione, questo viene gestito dalla funzione di Vibrate()
implementata in hal/sandbox/SandboxHal.cpp:
void Vibrate(const nsTArray<uint32_t>& pattern, const WindowIdentifier &id) { AutoInfallibleTArray<uint32_t, 8> p(pattern); WindowIdentifier newID(id); newID.AppendProcessID(); Hal()->SendVibrate(p, newID.AsArray(), GetTabChildFrom(newID.GetWindow())); }
Viene inviato un messaggio definito nell'interfaccia PHal
, definita in hal/sandbox/PHal.ipdl. Questo metodo è descritto più o meno come segue:
Vibrate(uint32_t[] pattern);
Il messaggio viene ricevuto dal metodo HalParent::RecvVibrate()
implementato in hal/sandbox/SandboxHal.cpp, come segue:
virtual bool RecvVibrate(const InfallibleTArray<unsigned int>& pattern, const InfallibleTArray<uint64_t> &id, PBrowserParent *browserParent) MOZ_OVERRIDE { hal::Vibrate(pattern, newID); return true; }
Sono stati omessi alcuni dettagli non rilevanti alla discussione; comunque, è stato mostrato come i messaggi progrediscono da un content process in Gecko a Gonk, quindi all'implementazione di Vibrate()
nella HAL di Gonk e infine al driver che gestisce il motore di vibrazionie.
Le API DOM
Le interfacce DOM rappresentano la modalità con cui i web content comunicano con Gecko. In realtà c'è molto più e se sei interessato, puoi leggere l'articolo about the DOM. Le interfacce DOM vengono definite utilizzando IDL, che comprende sia una funzione esterna (foreign function interface, FFI) che un modello di oggetto (object model, OM) fra Javascript e C++.
La API di Vibrazione viene esposta attraverso un'interfaccia IDL nsIDOMNavigator.idl:
[implicit_jscontext] void mozVibrate(in jsval aPattern);
L'argomento jsval
indica che mozVibrate()
accetta come parametro di input qualsiasi valore. Il compilatore IDL, xpidl
, genera l'interfaccia C++ che viene viene quindi implementata dalla classe Navigator
in Navigator.cpp.
NS_IMETHODIMP Navigator::MozVibrate(const jsval& aPattern, JSContext* cx) { // ... hal::Vibrate(pattern); return NS_OK; }
C'è molto più codice in questo metodo di quanto riportato, ma non è importante ai fini della discussione. Il punto è che la chiamata a hal::Vibrate()
trasferisce il controllo dal DOM all'HAL di Gecko. Da qui, entriamo nell'implementazione del HAL discusso precedentemente fino al driver della scheda di rete. Soprattutto, l'implementazione del DOM è indipendente dalla piattaforma su cui è in esecuzione (Gonk, Windows, Mac OS X o qualsiasi altra). Inoltre non si preoccupa se il codice viene eseguito in un content process o nel server Gecko. Questo dettagli sono lasciati ai layer di sistema che se ne occupa.
L'API di Vibrazione è molto semplice e ciò la rende un buon esempio. L' API di SMS è un esempio di API più complessa che utilizza un proprio strato "remoto" per connettere il content process al server.
Radio Interface Layer (RIL)
Il RIL è stato menzionato nella sezione L'archituttura dei processi userspace. Questa sezione esaminerà come le varie componenti di questo strato interagiscono in maggior dettaglio.
Le principali componenti sono:
rild
- Il daemon che interagisce con il firmware proprietario del modem.
rilproxy
- il daemon che agisce come proxy fra
rild
e Gecko (implementato nel processob2g
). Questo risolve il problema di autorizzazione che si pone quando si cerca di interagire direttamente con rild, in quanto è possibile farlo solo per chi appartiene al gruppo radio. b2g
- Questo processo, conoscuto anche col nome di chrome, implementa Gecko. Le componenti di b2g che interagiscono con RIL sono un worker thread implementato in dom/system/gonk/ril_worker.js che interagisce con
rild
attraversorilproxy
e implementa la macchina a stati del modem; e l'interfaccia nsIRadioInterfaceLayer che è il thread primario del servizio XPCOM e agisce principalmente per scambiare messaggi fra il worker threadril_worker.js
e le varie componenti di Gecko, inclusi i content process. - I content process di Gecko
- All'interno dei content process di Gecko l'interfaccia nsIRILContentHelper fornisce un servizio XPCOM che permette al codice che implementa parti del DOM, quali le API Telephony e SMS, di comunicare con l'interfaccia modem/radio, che risiede nel processo chrome.
Esempio: Communicazione da rild al DOM
Diamo un'occhiata ad un esempio di come le parti basse del sistema comunicano col DOM. Quando il modem riceve una chiamata in ingresso, avvisa rild
utilizzando un meccanismo proprietario. rild
quindi prepara un messaggio per il suo client in conformità con un protocollo "open" descritto in ril.h
. Sempre nel caso di una chiamata in ingresso, un messaggio RIL_UNSOL_RESPONSE_CALL_STATE_CHANGED
viene generato e inviato da rild
a rilproxy
.
rilproxy
, implementato in rilproxy.c
, riceve questo messaggio nel suo ciclo principale mettendosi in polling sulla connessione con rild
nel seguente modo:
ret = read(rilproxy_rw, data, 1024); if(ret > 0) { writeToSocket(rild_rw, data, ret); }
Dopo aver ricevuto il messaggio da rild
, rilproxy
lo invia a Gecko attraverso la socket che li connette. Gecko riceve il messaggio tramite l'IPC thread:
int ret = read(fd, mIncoming->Data, 1024); // ... handle errors ... mIncoming->mSize = ret; sConsumer->MessageReceived(mIncoming.forget());
Il consumatore di questi messaggi è il SystemWorkerManager, che riconfeziona il messaggio e lo inoltra al worker thread ril_worker.js
che implementa la macchina a stati di RIL; questo avviene nel metodo RILReceiver::MessageReceived()
:
virtual void MessageReceived(RilRawData *aMessage) { nsRefPtr<DispatchRILEvent> dre(new DispatchRILEvent(aMessage)); mDispatcher->PostTask(dre); }
PostTask a sua volta chiama la funzione onRILMessage(),
implementata in Javascript. Ciò avviene utilizzando la API JavaScript JS_CallFunctionName()
:
return JS_CallFunctionName(aCx, obj, "onRILMessage", NS_ARRAY_LENGTH(argv), argv, argv);
onRILMessage()
è implementata in dom/system/gonk/ril_woker.js, elabora il messaggio e lo scompone. Ciascun pacchetto viene quindi inviato ad un gestore appropriato:
handleParcel: function handleParcel(request_type, length) { let method = this[request_type]; if (typeof method == "function") { if (DEBUG) debug("Handling parcel as " + method.name); method.call(this, length); } }
Il gestore verifica la tipologia di richiesta assicurandosi che sia definita come funzione nel codice JavaScript, e solo successivamente chiamando il metodo. ril_worker.js implementa un metodo specifico per ciascun tipo di richiesta.
Nel nostro esempio in corrispondenza a RIL_UNSOL_RESPONSE_CALL_STATE_CHANGED
viene chiamato il seguente gestore:
RIL[UNSOLICITED_RESPONSE_CALL_STATE_CHANGED] = function UNSOLICITED_RESPONSE_CALL_STATE_CHANGED() { this.getCurrentCalls(); };
Come puoi notare dal codice, se lo stato è cambiato durante la ricezione della notifica viene aggiornato semplicemente chiamando il metodo getCurrentCall():
getCurrentCalls: function getCurrentCalls() { Buf.simpleRequest(REQUEST_GET_CURRENT_CALLS); }
il quale invia una richiesta a rild per acquisire lo stato di tutte le chiamate attive. La richiesta segue il medesimo percorso ma inverso rispetto a quello seguito da RIL_UNSOL_RESPONSE_CALL_STATE_CHANGED (cioè da ril_worker.js a SystemWorkerManager a Ril.cpp, quindi a rilproxy e finalmente a rild). rild quindi risponde, seguendo il medesimo percorso, fino al gestore dell'evento del messaggio REQUEST_GET_CURRENT_CALLS all'interno di ril_worker.js. In questo modo avvengono le comunicazioni bidirezionali.
Lo stato viene quindi comparato con lo stato precedente; se c'è stato un cambio, ril_worker.js aggiorna il servizio nsIRadioInterfaceLayer:
_handleChangedCallState: function _handleChangedCallState(changedCall) { let message = {type: "callStateChange", call: changedCall}; this.sendDOMMessage(message); }
nsIRadioInterfaceLayer è implementato in dom/syste/gonk/RadioInterfaceLayer.js; il messaggio viene ricevuto dal suo metodo onmessage():
onmessage: function onmessage(event) { let message = event.data; debug("Received message from worker: " + JSON.stringify(message)); switch (message.type) { case "callStateChange": // This one will handle its own notifications. this.handleCallStateChange(message.call); break; ...
e successivamente inviato al content process tramite il Parent Process Message Manager (PPMM):
handleCallStateChange: function handleCallStateChange(call) { [some internal state updating] ppmm.sendAsyncMessage("RIL:CallStateChanged", call); }
Nel content process, il messaggio viene ricevuto dal metodo receiveMessage()
definito nel servizio nsIRILContentHelper
, dal Child Process Message Manager (CPMM):
receiveMessage: function receiveMessage(msg) { let request; debug("Received message '" + msg.name + "': " + JSON.stringify(msg.json)); switch (msg.name) { case "RIL:CallStateChanged": this._deliverTelephonyCallback("callStateChanged", [msg.json.callIndex, msg.json.state, msg.json.number, msg.json.isActive]); break;
Questo, a sua volta, invoca nsIRILTelephonyCallback.callStateChanged()
per ciascun oggetto che ha registrato un metodo di callback. Ogni applicazione web che accede alla API window.navigator.mozTelephony registra un proprio metodo di callback che inoltrerà gli eventi al codice JavaScript all'interno dell'applicatione web, sia come evento di cambio stato che di un evento di chiamata in ingresso.
NS_IMETHODIMP Telephony::CallStateChanged(PRUint32 aCallIndex, PRUint16 aCallState, const nsAString& aNumber, bool aIsActive) { [...] if (modifiedCall) { // Change state. modifiedCall->ChangeState(aCallState); // See if this should replace our current active call. if (aIsActive) { mActiveCall = modifiedCall; } return NS_OK; } nsRefPtr<TelephonyCall> call = TelephonyCall::Create(this, aNumber, aCallState, aCallIndex); nsRefPtr<CallEvent> event = CallEvent::Create(call); nsresult rv = event->Dispatch(ToIDOMEventTarget(), NS_LITERAL_STRING("incoming")); NS_ENSURE_SUCCESS(rv, rv); return NS_OK; }
Le appicazioni possono ricevere tali eventi e aggiornare di conseguenza la propria interfaccia utente:
handleEvent: function fm_handleEvent(evt) { switch (evt.call.state) { case 'connected': this.connected(); break; case 'disconnected': this.disconnected(); break; default: break; } }
Dai uno sguardo all'implementazione di handleEvent()
nell'applicazione Dialer come ulteriore esempio.
3G dati
Una "chiamata dati" viene inizializzata tramite un messaggio RIL; questo abilita la modalità di trasferimento dati al modem. Tale chiamata crea e attiva un'interfaccia Point-to-Point Protocol (PPP) nel kernel linux che potrà essere configurata utilizzando le interfacce usuali.
Nota: Questa sessione necessita di essere scritta.
API DOM relative
Segue la lista delle API DOM relative alla comunicazione con RIL:
- Telephony API
- SMS API
- Mobile Connection API
Wi-Fi
La maggior parte del lavoro di back end per Firefox OS viene effettuato dal processo wpa_supplicant
. Ciò significa che l'attività principale del back end è quella di gestire il supplicant e di svolgere alcuni compiti ausiliari quali caricare il driver del WIFI e abilitare o disabilitare le interfacce di rete. In pratica, questo significa che il back end è la macchina a stati, con un'evoluzione degli stati che segue quella del supplicant.
Nota: Molto di ciò che accade nel WIFI dipende dai cambiamenti di stato nel processo wpa_supplicant
.
L'implementazione delle componenti che costituiscono il WiFi è suddivisa in due file:
- dom/wifi/DOMWifiManager.js
- Implementa le API esposte ai web content, come definite in
nsIWifi.idl
. - dom/wifi/WifiWorker.js
- Implementa la macchina a stati e il codice che gestisce il supplicant.
Questi due file comunicano tra loro utilizzando il message manager. Il back end rimane in ascolto di messaggi che richiedono certi azioni, quali l'"associazione" e risponde con un messaggio quando l'azione richiesta è stata espletata.
La parte del DOM si pone in ascolto sia sulle risposte che di eventuali eventi che notificano cambiamenti di stato o l'aggiornamento di informazioni.
Nota: Ogni API sincrona del DOM viene implementata tenendo in cache i dati nella sua parte dell'interfaccia. Messaggi sincroni vanno evitati ogni qualvolta sia possibile.
WifiWorker.js
Questo file implementa la logica principale dell'interfaccia WIFI. Viene eseguito nel processo chrome e istanziato dal SystemWorkerManager. È suddiviso in due sezioni: una funzione anonima e il WifiWorker
. La funzione anonima finisce per essere il WifiManager
fornendo una API locale, comprese le notifiche per eventi quali le connessioni al supplicant e i risultati. In generale, contiene poca logica, risponde semplicemente con le informazioni richieste e controlla i dettagli della connessione con il richiedente.
Il WifiWorker
si colloca fra il WifiManager
e il DOM. Risponde agli eventi e li inoltra al DOM, riceve richieste dal DOM ed esegue le appropriate azioni sul supplicant. Inoltre mantiene le informazioni di stato del supplicant e queli azioni intraprendere di conseguenza.
DOMWifiManager.js
Implementa la API del DOM, gestisce le richieste da e verso i chiamanti e il WiFi worker. C'è veramente poca logica.
Nota: Per evitare messaggi sincroni nel processo chrome il WIFI manager tenendo in cache lo stato in funzione dell'evento ricevuto.
C'è un unico messaggio sincrono, inviato nel momento in cui la API del DOM viene istanziata per ottenere lo stato del supplicant.
DHCP
Il DHCP e il DNS sono gestiti dal processo di dhcpcd
, lo standard DHCP client di Linux. Purtroppo esso non è in grado di reagire quanto la connessione di rete viene persa. Come conseguenza Firefox OS termina e riavvia dhcpcd
ogni qualvolta si connette ad una data rete wireless.
dhcpcd
è anche responsabile per la configurazione della rotta di default; viene inoltre effettuata una chiamata al network manager per configurare i server DNS.
Network Manager
Il gestore di rete (Network Manager) configura le interfacce di rete per le connettività 3G e WIFI.
Nota: Questa sessione necessita di essere scritta.
Processi e thread
Firefox OS utilizza i thread POSIX per implementare tutti i thread applicativi, inclusi i thread principali di ciascuna applicazione, i web workers e gli helper threads. I control group vengono utilizzati per gestire la priorità di esecuzione di processi e thread utilizzando lo scheduler standard di Linux. In funzione dello stato, il processo viene assegnato ad un differente control group. Abbiamo attualmente 6 livelli di priorità corrispondenti a 5 gruppi di controllo:
Priorità | Control group | Utilizzato da |
---|---|---|
MASTER |
Processo b2g | |
FOREGROUND_HIGH |
apps/critical | Applicazioni che utilizzano CPU wakelock |
FOREGROUND |
apps | Applicazioni in foreground (visibili all'utente) |
FOREGROUND_KEYBOARD |
apps | Applicazione tastiera |
BACKGROUND_PERCEIVABLE |
apps | Applicazione audio in background (non visibili all'utente) |
BACKGROUND |
apps/bg_non_interactive | Tutte le altre applicazioni in esecuzione in background |
Alcuni livelli condividono lo stesso control group, in quanto quei livelli differiscono nella modalità con cui vengono gestiti dall'out of memory killer. Tutte le proprietà possono essere configurate durante il processo di build attraverso le preferenze; le principali proprietà sono diponibili nel file b2g/app/b2g.js
.
I seguenti control groups vengono attualmente utilizzati:
Percorso | Allocazione CPU | Descrizione |
---|---|---|
50% del tempo totale di CPU | Control group di root riservato per il processo b2g principale per i deamon di sistema | |
apps |
50% del tempo totale di CPU | Applicazioni regolari |
apps/critical |
95% di apps |
Applicazioni critiche |
apps/bg_perceivable |
10% di apps |
Applicazioni di background percepibili |
apps/bg_non_interactive |
5% di apps |
Applicazioni di background |
Nota: Per maggiori informazioni sull'out-of-memory killer e sulla modalità con cui Firefox OS gestisce le situazioni di memoria scarsa puoi leggere Out of memory management on Firefox OS.
All'interno di un processo il thread principale eredita il valore di nice del processo, mentre ai thread dei web worker viene assegnato un valore di nice maggiore di un'unità rispetto al thread principale, pertanto vengono eseguiti con una priorità inferiore. Questo per evitare che worker che fanno uso intensivo di CPU rallentino il thread principale. Le priorità sui processi vengono modificate in occasioni di eventi maggiori quali l'invio in background o foreground dell'applicazione, l'avvio di una nuova applicazione o l'acquisizione di CPU wakelock.
Note: Attualmente non è possibile utilizzare cgroups su dispositivi ICS a causa di un bug nel kernel.