Arquitetura do Firefox OS

Draft
This page is not complete.

Este artigo traz uma visão geral da arquitetura da plataforma Firefox OS, introduzindo conceitos chaves e explicações básicas de como os componentes se interagem. Para explicações mais avançadas veja a seção See also.

Nota: Lembre-se que o Firefox OS ainda é um produto tipo pre-release, ou seja, a arquitetura não é final e está sujeita a mudanças. 

Terminologia Firefox OS

Existem alguns termos que você precisa saber para entender melhor o Firefox OS. 

B2G
Versão curta do Boot to Gecko.
Boot to Gecko
O codinome do projeto Firefox OS que era o termo anteriormente utilizado antes do nome oficial, Firefox OS, ser lançado. É comum ver o termo B2G ser utilizado em casos que não são específicos ao Firefox OS, por exemplo quando desenvolvedores estão interessados na infra estrutura geral e usos que não estão atrelados com a agenda ou prioridades do Firefox OS. 
Gaia
É a interface visual do Firefox OS, que apresenta a experiência do sistema ao usuário. A camada Gaia contém várias aplicações padrão como Tela de Bloqueio e Tela Inicial, e várias aplicações que são esperadas em um smartphone. O Gaia é completamente implementado com padrões web como HTML, CSS, JavaScript, dentre outros. As interfaces entre a camada web e os recursos do sistema operacional são feitas via APIs web — algumas abertas e outras que estão em desenvolvimento na camada Gecko. O Gaia oferece maneiras para a instalação de aplicações de terceiros. 
Gecko
Este é o runtime web específico para as aplicações do Firefox OS. O Gecko oferece uma infra estrutura de padrões web como HTML5, CSS, SVG, WebGL, JavaScript, dentre outros. Ele também oferece recursos do sistema como device API assim permitindo que aplicações web possam acessar serviços do disposivo móvel, como por exemplo o GPS e câmera. O Gecko também carrega várias funcionalidades básicas que são importantes para que aplicações web possam rodar corretamente, como a camada de rede e segurança, a camada de gráficos, o motor máquina virtual JavaScript, o engine de layout, dentre outros. 
Gonk
O Gonk é a camada mais baixa do sistema operacional e consiste em um kernel Linux (baseado no Android Open Source Project (AOSP)) e o HAL (hardware abstraction layer). O kernel e várias das bibliotecas são ligados a projetos de código aberto conhecidos: Linux, libusb, bluez, dentre outros. Algumas outras partes do HAL são compartilhadas com o projeto Android: GPS, camera, dentre outros. Pode-se dizer que o Gonk é uma distribuição Linux simplificada. O Gonk é uma plataforma alvo (porting target) do Gecko, ou seja, existe o target Gonk assim como existem plataformas de saída para Mac OS X, Windows, e Android. Uma vez que o projeto do Firefox OS tem controle sobre o Gonk, é possível que interfaces baixo nível sejam expostas ao Gecko, por exemplo, o acesso direto para a pilha de serviços de telefonia e também o acesso direto ao frame buffer gráfico. Estas características são novas e não ocorrem no caso de outros sistemas operacionais. 

Diagrama da arquitetura

Firefox OS Architecture

 
Arquitetura em alto nível do FirefoxOS

Procedimento de bootup do Firefox OS

Essa seção descreve o processo pelo qual o dispositivo Firefox OS inicializa, que partes são envolvidas e onde. Como uma referência rápida, o sistema geral de bootup flui dos bootloaders no Kernel space, para iniciar no código nativo, para o B2G e então para o Gecko no userspace, e finalmente na aplicação do sistema, gerenciador de janelas e na tela inicial dentro do Gecko. Os demais aplicativos são executados em cima dessa estrutura.

O processo de inicialização bootstrapping

Quando um dispositivo com o Firefox OS é ligado, a execução dá inicio no bootloader primário. A partir deste ponto é carregado o sistema operacional da forma tradicional. A partir desse momento, o processo e carga do sistema continua da forma típica; uma sucessão de bootloaders com nível cada vez mais alto inicializa a próxima carga na cadeia. Ao final desse processo, a execução é transferida para o kernel do Linux.

