mozilla

Firefox OS のアーキテクチャ

この記事では、Firefox OS プラットフォームの高水準層の概要、主要なコンセプトの紹介、そして高水準層のコンポーネントがどのように低水準層と対話するかを説明します。

注記: Firefox OS はまだプレリリース段階の製品であることを忘れないようにしてください。ここで説明するアーキテクチャは必ずしも最終的なものではなく、変更される場合があります。

Firefox OS の用語

Firefox OS のドキュメントをさらに読み進める前に理解しておくべき用語がいくつかあります。

B2G
Boot to Gecko の略語です。
Boot to Gecko
Firefox OS オペレーティングシステムのエンジニアリングコードネームです。プロジェクトに正式な名称がつくまでの長い間この言葉が使われたため、これが Firefox OS を指す言葉として使われるのをよく見るでしょう。
Firefox OS
Firefox OS は基本的に、最終リリース製品を製造するため Boot to Gecko に加えられた、Mozilla (および OEM パートナー) のブランディングとサポートサービスです。
Gaia
Firefox OS プラットフォームのユーザインタフェースです。Firefox OS が起動してからスクリーンに描画されるものはすべて、Gaia 層で書かれています。Gaia はロックスクリーン、ホームスクリーン、そして現代的なスマートフォンに求められる標準的なアプリケーションを実装します。Gaia はすべて、HTML、CSS、JavaScript で実装されています。下層のオペレーティングシステムとの唯一のインタフェースはオープンな Web API であり、それらは Gecko 層で実装されています。サードパーティのアプリケーションも、Gaia 層と一緒にインストールできます。
Gecko
これは、Firefox OS のアプリケーションランタイムです。すなわち HTML、CSS、JavaScript という 3 つのオープン標準のすべてをサポートする層です。Gecko がサポートするすべてのオペレーティングシステムで、それらの API が良好に動作することを確実にします。つまり Gecko には、例えばネットワークスタック、グラフィックスタック、レイアウトエンジン、JavaScript 仮想マシン、ポーティング層を含みます。
Gonk
Gonk は Firefox OS プラットフォームの低水準のオペレーティングシステムであり、Linux カーネル (Android Open Source Project (AOSP) を基にしています) とユーザ空間の Hardware Abstraction Layer (HAL) で構成されます。カーネルといくつかのユーザ層のライブラリは、一般的なオープンソースプロジェクトのものです: Linux、libusb、bluez などです。HAL の他の部分には、Android プロジェクトと共有しているものもあります: GPS、カメラなどです。Gonk はとてもシンプルな Linux ディストリビューションであると表せます。Gonk は Gecko の移植対象です。つまり OS X、Windows、Android に移植した Gecko があるように、Gonk に移植した Gecko があります。Firefox OS プロジェクトは Gonk を完全に管理しているため、他のオペレーティングシステムでは Gecko に開放されていないインタフェースを開放することができます。例えば、Gonk では Gecko がテレフォニースタック全体やディスプレイのフレームバッファへ直接アクセスできますが、他のオペレーティングシステムではこれらにアクセスできません。
Jank
モバイルアプリの分野でよく使用されるこの用語は、アプリ内の遅い、あるいは非効率なコード処理による影響を指します。これは UI の更新を妨げ、遅延や応答性の悪さを引き起こします。Gaia のエンジニアは何としてもこのような状態を避けるために、さまざまな最適化手法を使用しています。

アーキテクチャの全体像

以下の図は、プロプライエタリなプラットフォームと Firefox OS のアーキテクチャを比較したものです。

on the left is a native mobile architecture stack, on the right is the Firefox OS architecture. they are similarm except that the native stack is all proprietary device functionality, and the Firefox OS stack is all done with open source and web technologies.

Firefox OS では、オペレーティングシステムとアプリケーション層の間のネイティブ API がありません。この統合された設計により、パフォーマンスやユーザのリッチなスマートフォン体験を犠牲にすることなく、プラットフォームのオーバーヘッドを軽減するとともにセキュリティを簡略化します。

  1. Gaia はデバイスの中心的な Web アプリおよびユーザインターフェイス層です。これは HTML5、CSS、JavaScript で記述されており、UI がハードウェアや Gecko の機能と対話できるようにするためのさまざまな API を公開しています。
  2. Gecko は Firefox OS の Web エンジンおよびプレゼンテーション層であり、Web コンテンツと下層のデバイスの間のインターフェイスを提供することにより、ハードウェアと HTML を接続します。Gecko は HTML5 のパースとレンダリングエンジン、セキュアな Web API を通してハードウェア機能へプログラムからアクセス、包括的なセキュリティフレームワーク、アップデート管理、その他のコアサービスといった機能を提供します。
  3. Gonk は Firefox OS スタックのカーネルレベルのコンポーネントであり、Gecko と下層のハードウェアの間のインターフェイスとして働きます。Gonk は下層のハードウェアを制御して、ハードウェアの機能を Gecko に実装された Web API に公開します。Gonk はハードウェアレベルの要求を定めることによりモバイルデバイスを制御するため、内部で複雑かつ詳細な処理すべてを行う "ブラックボックス" であると考えることができます。
  4. モバイルデバイスは、Firefox OS を実行する携帯電話機器です。OEM がモバイルデバイスの供給について責任を持ちます。

