Este artículo es un panorama de alto nivel de la arquitectura de la plataforma Firefox OS, que introduce conceptos fundamentales y explica cómo sus componentes interactúan a un nivel básico.
Nota: recuerda que Firefox OS todavía se encuentra en estado de pre-lanzamiento. La arquitectura descrita aquí no necesariamente es la final y que otros elementos todavía pueden estar sujetos a cambios.
Terminología de Firefox OS
Existen algunos términos que debes entender antes de continuar leyendo nuestra documentación sobre Firefox OS.
- B2G
- Sigla de Boot to Gecko.
- Boot to Gecko
- El nombre código de ingeniería para el sistema operativo Firefox OS.
- Firefox OS
- Es básicamente la marca y servicios de soporte de Mozilla (y sus socios de OEM) aplicados sobre Boot to Gecko para crear el producto final de lanzamiento.
- Gaia
- La interfaz de usuario de la plataforma Firefox OS. Cualquier cosa dibujada en la pantalla una vez que Firefox OS ha sido iniciado es un producto de la capa Gaia. La misma implementa la pantalla de seguridad (lock screen), pantalla principal (home screen) y todas las aplicaciones estándares de cualquier teléfono inteligente moderno. Gaia se implementa en su totalidad empleando HTML, CSS y JavaScript. Las interfaces subyacentes al sistema operativo son Web APIs de código abierto, que se implementan por medio de la capa Gecko. Las aplicaciones de terceros se pueden instalar en paralelo con la capa Gaia.
- Gecko
- Este es el runtime de aplicaciones de Firefox OS, es decir, la capa que provee todo el soporte para el trío de estándares de código abierto: HTML, CSS y JavaScript. Es responsable de que esas APIs funcionen bien en cualquier sistema operativo soportado por Gecko. Esto implica que Gecko incluya, además de otras cosas, paquetes de redes y de gráficos, un motor de diagramación, una máquina virtual de JavaScript y capas de adaptación (porting).
- Gonk
- Gonk es el sistema operativo de bajo nivel de la plataforma Firefox OS que consiste un núcleo/kernel Linux (basado sobre el Android Open Source Project (AOSP)) y una capa de abstracción de hardware de espacio de usuario (HAL por su sigla en inglés). El kernel y varias de las librerías de espacio de usuario son proyectos comunes de código abierto: Linux, libusb, bluez, y sucesivos. Algunas de las otras partes de la HAL se comparten con la AOSP: GPS, cámara y otros. Se podría decir que Gonk es una distribución de Linux muy sencilla. Gonk es una capa de adaptación (port) de dispositivos: un adaptador entre el hardware y Gecko. Gonk es una distribución de Linux bastante sencilla que puede ser tratada como un adaptador Gecko empardado con capas de adaptación Gecko —entonces Gonk es un objetivo para adaptar Gecko a Firefox OS así como también hay adaptadores de Gecko para OS X, Windows y Android. Como el Proyecto Firefox OS goza de control total sobre Gonk, podemos exponer interfaces a Gecko que no podrían ser expuestas en otros sistemas operativos. Por ejemplo, Gecko posee a través de Gonk acceso directo al conjunto completo de telefonía y al buffer de pantalla pero no tiene este acceso en otros sistemas operativos.
- Jank
- Este término, generalmente empleado en el área de las aplicaciones móviles, se refiere al efecto causado por código lento o ineficiente en una aplicación, que podría bloquear la actualización de la interfaz de usuario y provocar su lentitud o que no responda. Nuestros ingenieros de Gaia se valen de numerosas técnicas de optimización para evitar esto lo mejor posible.
Diagrama estructural
Procedimiento de arranque de Firefox OS
Esta sección describe el proceso por el que los dipositivos con Firefox OS arrancan (butean), cuáles partes están involucradas en el proceso y dónde. A modo de referencia rápida, el flujo del arranque general del sistema va desde los cargadores de arranque (bootloaders) en el espacio del núcleo/kernel al init en el código nativo, a B2G y después a Geko en el espacio de usuario y después finalmente a la aplicación de sistema, gestor de ventanas y posteriormente a la pantalla de inicio de la aplicación en Gecko. Sobre ese conjunto se ejecutan todas las otras aplicaciones.
El proceso de arranque encadenado (bootstrapping)
Cuando un dispositivo con FirefoxOS se enciende, la ejecución se inicia en el cargador del arranque primario (bootloader). Desde allí, el proceso de la carga del SO principal procede normalmene; una sucesión de arranques de jerarquías crecientes inicia el siguiennte arranque en la cadena. Al final del proceso, se delega la ejecución al núcleo/kernel Linux.
Hay algunos puntos destcables sobre el proceso de arranque:
- Los arrancadores generalmente muestran la primera pantalla de sistema que ve el usuario durante el inicio; generalmente es un logo del fabricante/vendedor.
- Los arrancadores cargan por flash una imagen [virtual] en el dispositivo. Diferentes dispositivos emplean difierentes protocolos; la mayoría de los teléfonos emplean el fastboot protocol (protocolo de carga rápida), pero el Samsung Galaxy S II usa el protocolo Odin.
- Hacia el final del proceso de arranque encadenado, suele cargarse la imagen virtual del módem y se ejecuta en el procesador del módem. Cómo ocurre esto es específico de cada dispositivo y puede tambien serlo del fabricante.
El kernel Linux
El núcleo Linux empleado por Gonk es muy similar a la versión de Linux difundida (upstreamed) de la que deriva (basada sobre el Android Open Source Project). Existen cambios hehos por el AOSP que todavía no han sido difundidos. Además, los fabricantes y vendedores a veces modifican el núcleo y cargan esos cambios a la versión de difusión de acuerdo con su intinerario. En términos generales, el núcleo Linux es muy parecido al original.
El proceso de arranque de Linux se encuentra bien documentado en la internet por lo tanto este artículo no lo cubrirá.
El núcleo Linux activará dispositivos y ejecutará procesos esenciales definidos en init.rc
y su sucesor init.b2g
.rc
para arrancar procesos esenciales como b2g
[procesos básicos de Firefox OS, contenedores de Gecko] y rild
[un proceso relacionado con la telefonía que puede ser específico de cada chip] —vaya más abajo para ver más detalles. Al final del proceso, un proceso init
de espacio de usuario (userspace) se lanza, como ocurre en la mayoría de los sistemas operativos del tipo UNIX.
Una vez que el proceso init
se ha lanzado, el núcleo Linux administra las llamadas del sistema desde el espacio de usuario, las interrupciones y semejantes desde los dispositivos de hardware. Algunas de las características de hardware se exponen al espacio de usuario a través de sysfs
. Por ejemplo, aquí hay un fragmento de código que lee el estado de la batería en 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); }
Más sobre el proceso init
El proceso init
en Gonk gestiona el montaje de los archivos de sistema requeridos y activa los procesos de systema. Después de eso, se mantiene activo como gestor de procesos. Esto es muy similar al init en otros sistemas operativos similares a UNIX. Interpreta scripts [los archivos init.rc
] que consisten de comandos que describen lo que debería ser hecho para iniciar servicios varios. El init.rc
de FirefoxOS suele ser el init.rc original
de Android para ese dispositivo, parchado para incluir los requisitos de arranque de FirefoxOS, y varía de dispositivo a dispositivo.
Una de las tareas fundamentales que maneja el proceso init
es el inicio del proceso b2g
; éste es el núcleo del sistema operativo FirefoxOS.
El código para tal init.rc
es el siguiente:
service b2g /system/bin/b2g.sh class main onrestart restart media
Nota: las variaciones de init.rc
dependerán de dispositivo a dispositivo; a veces init.b2g.rc
sólo es anexadoo, y a veces los parches son más significativos.
Arquitectura de los procesos del espacio de usuario (userspace)
Resulta muy útil echar un vistazo de alto nivel a cómo varios componentes del Firefox OS se articulan e interactúan entre sí. Este diagrama muestra los procesos primarios de espacio de usuario en Firefox OS.
Nota: recuerda que como Firefox OS se encuentra en desarrollo activo, este diagrama puede estar sujeto a cambios y puede ser impreciso parcialmente.
El proceso b2g es el proceso primario de sistema. Se ejecuta con privilegios altos; tiene acceso a la mayoría del hardware. b2g se comunica con el módem, almacena en el buffer de pantalla e interactúa con el GPS, cámaras y otros dispositivos. Internamemte, se ejecuta con una capa de Gecko (implementada por libxul.so
). Ver Gecko para más detalles sobre cómo funciona la capa Gecko y cómo b2g se comunica con ella.
b2g
El proceso b2g
puede dar lugar a un número de procesos de contenido de privilegios limitados. Estos procesos albergan la carga de aplicaciones web y otros contenidos. Estos procesos se comunican con el proceso principal del servidor Gecko a través de IPDL, un sistema de envio de mensajes.
El proceso b2g
ejecuta lixbul, el cual referencia a b2g/app/b2g.js
para obtener las preferencias de fábrica. De las preferencias se abrirá el archivo HTML descriptor b2g/chrome/content/shell.html
, que es compilado en un archivo omni.ja.
El shell.html
incluye el archivo b2g/chrome/content/shell.js
, que dispara la aplicación system
de Gaia.
rild
El proceso rild
es la interfaz del proceso del módem. rild
es el daemon que implementa La capa de Interfaz de la Radio [Radio Interface Layer (RIL)]. Es un componente de codigo cerrado implementado por el fabricante/vendedor de hardware para comunicarse con el hardware del módem. rild
hace posible que el código cliente se comunique con un empalme de dominio-UNIX al que se enlaza. Se inicia con un código como este en el init
script:
service ril-daemon /system/bin/rild socket rild stream 660 root radio
rilproxy
En Firefox OS, el cliente rild
client es el proceso rilproxy
. Este actúa como un proxy de reenvio mudo (dumb proxy) entre rild
y b2g
. Este proxy es necesario como un detalle de implementación; es de hecho necesario. El
código de rilproxy se encuentra en GitHub.
mediaserver
El proceso mediaserver
controla la reproducción de audio y video. Gecko se comunica con él a través de un mecanismo de Llamada de Procedimiento Remota de Android [Android Remote Procedure Call (RPC)]. Algunos de los contenidos multimedia que Gecko puede reproducir (OGG Vorbis audio, OGG Theora video, y WebM video) son decodificados por Gecko y enviados directamente al proceso mediaserver
. Otros archivos multimedia son decodificados por libstagefright
, que puede acceder códecs del fabricante y codificadores del hardware.
Nota: El proceso mediaserver
es un componente "provisional" de Firefox OS; existe sólo para ayudar en el trabajo de desarrollo inicial pero se espera que se descarte con el tiempo; lo que seguramente no ocurrirá antes de la version 2.0 de Firefox OS.
netd
El proceso netd
se usa para configurar interfaces de red.
wpa_supplicant
El proceso wpa_supplicant
process es el daemon estándar tipo UNIX que maneja la conectividad con los puntos de acceso WiFi.
dbus-daemon
El dbus-daemon implementa el D-Bus, un sistema de mensajes de bus que Firefox OS emplea para las comunicaciones por Bluetooth.
Gecko
Gecko, como se lo mencionó previamente, es la implementación de estándares web (HTML, CSS, y JavaScript) que se usa para implementar todos lo que el usuario ve en Firefox OS, y controlar las interacciones con el hardware del telefono.
Las aplicaciones Web conectan HTML5 con el hardware de forma controlada a traves de API's web seguras, implementadas en Gecko. Las API's Web proveen de acceso programado a las caracteristicas implicitas en el hardware del dispositivo (como la bateria, o la vibracion), a medida que los datos son guardados, o estan disponibles, en el dispositivo. El contenido web invoca a las API's web accesibles con HTML5.
Una app consiste en una coleccion de codigos web HTML5 relacionados. Para construir aplicaciones web que funcionen en dispositivos Firefox OS, los desarrolladores simplemento ensamblan, empaquetan y distribuyen este contenido web. En tiempo de ejecucion, este contenido web es interpretado, compilado y renderizado en una navegador web. Para mas informacion sobre Apps, puedes consultar el App Center
Note: Para buscar en la base de código de Gecko, se puede usar https://dxr.mozilla.org. Es más elegante y ofrece buenas características de referemcias. pero con repositorios limitados. También podría usar el tradicional https://mxr.mozilla.org, que contiene más proyectos de Mozilla.
Diagrama de arquitectura de Gecko
- Framework de seguridad: formado por
- Gestor de Permisos: Da acceso a las funcionalidades de la API Web
- Lista de Control de Acceso: Matriz de roles y permisos requeridos para acceder a las funcionalidades de la API Web.
- Validador de Credenciales: Autentificacion de apps y usuarios
- Conjunto de Permisos: Conjunto de privilegios requeridos para acceder a las funcionalidades de la API Web
- API Web: Conjunto de APIs estandar que exponen las funcionalidades del hardware al contenido web.
Proveen aplicaciones web con seguridad, acceso programado a las caracteristicas implicitas del hardware del dispositivo movil, mientran el dato este almacenado -o accesible- al dispositivo. - I/O: Interfaz al hardware y almacenamiento de datos.
- Actualizaciones de Software: Obtienen e instalan las actualizaciones del software del sistema y aplicaciones de terceros.
- Diseñador de contenidos y renderizado: Motor que analiza sintacticamente, interpreta y ejecuta el contenido web y. con la informacion de formato, muestra el contenido formateado al usuario
- Proceso b2g: (Gecko) corre con alto nivel de privilegios los procesos del sistema que tienen acceso a las caracteristicas del telefono movil.
Las aplicaciones en ejecucion son procesos hijo de b2g.
Archivos de Gecko relacionados con Firefox OS
b2g/
La carpeta b2g contiene es su mayoría funciones relacionadas con Firefox OS.
b2g/chrome/content
Contiene archivos de Javascript ejecutados sobre la aplicación de sistema.
b2g/chrome/content/shell.html
El punto de entrada a Gaia — el HTML para la aplicación de sistema. shell.html
toma de settings.js
and shell.js
:
settings.js
contiene parámetros de configuración básicos (default) de sistema.
b2g/chrome/content/shell.js
shell.js
es el primer script que se carga en la aplicación de sistema de Gaia.
shell.js
importa todos los módulos requeridos, registra los detectores de clave (key listeners), define sendCustomEvent
y sendChromeEvent
para que se cominiquen con Gaia, y provee ayudantes de instalación de aplicaciones web: indexedDB quota, RemoteDebugger, ayudante de teclado, y la herramienta para captura de pantalla.
Pero la función más importante de shell.js
es lanzar la aplicación de sistema
de Gaia, después entregarle todo el trabajo general de administración del sistema.
let systemAppFrame = document.createElementNS('https://www.w3.org/1999/xhtml', 'html:iframe'); ... container.appendChild(systemAppFrame);
b2g/app/b2g.js
Este script contiene configuraciones predefinidas, como about:config en el navegador, y la misma que Gaia's pref.js. Estas configuraciones se pueden cambiar desde la aplicación de congifuraciones y se pueden sobreescribir con user.js en el script de construcción Gaia.
dom/{API}
Nuevas implementaciones de la API (post-b2g) se localizarán en dom/
. Las APIs anteiores se localizarán en dom/base
, for example Navigator.cpp
.
dom/apps
.jsm
se cargarán implementaciones de API — .js
API tales como webapp.js
install, getSelf
, etc.
dom/apps/PermissionsTable.jsm
Se definen todos los permisos en PermissionsTable.jsm
dom/webidl
WebIDL es el lenguaje empleado para definir web APIs. La información sobre los atributos soportados se encuentra en WebIDL_bindings.
hal/gonk
Este directorio contiene archivos sobre la capa de adaptación gonk..
Archivos generados
module/libpref/src/init/all.js
Contiene todos los archivos de configuración.
/system/b2g/ omni.ja and omni.js
Contiene el paquete de estilos para los recursos en el dispositivo.
Proceso de eventos de ingreso
La mayor parte de las acciones en Gecko se activan por acciones de usuario. Estas acciones son representadas por eventos de emtrada (tales como presionar botones, tocar la pantalla y similar). Estos eventos entran a Gecko a través de Gonk implementation perteneciente a nsIAppShell
, que es una interfaz de Gecko empleada para representar los puntos de entrada primaria de una aplicación de Gecko, es decir, el controlador del dispositivo de ingreso llama métodos en el objeto nsAppShell
que representa el subsistema de Gecko para así enviar eventos a la interfaz de usuario.
Por ejemplo:
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(); }
Estos eventos provienen del sistema estándar Linux input_event
. Firefox OS emplea light abstraction layer sobre eso; lo que provee algunas características útiles como filtrar los eventos. Se puede ver el código que crea eventos de ingreso en el método EventHub::getEvents()
que se encuentra en widget/gonk/libui/EventHub.cpp.
Un vez que Gecko recivió los eventos, se envían a DOM por 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); }
Después de lo anterior, los eventos son consumidos por el propio Gecko o despachados a aplicaciones web como Eventos DOM para ser procesados posteriormente.
Graficos
En si nivel más inferior, Gecko emplea OpenGL ES 2.0 para establecer un contexto GL que envuelva los buffers del hardware. Esto es realizado en la implementación de Gonk en nsWindow
por medio de un código similar a este:
gNativeWindow = new android::FramebufferNativeWindow(); sGLContext = GLContextProvider::CreateForWindow(this);
La clase FramebufferNativeWindow
es obtenida directamente desde Android; ver FramebufferNativeWindow.cpp
. Este emplea la API de gralloc para acceder al controlador de gráficos con el fin de mapear los buffers del dispositivo framebuffer a la memoria del dispositivo.
Gecko emplea su sistema (de) Layers para componer contenido dibujado en la pantalla. En resumen, ocurre lo siguiente:
- Gecko dibuja regiones distintas de las páginas en los buffers de memoria, A veces, estos buffers están en la memoria del sistema; otras veces, son texturas mapeadas en el espacio de direcciones de Gecko, lo que siginifica que Gecko está dibujando directamente en la memoria de video. Esto se realiza generalmente en el método
BasicThebesLayer::PaintThebes()
. - Entonces, Gecko, compone todas estas texturas en la pantalla empleando comandos OpenGL. Esta composición tiene lugar en
ThebesLayerOGL::RenderTo()
.
Los detalles de cómo Gecko maneja el muestreo (rendenring) de contenido web se encuentra fuera del alcance de este documento.
Capa de Abstracción de Hardware (HAL)
La capa de abstracción de hardware de Gecko es una de sus capas de adaptación (porting). Gestiona los accesos de bajo nivel a las interfaces del sistema a lo largo de múltiples plataformas usando una API de C++ accesible desde los niveles superiores de Gecko. Estas APIs son implementadas plataforma a plataforma dentro de la HAL de Gecko. Esta capa de abstracción de hardware no es expuesta directamente a código JavaScript dentro de Gecko.
Cómo funciona HAL
Vamos a considerar la API Vibration
como ejemplo. la HAL para esta API se define en hal/Hal.h. Resumiendo (simplificando el método de firma para hacerlo más claro), tienes esta función:
void Vibrate(const nsTArray &pattern);
Esta es la función que el código de Gecko llama par activar la vibración del dispositivo de acuerdo con un patrón específico; una función correspondiente existe para cancelar la vibración activa. La implementación de GONK para este método está en hal/gonk/GonkHal.cpp:
void Vibrate(const nsTArray &pattern) { EnsureVibratorThreadInitialized(); sVibratorRunnable->Vibrate(pattern); }
Este código envía la petición para el inicio de la vibración a otro conjunto de procesos, que se implementa en VibratorRunnable::Run()
. El bucle principal de este hilo seria parecido a esto:
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()
es la API HAL de GONK que enciende el motor de vibración. Internamente, este método envía un mensaje al controlador de núcleo (kernel driver) al escribir un valor en un objeto de kernel empleando sysfs
.
Implementaciones de la Fallback HAL API
Las APIs HAL de Gecko tienen soporte en todas las plataformas. Cuando se construye Gecko para una plataforma que no expone una interfaz a los motores de vibración (como una computadora de escritorio) entonces se vale de una implemenación de fallback de la API de HAL. Para la vibración, este esta implementado en hal/fallback/FallbackVibration.cpp.
void Vibrate(const nsTArray &pattern) { }
Implementaciones de Sandbox (entorno cerrado)
Debido a que la mayoría del contenido de la red se ejecuta en procesos de contenido con privilegios bajos, no podemos suponer que esos procesos tienen los privilegios necesarios para poder (por ejemplo) activar o desactivar el motor de vibración. Además, queremos tener una ubicación central para controlar las posibles condiciones de carrera (race conditions). En la HAL de Gecko, esto es realizado por medio de la implementación de una sandbox de la HAL. Esta sandbox simplemente funciona como un proxy para la peticiones realizadas por los procesos de contenido y las reenvía al proceso del "servidor Gecko". Las peticiones de proxy se envían empleando IPDL.
Para la vibración, la función Vibrate()
se encarga de la gestión y se la implementa en hal/sandbox/SandboxHal.cpp:
void Vibrate(const nsTArray& pattern, const WindowIdentifier &id) { AutoInfallibleTArray p(pattern); WindowIdentifier newID(id); newID.AppendProcessID(); Hal()->SendVibrate(p, newID.AsArray(), GetTabChildFrom(newID.GetWindow())); }
Esto envía un mensaje definido por la interfaz PHal, descrita por IPDL en hal/sandbox/PHal.ipdl. El método se describe aproximadamente de la siguiente manera:
Vibrate(uint32_t[] pattern);
El receptor de este mensaje es la HalParent::RecvVibrate()
method in hal/sandbox/SandboxHal.cpp, el cual seria algo parecido a esto:
virtual bool RecvVibrate(const InfallibleTArray& pattern, const InfallibleTArray &id, PBrowserParent *browserParent) MOZ_OVERRIDE { hal::Vibrate(pattern, newID); return true; }
Este omite algunos detalles que no son relevantes a este punto pero, en cualquier caso, demuestra cómo el mensaje progresa desde un proceso de contenido a través de Gecko hasta Gonk, luego a la implementación de la HAL de Gonk Vibrate()
, y finalmente al controlador de vibración.
APIs DOM
Las interfaces DOM son, esencialmente, la forma en la que el contenido web se comunica con Gecko. Hay más información al respecto y, si estás interesado en detalles extras, puedes leer sobre el DOM. Las interfaces DOM se definen empleando IDL, que compone una interfaz de función foránea (foreign function interface, FFI) y un objeto modelo (OM) entre JavaScript y C++.
La API de vibración se expone al contenido web por medio de una interfaz IDL, que se la provee en nsIDOMNavigator.idl:
[implicit_jscontext] void mozVibrate(in jsval aPattern);
El argumento jsval
indica que mozVibrate()
(que es nuestra implementación vendedor-prefijada de esta especificación no finalazida de vibración) acepta como ingreso cualquier valor de JavaScript. El compilador IDL, xpidl
, genera una interfaz C++ que entonces se implementa por la clase Navigator
en Navigator.cpp
.
NS_IMETHODIMP Navigator::MozVibrate(const jsval& aPattern, JSContext* cx) { // ... hal::Vibrate(pattern); return NS_OK; }
Hay mucho más código en este método de lo que ves aquí, pero no es importante para el proposito que estamos tratando. El hecho es que la llamada a hal::Vibrate()
transfiere el control de DOM a la HAL Gecko. Desde allí, entramos a la implementación de HAL tratada en la sección previa y continuamos hacia el controlador. Además, la implementación DOM no se preocupa por la plataforma en la que está corriendo (Gonk, Windows, OS X, o cualquier otra). Tampoco le interesa si el código está corriendo en un proceso de contenido o en un proceso del servidor Gecko. Esos detalles se dejan de lado para que los gestionen los procesos de nivel bajo del sistema.
La API de vibración es muy simple, lo que la convierte un ejemplo excelente. La API de SMS es más compleja porque emplea su propia capa de envío remoto que conecta los procesos de contenido con el servidor.
Capa de Interfaz de Radio (CIR/RIL)
La RIL (Radio Interface Lyer) ya se mencionó en la sección The userspace process architecture. Esta sección examinará con más detalle cómo las diferentes partes de esta capa interactúan.
Los componentes principales involucrados en la RIL son los siguientes:
rild
- El daemon que habla al firmware del módem de fábrica.
rilproxy
- El daemon que referencia mensajes (proxy) entre
rild
y Gecko (que está implementado en el procesob2g
). Esto soluciona el problema de permisos que surje cuando se trata de comunicar conrild
directamente ya que sólo se puede comunicar conrild desde el grupo
radio
. b2g
- Este proceso, también conocido como el proceso chrome, implementa Gecko. Las partes de el que se relacionan con la Capa de Interfaz de Radio son dom/system/gonk/ril_worker.js, las cuales implementan una cadena de procesos de trabajo (worker thread) que se comunica con
rild
a través derilproxy
es implementa el estadio radial (radio state machine) y la interfaznsIRadioInterfaceLayer
, que es el servicio XPCOM de la cadena de procesos principal, que actúa principalmente como un intercambio de mensajes entre la cadenaril_worker.js
y otros numerosos componentes de Gecko, incluidos el proceso de contenido de Gecko. - Proceso de contenido de Gecko (Gecko's content process)
- Dentro del proceso de contenido de Gecko, la interfaz
nsIRILContentHelper
provee un servicio XPCOM que permite a algunas partes de código implementado de DOM, tales como las APIs Telephony y SMS, comunicarse con la interfaz de radio, la cual es un proceso chrome.
Ejemplo: comunicación desde rild a DOM
Echemos una mirada al ejemplo de cómo las partes del nivel inferior del sistema se comunican con código DOM. Cuando el módem recibe una llamada entrante, la notifica a rild
empleando un mecanismo de fábrica. entonces rild
prepara un mensaje para su cliente de acuerdo con el protocolo "open", que se describe en ril.h
. En el caso de una llamada entrante, se genera un mensaje RIL_UNSOL_RESPONSE_CALL_STATE_CHANGED
y lo envía rild
a rilproxy
.
rilproxy
, implementada en rilproxy.c
, recibe este mensaje en bucle principal, que envía (poll) la conexión a rild
utilizando código como éste:
ret = read(rilproxy_rw, data, 1024); if(ret > 0) { writeToSocket(rild_rw, data, ret); }
Una vez que el mensaje es recibido por rild
, se lo reenvía a Gecko en el empalme que conecta rilproxy
con Gecko. Gecko recibe el mensaje enviado en su IPC thread:
int ret = read(fd, mIncoming->Data, 1024); // ... handle errors ... mIncoming->mSize = ret; sConsumer->MessageReceived(mIncoming.forget());
El consumidor de estos mensajes es SystemWorkerManager, que reempaqueta los mensajer y los envía a la cadena ril_worker.js
que implementa la máquina de estado RIL; que se realia en the RILReceiver::MessageReceived()
method:
virtual void MessageReceived(RilRawData *aMessage) { nsRefPtr dre(new DispatchRILEvent(aMessage)); mDispatcher->PostTask(dre); }
La tarea postada en esa cadena a su vez llama la función onRILMessage()
, que se implemente en JavaScript. Esto se realiza empleando la función JS_CallFunctionName()
de la API de JavaScript:
return JS_CallFunctionName(aCx, obj, "onRILMessage", NS_ARRAY_LENGTH(argv), argv, argv);
onRILMessage()
está implementada en dom/system/gonk/ril_worker.js, que procesa los bytes del mensaje y los corta en fragmentos. Cada fragmento completo se envía a métodos de gestión (handler) individuales apropiadamente:
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); } }
Este código trabaja por medio del tipo de petición (request) del objeto, asegurándose de que sea definido como una función en código de JavaScript, para después llamar al método. Ya que ril_worker.js implementa cada tipo de petición (request type) en un método con el mismo nombre que el tipo de petición, es muy sencillo.
En nuestro ejemplo RIL_UNSOL_RESPONSE_CALL_STATE_CHANGED
, se llama al siguiente handler:
RIL[UNSOLICITED_RESPONSE_CALL_STATE_CHANGED] = function UNSOLICITED_RESPONSE_CALL_STATE_CHANGED() { this.getCurrentCalls(); };
Como puedes ver en el código de más arriba, cuando se recibe la notificación que el estado de la llamada ha cambiado, la máquina de estado (state machine) simplemente trae para sí el estado actual de la llamafa invocando el método getCurrentCall()
:
getCurrentCalls: function getCurrentCalls() { Buf.simpleRequest(REQUEST_GET_CURRENT_CALLS); }
Esto envía una petición de vuelta a rild
para solicitar el estado de todas las llamadas actuales. La petición regresa por un camino similar al que siguió el mensaje RIL_UNSOL_RESPONSE_CALL_STATE_CHANGED
message pero en la dirección opuesta (es decir, desde ril_worker.js
a SystemWorkerManager
a Ril.cpp
, después a rilproxy
y finalmente al rild
socket). rild
responde por el camino inverso y la respuesta llega al handler de ril_worker.js
's handler para el mensaje REQUEST_GET_CURRENT_CALLS
. Así es como se da lugar la comunicación bidireccional.
El estado de la llamda se procesay compara con el estado previo; si hay un cambio de estado, ril_worker.js notifica al servicio nsIRadioInterfaceLayer
de la cadena principal:
_handleChangedCallState: function _handleChangedCallState(changedCall) { let message = {type: "callStateChange", call: changedCall}; this.sendDOMMessage(message); }
Se implementa nsIRadioInterfaceLayer
en dom/system/gonk/RadioInterfaceLayer.js; se recibe el mensaje por su método 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; ...
Lo que todo esto realmente hace es enviar el mensaje al proceso de contenidos empleando el Administrador de Mensajes de Procesos Principales [Parent Process Message Manager (PPMM)]:
handleCallStateChange: function handleCallStateChange(call) { [some internal state updating] ppmm.sendAsyncMessage("RIL:CallStateChanged", call); }
En el proceso de contenidos, el mensaje es recibido por el método receiveMessage()
en el servicio nsIRILContentHelper
, del Administrador de Mensajes de Procesos Secundarios [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;
Esto, a su vez, llama a los métodos nsIRILTelephonyCallback.callStateChanged()
de cada uno de los objetos registrados de las respuesta de telefonía (registered telephony callback object). Cada aplicación web que accede la API window.navigator.mozTelephony
API ha registrado uno de ese tipo de objeto que envía los eventos al código JavaScript code en la aplicación web, así sea como un cambio de estado de un objeto de llamada existente o un nuevo evento de llamada incoming
.
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 call = TelephonyCall::Create(this, aNumber, aCallState, aCallIndex); nsRefPtr event = CallEvent::Create(call); nsresult rv = event->Dispatch(ToIDOMEventTarget(), NS_LITERAL_STRING("incoming")); NS_ENSURE_SUCCESS(rv, rv); return NS_OK; }
Las aplicaciones pueden recibir estos eventos y actualizar su interfaz de usuario:
handleEvent: function fm_handleEvent(evt) { switch (evt.call.state) { case 'connected': this.connected(); break; case 'disconnected': this.disconnected(); break; default: break; } }
Echa una mirada a la implementación de handleEvent()
in the Dialer application como un ejemplo más.
Datos 3G
Hay un mensaje RIL que inicia una "petición de datos" al servicio móvil; este activa el modo de transferencia de datos en el modem. Esta petición de datos termina creando y activando una interfaz Point-to-Point Protocol (PPP) en el kernel Linux que pueden ser configuradas usando las interfaces comunes.
Nota: Esta sección necesita ser redactada
APIs relacionadas con DOM
Esta sección lista las APIs de DOM que estan relacionadas con comunicaciones RIL.
- Telephony API
- SMS API
- Mobile Connection API
WiFi
El backend de WiFi para Firefox OS simplemente usa wpa_supplicant para hacer la mayor parte del trabajo.
Esto significa que el principal trabajo del backend es simplemente gestionar el supplicant, y hacer algunas tareas auxiliares como cargar el driver del WiFI y activar o desactivar la interfaz de red.
En resumen, esto significa que el backend es una maquina de estados, con los estados que indican el estado del supplicant.
Note: Muchas cosas interesantes que suceden en WiFi dependen profundamente de posibles cambios de estado en el proceso wpa_supplicant
.
La implementación del componente WiFi esta dividida en dos archivos.
- dom/wifi/DOMWifiManager.js
- Implementa lo que la API muestra al contenido web, tal como esta definido en
nsIWifi.idl
. - dom/wifi/WifiWorker.js
- Implementa la máquina de estados y el código que controla el supplicant.
Estos dos archivos se comunican con otro usando el gestor de mensajes.
El backend escucha los mensajes que requieran una determinada acción, como "asociar" y responde con una mensaje cuando la acción requerida ha sido completada.
El lado del DOM escucha los métodos de respuesta, asi como muchos mensajes de eventos que indican cambios de estado y actualización de información.
Note: Algunas API's síncronas de DOM son implementadas leyendo datos en el otro extremo de la tubería. Los mensajes síncronos son evitados siempre que es posible
WifiWorker.js
Este archivo implementa la logica principal detras de la interfaz WiFi. Se ejecuta en el proceso chrome (en construcciones multiproceso) y es instanciado por el SystemWorkerManager. El fichero es generalmente dividido en dos secciones: una gigantesca funcion anonima y WifiWorker (y su prototipo). La funcion anonima termina siendo el WifiManager proveyendo una API local, incluyendo notificaciones para eventos como una conexion al supplicant y escaneando los resultados cuando estan disponibles. En general. este contiene la logica simple y manda a su control exclusivo de consumidor sus acciones mientras estas simplemente responden a la informacion requerida y controla los detalles de la conexion con el supplicant.
El objeto WifiWorker se situa entre el WifiManager y el DOM. Este reacciona a eventos y los sigue al DOM; a su vez, este recibe las peticiones del DOM y realiza las acciones apropiadas en el supplicant. Este tambien mantiene informacion sobre el supplicant y lo siguiente que necesita hacer.
DOMWifiManager.js
Este implementa la API de DOM, transmitiendo mensajes hacia atras y llamadas de regreso y el actual WiFi worker. Hay muy poca logica envuelta aqui.
Nota: Para poder permitir mensajes sincronos al proceso chrome, el WiFi Manager necesita cachear el estado basado en el envio recibido.
Hay un solo mensaje sincrono, el cual es enviado en cuanto la API DOM se instancia, para poder obtener el estado actual del supplicant.
DHCP
DHCP y DND van de la mano con dhcpcd, el cliente DHCP estandar en Linux.
Sin embargo, este no permite reaccionar cuando la conexion de red se pierde.
Por eso, Firefox OS mata y reinicia dhcpcd cada vez que se conecta a una red inalambrica dada.
dhcpcd
es tambien responsable de ajustar la ruta por defecto; nosotros llamamos al gestor de redes para informar al kernel sobre los servidores DNS.
Gestor de Redes
El Gestor de Redes configura las interfaces de red abiertas por los datos 3G y los componentes WiFi
Nota: Esto necesita ser redactado.
Procesos e hilos
Firefox OS usa los hilos de POSIX para implementar todos los hilos de las aplicaciones - esto incluye los hilos principales de cada aplicación asi como los trabajadores web y los hilos de ayuda. Los grupos de control son usados para priorizar procesos e hilos de ejecución que dependen del planificador completamente justo del kernel Linux. Dependiendo del estado del proceso le asignaremos un grupo de control distinto. Actualmente tenemos 6 niveles de prioridad correspondientes a 5 grupos de control
Priority | Nice | Used for |
---|---|---|
MASTER |
0 | proceso principal de b2g |
FOREGROUND_HIGH |
0 | aplicaciones criticas que sostienen la cpu y el wakelock de alta prioridad. Este es actualmente reservado para el reloj y las aplicaciones de comunicacion |
FOREGROUND |
1 | aplicaciones en primer plano |
FOREGROUND_KEYBOARD |
1 | aplicacion de teclado |
BACKGROUND_PERCEIVABLE |
7 | aplicaciones en segundo plano que ejecuten audio o sostengan la CPU o el wakelock de alta prioridad y tengan al menos un controlador de mensajes de sistema registrado |
BACKGROUND_HOMESCREEN |
18 | aplicacion de pantalla principal |
BACKGROUND |
18 | resto de aplicaciones que se ejecuten en segundo plano |
Algunos niveles comparten el mismo grupo de control, esto es asi porque dichos niveles actualmente difieren en la manera que son tratados por el liberador de memoria. Todas las prioridades pueden ser ajustadas en tiempo de ejecucion mediante preferencias; las entradas relativas a esto se pueden encontrar en el b2g/app/b2g.js
file.
Actualmente se usan los siguientes grupos de control:
Ruta | Ocupación de la CPU | Descripción |
50% del tiempo total de CPU | Grupo de control de Root reservado para el proceso principal de b2g y los servicios del sistema | |
apps | 50% del tiempo total de CPU | Aplicaciones comunes |
apps/critical | 95% de las apps | Aplicaciones criticas |
apps/bg_perceivable |
10% de las apps | Aplicaciones perceptibles en segundo plano |
apps/bg_non_interactive |
5% de las apps | Aplicaciones en segundo plano |
Nota: para mas informacion sobre el liberador de memoria, y como Firefox OS gestiona las situaciones de memoria baja, lease Out of memory management on Firefox OS
Sin un proceso, el hilo principal hereda el "valor seguro" del proceso, mientras que los procesos web en curso toman un "valor seguro" que es un punto mas alto que el hilo principal, que corre la prioridad mas baja.
Esto esta hecho para prevenir que los ciclos intensivos en curso de la CPU ralenticen excesivamente el hilo principal. Todos los hilos de una aplicación son actualmente asignados al mismo grupo de control. Las prioridades de los procesos son cambiadas cuando un evento importante sucede, como cuando una aplicación cambia de segundo plano al primer plano, o una nueva aplicación inicia, o una aplicación acapara la CPU.
Nota: los cgroups soportados en dispositivos ICS estan actualmente rotos debido a un bug del kernel.