Existem alguns pontos que merecem destaque no processo de inicialização:

  • Os bootloaders normalmente apresentam uma tela inicial vista pelo usuário durante a inicialização; normalmente um logo do fabricante.
  • As implementações de bootloaders normalmente são feitas copiando uma imagem para a memória flash do dispositivo. Diferentes dispositivos, utilizam diferentes protocolos; muitos dispositivos utilizam o protocolo fastboot, mas o Samsung Galaxy S II, por exemplo, utiliza o protocolo odin.
  • Ao final do processo de inicialização, a imagem do modem é normalmente carragada e executada no processador do modem. Como isso acontece é uma característica muito específica do dispositivo e costuma ser uma tecnologia proprietária.

O kernel do Linux

O(s) kernel(s) do Linux utilizado(s) pelo Gonk é muito similar ao original do Linux, do qual é derivado. Existem algumas alterações feitas pelo AOSP (Android Open Source Project) que ainda não foram migradas. Adicionalmente, fabricantes alteram o kernel e geram essas alterações no seu próprio cronograma. Em geral, porém, o kernel do Linux está próximo do original.

O processo de inicialização do Linux está bem documentado em diversos lugares da internet, assim esse artigo não irá cobrí-lo.

O Kernel do Linux executará processos essenciais definidos em init.rc e o sucessor init.b2g.rc para inicializar processos como b2g (processo básico do FirefoxOS, que contém o Gecko) e rild (processos relacionados à telefonia e que possuem diversos chipsets proprietários) — veja abaixo para maiores detalhes. Ao final desse processo, um novo processo do userspace chamado init é iniciado, como na maioria do sistemas tipo UNIX.

Neste ponto da execução, o único "disco" montado é a RAM disk. Que é compilada durante o processo de compilação do Firefox OS, e contém programas utilitários críticos, como o init, assim como outros scripts de inicialização e módulos carregáveis do kernel.

Uma vez que o processo init é iniciado, o kernel do Linux manipula as chamadas do sistema do userspace e as interrupções vindas dos dispositivos de hardware. Muitas características do hardware são apresentadas ao userspace através do sysfs. Por exemplo, a seguir um trecho do código que lê o estado de carga da bateria a partir do 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);
}

O processo de inicialização

No Gonk, o processo init faz a montagem dos sistemas de arquivos e dispara os serviços do sistema operacional. Após isso, ele funciona como um gerenciador de processos. Esse processo é semelhante ao init em outros sistemas operacionais tipo UNIX. Ele interpreta scripts (ou seja, os arquivos init*.rc) que  consistem em comandos que descrevem o que deve ser feito para iniciar vários serviços. O arquivo init.rc do Firefox OS é basicamente o arquivo init.rc do Android para o dispositivo com as correções necessárias para incluir as funcionalidades exigidas pelo kick-start do Firefox OS, e varia de acordo com o dispositivo.

Uma tarefa chave que o processo init gerencia é a inicialização do processo b2g; que é o núcleo do sistema operacional Firefox OS.

O código no arquivo init.rc que faz a inicialização parece com isso:

service b2g /system/bin/b2g.sh
    class main
    onrestart restart media

Nota: A maneira exata de como o init.rc difere da versão do Android varia de um dispositivo para o outro; algumas vezes o arquivo init.b2g.rc é simplesmente adicionado, outras vezes as correções são mais significantes.

A arquitetura do userspace de processos

Agora é útil uma visão geral de como os vários compontes do Firefox OS se encaixam e interagem uns com os outros. Esse diagrama mostra o processo primário do userspace no Firefox OS.

Userspace diagram

Nota: Tenha em mente que o Firefox OS encontra-se sob desenvolvimento, esse diagrama pode sofrer alterações e pode não estar totalmente correto.

O processo b2g é o processo primário de sistema. Ele roda sob alto privilégio; com acesso a maioria dos dispositivos de hardware. b2g comunica-se com o modem, escreve no buffer de tela, interage com GPS, câmeras e outras funcionalidades do hardware. Internamente b2g executa a camada Gecko (implementada por libxul.so). Veja Gecko para conhecer os detalhes de como a camada Gecko funciona, e como o b2g comunica-se com ele.

b2g

O processo b2g é responsável por disparar um número de processos de baixo privilégio.  Esses processos estão onde as aplicações web e outros conteúdos web são carregados. Eles comunicam-se com o processo principal do servidor Gecko através do IPDL, um sistema de transmissão de mensagens.