Firefox OS の詳細なアーキテクチャ

Firefox OS Architecture

Firefox OS のブート手順

このセクションでは Firefox OS デバイスのブートプロセスについて、どの部分がどのように関わるかを説明します。要約すると一般的なシステムの起動フローは、カーネル空間のブートローダーから始まりネイティブコードの init、B2G、ユーザ空間の Gecko、そしてシステムアプリ、ウィンドウマネージャー、Gecko 内のホームスクリーンアプリと進みます。これらの上で、他のアプリすべてを実行します。

ブートストラッププロセス

Firefox OS デバイスでまず電源を入れると、プライマリブートローダが実行されます。そこから主要なオペレーティングシステムを読み込むプロセスが、典型的な方法で進みます。これはチェイン内で順次、高水準ブートローダが次のローダを起動することの連鎖です。プロセスの最終段階で、実行は Linux カーネルに引き渡されます。

ブートプロセスについて注意すべき点がいくつかあります:

  • ブートローダは通常、デバイスの起動中にユーザへ見せるものとしてスプラッシュスクリーンを始めに表示します。これは一般的に、ベンダーのロゴです。
  • ブートローダは、デバイスへイメージを書き込む機能を実装しています。機種によりそのプロトコルは異なります。ほとんどの電話機は fastboot プロトコルを使用していますが、Samsung Galaxy S II は odin プロトコルを使用しています。
  • ブートストラッププロセスが終わるまでに、通常はモデムイメージが読み込まれてモデムプロセッサで実行されます。これがどのように行われるかはきわめてデバイス依存であり、独特な箇所になるでしょう。

Linux カーネル

Gonk で使用される Linux カーネルは、派生元である上流の Linux によく似ています (Android Open Source Project に基づきます)。AOSP によって行われたが上流に反映されていない変更点もあります。加えて、時にベンダーはカーネルに変更を加えて、彼ら自身のスケジュールに基づいて上流に反映させます。もっとも、一般的には Linux カーネルに集積されていきます。

Linux のスタートアッププロセスはインターネット上の他所で十分に文書化されていますので、この記事では扱いません。

Linux カーネルはデバイスを起動して、必要不可欠なプロセスを実行します。これは init.rc や、それに後続して b2g (Gecko を含む、Firefox OS の基本プロセス) および rild (さまざまなチップセットに固有であろう、テレフォニー関係のプロセス) などの必要なプロセスを起動するための init.b2g.rc で定義されているプロセスを実行します。詳しくは後で説明します。プロセスの最終段階では、ほとんどの UNIX ライクなオペレーティングシステムと同様に、ユーザ空間の init プロセスが起動します。

init プロセスが起動すると、Linux カーネルがユーザ空間やハードウェアデバイスからのシステムコールや割り込みを制御します。多くのハードウェア機能は、sysfs を通してユーザ空間に開放されています。例えば、以下は 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);
}

init プロセス

Gonk の init プロセスは、必要なファイルシステムのマウントやシステムサービスの起動を制御します。その後、プロセスマネージャとして働くために常駐します。これは他の UNIX ライクなオペレーティングシステムの init にとても似ています。init は、さまざまなサービスを起動するために行うべきことを示すコマンドで構成されるスクリプト (すなわち init*.rc ファイル) を実行します。Firefox OS の init.rc は、典型的には Firefox OS を起動するために必要なものを含むようパッチを当てた Android の init.rc であり、デバイスにより異なります。

init プロセスが制御する重要なタスクとして、b2g プロセスの起動があります。b2g は、Firefox OS オペレーティングシステムの中核部分です。

init.rc で b2g を起動するコードは以下のようになります:

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

注記: init.rc が Android のものとどれだけ違うかは、デバイスにより異なります。init.b2g.rc が単純に追加される場合もあれば、より多くのパッチが必要になる場合もあります。

ユーザ空間プロセスのアーキテクチャ

