Join MDN and developers like you at Mozilla's View Source conference, 12-14 September in Berlin, Germany. Learn more at https://viewsourceconf.org

초안
이 문서는 작성중입니다.

이 (게시)글은 Firefox OS platform의 구조(architecture)에 대한 고차원적인 개요이며, 주요 개념을 소개하고 구성요소들이 기본레벨에서 어떻게 상호동작 하는지를 설명합니다. 기술적인 레벨에서 어떻게 동작하는지의 복잡한 사항을 알려주지는 않습니다; 각각의 See also 섹션으로부터 참조된 글들을 보시기 바랍니다.

Note: Firefox OS는 이직 정식배포 전의 상품임을 유념해 주십시오. 여기에 설명된 구조(architecture)는 반드시 최종이라 할 수 없으며 변경될 수 있습니다.

Firefox OS 용어

이 문서를 이해하기 전에 알 필요가 있는 몇 가지 용어가 있습니다.

B2G
Boot to Gecko의 약어.
Boot to Gecko
전체적인 Firefox OS 프로젝트에 대한 코드명입니다. 프로젝트가 공식명칭을 갖기 오래 전 부터 사용되었기 때문에, Firefox OS를 나타내는 용어로 이 용어가 사용되는 것을 볼 수 있습니다.
Gaia
Firefox OS platform의 사용자 인터페이스. Firefox OS가 구동된 후에 화면에 표시되는 것들은 Gaia 층에서 생성된 것 입니다. Gaia는 최신의 스마트폰에서 기대할 수 있는 잠금 화면, 홈 화면, 그리고 모든 표준화된 어플리케이션들을 구현하고 있습니다. Gaia는 전적으로 HTML, CSS와 JavaScript로 구현되었습니다. 내부의 OS와의 유일한 인터페이스는 개방된 Web API들을 통해 이루어 집니다. 이는 Gecko 층(layer)에 구현되어 있습니다. 제 3자가 개발한 어플리케이션들은 Gaia 층에 나란히 설치될 수 있습니다.
Gecko
Firefox OS 어플리케이션 런타임; 즉, 공개된 표준의 3가지 펙터(HTML, CSS, JavaScript)에 대한 모든 지원을 제공합니다. Gecko가 지원하는 모든 운영체제상에서 관련 API들이 제대로 동작하는 것을 보장합니다. 이는 Gecko가 다른 여러가지 중에서도, 네트워킹 스택, 그래픽 스펙,  배치(layout) 엔진, JavaScript 버추얼 머신과 포팅 레이어들을 포함하고 있다는 것을 의미합니다.
Gonk
Gonk는 Firefox OS 플랫폼의 더 낮은 레벨의 운영체제로, (안드로이드 오픈 소스 프로젝트, Android Open Source Project (AOSP)를 기반으로 하는)리눅스 커널과 유저공간의 하드웨어 추상 계층(Userspace Hardware adstraction layer:HAL)로 구성되어 있습니다. 커널과 여러개의 라이브러리들은 일반적인 오픈 소스 프로젝트들(리눅스, libusb, bluz 등)입니다. HAL의 또 다른 부분들은 안드로이드 프로젝트(GPS, camera 등)과도 공유됩니다. Gonk는 아주 간단한 리눅스라 할 수 있습니다. Gecko는 Gonk에 포팅됩니다; 마치, Gecko가 Mac OS X, Windows와 Android에 포팅되듯이 Gecko는 Gonk에 포팅됩니다. Firefox OS 프로젝트는 Gonk에 대한 전반적인 통제를 가지고, 다른 운영체제에 대한 노출되지 않는 Gecko에 대한 인터페이스들을 노출시킬 수 있습니다. 예를 들어, Gecko는 전반적인 텔레포니 스택과 Gonk상의 디스플레이 프레임 버퍼에 대한 직접적인 접근이 가능하지만, 다른 운영체제로의 이러한 접근은 가능하지 않습니다.  
 
Firefox OS Architecture