O processo b2g executa libxul, que faz referência a b2g/chrome/app/b2g.js para obter as preferências padrão. A partir das preferências será aberto o arquivo HTML b2g/chrome/content/shell.html, que é compilado dentro do arquivo omni.ja. shell.html inclui o arquivo b2g/chrome/content/shell.js, que dispara o aplicativo Gaia system.

rild

O processo rild é a interface com o processador do modem. rild é um serviço que implementa a Radio Interface Layer (RIL). É um código proprietário implementado pelo fornecedor do hardware que conversa com o hardware do modem. O processo rild possibilita a conexão com um socket UNIX-domain com o qual é vinculado. O processo é iniciado por um código no script init como descrito abaixo.

service ril-daemon /system/bin/rild
    socket rild stream 660 root radio

rilproxy

No Firefox OS, o cliente do processo rild é o processo rildproxy. Atua como um dumb forwarding proxy entre o rild e o b2g. A necessidade desse proxy é um detalhe de implementação; por enquanto podemos resumir que ele é realmente necessário. The o código do rilproxy pode ser encontrado no GitHub.

mediaserver

O processo mediaserver controla a execução de vídeo e audio. O Gecko comunica-se com esse processo através de um mecanismo RPC (Remote Procedure Call) do Android. Algumas das mídias que o Gecko pode executar (audio OGG Vorbis, vídeo OGG Theora, e vídeo WebM) são decodificadas pelo Gecko e enviadas diretamente para o processo mediaservice. Outros arquivos de mídia são decoficiados pelo libstagefright,

Nota: O processo mediaserver é um componente "temporário" do Firefox OS; ele faz parte do projeto para ajudar o trabalho inicial do desenvolvimento,  mas há a expectativa de não ser mais utilizado. Mas provavelmente isso não acontecerá antes do lançamento do Firefox OS 2.0.

netd

O processo netd é utilizado para configurar interfaces de rede.

wpa_supplicant

O processo wpa_supplicant é um serviço padrão UNIX que gerencia a conectividade com os pontos de acesso WiFi.

dbus-daemon

O processo dbus-daemon implementa D-Bus, um sistema de barramento de mensagens utilizado pelo Firefox OS para comunicação Bluetooth.

Gecko

O Gecko, como já mencionado, é o responsável pela implementação dos padrões web (HTML, CSS, and JavaScript) que é a infraestrutura que implementa tudo que o usuário vê no Firefox OS.

Nota: Para procurar o Gecko codebase, você pode usar http://dxr.mozilla.org. É mais elegante e fornece boas referências futuras, mas com repositórios limitados. Ou você pode tentar o tradicional http://mxr.mozilla.org, que contém mais projetos Mozilla.

Arquivos Gecko relacionados com Firefox OS

b2g/

O diretório b2g contém principalmente funções relacionadas com o Firefox.

b2g/chrome/content

Contém arquivos JavaScript que são executados acima do aplicativo system.

b2g/chrome/content/shell.html

É o ponto de entrada do Gaia — o HTML para o aplicativo system. shell.html chamado em 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 contém parâmetros padrão do sistema.

b2g/chrome/content/shell.js

shell.js é o primeiro script a carregar no aplicativo Gaia system.

shell.js importa todos os módulos necessários, registra listeners chave, define sendCustomEvent e sendChromeEvent para comunicar com o Gaia e fornece a instalação de programas de ajuda de aplicativos web: indexedDB quota, RemoteDebugger, keyboard helper e screenshot tool.

Mas a função mais importante do shell.js é iniciar o aplicativo Gaia system, em seguida, entregar os sistemas  relacionados à gestão para o aplicativo system.

let systemAppFrame =
  document.createElementNS('http://www.w3.org/1999/xhtml', 'html:iframe');
    ...
  container.appendChild(systemAppFrame);
b2g/chrome/app/b2g.js

Esse script contém configurações pré-definidas, como about:config no navegador, e o mesmo pref.js do Gaia. Essas configurações podem ser alteradas do aplicativos Configurações e podem ser sobre-escritas pelo user.js do Gaia no script de compilação do Gaia.

mozilla/central/dom/{API}

Novas implementações de API (post-b2g) ficará no diretório dom/. APIs velhas ficarão em dom/base, por examplo navigator.cpp.

mozilla/central/dom/apps