ここで、Firefox OS のさまざまなコンポーネントがどのように組み合わされていて、また互いに作用しあうかを高い水準で見ておくと役に立ちます。こちらの図は、Firefox OS の基本的なユーザ空間プロセスを表しています。

Userspace diagram

注記: Firefox OS は現在も開発中であるため、この図は変更される可能性があり、また完全に正確なものではないことに注意してください。

b2g プロセスは、基本的なシステムプロセスです。これは高い権限で動作します。つまり、ほとんどのハードウェアデバイスにアクセスできます。b2g はモデムと通信したり、ディスプレイフレームバッファに描画したり、GPS やカメラや他のハードウェア機能と対話したりします。内部的には、b2g は Gecko 層で動作します (libxul.so として実装されています)。Gecko 層の動作や b2g が Gecko とどのように通信するかについて、詳しくは Gecko の章をご覧ください。

b2g

b2g プロセスは、低権限の content プロセス を順番に起動するでしょう。これらのプロセスは、Web アプリケーションや他の Web コンテンツが読み込まれるところです。またこれらのプロセスは主要な Gecko のサーバプロセスと、IPDL というメッセージ伝送システムを通して通信します。

b2g プロセスは libxul を実行しており、これはデフォルトの設定を取得するために b2g/app/b2g.js を参照します。b2g は設定により示された HTML ファイルである b2g/chrome/content/shell.html を開きます。このファイルは omni.ja ファイル内に集積されています。shell.html には b2g/chrome/content/shell.js ファイルが含まれており、これは Gaia の system アプリを起動します。

rild

rild プロセスは、モデムプロセッサへのインタフェースです。rildRadio Interface Layer (RIL) を実装するデーモンです。これは、モデムハードウェアと対話するためにハードウェアベンダーによって実装されるプロプライエタリなコードの部分です。rild は、クライアントコードがモデムに結びつけられた UNIX ドメインソケットへ接続できるようにします。rild は init スクリプト内の以下のようなコードによって起動されます :

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

rilproxy

Firefox OS では、rild のクライアントが rilproxy プロセスです。これは rildb2g の間で単なる転送プロキシとして動作します。このプロキシは細部の実装として必要です。とても必要なものであるとだけ言っておきましょう。rilproxy のコードは GitHub にあります

mediaserver

mediaserver プロセスは、音声や動画の再生を制御します。Gecko は Android Remote Procedure Call (RPC) の仕組みを通して mediaserver と対話します。Gecko が再生可能なメディア (OGG Vorbis audio、OGG Theora video、WebM video) は Gecko によってデコードされ、直接 mediaserver プロセスに送られます。他のメディアファイルは libstagefright によってデコードされます。libstagefright は、プロプライエタリなコーデックやハードウェアエンコーダにアクセスできます。

注記: mediaserver プロセスは、Firefox OS の "一時的な" コンポーネントです。初期の開発作業を支援するために存在しており、いずれはなくなる予定です。ただし、おそらく早くても Firefox OS 2.0 まではなくならないでしょう。

netd

netd プロセスは、ネットワークインタフェースの設定に使用されます。

wpa_supplicant

wpa_supplicant プロセスは Wi-Fi アクセスポイントとの接続を制御する、標準的な UNIX スタイルのデーモンです。

dbus-daemon

dbus-daemon は D-Bus を実装します。これは、Firefox OS が Bluetooth 通信のために使用するメッセージバスシステムです。

Gecko

Gecko は前述のとおり、Firefox OS でユーザが見るものすべてを実装するために使用される Web 標準技術 (HTMLCSSJavaScript) を実装したものです。また、電話機のハードウェアとの対話を制御します。HTML5 とハードウェアをつなぐ Web アプリは、Gecko に実装されたセキュアな Web API によって制御されます。 Web API は、下層のモバイルデバイスハードウェアの機能 (バッテリーやバイブレーションなど) やデバイスに保存されていたりデバイスで使用できるデータ (カレンダーや連絡先など) にプログラムからアクセスする機能を提供します。Web コンテンツは、HTML5 でアクセスできる Web API を使用します。

アプリは、関連する HTML5 Web コンテンツの集合体で構成されます。Firefox OS のモバイルデバイスで動作する Web アプリを構築するために開発者が行うことは、単なる Web コンテンツの組み立て、パッケージング、頒布です。この Web コンテンツは実行時に、Web ブラウザによって解釈、コンパイル、レンダリングされます。アプリについて詳しくは、アプリセンターをご覧ください。

注記: http://dxr.mozilla.org を使用して Gecko のコードベースを検索できます。見栄えがよく、また良好な参照機能を提供しますが、限られたリポジトリでしか使用できません。あるいは、旧来の http://mxr.mozilla.org も使用できます。こちらはより多くの Mozilla プロジェクトを包含しています。