부트스트래핑 프로세스

 맨 처음, Firefox OS를 구동하면, 첫 번째 부트로더부터 실행 하기 시작 합니다. 여기서부터, 일반적인 방법으로 주 운영체제를 불러오는 과정을 진행 합니다.  점진적으로 높은 레벨의 부트로더들을 연속으로 두어서 다음 로더를 연속적으로 부트스트래핑 합니다. 이 단계의 마지막에서, Linux Kernel로 실행이 넘어갑니다.

 부팅 프로세스에 대해 별 의미 없는 몇 가지 사항이 있습니다.

  • 부트로더들은 보통 장치를 시작 할 때 유저에게 첫 번째로 보이는 시작 화면(splash screen)이 있습니다. 이 시작 화면은 일반적으로 제조사의 로고 입니다.
  • 부트로더들은 장치로 이미지를 플래싱(flashing) 합니다. 각각 다른 장치들은 각각 다른 프로토콜을 사용 합니다. 대부분의 휴대폰은 fastboot protocol을 사용하지만, 삼성의 갤럭시 S II는 odin 프로토콜을 사용 합니다.
  • 부트스트래핑 과정이 종료되면서, 대개 모뎀 이미지를 로드하고 모뎀 프로세서에서 실행 합니다. 이런 과정은 굉장히 장치에 특화 되어 있고, 누군가가 소유권(proprietary)을 가지고 있을 수도 있습니다.

리눅스 커널

Gonk가 사용하는 리눅스 커널(들)은 리누스 토발즈와 세계의 해커들이 함께 개발하고 있는 업스트림의 리눅스로부터 만들어졌으며, 거의 똑같습니다. 다만 안드로이드 오픈 소스 프로젝트 로부터 만들어지고 아직 업스트림에 들어가지 않은 변경사항들을 가지고 있습니다. 또한, vendor들이 가끔 커널을 수정하며, 이 경우 그들은 그들 자체적 스케쥴로 업스트림에 변경 사항을 올립니다. 그렇지만 일반적으로 말하면 Gonk가 사용하는 리눅스 커널은 오리지날 리눅스와 거의 같다고 이야기 할 수 있습니다.

리눅스 구동 시작 과정 은 인터넷 상에 잘 문서화 되어 있으므로, 이 글에서는 그것까지 다루지는 않겠습니다. 구동 시작 과정의 마지막에, 대부분의 UNIX류 운영체제가 그러하듯이 userspace의 init 프로세스가 시작됩니다. 이 시점에서 마운트된 "disk"는 RAM disk 뿐입니다. 이 RAM disk는 Firefox OS 빌드 과정에서 만들어 졌으며, init나 시작 과정 스크립트들이나 로드할 수 있는 커널 모듈들과 같은 중요한 유틸리티들을 가지고 있습니다.

일단 init 프로세스가 시작되면, 리눅스 커널은 userspace 공간으로부터의 시스템 콜, 인터럽트, 하드웨어 기기로부터의 비슷한 요청들을 처리합니다. 많은 하드웨어 기능이 userspace에 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*.rc 파일들)을 수행합니다. Firefox OS의 init.rc 는 오리지날 안드로이드의 init.rc 에서 Firefox OS를 시작하는데 필요한 것들을 좀 추가한 형태이며, 기기에 따라 조금씩 다를 수 있습니다.

init 프로세스가 하는 가장 중요한 작업 중 하나는 b2g 프로세스를 시작시키는 것입니다; 이게 Firefox OS 운영체제의 중심입니다.

b2g를 시작시키는 init.rc의 코드는 다음과 같은 식입니다:

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

안드로이드의 init.rc에서 b2g 프로세스를 시작시키기 위한 코드가 추가된 init.b2g.rc 파일을 보는 것도 좋을 겁니다.

Note: 정확히 init.rc 가 안드로이드 버전과 얼마나 다른가는 실제 기기마다 다릅니다; 어떤 기기의 경우는 단지 init.b2g.rc 가 추가되어 있을 뿐이고, 어떤 기기는 그보다 더 많은 변경이 있을 수 있습니다.

사용자 영역(userspace) 프로세스 구조

Firefox OS의 많은 구성 요소들이 어떻게 서로 상호 동작하는지 상위 레벨에서 살펴보겠습니다. 이 그림은 Firefox OS의 주요 사용자 프로세스들을 보여줍니다.

Userspace diagram

Note: Firefox OS는 활발히 개발되고 있기 때문에, 이 그림은 변경 될 수 있습니다. 또한 본 그림이 포함하는 내용 중 일부가 올바르지 않을 수도 있습니다.