.jsm será carregado — .js implementações API como instalação webapp.js, getSelf, etc.

mozilla/central/dom/apps/src/

Todas as permissões são definidas em PermissionsTable.jsm

mozilla/central/dom/webidl

WebIDL é a linguagem usada para definir APIs web. Para os atributos suportados veja WebIDL_bindings.

mozilla/central/hal/gonk

Esse diretório contém arquivos relacionados à camada de portabilidade do gonk.

Generated files

module/libpref/src/init/all.js

Contém todos os arquivos de configuração.

/system/b2g/ omni.ja and omni.js

Contém o pacote de estilos para recursos do dispositivo.

Processando eventos de entrada

Muitas ações dentro do Gecko são desencadeadas por ações de usuários. Essas ações são representadas por eventos (como pressionar botões, toques na tela, assim por diante). Esses eventos entram no Gecko através da implementação do Gonk na nsIAppShell, uma interface Gecko que é usada para representar o ponto primário de entrada para uma aplicação Gecko; ou seja, o driver do dispositivo de entrada deve chamar métodos no objeto nsAppShell que representa o subsistema Gecko a fim de enviar eventos para a interface do usuário.

Por exemplo:

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();
}

Esses eventos vêm do sistema padrão do Linux input_event. O Firefox OS usa uma light abstraction layer sobre ela que provê algumas funcionalidades interessantes como filtro de evento. Você pode ver o código que cria os eventos de entrada no método  EventHub::getEvents()em EventHub.cpp.

Uma vez que os eventos são recebidos pelo Gecko, eles são despacahdos para dentro do 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);
}

Depois disso, os eventos ou são consumidos pelo próprio Gecko ou são enviados para as aplicações Web como os eventos DOM que serão processados posteriormente, nas páginas.

Gráficos

No nível mais baixo, o Gecko usa OpenGL ES 2.0 para desenhar em um contexto do GL que empacota os buffers de hardware frame. Isso é executado na implementação Gonk em nsWindow por um código semelhante a esse:

gNativeWindow = new android::FramebufferNativeWindow();
sGLContext = GLContextProvider::CreateForWindow(this);

A classe FramebufferNativeWindow veio diretamente do Android; veja FramebufferNativeWindow.cpp. Utiliza a API gralloc API para acessar os drivers gráficos a fim de mapear os buffers dos dispositivos framebuffer na memória.

O Gecko utiliza o seu sistema de camadas (Layers system) para desenhar um sistema composto na tela. Em resumo, o que acontece é isso:

  1. O Gecko desenha regiões separadas das páginas em buffers de memória. Algumas vezes esses buffers ficam na memória do sistema, outras vezes,  são texturas mapeadas no espaço de endereços do Gecko, o que significa que o Gecko desenha diretamente na memória de vídeo. Essa tarefa é feita no método BasicThebesLayer::PaintThebes().
  2. O Gecko então compõe todas essas texturas para a tela usando comandos OpenGL. Esse composição ocorre em ThebesLayerOGL::RenderTo().

Os detalhes de como o Gecko lida com a renderização do conteúdo web está fora do escopo desse documento.

Hardware Abstraction Layer (HAL)

O HAL é uma das camadas de portabilidade do Gecko. Ele manipula acessos de baixo nível às interfaces de sistema através de múltiplas plataformas usando uma API C++ que é acessível aos níveis mais altos do Gecko. Essas APIs são implementadas baseadas na plataforma dentro do próprio HAL no Gecko. Essa camada de abstração do hardware (HAL) não é acessível diretamente pelo código JavaScript no Gecko.

Como o HAL funciona

Vamos considerar a API de Vibração como um exemplo. O HAL do Gecko para essa API é definida em  hal/Hal.h. Em essência (simplificando a assinatura do método por uma questão de clareza), você tem esta função:

void Vibrate(const nsTArray<uint32> &pattern);

Essa é a função chamada pelo código do Gecko para acionar a vibração do dispositivo de acordo com o padrão especificado, uma função correspondente existe para cancelar qualquer vibração em curso. A implementação Gonk desse método está em hal/conk/GonkHal.cpp:

void Vibrate(const nsTArray<uint32_t> &pattern) {
  EnsureVibratorThreadInitialized();
  sVibratorRunnable->Vibrate(pattern);
}