Gecko のアーキテクチャ図

  • セキュリティフレームワークに含まれるもの:
    • Permission Manager: Web API の機能にアクセスするためのゲートウェイです。
    • Access Control List: Web API の機能へアクセスするために必要な役割や許可設定のマトリックスです。
    • Credential Validation: アプリやユーザの認証です。
    • Permissions Store: Web API の機能へアクセスするために必要な特権のセットです。
  • Web API: ハードウェアの機能を Web コンテンツに公開する標準 API のセットです。下層のモバイルデバイスハードウェアの機能やデバイスに保存されていたりデバイスで使用できるデータへ、安全にプログラムからアクセスする機能を Web アプリに提供します。
  • I/O: ハードウェアやデータストアへのインターフェイスです。
  • Software Updates: システムソフトウェアやサードパーティーのアプリの更新を取得およびインストールします。
  • Content Layout & Rendering: Web コンテンツの解析・解釈・実行、およびコンテンツに付随する書式情報に従って整形したコンテンツをユーザに対して表示するエンジンです。
  • b2g process: 携帯電話のハードウェア機能にアクセス可能な、高い特権を持ったシステムプロセスで実行される Gecko です。実行中のアプリは、b2g の子プロセスです。

b2g/

b2g フォルダには、主要な Firefox OS 関連の機能が入っています。

b2g/chrome/content

システムアプリ上で実行する JavaScript が入っています。

b2g/chrome/content/shell.html

Gaia へのエントリポイントである、システムアプリ向けの HTML です。shell.html は、settings.js および 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 は、システムのデフォルトの設定パラメータを持っています。

b2g/chrome/content/shell.js

shell.js は Gaia の system アプリが読み込む最初のスクリプトです。

shell.js はすべての必要なモジュールのインポート、重要なリスナの登録、Gaia と通信するための sendCustomEvent および sendChromeEvent の定義、そして webapp のインストールヘルパー (indexedDB クォータ、RemoteDebugger、キーボードヘルパー、スクリーンショットツール) の提供を行います。

しかし shell.js でもっとも重要な機能は Gaia の system アプリを起動して、システム全体に関する管理処理を Gaia の system アプリに引き渡すことです。

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

このスクリプトはブラウザでの about:config や Gaia の pref.js のように、あらかじめ定義された設定を持っています。これらの設定内容は設定アプリで変更可能であり、また Gaia のビルドスクリプトで user.js による上書きも可能です。

dom/{API}

新たな API の実装 (post-b2g) は dom/ に配置します。navigator.cpp など旧来の API は、dom/base に配置しています。

dom/apps

.jsm が読み込まれます。これは webapp.js のような .js ファイルによる API 実装のインストールや getSelf などがあります。

dom/apps/PermissionsTable.jsm

すべての許可設定を PermissionsTable.jsm で定義しています。

dom/webidl

WebIDL は、Web API の定義に使用する言語です。サポートする属性については WebIDL_bindings をご覧ください。

hal/gonk

このディレクトリには、Gonk のポーティング層に関するファイルが入っています。

Generated files

module/libpref/src/init/all.js

すべての設定ファイルを包含します。

/system/b2g/ omni.ja および omni.js

デバイス内のリソース向けのスタイルパックを包含します。

入力イベントの処理

Gecko 内部のほとんどのアクションは、ユーザのアクションによって発生します。これらのアクションは入力イベント (ボタン押下、タッチスクリーンデバイスのタッチなど) で表されます。これらのイベントは、Gecko アプリケーションに対する主要な入口を表すために使用される Gecko のインタフェースである、nsIAppShellGonk 実装を通して Gecko に入力されます。すなわち、入力デバイスのドライバがユーザインタフェースへイベントを送信するために、Gecko のサブシステムに対応する nsAppShell オブジェクトのメソッドを呼び出します。

例:

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

これらのイベントは、標準的な Linux の input_event システムから発生します。Firefox OS では light abstraction layer を使用します。これは、イベントのフィルタリングなど便利な機能を提供します。widget/gonk/libui/EventHub.cpp で、入力イベントを生成するコードをご覧いただけます。

イベントが Gecko に受け取られると、それらのイベントは nsAppShell によって DOM へ送られます:

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

その後、イベントは Gecko 自身によって処理されるか、さらなる処理のために DOM events として Web アプリケーションに発行されます。

グラフィックス

ごく低水準な部分では、Gecko はハードウェアのフレームバッファをラップする GL コンテキストの描画に OpenGL ES 2.0 を使用します。これは以下のようなコードにより、nsWindow の Gonk 実装によって行われます:

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

