1 of 25

Binderのはじめの一歩とAndroidのプロセス間通信(IPC)

横浜Androidプラットフォーム部

第3回勉強会

Android IPCのとりあえず1回目

2010/10/31

@l_b__

2 of 25

目次

  • Androidのプロセス間通信
  • Binderって?
  • 何故Binder?
  • Android独自のIPC
  • Binderの歴史
  • Binderの仕組み
  • 実際に使ってみる
  • Ashmemって?
  • Ashmemの関数
  • 実際にAshmemを使ってみる
  • 次回予告

3 of 25

Androidのプロセス間通信

  • AndroidのNative側でプロセス間通信(IPC,InterProcess Communication)を行うには以下の方法がある。
    • Binder
    • Ashmem(Anonymous Shared Memory)
    • UNIXドメインソケット
    • TCP/UDPソケット
    • 名前付きパイプ

 

 ※ただ、Androidソース内に名前付きパイプを使っている箇所は無い。

4 of 25

Binderって?

  • Android独自のプロセス間通信の一つ。
  • POSIXメッセージの置き換え。
  • プロセス間で小さいデータ(数百〜数千byte程度)を高速にやり取りするために使用。

  • 何故POSIXのIPCがあるのにBinderという機構が導入されたのか。

5 of 25

何故Binder

  • AndroidはSystemV(UNIXの1種)のIPCをサポートしていない!
  • NDKの/docs/system/libc/SYSV-IPC.TXTにサポートされない理由の記述あり。
    • カーネルでのリソースリークが発生する
    • カーネルリソースを枯渇させるサンプルコードも有り。(もちろんAndroidでは動かないのでLinuxで動作させる必要あります。)�

6 of 25

Android独自のIPC

  • System V IPCの代わりは以下の通りと思われる。
    • メッセージキューはBinderに。
    • 共有メモリはAshmemに。
    • セマフォはPOSIX IPCのセマフォに。

7 of 25