Esse código envia uma requisicão ao dispositivo para iniciar a vibração em outra thread, a qual é implementada em VibratorRunnable::Run(). O loop principal dessa thread pode ser vista aqui:

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()é uma API do HAL do Gonk que liga o motor que vibra o dispositivo. Internamente esse método envia uma mensagem para o drive do kernel gravando um valor específico o objeto do kernel usando sysfs.

Implementações de API de Fallback do HAL

As APIs (Application Programming Interface) HAL do Gecko são suportadas por todas as plataformas. Quando o Gecko é compilado para uma plataforma que não existe um motor para vibração (por exemplo, no caso de computadores de mesa), então uma rotina de fallback é usada. Para vibração é usada:  hal/fallback/FallbackVibration.cpp.

void Vibrate(const nsTArray<uint32_t> &pattern) {
}

Implementações de Sandbox

Devido ao fato de muitos conteúdos web são executados em processos com poucos privilégios, nós não podemos assumir que esses processos tem os privilégios necessários para, por exemplo, ligar e desligar o motor de vibração de um dispositivo. Adicionalmente, nós queremos ter um local central para lidar com algumas condições específicas em tempo de execução. No HAL do Gecko isso é feito através da implementação de uma sandbox que funciona como um proxy entre as requisições dos processos de conteúdo e as encaminha para o servidor Gecko. As requisições ao proxy são enviadas usando IPDL.

Para a vibração é utilizada função Vibrate() em 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()));
}

Essa função envia uma mensagem definida pela interface PHal, descrita pelo IPDL em hal/sandbox/PHal.ipdl. Esse método é descrito mais ou menos dessa forma:

Vibrate(uint32_t[] pattern);

O receptor dessa mensagem é o método HalParent::RecvVibrate() em  hal/sandbox/SandboxHal.cpp:

virtual bool RecvVibrate(const InfallibleTArray<unsigned int>& pattern,
            const InfallibleTArray<uint64_t> &id,
            PBrowserParent *browserParent) MOZ_OVERRIDE {

  hal::Vibrate(pattern, newID);
  return true;
}

Estão omitidos alguns detalhes que não são relevantes para essa discussão, porém mostra o caminho da mensagem desde o processo de conteúdo através do Gecko para o Gonk, então para a implementação Vibrate() do HAL no Gonk e eventualmente para os drivers gráficos.

APIs do DOM (Document Object Model)

Interfaces DOM são, em essência, como o conteúdo web comunica-se com o Gecko. Na verdade, existe muito mais coisa relacionada com esse assunto e para saber maiores detalhes verifique esse artigo. As interfaces DOM são definidas usando IDL, que inclui tanto a FFI (Foreign Funtion Interface) quanto OM (Object Model) entre JavaScript e C++.

A API de vibração está disponível para o conteúdo web através da interface IDL em  nsIDOMNavigator.idl:

[implicit_jscontext] void mozVibrate(in jsval aPattern);

O argumento jsval indica que mozVibrate()aceita como parâmetro de entrada qualquer valor JavaScript. O compilador IDL, xpidl, gera uma interface C++ que é implementada pela classe Navigator em Navigator.cpp.

NS_IMETHODIMP Navigator::MozVibrate(const jsval& aPattern, JSContext* cx) {
  // ...
  hal::Vibrate(pattern);
  return NS_OK;
}

Existe muito mais código nesse método, mas não aprofundaremos por não ser o propósito dessa discussão. O ponto é que a chamada para hal::Vibrate() transfere o controle do DOM para o HAL Gecko. A partir disso, nós entramos na implementação do HAL discutido na seção anterior e trabalhamos em direção ao driver gráfico.

Acima disso, a implementação DOM não se importa em qual plataforma que está sendo executado (Gonk, Windows, Mac OS X, ou qualquer outra). Ele também não se importa se o código está sendo executado em um processo de conteúdo ou no processo do servidor Gecko. Esses detalhes são tratados em níveis mais baixos do sistema.

A API de vibração é uma API muito simples, o que a torna um bom exemplo. The SMS API é um exemplo de uma API mais complexa que usa sua própria camada "remota" conectado o processo de contéudo ao servidor.

Radio Interface Layer (RIL)

A interface RIL foi mencionada na seção A arquitetura do userspace de processos. Essa seção examina como as várias peças dessa camada interage com um maior detalhe.