b2g 프로세스는 가장 중요한 시스템 프로세스입니다. b2g 프로세스는 높은 권한으로 실행되기 때문에 대부분의 하드웨어 기기에 접근할 수 있습니다. b2g는 modem과의 통신이 가능하며, display framebuffer에 접근할 수 있습니다. 그리고 GPS, camera 및 다른 하드웨어와 통신이 가능합니다. b2g는 내부적으로 Gecko layer (libxul.so으로 구현됨)를 실행합니다. 어떻게 Gecko layer가 동작하며, b2g와 통신하는지 알고 싶다면 Gecko를 참고하세요.

b2g

b2g 프로세스는 낮은 권한을 가진 다수의 content process 들을 생성할 수 있습니다. 이 프로세스에 web application과 web content이 적재되며, main Gecko server와 메시지-패싱 시스템인 IPDL을 이용하여 통신합니다.

rild

rild 프로세스는 모뎀 프로세서와의 인터페이스 입니다. rildRadio Interface Layer (RIL)를 구현한 daemon입니다. 이 코드는 하드웨어 vendor가 해당 vendor의 모뎀 하드웨어와 통신하기 위해 구현한 것입니다. rild은 client code를 unix-domain socket에 연결해 줍니다. rild은 다음 init 스크립트를 통해 시작됩니다.

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

rilproxy

Firefox OS에서 rild client는 rilproxy 프로세스입니다. rilproxy 프로세스는 rildb2g사이에서 dumb forwarding proxy처럼 동작합니다. 이 proxy는 implementation detail이 필요한 상태 입니다. rilproxy 코드는 GitHub에서 확인할 수 있습니다.

mediaserver

mediaserver process 는 오디오와 비디오 재생을 제어합니다. Gecko는 원격 절차 호출(Remote Procedure Call, RPC) 메커니즘을 통해 이와 통신합니다. 미디어들 중 Gecko가 재생할 수 있는 것들(OGG Vorbis audio, OGG Theora video, and WebM video)은 Gecko에 의해서 해독(decode)되며 직접 mediaserver 프로세스로 전달됩니다. 이외의 다른 미디어 파일들은 다른 외부 코덱과 하드웨어 인코더에 접근할 수 있는 libstagefright에 의해 해독됩니다.

Note: mediaserver 프로세는 Firefox OS의 "임시" 구성요소입니다. 이 프로세스는 초기 개발 업무를 위해서 사용되었으며, 최종적으로는 사용하지 않을 예정입니다. 하지만 최소한 Firefox OS 2.0까지는 사용 할 것입니다.

netd

netd 프로세스는 네트워크 인터페이스의 설정 조정(configure)에 사용합니다.

wpa_supplicant

wpa_supplicant 프로세스는 WiFi 액세스 포인트와의 연결을 처리하는 표준 UNIX 스타일 데몬입니다.

dbus-daemon

dbus-daemon은 FirefoxOS가 블루투스 통신을 위해 사용하는 메시지 버스 시스템인 D-Bus를 동작시킵니다.

Gecko

앞서 언급했듯이, Gecko는 사용자가 Firefox OS에서 볼 수 있는 모든 것을 구현할때 사용하는 웹 표준들(HTML, CSS, JavaScript)의 구현입니다.

입력 이벤트 처리 (Processing input events)

대부분의 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();
}

이 이벤트들은 표준 리눅스 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로서 이후 처리를 위해 웹 어플리케이션들로 전달됩니다.

 

그래픽 (Graphics)

가장 저차원 레벨에서 봤을 때, Gecko는 하드웨어 프레임 버퍼들을 감싸는(wrap) GL 컨텍스트에 접근하기 위해서 OpenGL ES 2.0을 사용합니다. 이러한 동작은 Gonk의 nsWindow의 다음과 비슷한 코드로 구현합니다.

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

FramebufferNativeWindow 클래스는 Android의 것을 그대로 차용하였습니다(FramebufferNativeWindow.cpp를 참고). 이 클래스는 프레임 버퍼 기기의 버퍼들과 메모리를 매핑하기 위해 그래픽스 드라이버에 접근하는데, 이 때 gralloc API를 사용합니다.