FramebufferNativeWindow クラスは Android からそのまま取り込んでいます。FramebufferNativeWindow.cpp をご覧ください。これはバッファをフレームバッファデバイスからメモリに対応付ける目的でグラフィックドライバへアクセスするために gralloc API を使用します。

Gecko は描画されたコンテンツをスクリーンに合成するために、自身のレイヤーシステムを使用します。要約すると、以下のようなことが行われます:

  1. Gecko がページの個々の部分をメモリバッファに描画します。これらのバッファはシステムメモリにある場合があります。あるいは、これらが Gecko のアドレス空間にマッピングされたテクスチャであることもあり、これは Gecko が直接ビデオメモリへ描画します。これは一般に、BasicThebesLayer::PaintThebes() メソッドで行われます。
  2. そして、Gecko はこれらすべてのテクスチャを OpenGL コマンドを使用してスクリーンに合成します。この合成処理は ThebesLayerOGL::RenderTo() で行われます。

Gecko が Web コンテンツのレンダリングをどのように制御するかの詳細は、本ドキュメントの範囲から外れます。

Hardware Abstraction Layer (HAL)

Gecko Hardware Abstraction Layer は、Gecko のポーティング層の一つです。これは、Gecko の高水準層が利用可能な C++ の API を使用して、複数のプラットフォームにまたがってシステムインタフェースへの低水準なアクセスを制御します。これらの API は Gecko HAL 自身の内部で、プラットフォームごとに実装されています。Gecko ではこの Hardware Abstraction Layer が、JavaScript に対して直接は公開されていません。この対話の部分は、Web API によって処理されます。

高いレベルで処理方式を見てみましょう。ユーザが電話機の機能 (電話をかける、Wi-Fi ネットワークにアクセスする、Bluetooth で接続するなど) を使用する要求を発すると、Firefox OS のスタックの全階層がその要求の伝達に関わります。Gaia 層のアプリや Web コンテンツが Web API の呼び出し (HTML5 内の機能で実行されます) により、下層のデバイスへのアクセスを要求します。この Web API は Gecko に実装しています。Gecko は同様に、Gonk へ要求します。Gecko が発したひとつの要求は複合的な一連の操作を発生させることもあり、Gonk が携帯電話機内で操作の生成や制御を行います。

HAL の動作

例として Vibration API について考えてみましょう。この API 向けの Gecko HAL は hal/Hal.h で定義されています。本質的には (明快さのために、メソッドのシグネチャを単純化します)、以下の関数があります:

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

これは、指定されたパターンに従ってデバイスのバイブレーション機能を起動するために Gecko のコードから呼び出される関数です。これに対応する関数として、実行中のバイブレーションを止める関数があります。このメソッドの Gonk 実装は hal/gonk/GonkHal.cpp にあります:

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

このコードはデバイスのバイブレーションを開始するためのリクエストを、別のスレッドに送信します。そのスレッドは、VibratorRunnable::Run() で実装されています。このスレッドのメインループは以下のようになります:

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() はバイブレーション装置を起動する、Gonk HAL の API です。内部では、このメソッドは sysfs を使用してカーネルオブジェクトに値を書き込むことにより、カーネルドライバへメッセージを送信します。

代替の HAL API 実装

Gecko HAL の API は、すべてのプラットフォームにわたってサポートされています。バイブレーション装置へのインタフェースを公開しないプラットフォーム (デスクトップコンピュータなど) 向けに Gecko がビルドされたときは、HAL API の代替実装が使用されます。バイブレーションでは、これは hal/fallback/FallbackVibration.cpp に実装されています。

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

サンドボックスの実装

ほとんどの Web コンテンツは低い権限の content プロセスで動作するため、これらのプロセスが (例えば) バイブレーション装置を起動・停止できるのに必要な権限を持っていると考えることはできません。加えて、潜在的な競合状態を制御するための中心的な場所が必要です。Gecko HAL ではこれを、HAL の "サンドボックス" 実装により実現しました。このサンドボックス実装は単純に、content プロセスから発生されたリクエストの代理となって "Gecko server" プロセスに転送します。代理のリクエストは IPDL を使用して送信されます。

バイブレーションの場合、これは hal/sandbox/SandboxHal.cpp で実装されている Vibrate() 関数で制御されます:

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

これは PHal インタフェースで定義されたメッセージを送信します。また PHal インタフェースは hal/sandbox/PHal.ipdl で、IPDL を用いて記述されています。このメソッドは、おおむね以下のように記述されています:

Vibrate(uint32_t[] pattern);

このメッセージの受信側は hal/sandbox/SandboxHal.cpp にある HalParent::RecvVibrate() メソッドであり、以下のようなものです:

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

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

ここでは、説明に関係ない詳細部分を省略しています。それでも、メッセージが content プロセスから Gecko を通して Gonk へ、そして Vibrate() の Gonk HAL 実装、最終的にバイブレーション機能のドライバまでどのように進むかを示しています。

DOM API

DOM インタフェース は、本質的には Web コンテンツが Gecko と通信する方法です。実際はさらに複雑であり、もし詳細に興味があるのでしたら DOM についてのドキュメントを読むとよいでしょう。DOM インタフェースは IDL を使用して定義されます。IDL は JavaScript と C++ との間の他言語関数インタフェース (FFI) とオブジェクトモデル (OM) で構成されます。

Vibration API は IDL インタフェースを通して Web コンテンツに公開されており、それは nsIDOMNavigator.idl: で提供されます。

[implicit_jscontext] void mozVibrate(in jsval aPattern);

jsval 引数は、mozVibrate() (これは未確定の Vibration 仕様に対する、私たちのベンダ接頭辞付きの実装です) が任意の JavaScript 値を入力として受け入れることを示します。IDL コンパイラである xpidl は、のちに Navigator.cppNavigator クラスによって実装される C++ のインタフェースを生成します。

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

このメソッドにはご覧いただいているものより多くのコードが含まれていますが、ここでの説明内容に対しては重要でないものです。ポイントは hal::Vibrate() の呼び出しが、制御を DOM から Gecko HAL に移していることです。ここから前の章で説明した HAL 実装に入り、デバイスドライバまで処理が進んでいきます。これに加えて、DOM 実装は実行しているプラットフォームが何か (Gonk、Windows、OS X など) はまったく気にしません。また、コードが content プロセスで実行しているか Gecko サーバプロセスで実行しているかも気にしません。これらの細部は、システムの低水準層が対処するために切り離されています。

Vibration API はとてもシンプルな API であり、例としてよいものです。SMS API は content プロセスとサーバとの接続に自身の "remoting" 層を使用する、より複雑な API の例になります。

Radio Interface Layer (RIL)

RIL は The userspace process architecture の章でも触れました。本章では、この層のさまざまな部品がどのように作用しあうかをもう少し詳しく見ていきます。

RIL に関係する主要コンポーネントは以下の通りです:

rild
プロプライエタリなモデムのファームウェアと対話するデーモンです。
rilproxy
rild と Gecko の間でメッセージを中継するデーモンです (これは b2g プロセス内に実装されています)。これは rild と直接対話しようとするときに発生するパーミッションの問題を克服するものです。この問題が発生する理由は、rildradio グループ内でのみ通信が可能であるためです。
b2g
chrome プロセスとしても知られるこのプロセスは、Gecko を実装します。そのうち Radio Interface Layer に関係する部分は、dom/system/gonk/ril_worker.js です。これは rilproxy を通して rild と対話するワーカースレッドや radio state machine、主に ril_worker.js と Gecko content プロセスを含む他の Gecko コンポーネントとの間でメッセージ交換を行うメインスレッドの XPCOM サービスである nsIRadioInterfaceLayer インタフェースを実装します。
Gecko の content プロセス
Gecko の content プロセスにおいて nsIRILContentHelper インタフェース は、Telephony API や SMS API といった部分の DOM を実装するコードが radio interface と対話できるようにする XPCOM サービスを提供するものであり、chrome プロセス内にあります。

例: rild から DOM への通信

システムの低水準層が DOM コードとどのように通信するかを見ていきましょう。モデムは着信を受けると、独自の方法を使用して rild にそれを通知します。すると rild は、"オープン" なプロトコルに従ってクライアント向けのメッセージを用意します。これは ril.h に記述されています。着信の場合は RIL_UNSOL_RESPONSE_CALL_STATE_CHANGED メッセージが生成されて、rildrilproxy へ送信します。

rilproxy.c で実装されている rilproxy は自身のメインループ内でそのメッセージを受け取ります。ループでは以下のようなコードを使用して、rild との接続に問い合わせを行います:

ret = read(rilproxy_rw, data, 1024);

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

rild からメッセージを受け取ると、そのメッセージは rilproxy と Gecko を接続するソケットを通じて Gecko へ転送されます。Gecko は転送されたメッセージを IPC スレッドで受け取ります:

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

これらのメッセージを消費するのは SystemWorkerManager であり、これはメッセージを再パッケージ化して、RIL ステートマシンを実装する ril_worker.js スレッドに配布します。これは RILReceiver::MessageReceived() メソッドで行われます:

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