Os principais componentes envolvidos no RIL são:

rild
O processo que conversa com o firmware do modem proprietário.
rilproxy
O processo que intermedeia as mensagem entre o rild e o Gecko (que é implementado no processo b2g). Isso supera o problema de permissão que aparece quando tentamos acessar o rild diretamente, uma vez que rild somente pode comunicar-se através do grupo radio.
b2g
Esse processo também conhecido como processo chrome, implementa o Gecko. As porções desses processo que se relaciona com o RIL (Radio Interface Layer) são  dom/system/gonk/ril_worker.js (que implementa ma thread que conversa com o rild através do rilproxy e implementa o radio state machine. A interface nsIRadioInterfaceLayer, que é a thread principal do serviço XPCOM que atua primariamente como uma troca de mensagens entre a thread ril_worker.js e os vários componentes Geko, incluindo o próprio processo de conteúdo Gecko.
Processo Gecko de conteúdo
Dentro do processo Gecko de conteúdo, a interface nsIRILContentHelper provê um serviço XPCOM que permite ao código implementar partes do DOM, como as APIs de Telefonia e SMS conversar com a interface de radio, que está no processo chrome.

Exemplo: Comunicando do rild para o DOM

Vamos ver um exemplo de como as partes de baixo nível do sistema comunicam-se com o código DOM. Quando o modem recebe uma chamada, o rild é notificado usando um mecanismo proprietário. rild então prepara uma mensagem para seu cliente de acordo com o protocolo "aberto", que é descrito em ril.h. No caso do recebimento de uma chamada, uma mensagem  RIL_UNSOL_RESPONSE_CALL_STATE_CHANGED é gerada e envida pelo rild ao rilproxy.

rilproxy, implementado em rilproxy.c, recebe essa mensagem em seu loop principal, que direciona a conexão para o rild usando um código como esse:

ret = read(rilproxy_rw, data, 1024);

if(ret > 0) {
  writeToSocket(rild_rw, data, ret);
}

Assim que a mensagem é recebida do rild é encaminda ao Gecko pelo socket que conecta rilproxy ao Gecko. Essa mensagem é recebida pelo Gecko no IPC thread:

int ret = read(fd, mIncoming->Data, 1024);
// ... handle errors ...
mIncoming->mSize = ret;
sConsumer->MessageReceived(mIncoming.forget());

O consumidor dessas mensagens é SystemWorkerManager, que decompõe a mensagem e despacha para ril_worker.js uma thread que implementa o estado de máquina RIL. Isso é ocorre no método RILReceiver::MessageReceived():

virtual void MessageReceived(RilRawData *aMessage) {
  nsRefPtr<DispatchRILEvent> dre(new DispatchRILEvent(aMessage));
  mDispatcher->PostTask(dre);
}

A tarefa enviada para essa thread, por sua vez chama a função onRILMessage (), implementada em JavaScript. Isso é feito usando a função da API JavaScript JS_CallFunctionName():

return JS_CallFunctionName(aCx, obj, "onRILMessage", NS_ARRAY_LENGTH(argv),
                           argv, argv);

onRILMessage()é implementada em dom/system/gonk/ril_worker.js, processo no qual processa a mensagem dividindo seus bytes em pedaços (parcelas). Cada pedaço completo é enviada para métodos apropriados para que sejam manipulados individualmente:

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);
  }
}

Esse código funciona obtendo o tipo de solicitação do objeto, certificando-se que é definido como uma função no código JavaScript, em seguida, chamando o método. Uma vez que ril_worker.js implementa cada tipo de pedido em um método com o mesmo nome que o tipo de solicitação, isso é muito simples.

No nosso exemplo, RIL_UNSOL_RESPONSE_CALL_STATE_CHANGED, e o método chamado:

RIL[UNSOLICITED_RESPONSE_CALL_STATE_CHANGED] = function UNSOLICITED_RESPONSE_CALL_STATE_CHANGED() {
  this.getCurrentCalls();
};

Como você pode ver no código acima, quando for recebida uma notificação de que o estado de chamada mudou, a máquina de estado simplesmente busca o estado da chamada atual chamando o método getCurrentCall ():

getCurrentCalls: function getCurrentCalls() {
  Buf.simpleRequest(REQUEST_GET_CURRENT_CALLS);
}

