草案
本页尚未完工.
这篇文章是对Firefox OS平台的高度概览,介绍了一些关键的概念以及组件如何交互的过程。
Note: Firefox OS 仍然是未发布的产品,这里描述的架构并不是最终版本。
Firefox OS 术语
在进一步学习 Firefox OS 文档前,请先了解下面的术语。
架构框图
- B2G
- Boot to Gecko 的简称。
- Boot to Gecko
- Firefox OS 操作系统的工程代号。 因为在该项目拥有官方名称之前B2G已经使用了很久的原因,它经常用于指代 Firefox OS。
- Firefox OS
- FIrefox OS 基本上是指 Mozilla及合作伙伴应用在 B2G上的品牌和服务支持, 最终将创建一个发布的产品。
- Gaia
- Firefox OS 平台的用户接口层。屏幕启动时渲染到屏幕上的一切都是Gaia层的产物。Gaia 实现了 lock screen, home screen, 和所有你所期待在智能手机上看到的标准应用。Gaia 完全使用 HTML, CSS, 和 JavaScript实现。Web APIs 是Gaia层到底层系统的唯一入口,Web APIs 是由Gecko 层实现的。第三方应用可以安装在Gaia层。
- Gecko
- Firefox OS 应用的运行环境;该层提供了对: HTML, CSS, and JavaScript三个标准的支持,它能确保APIs能够在gecko支持的系统上良好工作。也就是说,它包括了网络栈,图形栈,布局引擎,js虚拟机和端口层。
- Gonk
- Gonk 是Firefox OS平台更低层的系统,包括了 Linux kernel (基于 AOSP)和用户空间硬件抽象层 (HAL)。内核和一些用户空间库都是公共的开源项目:linux, libusb, bluez等。其他的一些硬件抽象层部分是与android项目共享的:GPS, camera等。你可以认为 Gonk 是一个非常简单的 Linux 版本。Gonk 是 Gecko 层的端口目标;也就是说 Gecko 层有到 Gonk 的端口,就像Gecko 到 Mac OS X, Windows, 和 Android 一样。因为Firefox OS 对 Gonk 拥有完全的控制权,相比其他操作系统,我们可以释放更多的接口到 Gecko。例如,Gecko 拥有到 Gonk 电话栈和帧缓冲区的直接入口,但在其他操作系统却没有。
- Jank
- 这个术语经常用在移动app空间的讨论中,主要是指在app中缓慢/低效的代码操作会导致 block UI的更新甚至出现无响应状态。我们的gaia工程师会不惜一切代价使用各种优化技术来避免这一问题。
Firefox OS 启动步骤
本节主要描述了FIrefox OS 设备启动以及各模块交互的过程。简而言之,整个系统的启动流程是从内核空间的bootloaders开始,继而对native code初始化,到B2G至用户空间的Gecko,最后会到 Gaia 层 system app,window manager,homescreen app。而其他应用都是在它们上面执行的。
自启动过程
当Firefox OS 设备启动时,执行过程从主要的引导装载程序(primary bootloader)开始。此时,会以常用的方式装载主操作系统。在这个链条中,一级级的装载会触发更高层次的迭代。这个过程最终是交由 Linux 内核处理。
关于启动过程,这里有几点需要注意的地方:
- 在设备启动时,装载程序会显示第一个 splash 界面给用户。界面通常是一个厂商的logo。
- 装载程序会为设备实现一个图片的动画。不同的设备使用的协议是有差别的,绝大多数手机会使用 fastboot protocol, 但是Samsung Galaxy S II 使用的是 odin 协议。
- 在启动过程结束时, modem image 通常会被装载并运行在 modem 处理器上。然而这些过程都是由设备不同而区分的,有的甚至是独有的。
Linux 内核
Gonk使用的 Linux内核与原生Linux派生出的AOSP是非常相似的。与AOSP (Android Open Source Project )相比,它还有一些小的变化。另外,厂商还可能会对内核进行修改,并将这些变化按照自身的要求来设定。 一般而言, Linux内核是非常相近的。
网络上对 startup process for Linux 有许多详细的介绍,此处不会再赘述。
Linux内核会带动设备的启动并且运行基本的过程。它会先执行 init.rc
中的步骤,继而运行 init.b2g.rc
来启动必须的步骤, 如包含的Gecko内的Firefox OS 基本过程以及 rild (电话相关由不同芯片组相关的进程)— 下面会有更详细的说明。 在过程结束时,正如绝大多数 UNIX-like 操作系统一样, 用户空间 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-like操作系统是非常类似的。它会 将init*.rc 脚本文件解释成包含启动各种服务的命令的集合。Firefox OS init.b2g.rc
其实就是在 Android init.rc
文件的基础上添加一些关于启动Firefox OS的命令的文件。
init
进程启动过程中一个关键任务就是启动 b2g
进程,它是 Firefox OS 操作系统的核心所在。
init.b2g.rc
文件中启动b2g进程的命令如下所示:
service b2g /system/bin/b2g.sh class main onrestart restart media
您也可以查看下 init.b2g.rc
文件内容,它是添加在 Android init.rc 文件后用来启动b2g进程的。
注意: 对\不同的设备而言,init.rc
与 Android 的 对应文件间的差别也是不同的。,init.b2g.rc
只是简单的附加在文件后面的,有时候 patch 文件显得会更重要些。
userspace 进程架构
现在如果概览一下Firefox OS的不同组件是如何组合在一起并与其他组件交互的过程是非常有用的。下图则包含了 Firefox OS 主要的 usersapce 进程。
注意: 我们应当知道,由于 FIrefox OS 正处在活跃开发阶段,这个框图可能会发生变化,不会那么准确。
b2g
进程是主要的系统进程。它会以非常高的优先级运行,并且会访问绝大多数的硬件设备。b2g 会和 modem 进行通信, 会在 display framerbuffer上绘图, 与 GPS, Camera 以及其他硬件交互。 b2g
内部运行在Gecko层(由 libxul.so
实现)。查看 Gecko 可以获得 Gecko层如何工作, b2g
与Gecko如何通信的细节。
b2g
b2g
进程可能会反过来孵化一些低优先级的 content processes。 这些进程都是关于 web 应用或其他的 web 内容装载的过程。这些进程会和Gecko server主进程通过 IPDL 消息传送系统进行通信。
rild
rild
进程是 modem 处理器的接口。 rild
是实现 Radio Interface Layer (RIL) 的守护进程。 它是一个专有的代码,由硬件供应商与自己的modem 硬件实现。rild
使客户端代码连接它所绑定的 UNIX域的 socket成为可能。它会由 init
脚本中的如下代码启动:
service ril-daemon /system/bin/rild socket rild stream 660 root radio
rilproxy
在Firefox OS 中, rild
客户端就是 rilproxy
进程。它在 rild
和 b2g 扮演着简单的转发代理服务器的角色。这个代理需要的是实现的细节,可以说,这确实是非常必要的。riproxy
代码可以在Github上找到。
mediaserver
mediaserver
进程控制着音频和视频的播放。Gecko会通过 Android远程过程调用机制(RPC)与 其通信。Gecko可以播放的多媒体(OGG Vorbis 音频, OGG Theora 视频,以及 WebM 视频)都是由 Gecko解码并直接发送给 mediaserver
进程。其他的多媒体文件是由 libstagefright 解码的,它能够访问专用编码器和硬件编码器。
注意: mediaserver
进程时一个临时的Firefox OS组件, 对我们初始的开发会有帮助,但最终会离开。然而在 Firefox OS 2.0 之前,可能不会发生。
netd
netd
进程用来对网络接口进行配置。
wpa_supplicant
wpa_supplicant
进程是标准的 UNIX样式的守护进程,会处理 WIFI 的连接。
dbus-daemon
dbus-daemon 实现了 D-Bus( DBus 是指Firefox OS 用于蓝牙通信的一种消息总线系统)。
Gecko
Gecko,正如之前提到的,是对 web标准( HTML, CSS, andJavaScript)的实现,用于实现用户在Firefox OS 上看到的一切界面。
注意: 您可以使用 https://dxr.mozilla.org 来查找Gecko代码库。这个网站非常有趣,并且提供了较好的参照特性,但是代码库的数量有限。或者您可以尝试下传统的 https://mxr.mozilla.org ,其中会包括更多的Mozilla 项目。
与Firefox OS 相关的 Gecko 文件
b2g/
b2g 文件夹会包含了一些主要的Firefox OS 相关的功能。.
b2g/chrome/content
包含了在system app 之上运行的 Javascript 文件。
b2g/chrome/content/shell.html
Gaia的入口 — system app的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
app中装载的第一个脚本文件。.
shell.js
导入了所有需要的模块,注册键值监听,定义了 sendCustomEvent
和sendChromeEvent
与Gaia 通信,并且提供了 webapp 的安装助手: indexedDB quota, RemoteDebugger, keyboard helper, 和screenshot 工具。
但是shell.js
最重要的功能就是启动了 Gaia system
app, 之后又将整个系统相关的管理工作移交给了 Gaia system
app。
let systemAppFrame =
document.createElementNS('https://www.w3.org/1999/xhtml', 'html:iframe');
...
container.appendChild(systemAppFrame);
b2g/app/b2g.js
这个脚本中包含了预定义的配置信息,如浏览器中的 about:config, 文件与 Gaia 中的 pref.js 类似。这些配置可以在 Settings app中更改,可以使用Gaia 构建脚本文件 Gaia user.js文件覆写。
dom/{API}
新的 API 实现(post-b2g)放置在 dom/ 文件夹中;而旧的API 的 dom/base 文件夹中,如
navigator.cpp。
dom/apps
.jsm
文件会被加载,是对.js
API 的实现,如 webapp.js
等。
dom/apps/src/
所有的权限信息都在 PermissionsTable.jsm 文件中定义。
dom/webidl
WebIDL 是一种用来定义 web APIs 的语言。可查看 WebIDL_bindings 文件来获取它所支持的特性。
hal/gonk
这个文件夹包含了 gonk 接口层的相关文件。
Generated files
module/libpref/src/init/all.js
包含所有的配置文件。
/system/b2g/ omni.ja and omni.js
包含了设备中的资源样式包。
输入事件处理
在Gekco层绝大多数动作都是由用户行为触发的。这些动作是由输入事件所表示的(如按钮点击,触屏设备的触控,等等)。这些事件是通过 nsIAppShell
的 Gonk implementation 传入的,这个接口用来表示Gecko 应用的最初入口点。也就是说, 输入设备会调用 nspAppShell对象的方法,来表示 Gecko子系统想要发送事件到用户界面。
例如:
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 文件的 EventHub::getEvents()
的方法中找到创建输入事件的代码。
当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 会使用 OpenGL ES 2.0 to draw to a GL context that wraps the hardware frame buffers. 这是由 Gonk 层nsWindow 实现的,代码如下:
gNativeWindow = new android::FramebufferNativeWindow();
sGLContext = GLContextProvider::CreateForWindow(this);
FramebufferNativeWindow
类是直接 从Android拿过来的,可参考 FramebufferNativeWindow.cpp
。为了能够将缓存从帧缓存设备映射到内存中,它使用了 gralloc API 来访问图像驱动。
Gecko 使用了 Layers 系统来将复合后的内容画在屏幕上。总结一些,基本步骤是:
- Gecko draws separate regions of pages into memory buffers. Sometimes these buffers are in system memory; other times, they're textures mapped into Gecko's address space, which means that Gecko is drawing directly into video memory. This is typically done in the method
BasicThebesLayer::PaintThebes()
. - Gecko then composites all of these textures to the screen using OpenGL commands. This composition occurs in
ThebesLayerOGL::RenderTo()
.
The details of how Gecko handles the rendering of web content is outside the scope of this document.
硬件抽象层 (HAL)
The Gecko hardware abstraction layer is one of the porting layers of Gecko. It handles low-level access to system interfaces across multiple platforms using a C++ API that's accessible to the higher levels of Gecko. These APIs are implemented on a per-platform basis inside the Gecko HAL itself. This hardware abstraction layer is not exposed directly to JavaScript code in Gecko.
How the HAL works
Let's consider the Vibration
API as an example. The Gecko HAL for this API is defined in hal/Hal.h. In essence (simplifying the method signature for clarity's sake), you have this function:
void Vibrate(const nsTArray<uint32> &pattern);
This is the function called by Gecko code to turn on vibration of the device according to the specified pattern; a corresponding function exists to cancel any ongoing vibration. The Gonk implementation of this method is in hal/gonk/GonkHal.cpp:
void Vibrate(const nsTArray<uint32_t> &pattern) {
EnsureVibratorThreadInitialized();
sVibratorRunnable->Vibrate(pattern);
}
This code sends the request to start vibrating the device to another thread, which is implemented in VibratorRunnable::Run()
. This thread's main loop looks like this:
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()
is the Gonk HAL API that turns on the vibrator motor. Internally, this method sends a message to the kernel driver by writing a value to a kernel object usingsysfs
.
Fallback HAL API implementations
The Gecko HAL APIs are supported across all platforms. When Gecko is built for a platform that doesn't expose an interface to vibration motors (such as a desktop computer), then a fallback implementation of the HAL API is used. For vibration, this is implemented in hal/fallback/FallbackVibration.cpp.
void Vibrate(const nsTArray<uint32_t> &pattern) {
}
Sandbox implementations
Because most web content runs in content processes with low privileges, we can't assume those processes have the privileges needed to be able to (for example), turn on and off the vibration motor. In addition, we want to have a central location for handling potential race conditions. In the Gecko HAL, this is done through a "sandbox" implementation of the HAL. This sandbox implementation simply proxies requests made by content processes and forwards them to the "Gecko server" process. The proxy requests are sent using IPDL.
For vibration, this is handled by the Vibrate()
function implemented in hal/sandbox/SandboxHal.cpp:
void Vibrate(const nsTArray<uint32_t>& pattern, const WindowIdentifier &id) {
AutoInfallibleTArray<uint32_t, 8> p(pattern);
WindowIdentifier newID(id);
newID.AppendProcessID();
Hal()->SendVibrate(p, newID.AsArray(), GetTabChildFrom(newID.GetWindow()));
}
This sends a message defined by the PHal
interface, described by IPDL in hal/sandbox/PHal.ipdl. This method is described more or less as follows:
Vibrate(uint32_t[] pattern);
The receiver of this message is the HalParent::RecvVibrate()
method in hal/sandbox/SandboxHal.cpp, which looks like this:
virtual bool RecvVibrate(const InfallibleTArray<unsigned int>& pattern,
const InfallibleTArray<uint64_t> &id,
PBrowserParent *browserParent) MOZ_OVERRIDE {
hal::Vibrate(pattern, newID);
return true;
}
This omits some details that aren't relevant to this discussion; however, it shows how the message progresses from a content process through Gecko to Gonk, then to the Gonk HAL implementation of Vibrate()
, and eventually to the Vibration driver.
DOM APIs
DOM interfaces 从本质上说,是指web内容与Gecko的如何通信的。当然此处会涉及到更多的内容,如果您对更多的细节感兴趣,可以参考 about the DOM 。. DOM 接口是使用 IDL 定义的,包括外部功能接口(FFI) 和使用JavaScript 和 C++ 的对象模型(OM)。
震动API是通过IDL接口暴露给web内容的,IDL接口定义在nsIDOMNavigator.idl:
[implicit_jscontext] void mozVibrate(in jsval aPattern);
jsval
参数表示 mozVibrate()
(这个未定版的震动规范是以我们的厂商作为前缀实现的)会接受任何输入的JavaScript值。 IDL编译器 xpidl
会产生一个C++接口,这个接口由 定义在 Navigator.cpp 文件中的 Navigator
类 实现。
NS_IMETHODIMP Navigator::MozVibrate(const jsval& aPattern, JSContext* cx) {
// ...
hal::Vibrate(pattern);
return NS_OK;
}
您在这个方法中看到的源码要比这里要多,但这并非我们此次讨论的目的。关键点在于此处会调用 hal::Vibrate()
将控制行为从DOM层传送到GECKO HAL层。 在那里,我们会进入 在之前一节HAL 实现的讨论,并会一直走到图形驱动层。最重要的是,DOM的实现并不会关心它运行在那个平台 (Gonk, Windows, Mac OS X, 或者其他)。、它也不会关心这些代码时运行在 content 进程或者是 Gecko 服务器进程。这些细节都会由系统底层处理。
震动 API 非常简单, 可以作为一个好的例子来讲解。 SMS API 则比较复杂会使用到自身的“远程”层级连接content 进程到服务器。
Radio Interface Layer (RIL)
The RIL was mentioned in the section The userspace process architecture. This section will examine how the various pieces of this layer interact in a bit more detail.
The main components involved in the RIL are:
rild
- The daemon that talks to the proprietary modem firmware.
rilproxy
- The daemon that proxies messages between
rild
and Gecko (which is implemented in theb2g
process). This overcomes the permission problem that arises when trying to talk torild
directly, sincerild
can only be communicated with from within theradio
group. b2g
- This process, also known as the chrome process, implements Gecko. The portions of it that relate to the Radio Interface Layer are dom/system/gonk/ril_worker.js (which implements a worker thread that talks to
rild
throughrilproxy
and implements the radio state machine; and thensIRadioInterfaceLayer
interface, which is the main thread's XPCOMservice that acts primarily as a message exchange between theril_worker.js
thread and various other Gecko components, including the Gecko content process. - Gecko's content process
- Within Gecko's content process, the
nsIRILContentHelper
interface provides an XPCOM service that lets code implementing parts of the DOM, such as theTelephony and SMS APIs talk to the radio interface, which is in the chrome process.
Example: Communicating from rild to the DOM
Let's take a look at an example of how the lower level parts of the system communicate with DOM code. When the modem receives an incoming call, it notifies rild
using a proprietary mechanism. rild
then prepares a message for its client according to the "open" protocol, which is described in ril.h
. In the case of an incoming call, aRIL_UNSOL_RESPONSE_CALL_STATE_CHANGED
message is generated and sent by rild
torilproxy
.
rilproxy
, implemented in rilproxy.c
, receives this message in its main loop, which polls its connection to rild
using code like this:
ret = read(rilproxy_rw, data, 1024);
if(ret > 0) {
writeToSocket(rild_rw, data, ret);
}
Once the message is received from rild
, it's then forwarded along to Gecko on the socket that connects rilproxy
to Gecko. Gecko receives the forwarded message on its IPC thread:
int ret = read(fd, mIncoming->Data, 1024);
// ... handle errors ...
mIncoming->mSize = ret;
sConsumer->MessageReceived(mIncoming.forget());
The consumer of these messages is SystemWorkerManager, which repackages the messages and dispatches them to theril_worker.js
thread that implements the RIL state machine; this is done in theRILReceiver::MessageReceived()
method:
virtual void MessageReceived(RilRawData *aMessage) {
nsRefPtr<DispatchRILEvent> dre(new DispatchRILEvent(aMessage));
mDispatcher->PostTask(dre);
}
The task posted to that thread in turn calls the onRILMessage()
function, which is implemented in JavaScript. This is done using the JavaScript API functionJS_CallFunctionName()
:
return JS_CallFunctionName(aCx, obj, "onRILMessage", NS_ARRAY_LENGTH(argv), argv, argv);
onRILMessage()
is implemented in dom/system/gonk/ril_worker.js, which processes the message bytes and chops them into parcels. Each complete parcel is then dispatched to individual handler methods as appropriate:
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);
}
}
This code works by getting the request type from the object, making sure it's defined as a function in the JavaScript code, then calling the method. Since ril_worker.js implements each request type in a method given the same name as the request type, this is very simple.
In our example, RIL_UNSOL_RESPONSE_CALL_STATE_CHANGED
, the following handler is called:
RIL[UNSOLICITED_RESPONSE_CALL_STATE_CHANGED] = function UNSOLICITED_RESPONSE_CALL_STATE_CHANGED() {
this.getCurrentCalls();
};
As you see in the code above, when notification is received that the call state has changed, the state machine simply fetches the current call state by calling thegetCurrentCall()
method:
getCurrentCalls: function getCurrentCalls() {
Buf.simpleRequest(REQUEST_GET_CURRENT_CALLS);
}
This sends a request back to rild
to request the state of all currently active calls. The request flows back along a similar path the RIL_UNSOL_RESPONSE_CALL_STATE_CHANGED
message followed, but in the opposite direction (that is, from ril_worker.js
toSystemWorkerManager
to Ril.cpp
, then rilproxy
and finally to the rild
socket). rild
then responds in kind, back along the same path, eventually arriving in ril_worker.js
's handler for the REQUEST_GET_CURRENT_CALLS
message. And thus bidirectional communication occurs.
The call state is then processed and compared to the previous state; if there's a change of state, ril_worker.js notifies the nsIRadioInterfaceLayer
service on the main thread:
_handleChangedCallState: function _handleChangedCallState(changedCall) {
let message = {type: "callStateChange",
call: changedCall};
this.sendDOMMessage(message);
}
nsIRadioInterfaceLayer
is implemented in dom/system/gonk/RadioInterfaceLayer.js; the message is received by itsonmessage()
method:
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;
...
All this really does is dispatch the message to the content process using the Parent Process Message Manager (PPMM):
handleCallStateChange: function handleCallStateChange(call) {
[some internal state updating]
ppmm.sendAsyncMessage("RIL:CallStateChanged", call);
}
In the content process, the message is received by receiveMessage()
method in the nsIRILContentHelper
service, from the 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;
This, in turn, calls the nsIRILTelephonyCallback.callStateChanged()
methods on every registered telephony callback object. Every web application that accesses the window.navigator.mozTelephony
API has registered one such callback object that dispatches events to the JavaScript code in the web application, either as a state change of an existing call object or a new incoming
call event.
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;
}
Applications can receive these events and update their user interface and so forth:
handleEvent: function fm_handleEvent(evt) {
switch (evt.call.state) {
case 'connected':
this.connected();
break;
case 'disconnected':
this.disconnected();
break;
default:
break;
}
}
Take a look at the implementation of handleEvent()
in the Dialer application as an extended example.
3G data
There is a RIL message that initiates a "data call" to the cellular service; this enables data transfer mode in the modem. This data call ends up creating and activating a Point-to-Point Protocol (PPP) interface device in the Linux kernel that can be configured using the usual interfaces.
Note: This section needs to be written.
Related DOM APIs
This section lists DOM APIs that are related to RIL communications:
- Telephony API
- SMS API
- Mobile Connection API
WiFi
Firefox OS 的WIFI后台只是简单的借助 wpa_supplicant
来完成绝大部分工作。也就是说,后台的主要工作就是对 supplicant 的简单管理以及一些初始化的工作,如装载WiFi 驱动和使能或禁止网络接口。后台本质上只是一个跟随supplicant 状态而变化的状态机。
注意: 在WIFI中发生的绝大部分事情会取决于 wpa_supplicant
进程状态的变化。
WIFI组件的实现可以分成两个文件:
- dom/wifi/DOMWifiManager.js
- 实现了暴露给web 内容的API,这些API是定义在
nsIWifi.idl
中的。 - dom/wifi/WifiWorker.js
- 实现了关于 supplicant 的状态机和驱动代码。
这两个文件之间使用 message manager 通信。消息的后台监听需要特定的动作触发,如 "associate", 当动作执行完成时也会返回一个消息。
对响应方法的DOM侧监听与一些事件消息一样,都表示状态的变化和信息的更新。
Note: 任何同步的 DOM APIs 都是通过管道一侧的缓存数据实现的。如果可能的话,要尽量避免使用同步消息。
WifiWorker.js
This file implements the main logic behind the WiFi interface. It runs in the chrome process (in multi-process builds) and is instantiated by the SystemWorkerManager. The file is generally broken into two sections: a giant anonymous function and WifiWorker
(and its prototype). The anonymous function ends up being the WifiManager
by providing a local API, including notifications for events such as connection to the supplicant and scan results being available. In general, it contains little logic and lets its sole consumer control its actions while it simply responds with the requested information and controls the details of the connection with the supplicant.
The WifiWorker
object sits between the WifiManager
and the DOM. It reacts to events and forwards them to the DOM; in turn, it receives requests from the DOM and performs the appropriate actions on the supplicant. It also maintains state information about the supplicant and what it needs to do next.
DOMWifiManager.js
This implements the DOM API, transmitting messages back and forth between callers and the actual WiFi worker. There's very little logic involved.
Note: In order to avoid synchronous messages to the chrome process, the WiFi Manager does need to cache the state based on the received event.
There's a single synchronous message, which is sent at the time the DOM API is instantiated, in order to get the current state of the supplicant.
DHCP
DHCP and DNS are handled by dhcpcd
, the standard Linux DHCP client. However, it's not able to react when the network connection is lost. Because of this, Firefox OS kills and restarts dhcpcd
each time it connects to a given wireless network.
dhcpcd
is also responsible for setting the default route; we call into the network manager to tell the kernel about DNS servers.
网络管理器
网络管理器会对由3G数据和WIFI组件打开的网络接口进行配置。
注意: 这部分内容需要完善。
进程和线程
Firefox OS 操作系统使用 POSIX 线程机制实现所有的应用线程,包括每个应用的主线程,Web workers和辅助线程。优先值是由标准的Linux内核调度程序决定用来表示进程和线程执行时的优先级。根据进程的状态不同,我们设定了不同的优先值。当前共有7个等级:
进程优先级等级
Priority | Nice | Used for |
---|---|---|
MASTER |
0 | b2g 主进程 |
FOREGROUND_HIGH |
0 | 持有 CPU wakelock 的应用 |
FOREGROUND |
1 | 前台应用 |
FOREGROUND_KEYBOARD |
1 | 键盘应用 |
BACKGROUND_PERCEIVABLE |
7 | 播放音乐的后台应用 |
BACKGROUND_HOMESCREEN |
18 | homescreen 应用 |
BACKGROUND |
18 | 运行在后台的所有其他应用 |
有些等级优先值是相同的,这是因为这些等级是低内存查杀机制处理的方式是不同的。所有的优先级都可以在构建时通过参数配置进行调整。您可以在 b2g/app/b2g.js文件中找到相关实体。
注意: 要获取更多关于内存溢出(out-of-memory killer)以及 Firefoex OS 是如何管理低内存状态的内容,可以参阅 Out of memory management on Firefox OS.
在一个进程内部,主线程继承了进程的优先值,同时则给定了web worker线程比主线程高一个值的优先值。因此web worker 线程优先级与主线程相比要低一些。之所以这么做是为了避免 CPU密集性的工作降低主线程的处理速度。当比较重要的事件(如一个应用转到前台或后台,启动一个新的应用或一个已存在的应用获得 CPU wakelock)发生时,进程优先级就会发生变化。当一个进程优先级发生变化时,其中所有的线程优先级就会对应的作出调整。
注意: cgroups 当前并没有用于资源的管理,它们在特定的设备及内核中显得并不可靠。