そのスレッドに渡されたタスクは次に、JavaScript で実装されている onRILMessage() 関数を呼び出します。これには、JavaScript の API 関数である JS_CallFunctionName() を使用します:

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

onRILMessage()dom/system/gonk/ril_worker.js で実装されており、メッセージバイトを処理して小分けします。小分けされたものはすべて、それぞれの適切なハンドラメソッドへ配られます:

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

このコードはオブジェクトからリクエストタイプを取得して、それが JavaScript コードで関数として定義されているかを確かめて、それからメソッドを呼び出すように動作します。ril_worker.js ではメソッドの各リクエストタイプにリクエストタイプと同じ名前を与えていますので、この処理はとてもシンプルです。

今回の例である RIL_UNSOL_RESPONSE_CALL_STATE_CHANGED では、以下のハンドラが呼び出されます:

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

前出のコードで見たように通話の状態が変化したことの通知を受けたときは、ステートマシンが getCurrentCall() メソッドを呼び出すことで現在の通話の状態を取得します:

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

これは現在アクティブなすべての通話の状態を問い合わせるために、rild へリクエストを送ります。リクエストは RIL_UNSOL_RESPONSE_CALL_STATE_CHANGED メッセージがたどったものと似た経路を、逆方向に戻っていきます (すなわち、ril_worker.js から SystemWorkerManagerRil.cpprilproxy、そして rild のソケットへ向かいます)。同様に rild は同じ経路で応答を返して、最終的に ril_worker.jsREQUEST_GET_CURRENT_CALLS メッセージ用ハンドラへたどり着きます。そして、双方向の通信が始まります。

それからは通話の状態が処理されて、以前の状態と比較されます。状態が変化した場合は、ril_worker.js がメインスレッドの nsIRadioInterfaceLayer サービスに通知します:

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

nsIRadioInterfaceLayerdom/system/gonk/RadioInterfaceLayer.js に実装されており、メッセージはこれの 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;
   ...

ここで実際に行っていることは、Parent Process Message Manager (PPMM) を使用した、content プロセスへのメッセージ配送です:

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

content プロセスではメッセージを Child Process Message Manager (CPMM) から、nsIRILContentHelper サービスの receiveMessage() で受け取ります:

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;

これは、登録済みであるすべてのテレフォニーコールバックオブジェクトの nsIRILTelephonyCallback.callStateChanged() メソッドを順々に呼び出します。window.navigator.mozTelephony API にアクセスするすべての Web アプリケーションに、既存の call オブジェクトの状態の変化あるいは新たな incoming call イベントの発生といったイベントを Web アプリケーション内の JavaScript へ配送する、コールバックオブジェクトのようなものが登録されています。

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

アプリケーションはこれらのイベントを受け取って、自身のユーザインタフェースなどを更新できます:

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

高度な例として、ダイヤラアプリケーションの handleEvent() をご覧ください。

3G データ

RIL メッセージに、携帯電話サービスの "データ通信" を開始するものがあります。これは、モデムのデータ通信モードを有効にします。このデータ通信は、通常のインタフェースで設定可能な Linux カーネルの Point-to-Point Protocol (PPP) インタフェースデバイスを生成および起動します。

注記: この章は記述が必要です。

ここでは、RIL 通信に関連する DOM API を紹介します:

WiFi

Firefox OS の WiFi バックエンドは、単にほとんどの処理で wpa_supplicant を使用します。つまり、バックエンドの主な役割は単にサプリカントの管理や、WiFi ドライバの読み込みやネットワークインタフェースの有効化・無効化といった付加的な作業になります。つまりバックエンドは、サプリカントの状態を追跡するステートによるステートマシンになります。

注記: WiFi で発生する興味深い事象のほとんどは、wpa_supplicant プロセスで起こりうる状態の変化に深く依存しています。

WiFi コンポーネントの実装は、2 つのファイルに分かれています:

dom/wifi/DOMWifiManager.js
nsIWifi.idl で定義されており、Web content に公開する API を実装します。
dom/wifi/WifiWorker.js
ステートマシンと、サプリカントを操作するコードを実装します。

これら 2 つのファイルは互いにメッセージマネージャを使用して通信します。バックエンドは "associate" などのアクションを求めるメッセージを受け入れて、アクションが完了したときに応答メッセージを返します。

DOM 側は状態の変化や情報の更新を示すさまざまなメッセージと同様に、応答メッセージを受け入れます。

注記: どの同期 DOM API も、パイプの API 側にデータをキャッシュするよう実装されています。可能であれば同期メッセージは避けてください。