Gecko는 Layers 시스템을 이용하여 그려진 내용을 화면에 합성합니다. 이를 위한 과정은 다음과 같이 요약할 수 있습니다:

  1. Gecko는 각각의 독립된 영역들을 메모리 버퍼에 그립니다. 때때로 이 버퍼들은 시스템 메모리에 있을 수 있습니다. 또 다른 경우에 이것들은 Gecko의 주소 공간에 맵핑된 텍스처(texture)들 일 수도 있습니다. 다시 말하자면, Gecko가 비디오 메모리에 직접 영역을 그린다는 것을 의미합니다. 이 동작은 일반적으로 BasicThebesLayer::PaintThebes()에 의해서 이루어 집니다.
  2. Gecko는 OpenGL 명령들을 이용하여 모든 texture들을 화면에 합성합니다. 이 합성 과정은 ThebesLayerOGL::RenderTo()에서 일어나게 됩니다.

Gecko가 웹 컨텐츠 렌더링을 어떻게 처리하는지에 대한 자세한 내용은 이 문서의 범위 밖의 내용입니다.

하드웨어 추상화 계층 (Hardware Abstraction Layer, HAL)

Gecko의 하드웨어 추상화 계층(Hardware Abstraction Layer, HAL)은 Gecko의 이식 계층(porting layer) 중 하나 입니다. 이것은 Gecko의 상위 계층에 접근이 쉬운 C++ API를 이용하여, 다양한 플랫폼에 대한 시스템 인터페이스로의 저수준 접근을 처리합니다. 이 API들은 Gecko HAL 내부에 플랫폼 단위(per-platform basis)로 구현되어 있습니다. 이 하드웨어 추상화 계층은 Gecko 내부의 JavaScript 코드를 통해 직접 접근할 수 없습니다.

HAL의 동작

Vibration를 예로 들어 살펴보겠습니다. 이 API를 위한 Gecko의 HAL은 hal/Hal.h에 정의되어 있습니다. 본질적으로 (명확성을 위해서 간략하게 표현하면), 이러한 함수가 있습니다.

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

이 함수는 Gecko code에 의해 명시된 패턴대로 기기의 진동을 켜기 위해 호출되며, 이에 대응되는 함수는 울리고 있는 진동을 끄기 위해 존재합니다. 이 함수의 Gonk 구현은 hal/conk/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 HAL API implementations)

Gecko의 HAL API 들은 모든 플랫폼을 지원합니다. Gecko가 진동 모터에 대한 인터페이스를 지원하지 않는 플랫폼(예를 들면 데스크탑 컴퓨터)을 위해 빌드 될 경우, HAL API의 대체 구현(fallback implementation)이 사용됩니다. 진동을 위한 대체 구현은 hal/fallback/FallbackVibration.cpp에서 찾을 수 있습니다.

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

Sandbox 구현 (Sandbox implementations)

대부분의 web content들이 낮은 권한의 컨텐트 프로세스에서 동작하기 때문에, 이 프로세스들은 진동 모터를 켜고 끄는 것(예를 들면)과 같은 권한을 가지고 있지 않다고 할 수 있습니다. 게다가, 잠재적인 경쟁 상태(race condition)를 처리할 수 있도록, 중앙 처리 위치(a central location을 의역하였습니다)가 필요합니다. Gecko의 HAL에서는 이러한 동작이 "sandbox"를 통해 이루어 집니다. 이 sandbox는 컨텐트 프로세스의 요청들을 대신 처리하고 그 요청들을 "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()));
}

이 함수는 hal/sandbox/PHal.ipdl에 서술 된 PHal 인터페이스에 의해 정의되는 메시지를 전송합니다. 이 메소드는 대략 다음과 같이 서술되어 있습니다:

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

이것은 현재 주제와 관련이 없는 세부사항을 제거한 상태입니다. 하지만 이를 통해 컨텐트 프로세스에서 발생한 메시지가 Gecko부터 Gonk, Vibrate()의 Gonk HAL 구현부, 그리고 최종적으로 그래픽 드라이버까지 전달되는 과정을 보여줍니다. 

DOM APIs

DOM 인터페이스는 웹 컨텐트가 Gecko와 통신하는 방법입니다. 만약 더 자세한 내용에 대해 관심이 있다면, 여기를 참고하세요. DOM 인터페이스는 IDL로 정의됩니다. IDL은 자바스크립트와 C++ 사이의 외래 함수 인터페이스(Foreign Function Interface, FFI)와 객체 모델(Object Model, OM)로 구성됩니다.