Binderの歴史

  • 元々はNext Generation BeOSに採用される予定だったOpenBinder。(http://www.angryredplanet.com/~hackbod/openbinder/)
  • UNIXのCORBA、WindowsのCOMのように分散コンポーネント環境を提供するフレームワーク。
  • BeOSポシャっちゃったので残念ながら動くものとしては採用されず。
  • OpenBinderをメンテしていたHackbornさんは今はAndroidのプラットフォームエンジニア。よくGroupに投稿しています。
  • 余談ですがBeの創業者Gasseeも、Danger、Androidの創業者Andy Rubinも元Apple。Appleすごいですね。

8 of 25

Binderの仕組み

Kernel

Binder Driver(/dev/binder)

ServiceManager

libBinder

Receiver�Application

Sender�Application

9 of 25

Binderの仕組み

  • Binder Driver(/dev/binder)にアクセスしているのはフレームワーク中、ServiceManager(ソースの/framework/base/cmds/servicemanager/service_manager.c)とlibBinder(/framework/base/libs/binder/ProcessState.cpp)のみ。
    • この中の仕組みはまだ追えていません。

  • ユーザーアプリケーションは直接Binderドライバを操作することはない。

10 of 25

Binderの仕組み

  • Binderを受信するアプリはServiceManagerに自身をサービスとして登録する。(図の水色矢印)
  • 受信するアプリはBBinderを継承し、onTransact()で受信処理を実装。
  • Binderを送信するアプリはServiceManagerから送信先サービスを取得し(図の緑矢印)、取得したIBinderサービスに対しtransact()でメッセージを送信(図の赤矢印)。

11 of 25

実際に使ってみる

Binderを受信するNativeデーモン(Binder

Receiver)と、送信するNativeアプリ(BinderSender)を作ってみます。

12 of 25

実際に使ってみる 受信側

  • main.cpp 受信側起動処理 SurfaceFlingerやAudioFlingerなどが参考になります。

#define LOG_TAG "RECEIVER"

#include <binder/IPCThreadState.h>

#include <binder/ProcessState.h>

#include <binder/IServiceManager.h>

#include <utils/Log.h>

#include "receiver.h"

int main(int argc, char** argv) {

LOGD("Reciever start.");

sp<ProcessState> proc(ProcessState::self());

sp<IServiceManager> sm = defaultServiceManager();

LOGD("ServiceManager: %p", sm.get());

Receiver::instantiate();

ProcessState::self()->startThreadPool();

IPCThreadState::self()->joinThreadPool();

}

13 of 25

実際に使ってみる 受信側

  • receiver.h BBinderを継承した受信処理クラスの定義

#ifndef RECEIVER_H_

#define RECEIVER_H_

#include <utils/RefBase.h>

#include <binder/IInterface.h>

#include <binder/Parcel.h>

using namespace android;

class Receiver:public BBinder {

public:

static void instantiate();

Receiver();

virtual ~Receiver();

virtual status_t onTransact(

uint32_t, const Parcel&, Parcel*, uint32_t);

};

#endif /* RECEIVER_H_ */

14 of 25

実際に使ってみる 受信側

  • receiver.cpp Receiverクラスの実装。受けた数値を5倍して返す。

#define LOG_TAG "RECEIVER"

#include <binder/IServiceManager.h>

#include <binder/IPCThreadState.h>

#include <utils/Log.h>

#include "receiver.h"

using namespace android;

void Receiver::instantiate() {

    defaultServiceManager()->addService(

            String16("Receiver"), new Receiver());

}

Receiver::Receiver() {

LOGD("Receiver created.\n");

}

Receiver::~Receiver() {

LOGD("Receiver destroyed.\n");

}

15 of 25

実際に使ってみる 受信側

  • receiver.cpp 続き BBinder::onTransactの実装部分

status_t Receiver::onTransact(uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags) {

LOGD("Message received code=%d.\n", code);

pid_t pid;

int num;

switch(code) {

case 0:

pid = data.readInt32();

LOGD("Sender pid=%d", pid);

num = data.readInt32();

LOGD("Number Data=%d\n",num);

reply->writeInt32(num*5);

break;

default:

//do nothing.

break;

}

return NO_ERROR;

}

16 of 25

実際に使ってみる 送信側

  • receiver.cpp 送信処理。自プロセスIDと数値の12を送信する。

#define LOG_TAG "SENDER"

#include <binder/IServiceManager.h>

#include <binder/IPCThreadState.h>

#include <binder/IInterface.h>

#include <binder/Parcel.h>

#include <utils/RefBase.h>

#include <utils/Log.h>

using namespace android;

int main() {

sp<IServiceManager> sm = defaultServiceManager();

sp<IBinder> binder = sm->getService(String16("Receiver"));

LOGD("Sender getService %p\n",sm.get());

if (binder == NULL) {

         LOGE("Receiver Service not found.\n");

         return -1;

}

17 of 25

実際に使ってみる 送信側

  • receiver.cpp 続き。実際の送信処理部分。

Parcel data, reply;

pid_t pid = getpid();

LOGD("pid=%d", pid);

data.writeInt32(pid);

int num = 12;

LOGD("num=%d", num);

data.writeInt32(num);

//非同期メッセージはFLAG_ONEWAYを4番目に追加

binder->transact(0, data, &reply);

LOGD("reply num=%d", reply.readInt32());

return NO_ERROR;

}

18 of 25

実際に使ってみる 受信側makefile

  • receiver/Android.mk

LOCAL_PATH:= $(call my-dir)

include $(CLEAR_VARS)

LOCAL_SRC_FILES:= \

receiver.cpp \

main.cpp

LOCAL_SHARED_LIBRARIES:= \

libcutils \

libbinder

LOCAL_MODULE:= BinderReceiver

include $(BUILD_EXECUTABLE)

19 of 25

実際に使ってみる 送信側makefile

  • sender/Android.mk

LOCAL_PATH:= $(call my-dir)

include $(CLEAR_VARS)

LOCAL_SRC_FILES:= \

sender.cpp

LOCAL_SHARED_LIBRARIES:= \

libcutils \

libbinder

LOCAL_MODULE:= BinderSender

include $(BUILD_EXECUTABLE)

20 of 25

実際に使ってみる ビルドスクリプト

  • build/build.sh

#!/bin/bash

ANDROID_ROOT=~/android/myfroyo

source $ANDROID_ROOT/build/envsetup.sh

cd ~/android/myfroyo/external/binder_sample

mm

  • 全体のAndroid.mk

include $(all-subdir-makefiles)

21 of 25

実行結果のログ

D/RECEIVER(  286): Reciever start.

D/RECEIVER(  286): ServiceManager: 0xb678

D/RECEIVER(  286): Receiver created.

D/SENDER  (  288): Sender getService 0xa678

D/SENDER  (  288): pid=288

D/SENDER  (  288): num=12

D/RECEIVER(  286): Message received code=0.

D/RECEIVER(  286): Sender pid=288

D/RECEIVER(  286): Number Data=12

D/SENDER  (  288): reply num=60

22 of 25

Ashmemって?

  • プロセスの間で複数のプロセスからアクセスできる共有メモリのAndroid版。
  • ashmemドライバ(/dev/ashmem)を介して使用する。
  •  作成したashmemのファイルディスクリプタをmmap()して使うことができる。
  •  Androidでは以下で使われている。
    • システムプロパティの格納領域(@shigepon7 さん情報)
    •  andrpoid.os.MemoryFileクラスの内部
    •  グラフィックメモリのバッファ領域
    •  Skia(2Dグラフィック&フォントエンジン)のバッファ領域

23 of 25

Ashmemの関数

  • libcutils.soに含まれる。
  • 定義は/system/core/include/cutils/ashmem.h

  •  ashmem_create_region
    •  新規に共有メモリ領域を作成し、そのfdを返す。ラベルをつけることが可能。
  •  ashmem_set_prot_region
    • メモリ領域のアクセス権限などのオプションを設定する。
  •  ashmem_pin_region
    • pinを立てる。pinを立てている領域は使用中と見倣される。
  •  ashmem_unpin_region
    • pinを外す。pinが外れた領域は未使用としてカーネルがメモリ領域を回収する。
  •  ashmem_get_size_region
    • メモリ領域のサイズを取得。

24 of 25

実際にAshmemを使ってみる

  • initやbionicのシステムプロパティでashmemを使っている箇所を参考にサンプルを書いてみましたが、残念ながらエラー。
  • 共有メモリにアクセスするためのfdに、共有メモリ作成したのとは別のプロセスからアクセスしましたが、EBADF(ファイルディスクリプタ不正)が発生...
  • 以前Donutで同じようなコードを書いたときはEACCES(パーミッションエラー)が発生...

  • もう少し試してうまくいったら資料を更新します。

25 of 25

次回予告

  • Ashmemがうまくいったら、Binder、Ashmem、ソケット通信のベンチマーク(送信データサイズごとに)を計りたいと思います。
  • libbinder(/frameworks/base/libs/binder)内のクラス構成も調べたいなと。