É enviada de volta uma requisição para rild para solicitar o estado de todas as chamadas ativas no momento. A requisição volta ao longo de um caminho similar ao caminho seguido por RIL_UNSOL_RESPONSE_CALL_STATE_CHANGED, mas numa direção oposta (ou seja, de ril_worker.js para SystemWorkerManager para Ril.cpp, então rilproxy e finalmente para o socket rild). rild então responde de volta pelo mesmo caminho, eventualmente chegando em   ril_worker.js que manipula a mensagem REQUEST_GET_CURRENT_CALLS. E assim ocorre a comunicação bidirecional.

O estado de chamada é então processada e comparada com o estado anterior. Se é identificada uma mudança no estado, ril_worker.js notifica o serviçonsIRadioInterfaceLayer na thread principal:

_handleChangedCallState: function _handleChangedCallState(changedCall) {
  let message = {type: "callStateChange",
                 call: changedCall};
  this.sendDOMMessage(message);
}

nsIRadioInterfaceLayer é implementada em dom/system/gonk/RadioInterfaceLayer.js; a mensagem é recebida pelo 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;
   ...

O que tudo isso realmente faz é enviar a mensagem para o processo de conteúdo usando o Parent Process Message Manager (PPMM):

handleCallStateChange: function handleCallStateChange(call) {
  [some internal state updating]
  ppmm.sendAsyncMessage("RIL:CallStateChanged", call);
}

No processo de conteúdo a mensagem é recebida pelo método receiveMessage()no serviço nsIRILContentHelper, do 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;

Esse, por sua vez, chama os métodos nsIRILTelephonyCallback.callStateChanged() em todo objeto registrado como telephony callback. Toda aplicação web que acessa a API window.navigator.mozTelephony tem registado um objeto de retorno de chamada que despacha eventos para o código JavaScript na aplicação web, como uma mudança de estado de um objeto chamada existente ou um novo evento de chamada recebida (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<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;
}

Aplicações podem receber esses eventos e atualizar sua interface de usuário e assim por diante:

handleEvent: function fm_handleEvent(evt) {
  switch (evt.call.state) {
    case 'connected':
      this.connected();
      break;
    case 'disconnected':
      this.disconnected();
      break;
    default:
      break;
  }
}

Verifique a implementação de handleEvent() na aplicação de discagem como um exemplo.

Dados 3G

Existe uma mensagem RIL que inicia uma chamada de dados para o serviço celular. Isso habilita o modo de transferência de dados no modem. Esta chamada de dados acaba criando e ativando uma interface com Protocolo Ponto-a-Ponto Point-to-Point Protocol (PPP) no Kernel do Linux que pode ser configurado utilizando interfaces comuns.

Note: This section needs to be written.

APIs DOM relacionadas

Essa seção lista as APIs DOMs que são relacionadas com a comunicação RIL

WiFi

No Firefox OS o backend do WiFi simplesment usa wpa_supplicant para realizar a maioria do trabalho. Isso significa que o principal trabalho do backend é gerenciar o supplicant e fazer outras tarefas auxiliares como carregar o driver do WiFi e habilitar ou desabilitar a interface de rede. Em essência, isso significa que o backend é uma máquina de estados cujos estados seguem o estado do supplicant.

Nota: Muitas das coisas interessantes que acontecem no WiFi depende profundamente das mudanças de estados no processo wpa_supplicant.

A implementação do componente WiFi é dividido em dois arquivos:

dom/wifi/DOMWifiManager.js
Implementa o API que é accessível pelo conteúdo web, como definido em nsIWifi.idl.
dom/wifi/WifiWorker.js
Implementa a máquina de estados e o código que aciona o supplicant.

Esses dois arquivos comunicam-se entre sim usando o gerenciador de mensagens. O backend escuta as mensagens solicitando determinadas ações, como um "associado", e responde com uma mensagem quando a ação solicitada foi concluída.

O lado do DOM escuta os métodos de resposta bem como muitas mensagens de eventos que indica a alteração de estado e atualizações de informações.

Nota: Quaisquer APIs DOM síncronas são implementadas por cache de dados no lado do "pipe". Mensagens síncronas são evitadas sempre que possível.

WifiWorker.js

