本文件是 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
- Firefox OS 即是將 Mozilla (及其 OEM 夥伴) 的品牌與服務,套用至 Boot to Gecko 的基礎之上開發而得的最終產品。
- Gaia
- Firefox OS 平台的使用者介面 (UI)。 在啟動 Firefox OS 之後,都是透過 Gaia 層產出螢幕上的任何東西。Gaia 建構出鎖定畫面、主畫面,以及大家預期智慧型手機所應提供的其他標準 App。Gaia 完全使用 HTML、CSS、JavaScript 實作而成。透過實作在 Gecko 層的開放 Web API,與其底層的作業系統溝通。第三方 App 均可安裝於 Gaia 層。
- Gecko
- Firefox OS App 的執行環境 (Runtime),也就是本層可支援所有的開放標準:HTML、CSS、JavaScript。本層另必須確保這些 API 可正確運作於 Gecko 支援的所有 OS。意即 Gecko 另包含了網路連線堆疊、圖形堆疊、配置引擎、JavaScript 虛擬機器、移植層。
- Gonk
- Gonk 是 Firefox OS 平台的底層作業系統,是由 Linux 核心 (以 Android Open Source Project 為架構),與使用者空間的硬體抽象層 (Hardware abstraction layer,HAL) 所組成。核心以及許多使用者空間的函式庫,均來自常見的開放源碼專案,如 Linux、libusb、bluez 等。HAL 的某些部分是與 AOSP 共享,如 GPS 或相機。你可將 Gonk 當成是簡單的 Linux 分支版本。Gonk 又是 Gecko 的移植目標;意即有一套 Gecko 可在 Gonk 上執行,就像是 Gecko 也有 Mac OS X、Windows、Android 等版本。因為 Firefox OS 可完整控制 Gonk,所以某些不能開放給其他 OS 用的介面,還是可開發給 Gecko 使用。舉例來說,Gecko 可直接存取 Gonk 的完整電話功能堆疊,並於 Gonk 上顯示緩衝區 (Frame buffer),但是其他 OS 不能進行類似的存取。
- Jank
- 此一詞彙常見於行動 App 領域,指在 APP 中執行了緩慢或沒有效率的程式碼,進而阻礙了 UI 更新並發商系統延遲或無回應的情形。Gaia 開發者使用不同的最佳化方法,要儘力避免此狀況發生。
整體架構示意圖
下圖比較了專利平台與 Firefox OS 之間的架構。
Firefox OS 即減去了 OS 與 App 層之間的原生 API 層。這種統合式的設計可減輕平台負擔並簡化安全機制,卻又不致犧牲效能,也不會影響智慧型手機的使用體驗。
- Gaia 作為裝置的核心 Web App 與 UI 層,並全以 HTML5、CSS、JavaScript 撰寫而成,並有許多 API 可供 UI 程式碼存取手機硬體與 Gecko 功能。
- Gecko 即為 Firefox OS 的 Web 引擎與表現層 (Presentation layer),作為 Web 內容與底層裝置的之間介面,進而將硬體接上 HTML。Gecko 亦提供 HTML5 剖析 (Parsing) 與繪圖引擎,並透過程式設計的方式,以安全的 Web API 存取硬體功能、更新作業,以及其他核心服務。
- Gonk 是 Firefox OS 堆疊中的核心層級元件,作為 Gecko 與底層硬體之間的介面。Gonk 可控制底層硬體,並將硬體功能告知 Gecko 中建構的 Web API。Gonk 可視為「黑盒子」,暗自進行所有複雜又零碎的背景作業,以於硬體層制定請求,進而控制行動裝置。
- 行動裝置,就是執行 Firefox OS 的行動電話硬體。OEM 廠商則負責提供行動裝置。
特殊 Firefox OS 架構
Firefox OS 開機流程
本段將說明 Firefox OS 的開機流程,包含整個流程所牽涉的部份,以及相關部分在系統裡的位置。流程順序大致如下:最先從核心空間裡的開機載入器 (Bootloader) 開始,到原生程式碼中開始程序,進入 B2G 再到使用者空間的 Gecko,最後到 Gecko 裡的系統 App、視窗管理器、主畫面 App。而其他 App 也同樣依照上述程序執行。
開機程序
在首次啟動 Firefox OS 裝置時,就會於主要的開機載入器 (bootloader) 中開始作業。自此,就如同一般作業系統程序,更高一階的開機相關載入器會載入執行,然後往上更高一階的載入器又會被接續執行,如此下去形成一串開機載入流程鏈,直到最後程序就會將執行權交到 Linux 核心。
關於開機程序有幾點需要注意:
- 開機載入器通常顯示裝置開機時的第一個畫面,一般為硬體製造商的標誌。
- 開機載入器可將映像檔 (Image) 建構於裝置之中。不同的裝置另使用不同的通訊協定。大部分的手機均使用 fastboot 協定。但 Samsung Galaxy II 使用 odin 協定。
- 在開機流程的尾聲,往往會載入數據機映像檔,並於數據機處理器上執行之。各款裝置的方法均有所不同。
Linux 核心
Gonk 使用 Linux 核心的版本,與 Android 開放源碼專案 (Android Open Source Project,AOSP) 所衍生使用的版本非常相似,但是並未沿用幾項 AOSP 所進行的修改。除外,雖然 Lunix 基本上已經相當穩定,但仍有些製造商會自行修改。
Linux 的啟動程序已經可在網路上找到相當詳盡的文件說明,所以在此不再贅述。
Linux 核心會啟動一些基本的程序 (Process)。在執行 init.rc 中定義的程序後,會再執行 init.b2g.rc 以啟動基本核心程序,如 b2g
(Firefox OS 基礎程序,內含 Gecko) 以及 rild
(通話功能的相關程序,各晶片組多有不同),請見下方所述細節。和大部分的 Unix 作業系統一樣,最後會啟動使用者空間 (Userspace) 程序。
在啟動 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
程序,將處理必要的檔案系統並產生系統服務,之後就如同程序管理一般繼續待命,並將編譯指令碼 (也就是 init*.rc
檔案);指令碼內的指令則說明在啟動不同服務時所應進行的作業。
init
程序必須處理一個相當關鍵的作業,即啟動 b2g
程序;這也是 Firefox OS 的核心。
以下為 init.rc
中用以啟動 b2g 的程式碼:
service b2g /system/bin/b2g.sh class main onrestart restart media
注意:Firefox OS 與 Android 的 init.rc 之間的差異,將各款裝置而有所不同。有時候只是附加 init.b2g.rc
,但有時修改的幅度甚大。
使用者空間 (Userspace) 的程序架構
現在讓我們來看看高階一點的部分,到底Firefox OS的各個元件之間是如何運作互動的。下圖所描述的是Firefox OS的主要使用者空間程序。
接著可進一步了解 Firefox OS 不同元件是如何運作互動。下圖呈現 Firefox OS 的主要使用者空間程序。
注意:由於 Firefox OS 尚處於開發階段,所以此示意圖可能隨時修改而非完全正確。
b2g 程序是主要的系統程序,能以高權限存取大部分的硬體;b2g 可溝通數據機、畫出顯示緩衝區 (Framebuffer),並與 GPS 或相機等硬體溝通。在系統內部,b2g 則執行 Gecko 層 (即以 libxul.so 所實作)。另可參考 Gecko 以了解 Gecko 層的運作方式,以及 b2g 與 Gecko 層溝通的方法。
b2g
b2g 程序會生成許多低權限的內容程序 (Content process),並透過這些程序來載入 Web 的 App 與內容。這些程序會再透過訊息傳遞系統「IPDL」溝通 Gecko 伺服器。
b2g 程序所執行
的 libxul,即參照 b2g/app/b2g.js
取得預設偏好設定。透過偏好設定,程序將開啟上述的 HTML 檔案「b2g/chrome/content/shell.html
」,即於 omni.ja
檔案中編譯而得。而 shell.html
則包含了 b2g/chrome/content/shell.js
檔案,將觸發 Gaia 的「system」
這個 App。
rild
rild 為銜接
數據機處理器的介面,也是用以建構無線介面層 (Radio Interface Layer,RIL) 的常駐程式 (Daemon)。往往是由硬體製造商所撰寫的專屬程式碼,以溝通自家的數據機硬體。rild 可讓用戶端
程式碼連接 rild 本身
綁定的 Unix-domain socket。可經由以下類似的 init 指令碼
啟動之:
service ril-daemon /system/bin/rild socket rild stream 660 root radio
rilproxy
在 Firefox OS 中,rild
用戶端即屬於 rilproxy
程序,作為 rild
與 b2g
之間不具功能的傳送代理dumb forwarding proxy。為何需要此代理伺服器,就必須從實作細節來說明了。但簡單來說,proxy 確實有其存在的必要。可到 GitHub 上找到 rilproxy
原始碼。
mediaserver
mediaserver
程序用以控制音訊與視訊的播放作業。Gecko 透過 Android 的遠端程序呼叫 (Remote Procedure Call,RPC) 和其溝通。某些可由 Gecko 撥放的媒體檔案 (如 OGG Vorbis 音訊、OGG Theora 視訊、WebM 視訊等),均是由 Gecko 解碼後直接傳送給 mediaserver
程序。其他的媒體檔案則是交由 libstagefright
解碼。libstagefright 可存取特定
編碼與硬體編碼器 (Hardware encoder)。
注意:mediaserver 程序為 Firefox OS 的「暫時性」元件,主要是協助早期的開發作業。預計最快在 Firefox OS 2.0 階段就會移除。
netd
netd
程序可用來設定網路介面。
wpa_supplicant
wpa_supplicant
程序為標準 UNIX 樣式常駐程式,可處理 WiFi 存取點的連線作業。
dbus-daemon
dbus-daemon 用以建構 D-Bus 訊息匯流系統,可供 Firefox OS 進行藍牙通訊作業。
Gecko
如前所述,Gecko 即由 Web 標準 (HTML、CSS、JavaScript) 建構而成,並用以打造 Firefox OS 上所顯示的一切,另可控制手機硬體。Web App 則透過安全且受控制的 Web API (同樣以 Gecko 所建構而得) 來將 HTML5 連上硬體。Web API 可透過程式設計的方式,存取底層行動裝置硬體 (如電池或振動) 的功能,或取得儲存的資料 (如行事曆或聯絡資訊)。Web 內容亦可透過content invokes the accessible Web APIs within HTML5.
App 是由相關 HTML5 網頁內容所組成。如果要撰寫 Web App 並於 Firefox OS 行動裝置上執行,則開發者也只要組合、封包、發佈此網頁內容即可。而在執行期間,即交由瀏覽器轉譯、編譯、換至此網頁內容。可參閱應用程式中心 (App Center) 進一步了解。
注意:如果要搜尋 Gecko 的 Codebase,則可前往 https://dxr.mozilla.org,功能比較炫、參照功能不錯,但 Repo 較有限。亦可使用傳統的 https://mxr.mozilla.org,內含較多的 Mozilla 專案。
Gecko 架構圖
- 安全架構:包含
- Permission Manager:用以存取 Web API 功能的閘門。
- Access Control List:存取 Web API 功能所必備的規則與許可。
- Credential Validation:App 與使用者的驗證。
- Permissions Store:存取 Web API 功能所必備的權限。
- Web API:標準 API 的集合,可將硬體功能提供予網頁內容,讓 Web App 能透過程式設計的方式,安全存取行動裝置底層硬體的功能,以及裝置所儲存\可用的資料。
- I/O:銜接硬體與資料儲存區的介面。
- 軟體更新:取得\安裝更新檔至系統軟體與第三方 App。
- 內容配置與繪製:用以剖析、編譯、執行網頁內容的引擎,並可搭配格式資訊,將格式化過後的內容呈現於使用者眼前。
- b2g 程序:(Gecko) 於高度權限的系統程序中執行,可存取行動電話的硬體功能。執行 App 屬於 b2g 的子程序。
和 Firefox OS 相關的 Gecko 檔案
b2g/
b2g 資料夾主要包含 Firefox OS 相關功能。
b2g/chrome/content
內含於系統 App 上運行的 Javascript 檔。
b2g/chrome/content/shell.html
Gaia 的進入點。如前所述,shell.html 會拉進 shell.js 與 settings.js。用於系統 App 的 HTML。
<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 配額、遠端除錯器、鍵盤助手、螢幕截圖工具。
不過 shell.js
最重要的功能,則是啟動 Gaia 的「system」
App,然後將整體系統相關的管理作業交給該「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’s user.js 覆寫之。
/dom/{API}
新實作的 API (post-b2g) 會位於 dom/ 資料夾。
舊 API 位於 dom/base
,例如navigator.cpp
。
/dom/apps
將載入 .jsm。而
.js
API 實作即如 webapp.js 安裝、getSelf 等。
dom/apps/PermissionsTable.jsm
所有的存取權限都定義在 PermissionsTable.jsm 之中。
/dom/webidl
WebIDL是用以定義 Web API 的語言,請參閱 WebIDL_bindings 了解所支援的屬性。
/hal/gonk
此目錄包含 gonk 銜接層 (Port layer) 的相關檔案。
所產生的檔案
module/libpref/src/init/all.js
內有全部的設定檔。
/system/b2g/ omni.ja and omni.js
內有裝置內資源所適用的樣式。
處理輸入事件
Gecko 內絕大多數的行動,都是由使用者的行動所觸發,可用輸入事件為代表 (如按下按鈕、觸碰裝置的螢幕等)。這些事件必須透過 nsIAppShell
的 Gonk implementation (為 Gecko 介面之一,作為 Gecko App 的主要進入點),進入到Gecko。也就是說,輸入裝置驅動程式將呼叫 nsAppShell
物件 (代表 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在其上添加了一層簡單的抽象層,提供了一些像是事件過濾等不錯的功能。產生事件的程式碼在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事件被發送到網頁應用程式做進一步處裡。
影像繪圖
在最底層,Gecko利用 OpenGL ES 2.0 向包有硬體影格緩衝區(frame buffers)的GL context物件繪圖,這是由Gonk所實作的nsWindow完成
:
gNativeWindow = new android::FramebufferNativeWindow(); sGLContext = GLContextProvider::CreateForWindow(this);
FramebufferNativeWindow類別(
class)是直接從Android而來,請見FramebufferNativeWindow.cpp;使用了
gralloc API存取繪圖驅動,好配對影格緩衝裝置的緩衝到記憶體之中。
Gecko透過圖層(Layers)系統合成螢幕上的影像,大體而言如下:
- Gecko將不同部分的記憶體分頁(pages)畫入記憶體緩衝,有時後這些緩衝存在系統記憶體,有時候因為Gecko會直接畫入視訊記憶體(video memory),所以這些緩衝也可能會是配對在Gecko定址空間的材質(textures) ,以上的工作主要是由
BasicThebesLayer::PaintThebes()方法完成。
- Gecko利用OpenGL命令將這些材質合成起來,合成工作由
ThebesLayerOGL::RenderTo()方法執行。
因為有關Gecko如何處理影像繪圖的作法已經超出本文範圍,所以本文不再做進一步討論了。
硬體抽象層(Hardware Abstraction Layer, HAL)
Gecko HAL是Gecko接口層之一,它使用高階層Gecko能取用的C++ API存取跨平台間的底層系統介面,這些API以每個平台為基礎實作在Gecko HAL之內。Gecko的Javacsript程式碼並不能看到這個HAL層。
HAL如何運作
讓我們先來看看Vibration
API。這個Gecko HAL API被定義在hal/Hal.h,基本上你可以看到如下函式(為了說明需要,經過函式簽名(function signature)簡化):
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退回替代(Fallback)實作
Gecko HAL API 跨平台支援。當為沒有震動馬達的平台(如桌上型電腦)建置Gecko,則另一個HAL退回替代API會被使用,這個替代API實作於hal/fallback/FallbackVibration.cpp:
void Vibrate(const nsTArray<uint32_t> &pattern) { }
沙箱(Sandbox)實作
因為大多數的網頁內容都是在低權限的內容程序運行,所以我們無法確保這些程序有權限去,例如,打開震動馬達,除此之外,為了避免出現程序爭相啟動震動的狀況出現,採用中央控制的作法較為理想。在Gecko HAL,透過sandbox作法可以達到中央控制的目標;其實sandbox也就是一個由內容程序發出到Gecko"伺服器"的代理請求,這個代理請求式透過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 介面定義的訊息,亦即IPDL的hal/sandbox/PHal.ipdl內所描述的。其中呼叫的函式大致如下:
Vibrate(uint32_t[] pattern);
訊息的接收端是HalParent::RecvVibrate()函式,這個函式定義在
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; }
本段範例雖然有省略一些相關性不高的部分,不過確實的說明訊息傳遞是如何由Gecko到Gonk,再到Gonk HAL實作Vibrate(),最後到震動馬達驅動程式。
DOM APIs
DOM介面是網頁根本上和Gecko溝通的方法,如果想知道更多這方面的細節,請參考關於DOM。 DOM介面是用IDL定義的,其中包括了外部函式介面(foreign function interface, FFI)以及介於javascript和C++的物件模型(object model, OM) 。
像是vibration API便是透過IDL讓網頁內容使用,這部分程式碼定義在nsIDOMNavigator.idl:
[implicit_jscontext] void mozVibrate(in jsval aPattern);
mozVibrate(冠有廠商前綴的函式,這個函式背後的震動規格尚未定案)接受jsval型態的參數,也就是Javascript的值。IDL編譯器,
xpidl,會產生C++的介面,這個介面會由
Navigator.cpp內的Navigator類別實作之。
NS_IMETHODIMP Navigator::MozVibrate(const jsval& aPattern, JSContext* cx) { // ... hal::Vibrate(pattern); return NS_OK; }
這個函式最關鍵的部分在於呼叫 hal::Vibreate() 將控制權由DOM移交給Gecko HAL,自此我們便會如前所述地進入HAL層,最後到達驅動程式。在上層的 DOM 並不在意現在是在哪個平台上執行(Gonk, WIndows, Mac OS X等等),DOM 也不在意現在是內容程序的程式碼在跑或是Gecko伺服器的在跑,所有的一切都交由底層系統處理。
vibration API相對單純,適合用於說明,而SMS API 便是比較複雜的API,它用到自己的"遠端"層將內容程序和伺服器連接起來。
無線介面層(Radio Interface Layer, RIL)
The userspace process architecture段落已經提過RIL,這邊我們將更進一步檢視各個元件是如何互動運作。
和RIL有關的元件有:
rild
- 和特定數據機韌體溝通的背景程序(daemon)。
rilproxy
- rild和Gecko b2g程序之間訊息傳遞的代理背景程序(daemon)。rilproxy的存在解決了無法和rild直接溝通的問題,因為rild只可以在radio群組內溝通。
b2g
- 實作Gecko的程序,又稱為chrome process。其中和RIL相關的部分有dom/system/gonk/ril_worker.js(這是一個worker執行緒,會透過rilproxy和rild溝通,並且實作了無線狀態機(radio state machine))以及
nsIRadioInterfaceLayer
介面(是主執行緒的XPCOM服務,主要是作為ril_worker.js和Gecko各元件,包括Gecko內容程序,之間的訊息傳遞)。 - Gecko's content process
nsIRILContentHelper
介面會提供XPCOM服務,有了這個服務,Gecko內容程序會使用到的DOM API,例如Telephony與SMS API,便能夠和Chrome程序的無線介面溝通。
範例: Example: Communicating from rild to the DOM
讓我們來看看一段例子,看一下系統底層是如何和DOM程式碼溝通。當數據機接收到來電時,它會透過專屬機制通知rild,rild接著按照ril.h內定義的
"open"協定發送訊息到rild客戶端,以來電通話為例,RIL_UNSOL_RESPONSE_CALL_STATE_CHANGED
訊息會被rild發送至rilporxy。
實作在rilproxy.c的rilproxy會透過以下程式碼,和rild
建立連線:
ret = read(rilproxy_rw, data, 1024); if(ret > 0) { writeToSocket(rild_rw, data, ret); }
一旦從rild那裏取回訊息後,訊息便會經由連接rilproxy和Gecko的socket傳給Gecko,Gecko是藉由IPC thread接收訊息:
int ret = read(fd, mIncoming->Data, 1024); // ... handle errors ... mIncoming->mSize = ret; sConsumer->MessageReceived(mIncoming.forget());
訊息的接收端是SystemWorkerManager,然後訊息又會被重新打包發送到{source("dom/system/gonk/ril_worker.js", "ril_worker.js")}}執行緒,這個
ril_worker.js實做了RIL狀態機(
RIL state machine); 這其中的過程是透過 RILReceiver::MessageReceived()函式完成
:
virtual void MessageReceived(RilRawData *aMessage) { nsRefPtr<DispatchRILEvent> dre(new DispatchRILEvent(aMessage)); mDispatcher->PostTask(dre); }
訊息傳遞到ril_worker.js執行緒後,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); } }
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
到 SystemWorkerManager
、到 Ril.cpp、
再到rilproxy,最後是
rild
socket)。 rild
然後會回應請求,再回到ril_worker.js的REQUEST_GET_CURRENT_CALLS訊息處理器,整個過程是雙向溝通。
通話狀態然後會被處理,和之前的狀態比較,如果狀態有變更,ril_worker.js 會再通知在主執行緒上的nsIRadioInterfaceLayer
服務:
_handleChangedCallState: function _handleChangedCallState(changedCall) { let message = {type: "callStateChange", call: changedCall}; this.sendDOMMessage(message); }
nsIRadioInterfaceLayer
實作在dom/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)送到內容程序:
handleCallStateChange: function handleCallStateChange(call) { [some internal state updating] ppmm.sendAsyncMessage("RIL:CallStateChanged", call); }
內容程序透過nsIRILContentHelper
服務的receiveMessage()從子程序訊息管理器(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;
接著,每一個電話callback物件的nsIRILTelephonyCallback.callStateChanged()
方法會被呼叫;每一個有存取window.navigator.mozTelephony
API的網頁應用程式會註冊這樣一個callback物件,這個callback物件會發送事件到網頁應用程式,如現階段通話物件狀態改變或有新通話來電事件。
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 data
有一個 RIL 訊息會向電信服務(cellular service)發起數據請求(data call),這會啟動數據機的數據傳輸狀態。數據請求會開啟 Linux 核心的Point-to-Point Protocol (PPP) 介面裝置,這個裝置能夠透過使用者介面設定。
相關DOM APIs
以下是和 RIL 溝通相關的DOM APIs:
- Telephony API
- SMS API
- Mobile Connection API
WiFi
Firefox OS 的 WiFi 後端使用 wpa_supplicant
處裡大部份工作。所以說WiFi 後端的主要工作是管理請求者(supplicant),還有輔助一些任務,例如載入 WiFi 驅動程式和開關網路介面;從根本上來看,WiFi 後端就是一個狀態機(state machine),而其狀態會跟隨請求者狀態。
Note: 許多 WiFi 有關的有趣東西都和 wpa_supplicant 程序可能的狀態變更息息相關。
WiFi 元件的實作可以分成兩份檔案:
- dom/wifi/DOMWifiManager.js
- 實作網頁內容所看到的API;定義在
nsIWifi.idl
. - dom/wifi/WifiWorker.js
- 實作狀態機和驅動請求者的程式碼。
這兩份檔案利用訊息管理員(message manager) 溝通。 後端聆聽請求訊息,比如說"連接",然後回應訊息當請求行動完成後;DOM 端聆聽回應以及狀態改變和資訊更新的訊息。
Note: 任何同步 DOM API 都會藉由快取資料來實作,同步訊息會盡量避免。
WifiWorker.js
這份檔案實作了 WiFi 介面背後的主要邏輯,由 SystemWorkerManager 喚起,然後運行在 chrome 程序裡(對多執行緒版本來說)。 WifiWorker.js 可以切成兩塊: 一個巨大無名函式與 WifiWorker;無名函式會產出 WifiManager,為本地提供 API,包含和請求者連線以及可取得的掃描結果等事件通知,它會回應請求的資訊,控管和請求者連線的細節,遵照 API 使用客戶的指示行動。
WifiManager 和 DOM 中間是 WifiWorker 物件。WifiWorker 根據事件行動並且轉傳事件給 DOM,反過來,它也會收到 DOM 傳過來的請求,並且對請求者(supplicant)採取適當的動作,WifiWorker 也會維護請求者的狀態資訊和下不步行動的資訊。
DOMWifiManager.js
實作 DOM API,負責在呼叫方和 WiFi worker 之間傳遞訊息。
Note: 為了避免和 chrome 程序之間同步訊息,WiFi Manager 需要根據收到的事件快取狀態。
有一個同步訊息,那就是當 DOM API 初始化時為了取得請求者(supplicant)當下的狀態。
DHCP
DHCP 和 DNS 由標準 Linux DHCP 客戶端,dhcpcd,負責。當網路連線中斷後,因為無法運作,Firefox OS 會終止 dhcpcd,直到又重新連上網路,dhcpcd 會被再重啟。dhcpcd
也負責設定預設路徑(route)。
Network Manager
Network Manager 負責設定 3G 數據和 WiFi 元件的網路介面。
程序與執行緒
Firefox OS 採用 POSIX threads 實現所有應用程式的執行緒,這包括了應用成程式主執行緒、Web Worker 和輔助執行緒。Nice值決定了程序和執行緒的執行優先順序,然後再依賴 Linux 核心排程器先後執行;根據程序不同的狀態會有不同的Nice值。目前有7種Nice層級:
優先順序 | Nice | 用於 |
---|---|---|
MASTER |
0 | 主 b2g process |
FOREGROUND_HIGH |
0 | 持有 CPU wakelock 的程式 |
FOREGROUND |
1 | 前景(foreground)程式 |
FOREGROUND_KEYBOARD |
1 | 鍵盤程式 |
BACKGROUND_PERCEIVABLE |
7 | 音樂播放背景(background)程式 |
BACKGROUND_HOMESCREEN |
18 | 主畫面程式 |
BACKGROUND |
18 | 其他背景程式 |
有些層級的 Nice 值雖然一樣,不過記憶體不足時的終止行為不一樣。所有的優先順序可以在建置時調整,相關的程式碼請見b2g/app/b2g.js
。
一個程序(process)之內,主執行緒繼承了該程序的 Nice 值,而 Web Worker 執行緒所得到的 Nice 值為主執行緒的加一點,所以優先權較低,這麼做是為了避免耗費 CPU 資源的worker 拖慢的主執行續的運作。程序優先順趣會因為事件發生而變化,比如說當一個程式由前景轉到背景時、一個新的程式啟動、現存程式取得 CPU wakelock,一旦程序的執行優先權變動,旗下所屬的執行緒的執行優先權也會跟著變動。
Note: cgroups 目前沒有在資源管理用上,因為在某些裝置和核心上並不可靠。