WifiWorker.js

このファイルでは、WiFi インタフェースの背後にある主要なロジックを実装しています。これは chrome プロセス (マルチプロセスビルド) で実行され、SystemWorkerManager によってインスタンス化されます。このファイルはおおむね 2 つのセクションに分けられます: 大きな無名関数と WifiWorker (およびそのプロトタイプ) です。無形関数は最終的に、サプリカントへの接続やスキャン結果が利用可能になったといったイベントの通知を含むローカル API を提供することで、WifiManager になります。通常これには小さなロジックが含まれ、要求された情報への応答やサプリカントとの接続の細部を管理するのと合わせて唯一の利用者がそのアクションを制御できるようにします。

WifiWorker オブジェクトは、WifiManager とDOM の間にあります。これはイベントに反応して、それらを DOM に転送します。同様に DOM からの要求を受け取って、対応するアクションをサプリカントで実行します。また、サプリカントの状態に関する情報や次に何を行うべきかの管理も行います。

DOMWifiManager.js

これは DOM API を実装しており、メッセージを呼び出し元と実際の WiFi worker との間でやり取りします。これに関係する、とても小さなロジックがあります。

注記: chrome プロセスへの同期メッセージを避けるため、WiFi Manager は受け取ったメッセージに基づいて状態をキャッシュすることが必要です。

同期メッセージが 1 つあり、これは現在のサプリカントの状態を取得するために DOM API がインスタンス化されたときに送信されます。

DHCP

DHCP および DNS は標準的な Linux の DHCP クライアントである dhcpcd によって制御されます。しかし、これはネットワーク接続が失われたときに対応できません。このため、Firefox OS はワイヤレスネットワークに接続するたびに dhcpcd を停止および再起動します。

dhcpcd はデフォルトルートの設定も担います。DNS サーバについてカーネルに伝達するためには Network Manager を呼び出します。

Network Manager

Network Manager は 3G データ通信や WiFi コンポーネントによって開かれたネットワークインタフェースの設定を行います。

注記: この章は記述が必要です。

プロセスとスレッド

Firefox OS はすべてのアプリケーションスレッドの実装に POSIX スレッドを使用しており、それには各アプリケーションのメインスレッドだけでなく Web worker やヘルパースレッドも含まれます。プロセスやスレッドの優先度付けに nice 値を使用していることから、標準的な Linux カーネルのスケジューラに依存しています。プロセスの状態に応じて、異なる nice 値を割り当てています。現在私たちは 7 種類のレベルを使用します:

プロセスのプライオリティ値
プライオリティ nice 値 用途
MASTER 0 メインの b2g プロセス
FOREGROUND_HIGH 0 cpu または highpriority の Wakelock を保持する重要なアプリケーション。現在は時計や通信のアプリケーション向けに予約されています。
FOREGROUND 1 フォアグラウンドのアプリケーション
FOREGROUND_KEYBOARD 1 キーボードアプリケーション
BACKGROUND_PERCEIVABLE 7 音声を再生している、あるいは cpu または highpriority の Wakelock を保持しており少なくともシステムメッセージハンドラを登録しているバックグラウンドアプリケーション
BACKGROUND_HOMESCREEN 18 homescreen application
BACKGROUND 18 バックグラウンドで実行している、その他すべてのアプリケーション

同じ nice 値を共有するレベルがありますが、これは今のところレベルを Out of Memory Killer による扱われ方の違いとしているためです。すべてのプライオリティは、設定によりビルド時に調節できます。関係する項目は b2g/app/b2g.js ファイルにあります。

注記: Out of Memory Killer の詳細情報および Firefox OS が低メモリ状態にどう対処するかについては、Firefox OSの低メモリ管理をご覧ください。

プロセス内ではメインスレッドがプロセスの nice 値を継承する一方、Web Worker のスレッドは 1 段階大きな nice 値が与えられますので低いプライオリティで実行されます。この措置は、CPU 負荷が高い worker がメインスレッドの実行を過度に遅くさせないために行います。プロセスのプライオリティは、アプリケーションがバックグラウンドまたはフォアグラウンドに移行する、新たなアプリケーションが起動する、既存のアプリケーションが CPU Wakelock を取得するなどのイベントが発生するたびに変更されます。プロセスのプライオリティが調節されたら、そのスレッドのプライオリティも併せて調整されます。

注記: cgroups については、一部のデバイスやカーネルで信頼性がないことが判明したため、現在はリソースの管理に使用していません。

 

ドキュメントのタグと貢献者

Contributors to this page: yyss, ethertank
最終更新者: yyss,
サイドバーを隠す