Esse arquivo implementa a lógica principal por trás da interface WiFi. Ele é executado no processo chrome (em compilações multiprocessadas) e é instanciada pelo SystemWorkerManager. O arquivo é geralmente dividido em duas seções: uma função "gigante" anônima e o WifiWorker (e seu protótipo). A função anônima acaba sendo o WifiManager fornecido pela API local, incluindo notificações para eventos como a conexção ao supplicant e pesquisa de resultados disponíveis. Em geral contém pouca lógica e deixa exclusivamente o controle de suas ações ao consumidor enquanto ele simplesmente responde com as informações solicitadas controlando os detalhes da conexão com o supplicant.

Os objetos WifiWorker ficam entre o WifiManager e o DOM. Repondem a eventos e os encaminham ao DOM, depois recebe as requisições do DOM e executa as ações apropriadas no supplicant. Também mantém a informação do estado do supplicant e o que é necessário fazer em seguida.

DOMWifiManager.js

Implementa a API DOM, transmitindo as mensagens para/de os chamadores e o real WiFi worker. Tem pouca lógica envolvida.

Nota: A fim de evitar mensagens síncronas para o processo chrome, o Gerenciador de Wi-Fi não precisa armazenar o estado com base no evento recebido.

Existe uma mensagem síncrona que é enviada no momento que a API DOM é instanciada, a fim de obter o estado atual do supplicant.

DHCP

DHCP e DNS são gerenciados pelo dhcpcd, o cliente DHCP padrão do Linux. Portanto, não é capaz de responder quando a conexão da rede é perdida. Por causa disso, o Firefox OS derruba e reinicia o processo dhcpcd assim que é conectado a uma rede wireless.

dhcpcd também é responsável por definir a rota padrão, chamando no gerenciador de rede para informar ao kernel sobre os servidores DNS.

Gerenciador de Rede

O Gerenciador de Rede configura as interfaces de rede abertas pelos Dados 3G e os componentes WiFi.

Nota: Isso precisa ser escrito.

Processos e threads

Firefox OS usa threads POSIX para implementar as threads de aplicações, isso inclui a thread principal de cada aplicação bem como as Web workers e helper threads. Valores "Nice" são usados para priorizar os processos e a execução das threads confiando no agendador padrão do kernel do Linux. Dependendo do status do processo nós atribuimos um diferente valor "Nice" do nível. Temos atualmente 7 níveis:

Process priority levels
Priority Nice Used for
MASTER 0 main b2g process
FOREGROUND_HIGH 0 applications holding a CPU wakelock
FOREGROUND 1 foreground applications
FOREGROUND_KEYBOARD 1 keyboard application
BACKGROUND_PERCEIVABLE 7 background applications playing audio
BACKGROUND_HOMESCREEN 18 homescreen application
BACKGROUND 18 all other applications running in the background

Alguns níveis possuem os mesmos "nice values", isso acontece porque esses níveis diferem da forma que são tratados pelo "out-of-memory killer". Todas as prioridades podem ser ajustadas em tempo de compilação via "Preferências". Os valores relevantes podem ser encontrados no arquivo b2g/app/b2g.js.

Dentro de um processo a thread principal herda o "nice value" do processo, enquanto às threads do web worker é atribuido um "nice value" um ponto  maior que a thread principal que roda na menor prioridade. Isso é feito para evitar que processos que utilizem muita CPU degradem a performance da thread principal. Prioridades de processos são alterados sempre que acontece um grande evento como quando uma aplicação é enviada para rodar em background ou foreground, uma nova aplicação é iniciada, ou uma aplicação existente executa uma wake-lock da CPU. Toda vez que a prioridade de um processo é ajustada todas as prioridades de suas threads também são ajustadas em conformidade.

Nota: cgroups não são usadas atualmente para gerenciamento de recursos por não serem confiáveis em certos dispositivos e kernels.

 

Anexos

Arquivo Tamanho Data Anexado por
B2G userspace architecture
user architecture diagram
20562 bytes 2012-08-24 15:17:27 Sheppy
Firefox OS Architecture
Firefox OS Architecture
126333 bytes 2013-01-28 08:11:44 jmcanterafonseca
bootup procedure
62009 bytes 2014-03-26 05:59:20 chrisdavidmills

Document Tags and Contributors

Contributors to this page: rbrandao, rodrigopadula, TelaSocial
Última atualização por: rbrandao,