vibrartion API는 IDL 인터페이스(nsIDOMNavigator.idl)를 통해 web content에서 접근할 수 있습니다:

[implicit_jscontext] void mozVibrate(in jsval aPattern);

jsval 인자는 mozVibrate()(아직 확정되지 않은 vibration 명세를 Mozilla가 구현한 것)가 자바스크립트 값을 입력으로 받아들일 수 있다는 것을 나타냅니다. IDL 컴파일러(xpidl)는 이후에 Navigator 클래스(Navigator.cpp)에서 구현될 C++인터페이스를 만듭니다.

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

원래는 위에서 보시는 것보다 훨씬 더 많은 코드가 있지만, 현재 이 문서의 목적을 벗어나는 부분입니다. 중요한 점은 hal::Vibrate()를 호출하게 되면 control이 DOM으로부터 Gecko HAL로 이동하게 된다는 것입니다. 거기서부터 이전 섹션에서 이야기한 HAL 구현부에 진입하며, 그래픽 드라이버 방향으로 작업이 진행되게 됩니다. 최상위 레벨에서 보면, DOM 구현부는 자신이 어떤 플랫폼(Gecko, Windows, Mac OS X 등)에서 동작하는지 상관하지 않습니다. 또한 DOM은 자신이 컨텐트 프로세스에서 동작하든 Gecko 서버 프로세스에서 동작하는지 고려하지도 않습니다. 위와 같이 상세한 부분들은 시스템의 하위 계층이 관장하게 됩니다.

vibration API는 예로 쓰기에 좋은 아주 단순한 API입니다. 이에 비해, SMS API는 컨텐트 프로세스들을 서버에 연결할 때 자신이 가진 "remoting" 계층을 이용하는 좀 더 복잡한 API라고 할 수 있습니다.

Radio Interface Layer (RIL)

RIL에 대해서는 사용자 영역(userspace) 프로세스 구조 섹션에서 간단하게 설명하였습니다. 이 섹션에서는 RIL을 구성하는 여러 부분들이 어떻게 상호작용하는지 좀 더 자세하게 살펴 보겠습니다.

RIL과 관련있는 주요 요소(component)들은 다음과 같습니다:

rild
모뎀 펌웨어와 통신하는 데몬입니다.
rilproxy
The daemon that proxies messages between rild and Gecko (which is implemented in the b2g process). This overcomes the permission problem that arises when trying to talk to rild directly, since rild can only be communicated with from within the radio 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 through rilproxy and implements the radio state machine; and the nsIRadioInterfaceLayer interface, which is the main thread's XPCOM service that acts primarily as a message exchange between the ril_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 the Telephony 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, a RIL_UNSOL_RESPONSE_CALL_STATE_CHANGED message is generated and sent by rild to rilproxy.

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 the ril_worker.js thread that implements the RIL state machine; this is done in the RILReceiver::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 function JS_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 the getCurrentCall() 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 to SystemWorkerManager 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 its onmessage() 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.

This section lists DOM APIs that are related to RIL communications.

WiFi

The WiFi backend for Firefox OS simply uses wpa_supplicant to do most of the work. That means that the backend's primary job is to simply manage the supplicant, and to do some auxiliary tasks such as loading the WiFi driver and enabling or disabling the network interface. In essence, this means that the backend is a state machine, with the states following the state of the supplicant.

Note: Much of the interesting stuff that happens in WiFi depends deeply on possible state changes in the wpa_supplicant process.

The implementation of the WiFi component is broken up into two files:

dom/wifi/DOMWifiManager.js
Implements the API that's exposed to web content, as defined in nsIWifi.idl.
dom/wifi/WifiWorker.js
Implements the state machine and the code that drives the supplicant.

These two files communicate with one another using the message manager. The backend listens for messages requesting certain actions, such as "associate", and responds with a message when the requested action has been completed.

The DOM side listens for the response methods as well as several event messages that indicate state changes and information updates.

Note: Any synchromous DOM APIs are implemented by caching data on that side of the pipe. Synchronous messages are avoided whenever possible.

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.

Network Manager

The Network Manager configures network interfaces opened by the 3G data and WiFi components.

Note: This needs to be written.

 

문서 태그 및 공헌자

 최종 변경: jwhitlock,