ラベル Android の投稿を表示しています。 すべての投稿を表示
ラベル Android の投稿を表示しています。 すべての投稿を表示

2012年1月3日火曜日

位置情報取得について調べてみた(その3)

さて、ではGpsLocationProviderを中心に調べてみる事にしよう。
前回、GpsLocationProviderを呼び出すとnativeの初期化関数が呼び出されるという話をした。実際に何をやっているのかというと以下のようになっています。


static void android_location_GpsLocationProvider_class_init_native(JNIEnv* env, jclass clazz) {
    int err;
    hw_module_t* module;

    method_reportLocation             = env->GetMethodID(clazz, "reportLocation", "(IDDDFFFJ)V");
    method_reportStatus                   = env->GetMethodID(clazz, "reportStatus", "(I)V");
    method_reportSvStatus              = env->GetMethodID(clazz, "reportSvStatus", "()V");
    method_reportAGpsStatus         = env->GetMethodID(clazz, "reportAGpsStatus", "(III)V");
    method_reportNmea                    = env->GetMethodID(clazz, "reportNmea", "(J)V");
    method_setEngineCapabilities = env->GetMethodID(clazz, "setEngineCapabilities", "(I)V");
    method_xtraDownloadRequest = env->GetMethodID(clazz, "xtraDownloadRequest", "()V");
    method_reportNiNotification     = env->GetMethodID(clazz, "reportNiNotification",
                                                                "(IIIIILjava/lang/String;Ljava/lang/String;IILjava/lang/String;)V");
    method_requestRefLocation      = env->GetMethodID(clazz,"requestRefLocation","(I)V");
    method_requestSetID                   = env->GetMethodID(clazz,"requestSetID","(I)V");
    method_requestUtcTime             = env->GetMethodID(clazz,"requestUtcTime","()V");

    err = hw_get_module(GPS_HARDWARE_MODULE_ID, (hw_module_t const**)&module);
    if (err == 0) {
        hw_device_t* device;
        err = module->methods->open(module, GPS_HARDWARE_MODULE_ID, &device);
        if (err == 0) {
            gps_device_t* gps_device = (gps_device_t *)device;
            sGpsInterface = gps_device->get_gps_interface(gps_device);
        }
    }

    if (sGpsInterface) {
        sGpsXtraInterface      = (const GpsXtraInterface*)sGpsInterface->get_extension(GPS_XTRA_INTERFACE);
        sAGpsInterface           = (const AGpsInterface*)sGpsInterface->get_extension(AGPS_INTERFACE);
        sGpsNiInterface         = (const GpsNiInterface*)sGpsInterface->get_extension(GPS_NI_INTERFACE);
        sGpsDebugInterface = (const GpsDebugInterface*)sGpsInterface->get_extension(GPS_DEBUG_INTERFACE);
        sAGpsRilInterface     = (const AGpsRilInterface*)sGpsInterface->get_extension(AGPS_RIL_INTERFACE);
    }
}

やっている事は以下の事になります。
  1. Native層から呼び出すいくつかのJavaのMethod IDを入手し、globalに保存
  2. gpsのHAL実装ライブラリのloadとModuleのopen, Interfaceの入手
  3. 拡張Interface情報の取得
なお、上記で入手している以下の情報については、HAL層の実装(gps.xxxxx.so)で用意する必要があります。
  • hw_module_t
  • hw_device_t (gps_device_t)

これらの基本的なstructは、hardware/libhardware/include/hardware/hardware.h に定義されています。

最初の hw_module_t は、hw_get_module()関数で、以下のようにして取得されます。

    const char *sym = HAL_MODULE_INFO_SYM_AS_STR;
    hmi = (struct hw_module_t *)dlsym(handle, sym);

この事から、HAL(gps.xxxx.so)内部では、HAL_MODULE_INFO_SYM_AS_STRの名前のhw_module_t構造体が実体定義されている必要があります。

なお、hardware.hの中の定義は以下の通りです。

#define HAL_MODUULE_INFO_SYM HMI
#define HAL_MODUULE_INFO_SYM_AS_STR HMI

typedef struct hw_module_t {
    uint32_t tag;
    uint16_t version_major;
    uint16_t version_minor;
    const char *id;
    const char *name;
    const char *author;
    struct hw_module_methods_t* methods;
    void* dso;
    uint32_t reserved[32-7];
} hw_module_t;

typedef struct hw_module_methods_t {
    int (*open)(const struct hw_module_t* module, const char* id,
            struct hw_device_t** device);
} hw_module_methods_t;


実際、gpsの実装例であるqcom用のコードでは以下のようになっています。
[hardware/qcom/gps/loc_api/libloc_api/gps.c]

static struct hw_module_methods_t gps_module_methods = {
    .open = open_gps
};

const struct hw_module_t HAL_MODULE_INFO_SYM = {
    .tag = HARDWARE_MODULE_TAG,
    .version_major = 1,
    .version_minor = 0,
    .id = GPS_HARDWARE_MODULE_ID,
    .name = "loc_api GPS Module",
    .author = "Qualcomm USA, Inc.",
    .methods = &gps_module_methods,
};

続いて、hw_device_tのポインタの取得になります。ただし、これには1点注意が必要で、構造体のキャストによるハックが含まれています。
hw_module_t.hw_module_methods_t.open() では、hw_device_t** にポインタが含まれて帰ります。ただし、この実体は、hw_device_t型構造体の実体ではありません。 gpsの場合、gps_device_t型の構造体のポインタとなります。

hw_device_t* でアクセスしている場合は、先頭のcommon領域にポインタがあう事になり、gps_device_t*でアクセスした場合は、get_gps_interfaceの関数ポインタまで含めたメモリ領域にアクセスできるようになります。

typedef struct hw_device_t {
    uint32_t tag;
    uint32_t version;
    struct hw_module_t* module;
    uint32_t reserved[12];
    int (*close)(struct hw_device_t* device);
} hw_device_t;

[hardware/libhardware/include/hardware/gps.h]
struct gps_device_t {
    struct hw_device_t common;
    const GpsInterface* (*get_gps_interface)(struct gps_device_t* dev);
};

[hardware/qcom/gps/loc_api/libloc_api/gps.c]
static int open_gps(const struct hw_module_t* module, char const* name, struct hw_device_t** device)
{
    struct gps_device_t *dev = malloc(sizeof(struct gps_device_t));
    memset(dev, 0, sizeof(*dev));

    dev->common.tag = HARDWARE_DEVICE_TAG;
    dev->common.version = 0;
    dev->common.module = (struct hw_module_t*)module;
    dev->get_gps_interface = gps__get_gps_interface;

    *device = (struct hw_device_t*)dev;
    return 0;
}

この辺りからは、HALの中でもGPS固有の処理となります。
const GpsInterface* (*get_gps_interface)(struct gps_device_t* dev) に設定されているgps__get_gps_interfaceで、GPSドライバのインターフェースを入手します。

typedef struct {
    size_t          size;
    int   (*init)( GpsCallbacks* callbacks );
    int   (*start)( void );
    int   (*stop)( void );
    void  (*cleanup)( void );
    int   (*inject_time)(GpsUtcTime time, int64_t timeReference,int uncertainty);
    int  (*inject_location)(double latitude, double longitude, float accuracy);
    void  (*delete_aiding_data)(GpsAidingData flags);
    int   (*set_position_mode)(GpsPositionMode mode, GpsPositionRecurrence recurrence,
            uint32_t min_interval, uint32_t preferred_accuracy, uint32_t preferred_time);
    const void* (*get_extension)(const char* name);
} GpsInterface;

つまり、HALのGPSドライバとしてはこのインターフェースを実装する事になります。
さらに、この最低限のAPIに加え、実装に応じて追加の機能を提供するための拡張用のAPIを取得するのが、get_extension関数ですね。


GPS_XTRA_INTERFACE
typedef void (* gps_xtra_download_request)();

typedef struct {
    gps_xtra_download_request download_request_cb;
    gps_create_thread create_thread_cb;
} GpsXtraCallbacks;

typedef struct {
    size_t          size;
    int  (*init)( GpsXtraCallbacks* callbacks );
    int  (*inject_xtra_data)( char* data, int length );
} GpsXtraInterface;


AGPS_INTERFACE
typedef struct {
    size_t          size;
    AGpsType        type;
    AGpsStatusValue status;
    uint32_t        ipaddr;
} AGpsStatus;

typedef void (* agps_status_callback)(AGpsStatus* status);

typedef struct {
    agps_status_callback status_cb;
    gps_create_thread create_thread_cb;
} AGpsCallbacks;

typedef struct {
    size_t          size;
    void  (*init)( AGpsCallbacks* callbacks );
    int  (*data_conn_open)( const char* apn );
    int  (*data_conn_closed)();
    int  (*data_conn_failed)();
    int  (*set_server)( AGpsType type, const char* hostname, int port );
} AGpsInterface;


GPS_NI_INTERFACE
typedef struct {
    size_t          size;
    int             notification_id;
    GpsNiType       ni_type;
    GpsNiNotifyFlags notify_flags;
    int             timeout;
    GpsUserResponseType default_response;
    char            requestor_id[GPS_NI_SHORT_STRING_MAXLEN];
    char            text[GPS_NI_LONG_STRING_MAXLEN];
    GpsNiEncodingType requestor_id_encoding;
    GpsNiEncodingType text_encoding;
    char           extras[GPS_NI_LONG_STRING_MAXLEN];
} GpsNiNotification;

typedef void (*gps_ni_notify_callback)(GpsNiNotification *notification);

typedef struct
{
    gps_ni_notify_callback notify_cb;
    gps_create_thread create_thread_cb;
} GpsNiCallbacks;

typedef struct
{
    size_t          size;
   void (*init) (GpsNiCallbacks *callbacks);
   void (*respond) (int notif_id, GpsUserResponseType user_response);
} GpsNiInterface;

GPS_DEBUG_INTERFACE
typedef struct {
    size_t          size;
    size_t (*get_internal_state)(char* buffer, size_t bufferSize);
} GpsDebugInterface;


AGPS_RIL_INTERFACE
typedef struct {
    agps_ril_request_set_id request_setid;
    agps_ril_request_ref_loc request_refloc;
    gps_create_thread create_thread_cb;
} AGpsRilCallbacks;

typedef struct {
    size_t          size;
    void  (*init)( AGpsRilCallbacks* callbacks );
    void (*set_ref_location) (const AGpsRefLocation *agps_reflocation, size_t sz_struct);
    void (*set_set_id) (AGpsSetIDType type, const char* setid);
    void (*ni_message) (uint8_t *msg, size_t len);
    void (*update_network_state) (int connected, int type, int roaming, const char* extra_in
fo);
    void (*update_network_availability) (int avaiable, const char* apn);
} AGpsRilInterface;

拡張のAPIも含めると、HALでは結構な数のAPIを実装する必要がありそうですね。
ちなみに、先ほどみたqcomの実装だと3種類の拡張を用意しているようですね。

static const void* loc_eng_get_extension(const char* name)
{
    if (strcmp(name, GPS_XTRA_INTERFACE) == 0)
    {
        return &sLocEngXTRAInterface;
    }
    else if (strcmp(name, AGPS_INTERFACE) == 0)
    {
        return &sLocEngAGpsInterface;
    }
    else if (strcmp(name, GPS_NI_INTERFACE) == 0)
    {
        return &sLocEngNiInterface;
    }
    return NULL;
}

というわけで、GpsProviderLocationは、まず真っ先にHALのAPIを読み込んでいます。
その後、GpsLocationProvier.isSupported()が呼ばれているわけですが、これは、

public static boolean isSupported() {
    return native_is_supported();
}

static jboolean android_location_GpsLocationProvider_is_supported()
{
    return (sGpsInterface != NULL);
}

とあるように、HALから基本となるInterfaceが取得できているかのチェックとなります。
さて、実際のLocationProviderのInterfaceですが、以下のように定義されています。

public interface LocationProviderInterface {
    String getName();
    boolean requiresNetwork();
    boolean requiresSatellite();
    boolean requiresCell();
    boolean hasMonetaryCost();
    boolean supportsAltitude();
    boolean supportsSpeed();
    boolean supportsBearing();
    int getPowerRequirement();
    boolean meetsCriteria(Criteria criteria);
    int getAccuracy();
    boolean isEnabled();
    void enable();
    void disable();
    int getStatus(Bundle extras);
    long getStatusUpdateTime();
    void enableLocationTracking(boolean enable);
    /* returns false if single shot is not supported */
    boolean requestSingleShotFix();
    String getInternalState();
    void setMinTime(long minTime, WorkSource ws);
    void updateNetworkState(int state, NetworkInfo info);
    void updateLocation(Location location);
    boolean sendExtraCommand(String command, Bundle extras);
    void addListener(int uid);
    void removeListener(int uid);
}

ちなみに、GpsLocationProviderでは
    /**
     * Returns true if the provider requires access to a
     * data network (e.g., the Internet), false otherwise.
     */
    public boolean requiresNetwork() {
        return true;
    }

    /**
     * Returns true if the provider requires access to a
     * satellite-based positioning system (e.g., GPS), false
     * otherwise.
     */
    public boolean requiresSatellite() {
        return true;
    }

    /**
     * Returns true if the provider requires access to an appropriate
     * cellular network (e.g., to make use of cell tower IDs), false
     * otherwise.
     */
    public boolean requiresCell() {
        return false;
    }

が設定されています。A-GPSかどうかに関わらず、Network必要なのか。

クライアントの処理を見た時の、LocationManager.getBestProvider(criteria,truer); が実行されると条件にあうProviderを探しながら最適なProviderを返すわけですが、この処理が以下のようになっています。

    public String getBestProvider(Criteria criteria, boolean enabledOnly) {
        List<String> goodProviders = getProviders(criteria, enabledOnly);
        if (!goodProviders.isEmpty()) {
            return best(goodProviders).getName();
        }

        // Make a copy of the criteria that we can modify
        criteria = new Criteria(criteria);

        // Loosen power requirement
        int power = criteria.getPowerRequirement();
        while (goodProviders.isEmpty() && (power != Criteria.NO_REQUIREMENT)) {
            power = nextPower(power);
            criteria.setPowerRequirement(power);
            goodProviders = getProviders(criteria, enabledOnly);
        }
        if (!goodProviders.isEmpty()) {
            return best(goodProviders).getName();
        }

        // Loosen accuracy requirement
        int accuracy = criteria.getAccuracy();
        while (goodProviders.isEmpty() && (accuracy != Criteria.NO_REQUIREMENT)) {
            accuracy = nextAccuracy(accuracy);
            criteria.setAccuracy(accuracy);
            goodProviders = getProviders(criteria, enabledOnly);
        }
        if (!goodProviders.isEmpty()) {
            return best(goodProviders).getName();
        }

        // Remove bearing requirement
        criteria.setBearingRequired(false);
        goodProviders = getProviders(criteria, enabledOnly);
        if (!goodProviders.isEmpty()) {
            return best(goodProviders).getName();
        }

        // Remove speed requirement
        criteria.setSpeedRequired(false);
        goodProviders = getProviders(criteria, enabledOnly);
        if (!goodProviders.isEmpty()) {
            return best(goodProviders).getName();
        }

        // Remove altitude requirement
        criteria.setAltitudeRequired(false);
        goodProviders = getProviders(criteria, enabledOnly);
        if (!goodProviders.isEmpty()) {
            return best(goodProviders).getName();
        }

        return null;
    }

以下の順で条件を緩くしながら、合致するProviderを探して、条件が合致するProviderが見つかったら、その中から最適なものを返しています。
  1. ユーザー指定のCriteriaに合致するProviderを探す。
  2. Power条件を下げながらその他がCriteriaに合致するProviderを探す。
  3. 精密さの条件を下げながらその他のCriteriaに合致するProviderを探す。
  4. 方角に関する条件を削除してその他のCriteriaに合致するProviderを探す。
  5. 速度に関する条件を削除してその他のCriteriaに合致するProviderを探す。
  6. 高度に関する条件を削除してその他のCriteriaに合致するProviderを探す
この時の、条件合致は、getProviders()でProvider側で実装している
boolean meetsCriteria(Criteria criteria);
でチェックされていますね。ちなみに、GpsLocationProviderの条件はPowerしかみてません。

    public boolean meetsCriteria(Criteria criteria) {
        return (criteria.getPowerRequirement() != Criteria.POWER_LOW);
    }

とりあえず、ここまでで初期処理とProviderの選択のうっすらとしたイメージがつかめたので、次回は、ようやくLocationの取得を調べてみたいと思います。

2012年1月2日月曜日

位置情報取得について調べてみた(その2)

さて、昨日は位置情報の取得技術と、Androidのアプリからの利用方法について軽く調べてみました。というわけで、続きの調査メモです。

前回のエントリで見た位置情報関連クラスは以下の通りです。
  • Address
  • Criteria
  • Geocoder
  • GpsSatelite
  • GpsStatus
  • Location
  • LocationManager
  • LocationProvider
さて、この中で真っ先に取得するのは、LocationManager [ frameworks/base/location/java/android/location/LocationManager.java ]
でした。LocationManagerは、Binderを使ってSystem Location Service(LocationManagerService)へアクセスするためのクラスのようです。

実体は、ContextImplクラスのstaticブロックで生成・登録されています。
[src] frameworks/base/core/java/android/app/ContextImpl.java
        registerService(LOCATION_SERVICE, new StaticServiceFetcher() {
                public Object createStaticService() {
                    IBinder b = ServiceManager.getService(LOCATION_SERVICE);
                    return new LocationManager(ILocationManager.Stub.asInterface(b));
                }});

というわけで、ここでPF部として目を付けなくてはならないのは、LocationManagerServiceですね。で、このLocationManagerServiceが生成されているのは何処か?というと、いつか何処かでみた場所なんですね。この辺りはPF部でも発表してますし、さらっと呼び出しだけ見ておきましょう。

SystemServer::init2()
  ServerThread::start()
    ServerThread::run() [frameworks/base/services/java/com/android/server/SystemServer.java]
        location = new LocationManagerService(context);
        ServiceManager.addService(Context.LOCATION_SERVICE, location);
        final LocationManagerService locationF = location;
        locationF.systemReady(); [frameworks/base/services/java/com/android/server/LocationManagerService.java]
            Thread thread = new Thread(null, this, "LocationManagerService");
            thread.start();

注: 例によって上記はC++チックなメソッド名の表記をしているだけです。

init周りを追いかけた時にでてきたSystemServerのServerThread中で生成され、LocationManagerServiceスレッドがstartされています。

public void LocationManagerService::run()
{
    Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
    Looper.prepare();
    mLocationHandler = new LocationWorkerHandler();
    initialize();
    Looper.loop();
}

Runされると、initialize処理を行った後、Looperでまわしています。このLocationManagerService::initialize()の中で、呼んでいるLocationManagerSerivce::loadProviders()でAndroid共通のProviderの生成と登録、Geocoderの生成が行われています。

LocationManagerService::loadProviders()
    LocationManagerService::loadProvidersLocked()
        LocationManagerService::_loadProvidersLocked()
        {
                if (GpsLocationProvider.isSupported()) {
                    GpsLocationProvider gpsProvider = new GpsLocationProvider(mContext, this);
                    mGpsStatusProvider = gpsProvider.getGpsStatusProvider();
                    mNetInitiatedListener = gpsProvider.getNetInitiatedListener();
                    addProvider(gpsProvider);
                    mGpsLocationProvider = gpsProvider;
                }
                PassiveProvider passiveProvider = new PassiveProvider(this);
                addProvider(passiveProvider);
                mEnabledProviders.add(passiveProvider.getName());

                PackageManager pm = mContext.getPackageManager();
                if (mNetworkLocationProviderPackageName != null &&
                    pm.resolveService(new Intent(mNetworkLocationProviderPackageName), 0) != null) {
                    mNetworkLocationProvider =
                        new LocationProviderProxy(mContext, LocationManager.NETWORK_PROVIDER,
                                mNetworkLocationProviderPackageName, mLocationHandler);
                    addProvider(mNetworkLocationProvider);
                }

                if (mGeocodeProviderPackageName != null &&
                         pm.resolveService(new Intent(mGeocodeProviderPackageName), 0) != null) {
                    mGeocodeProvider = new GeocoderProxy(mContext, mGeocodeProviderPackageName);
                }
                updateProvidersLocked();
        }

生成/AddされているProviderは、3つ
  • public class GpsLocationProvider implements LocationProviderInterface
  • public class PassiveProvider implements LocationProviderInterface
  • public class LocationProviderProxy implements LocationProviderInterface
GeocodeProviderとしては
  • public class GeocoderProxy
が生成されています。

またこのうちPassiveProviderは、mNetworkLocationProviderPackageNameのパッケージがある場合に生成されています。これは、リソース中のcom.android.internal.R.string.config_networkLocationProvider の文字列になります。
また、GeocodeProxyについても、mGeocodeProviderPackageNameのパッケージがある場合にのみ生成されており、こちらは、com.android.internal.R.string.config_geocodeProvider の文字列になります。

4.0のソースコードに含まれるtunaのconfig設定 [
device/samsung/tuna/overlay/frameworks/base/core/res/res/values/config.xml]  ですと

<string name="config_networkLocationProvider">com.google.android.location.NetworkLocationProvider</string>
<string name="config_geocodeProvider">com.google.android.location.GeocodeProvider</string>

となっています。
ちなみに、GplLocationProvider classには

    static { class_init_native(); }

となっています。ですので、最初のif文の時にこの関数が呼び出されます。
こちらは、nativeコードで実装されており、実際には

android_location_GpsLocationProvider_class_init_native()

で、その中でhw_get_module("gps",....)が呼び出されます。この辺りはHALの実装の周りの部分でして、必要になるライブラリを探して行きます。4.0のコードでは以下の順にライブラリを探し見つかったものをdlopenを使ってZygoteとリンクします。


  1. /vendor/lib/hw/gps.<ro.hardware>.so
  2. /system/lib/hw/gps.<ro.hardware>.so
  3. /vendor/lib/hw/gps.<ro.product.board>.so
  4. /system/lib/hw/gps.<ro.product.board>.so
  5. /vendor/lib/hw/gps.<ro.board.platform>.so
  6. /system/lib/hw/gps.<ro.board.platform>.so
  7. /vendor/lib/hw/gps.<ro.arch>.so
  8. /system/lib/hw/gps.<ro.arch>.so
  9. /system/lib/hw/gps.default.so

以前は、libがついていた気がするんですけどね。最近はlibがつかないのかな。

ついでにいうと、implements LocationProviderInterfaceなclassは他にもあって
  • public class MockProvider implements LocationProviderInterface
というのがあります。こちらは、LocationManager::addTestProviderを行った時に内部でaddProviderされるProviderのようです。CTS等のテスト時に使われるようですね。


さて、とりあえずここまでで見た限りは、LocationManagerはLocation情報を管理しているわけではなく、LocationProviderの管理をしていると見るのが自然なようです。
というわけで、次回は、LocationProviderについて、Gpsを中心にもう少し調べてみたいなと思います。

2012年1月1日日曜日

位置情報取得について調べてみた(その1)

Androidに関する位置情報についての下回りをちょっと調べてみようかと思っています。GPSやその他の位置情報取得に関する記事なんてそれこそ山のようにあるため、あえてエントリにする必要もないのですが、自分用メモということで。

位置情報取得というと、すぐに思い浮かぶのはGPSや基地局情報だったりするのですが、現在はそれらの組み合わせや、Wi-Fiの位置データベースを使った方法など、多くの方法が存在するようです。Androidと直接関係が無いものもあるでしょうが、まずは基礎知識を知らなければ駄目でしょうということで調べてみました。まぁ、仕様書を読んだわけではなく、いろいろなサイトを眺めて歩いただけなので、間違いもあるかもしれませんけど。

位置情報取得方法


・GPS測位
複数のGPS衛星から送信されている電波を受信することで、位置を特定する技術です。
GPS衛星からは、2種類のデータが30秒周期で送信されています。
  - アルマナックデータ (すべての衛星の軌道データ)
  - エフェメリスデータ (自身の正確な位置データとこのデータ発送時の正確な時刻データ)
GPS受信機は、初期状態ではアルマナックデータを使い、位置測定に利用できる衛星を確認し、最初のエフェメリスデータの時刻情報で時刻を設定後、次のデータでデータ受信の遅延を計測することで、衛星までの距離を計算します。
これを最低3つの衛星で繰り返すと、三角交差法を使い、経度と緯度を導きだす事ができます。4つの衛星で行えば高度も出せるという仕組みです。。
アルマナックデータはだいたい1週間程度、エフェメリスデータは1時間半程度は有効なため、2つのデータの有効時間中は、時刻情報から距離を割り出すだけで、位置情報を取得できますが、データの存在しない初期処理からの測位では数分、その後の位置情報収集は最低でも30秒程度はかかるようですね。

・DGPS(Differential GPS)
もともとGPSは軍事目的の衛星で、軍用に使う正確な位置を割り出せる暗号データとあえてノイズを含めた若干不正確なGPSデータを送信していたそうです。
このため、一般の利用では、100m程度の誤差がでていたものを補正するために考案された手法で、位置のわかっている地上の基地局との比較で誤差補正を行い制度を高める技術です。

・A-GPS(Assisted GPS)
GPSは、情報の取得を開始してから最低でも30秒程度、最悪数分間は位置を特定できません。また、起動データや位置データの受信は建物内にいるとノイズによりデータ受信は難しく、位置の特定が困難になります。
携帯電話は、例えば基地局情報などで、携帯端末のおおざっぱな位置が特定できています。そこで3G回線等別の経路で全衛星軌道データと、衛星位置情報を取得し、GPSからは比較的ノイズに強い時刻情報だけを取得することで、位置情報をGPSより高速に、建物内でもわりと正確に特定できるようにする技術がA-GPSです。


・セルベース測位
現行の携帯電話は、セル方式というものをとり、無線基地局を多数設置し、ある一定の範囲に留めることで、同じ周波数帯域をできるだけ再利用するように設計されているそうです。セルというのは、無線基地局の電波が届く範囲の事になります。携帯電話に通知を行うために、携帯電話が最後に確認できたセルIDが記録されており、定期的に更新されています。このセルIDを元にどの位置に居るのかを特定するという技術です。
携帯でGPSサービスと言われるものが出始めた初期の頃は、実際にGPS受信をしておらず、このおおよその位置情報を使った機種も多かったようですよね。
位置に関する誤差については、セルの広さによりますので、狭い範囲に設置されている都市部は小さく、田園部とか湾岸部等の場合は誤差が大きくなるようです。

・基地局測位
この辺りからキャリアによっていろいろ変わってたりするようで、いまいちつかめていません。
KDDIでは、3つ以上の基地局との同期信号を使って、位置を計算するようです。GPSの衛星からの時刻データの代わりに、基地局との同期信号を使うことで測定するようですね。
DoCoMoさんでは、基地局に同期された時間情報を持っていないため、何か別の方法で測位しているようですが。
ウィルコムは、アンテナの受信強度等から特定しているような記載をみかけましたし、
ソフトバンクはどうなんだろう。もうちょっと調べてみたいなと思っていますが、とりあえずおいておきましょう。

・Wi-Fiアクセスポイントによる測位
Wi-FiのアクセスポイントのMACアドレスと位置情報のデータベースを使って、現在の位置を特定する方法
ローリングで都市を回り、Wi-Fiのアンテナ情報を一気に収集したものと位置情報をあわせて保持している企業のデータを使って特定するようなので。そういえば、僕のHTC AriaのWi-Fiの位置が、自宅に登録されていそうなんですよね。外出先でAria経由で接続すると自宅に居る事になるし・・・・。


とまぁ、こんなあたりが、ざっと主要な位置情報取得技術となるでしょうか。


Android Frameworkでの実装
さて、ようやく本題のAndroidについてです。
いきなり下回りのコードを読むにもあたりがつかないとならないので、クライアント側でおこなうサンプルをみながら、キーになる所を抜粋してみてみました。

(1) LocationManagerの取得
LocationManager locMgr  = (LocationManager)getSystemService(LOCATION_SERVICE);

(2) 最適なLocationProviderを選択
Criteria criteria = new Criteria();
String provider = locMgr.getBestProvider(criteria, true);

(3) 最後にわかっている位置情報を取得
Location location = locMgr.getLastKnownLocation(provider);

(4) 定期的に位置の変化を取得するためのListner登録

LocationListener listener = new LocationListener() {
    @Override
    public void onLocationChanged(Location location) {
    }

    @Override
    public void onProviderDisabled(String provider) {
    }

    @Override
    public void onProviderEnabled(String provider) {
    }

    @Override
    public void onStatusChanged(String provider, int status, Bundle extras) {
    }
};

long minTime = 15000;
float minDistance = 1.0;
locMge.requestLocationUpdates(provider, minTime, minDistance, listener);

とすると、minTime[msec]以上の間隔で、minDistance[m]以上の変化があれば情報が取得される事になります。


(5) 経度、緯度、高度等の情報から住所情報への変換
Geocoder geocoder = new Geocoder(context, Locale.JAPAN);
List<Address> addressList = geocoder.getFromLocation(latitude, longitude, 5);

(6) GPSの状態取得/衛星情報の取得
GpsStatus gpsStat = locMgr.getGpsStatus(null);
Iterable<GpsSatellite> satellites = gpsStat.getSatellites();

(7) GPSの状態変化イベント取得方法

GpsStatus.Listener statListner = new GpsStatus.Listener() {
    @Override
    public void onGpsStatusChanged(int event) {
        switch(event){
        case GpsStatus.GPS_EVENT_STARTED:
            break;
        case GpsStatus.GPS_EVENT_STOPPED:
            break;
        case GpsStatus.GPS_EVENT_FIRST_FIX:
            break;
        case GpsStatus.GPS_EVENT_SATELLITE_STATUS:
            break;
        }
    }
}
locMgr.addGpsStatusListener(statListner);


とまぁ、位置情報周りの主要なクラスの使い方というのはこんな所でしょうか。

アプリ側は、管理クラス LocationManagerから位置情報を提供するLocationProviderを選択し、Locationを取得するという流れとなっています。ここまで見た感じだと、Frameworkの実装としては、位置情報はGPSに限らずProviderから取得できるように設計されており、アプリケーションは、Criteriaを使ってProviderの条件を取得して、適切なProviderを選択させる事が可能です。もちろん、getBestProvider()を使わずに、providerにLocationManager.GPS_PROVIDERを指定することで、GPSの情報だけを使うようにも実装できるわけですが。

どんなProviderが実装されているのかについては、List<String> getProviders(boolean enabledOnly); を呼び出せば、取得できるようですね。あとで、自分の端末で試してみようかな。

各クラスの詳細やその先の実装はまた次回ということで。

2011年2月2日水曜日

Zygoteへの起動要求を出している箇所を探す(その2)

というわけで、先日の続きです。

Zygoteを使って新しいVMプロセスを生成していそうな箇所は
  • android.os.Process.start()を呼び出すコード
  • /system/bin/dvz
の二つにとりあえず絞りました。
というわけで、Process.start()を呼び出していそうな箇所をきちんと探してみたら、あっさり見つかりました。

frameworks/base/services/java/com/android/server/am/ActivityManagerService.java

private final void startProcessLocked(ProcessRecord app, String hostingType, String hostingNameStr) {
        :
        :
    int pid = Process.start("android.app.ActivityThread",
        mSimpleProcessManagement ? app.processName : null, uid, uid,
        gids, debugFlags, null);
        :
        :
}

しかしまぁ、このActivityManagerServiceさんは難敵です。
何せ、メソッドの行数の長いこと・・・・。このコード、なかなか難易度が高いです。
とりあえず、こういうときは焦らず最初から。

横浜PF部の勉強会ではちらりと述べたのですが、Zygoteは起動時に--start-system-serverという引数を受け取っており、プロセス生成後、最初の子プロセスとしてsystem_serverを生成しています。このSystemServerは、内部でServerThreadというスレッドをrunしています。

frameworks/base/services/java/com/android/server/SystemServer.java
public void run() {
        :
        :
    context = ActivityManagerService.main(factoryTest);
        :
        :
}

というわけで、mainが直接呼び出されています。mainはというと

frameworks/base/services/java/com/android/server/am/ActivityManagerService.java
public static final Context main(int factoryTest) {
    AThread thr = new AThread();
    thr.start();
    synchronized (thr) {
        while (thr.mService == null) {
            try {
                thr.wait();
            } catch (InterruptedException e) {
            }
        }
    }
    ActivityManagerService m = thr.mService;
    mSelf = m;
    ActivityThread at = ActivityThread.systemMain();
    mSystemThread = at;
    Context context = at.getSystemContext();

    m.mContext = context;
    m.mFactoryTest = factoryTest;
    m.mMainStack = new ActivityStack(m, context, true);
    m.mBatteryStatsService.publish(context);
    m.mUsageStatsService.publish(context);

    synchronized (thr) {
        thr.mReady = true;
        thr.notifyAll();
    }
    m.startRunning(null, null, null, null);
    return context;
}

まずは、Athreadを生成してrunした後、AThreadのmServiceが設定されるのを待っています。
Athreadとはというと
frameworks/base/services/java/com/android/server/am/ActivityManagerService.java
static class AThread extends Thread {
    ActivityManagerService mService;
    boolean mReady = false;

    public AThread() {
        super("ActivityManager");
    }

    public void run() {
        Looper.prepare();
        android.os.Process.setThreadPriority(
        android.os.Process.THREAD_PRIORITY_FOREGROUND);
        android.os.Process.setCanSelfBackground(false);
        ActivityManagerService m = new ActivityManagerService();
        synchronized (this) {
            mService = m;
            notifyAll();
        }
        synchronized (this) {
            while (!mReady) {
                try {
                    wait();
                } catch (InterruptedException e) {
                }
            }
        }
        Looper.loop();
    }
}
ということで、Looperのprepareを呼び出した後、ActivityManagerServiceの生成を行いmServiceに設定したうえで、mReadyのフラグが立つのを待っています。
スレッドを生成したmainに処理が戻ると、mServiceのActivityManagerServiceにcontext等を設定した上で、mReadyフラグを立てて、AThreadの処理を実行させた後、AThreadのActivityManagerService#startRunning()を呼び出します。
AThreadの方はというと、Looper.loop()でメッセージループを実行していますね。

一方、startRunning()はというと
public final void startRunning(String pkg, String cls, String action,String data) {
    synchronized(this) {
        if (mStartRunning) {
            return;
        }
        mStartRunning = true;
        mTopComponent = pkg != null && cls != null ? new ComponentName(pkg, cls) : null;
        mTopAction = action != null ? action : Intent.ACTION_MAIN;
        mTopData = data;
        if (!mSystemReady) {
            return;
        }
    }
    systemReady(null);
}

初回起動ですので、systemReady(null)が呼び出されます。
正直な話、真面目に話そうと思うと、PF部の発表時間の枠に収まりませんし、とりあえず、重要そうな処理だけを追いかけます。
public void systemReady(final Runnable goingCallback) {
        :
        :
    Intent intent = new Intent(Intent.ACTION_PRE_BOOT_COMPLETED);
    ris = AppGlobals.getPackageManager().queryIntentReceivers(intent, null, 0);
        :
        :
    for (int i=ris.size()-1; i>=0; i--) {
        if ((ris.get(i).activityInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) == 0) {
            ris.remove(i);
        }
    }
        :
        :

     intent.addFlags(Intent.FLAG_RECEIVER_BOOT_UPGRADE);
    ArrayList<ComponentName> lastDoneReceivers = readLastDonePreBootReceivers();


                    final ArrayList<ComponentName> doneReceivers = new ArrayList<ComponentName>();
                    for (int i=0; i<ris.size(); i++) {
                        ActivityInfo ai = ris.get(i).activityInfo;
                        ComponentName comp = new ComponentName(ai.packageName, ai.name);
                        if (lastDoneReceivers.contains(comp)) {
                            ris.remove(i);
                            i--;
                        }
                    }
                  
                    for (int i=0; i<ris.size(); i++) {
                        ActivityInfo ai = ris.get(i).activityInfo;
                        ComponentName comp = new ComponentName(ai.packageName, ai.name);
                        doneReceivers.add(comp);
                        intent.setComponent(comp);
                        IIntentReceiver finisher = null;
                        if (i == ris.size()-1) {
                            finisher = new IIntentReceiver.Stub() {
                                public void performReceive(Intent intent, int resultCode,
                                        String data, Bundle extras, boolean ordered,
                                        boolean sticky) {
                                    mHandler.post(new Runnable() {
                                        public void run() {
                                            synchronized (ActivityManagerService.this) {
                                                mDidUpdate = true;
                                            }
                                            writeLastDonePreBootReceivers(doneReceivers);
                                            systemReady(goingCallback);
                                        }
                                    });
                                }
                            };
                        }
                        Slog.i(TAG, "Sending system update to: " + intent.getComponent());
                        broadcastIntentLocked(null, null, intent, null, finisher,
                                0, null, null, null, true, false, MY_PID, Process.SYSTEM_UID);
                        if (finisher != null) {
                            mWaitingUpdate = true;
                        }
                    }

ということで、詳しいことは置いておいてコードを見る限り
1. PackageManagerからACTION_PRE_BOOT_COMPLETEDのReceiverのリストを取得
2. リストから、FLAG_SYSTEMを持たないものを削除
3. 既にreadLastDonePreBootReceiversで、既に起動済みのものを削除?
4. 残ったリストにintent処理の終了用コードを設定して
5. FLAG_RECEIVER_BOOT_UPGRADEフラグを追加したIntentを送信

といった処理のようですね。
割と込み入っているところを、かなり適当にこんな時間に追いかけているので、何か見落としもありそうですが、とりあえず、この辺りが起動処理に絡んでいそうだという雰囲気です。

追記:
とまぁ、ここまでコードを追いかけてみたもののここが当たりかはちょっと不安を抱いてます。
そもそもActivityManagerServiceとPackageManagerServiceだと、PackageManagerServiceの方が後に生成されたりとかしていますし。
ちょっとログを仕込んでビルドして、実際に動かしてみないと駄目だなぁ・・・。

2011年2月1日火曜日

Zygoteへの起動要求を出している箇所を探す(その1)

週末は病院巡りとQt的なお話で少々忙しかったのでお休みしてました。
で、続き行きます。そろそろ真面目にペースを上げないと、資料作成に入れませんし。

ここまでの調査でZygote(app_process)が、socketを待ち受け、sokectに起動パラメータが来るとそれに従って自身のコピーを使ってJavaアプリの起動を行うという流れまで確認しました。
では、誰がソケットを開いて要求を投げてくるのかを探してみたいなと思っています。

まぁ、まずは待ち受けてるソケットを調べて見るのがセオリーでしょうね。
先日は待ち受けているコードは見つかりました。

private static final String ANDROID_SOCKET_ENV = "ANDROID_SOCKET_zygote";
        :
private static void registerZygoteSocket() {
        :
    String env = System.getenv(ANDROID_SOCKET_ENV);
    fileDesc = Integer.parseInt(env);
    sServerSocket = new LocalServerSocket(createFileDescriptor(fileDesc));
        :
}

でも、環境変数から先がよくわかっていなかったので、とりあえず、実際に動いているエミュレータで当たってみましょう。
とりあえずadb shellでzygoteのプロセス番号をしらべます。
# ps
USER PID PPID VSIZE RSS WCHAN PC NAME
        :
        :
radio 32 1 5412 484 ffffffff afd0bdac S /system/bin/rild
root 33 1 63964 20588 c009b74c afd0b844 S zygote
media 34 1 17212 1364 ffffffff afd0b6fc S /system/bin/mediaserver
        :
        :

続いて、openされているfdを調べます。開いているsocketが2つありますね。
# ls -l /proc/33/fd
lrwx------ root root 2011-01-31 22:51 0 -> /dev/null
lrwx------ root root 2011-01-31 22:51 1 -> /dev/null
lrwx------ root root 2011-01-31 22:51 2 -> /dev/null
l-wx------ root root 2011-01-31 22:51 3 -> /dev/log/main
l-wx------ root root 2011-01-31 22:51 4 -> /dev/log/radio
l-wx------ root root 2011-01-31 22:51 5 -> /dev/log/events
lr-x------ root root 2011-01-31 22:51 6 -> /system/framework/core.jar
lr-x------ root root 2011-01-31 22:51 7 -> /system/framework/bouncycastle.jar
lr-x------ root root 2011-01-31 22:51 8 -> /dev/__properties__ (deleted)
lrwx------ root root 2011-01-31 22:51 9 -> socket:[289]
lr-x------ root root 2011-01-31 22:51 10 -> /system/framework/ext.jar
lr-x------ root root 2011-01-31 22:51 11 -> /system/framework/framework.jar
lr-x------ root root 2011-01-31 22:51 12 -> /system/framework/android.policy.jar
lr-x------ root root 2011-01-31 22:51 13 -> /system/framework/services.jar
lr-x------ root root 2011-01-31 22:51 14 -> /system/framework/core-junit.jar
lr-x------ root root 2011-01-31 22:51 15 -> /system/framework/framework.jar
lr-x------ root root 2011-01-31 22:51 16 -> /system/fonts/DroidSans.ttf
lr-x------ root root 2011-01-31 22:51 17 -> /system/framework/core.jar
lr-x------ root root 2011-01-31 22:51 18 -> /dev/urandom
lr-x------ root root 2011-01-31 22:51 19 -> /system/framework/framework-res.apk
lrwx------ root root 2011-01-31 22:51 20 -> socket:[583]
では、開いているsocketを調べてみましょう。LocalServerなんて名前からもunixかなぁと当て推量で・・・・。
# cat /proc/net/unix
Num RefCount Protocol Flags Type St Inode Path
c5924de0: 00000002 00000000 00010000 0001 01 257 /dev/socket/property_service
c5924960: 00000002 00000000 00010000 0001 01 276 /dev/socket/vold
c59247e0: 00000002 00000000 00010000 0001 01 283 /dev/socket/netd
c5924660: 00000002 00000000 00010000 0001 01 285 /dev/socket/rild-debug
c59244e0: 00000002 00000000 00010000 0001 01 287 /dev/socket/rild
c5924360: 00000002 00000000 00010000 0001 01 289 /dev/socket/zygote
c5b34800: 00000002 00000000 00010000 0001 01 319 @jdwp-control
c59241e0: 00000002 00000000 00010000 0001 01 296 /dev/socket/installd
c5924060: 00000002 00000000 00010000 0001 01 298 /dev/socket/keystore
c5b34e00: 00000002 00000000 00010000 0001 01 305 /dev/socket/qemud
c5b34c80: 00000002 00000000 00010000 0001 01 308 @android:debuggerd
        :
        :
c40fb3c0: 00000003 00000000 00000000 0001 03 613 /dev/socket/qemud
c40fb240: 00000003 00000000 00000000 0001 03 612
c40fb0c0: 00000002 00000000 00000000 0001 03 606
c40fbb40: 00000003 00000000 00000000 0001 03 583 /dev/socket/zygote
c40fbcc0: 00000003 00000000 00000000 0001 03 582
c40fbe40: 00000003 00000000 00000000 0001 03 564
c40716a0: 00000003 00000000 00000000 0001 03 563
c4071520: 00000003 00000000 00000000 0001 03 561
c40713a0: 00000003 00000000 00000000 0001 03 560
c4071220: 00000003 00000000 00000000 0001 03 558 /dev/socket/vold
c40710a0: 00000003 00000000 00000000 0001 03 557
c4071820: 00000003 00000000 00000000 0001 03 550 /dev/socket/netd
c40719a0: 00000003 00000000 00000000 0001 03 549
c4071b20: 00000003 00000000 00000000 0001 03 538 /dev/socket/qemud
c4071ca0: 00000003 00000000 00000000 0001 03 537
c4071e20: 00000003 00000000 00000000 0001 03 317 /dev/socket/installd
c5b34380: 00000003 00000000 00000000 0001 03 523
c5b34200: 00000003 00000000 00000000 0001 03 404 @jdwp-control
c5b34080: 00000003 00000000 00000000 0001 03 402
c5b34500: 00000003 00000000 00000000 0001 03 354 /dev/socket/qemud
c5b34680: 00000003 00000000 00000000 0001 03 353
c5b34980: 00000003 00000000 00000000 0001 03 316
c5b34b00: 00000003 00000000 00000000 0001 03 315
c5924ae0: 00000003 00000000 00000000 0001 03 260
c5924c60: 00000003 00000000 00000000 0001 03 259

どうやら、ビンゴです。/dev/socket/zygoteでUNIXドメインソケットを開いているようですね。肝心の起動要求はというと、いちいちaccept()して読み込んで閉じるという作業を繰り返しているわけですから、エミュレータでは探れません。というわけで、ソースコードに戻ります。

とりあえず、zygoteであることはわかったので、こういう時は"で囲まれた文字列としてのzygoteをコードで検索します。
gingerbread hermit4$ source build/envsetup.sh
gingerbread hermit4$ sgrep \"zygote\"
./cts/tools/device-setup/TestDeviceSetup/src/android/tests/getinfo/RootProcessScanner.java:36: "zygote"
./dalvik/vm/Jni.c:4322: * only be called in "zygote" mode, when we have one thread running.
./dalvik/vm/alloc/HeapDebug.c:87: if (strcmp(buf, "zygote") != 0) {
./dalvik/vm/alloc/HeapDebug.c:88: /* If the process is no longer called "zygote",
./dalvik/vm/hprof/HprofHeap.c:264: nameId = hprofLookupStringId("zygote");
./dalvik/vm/native/dalvik_system_Zygote.c:243: dvmDumpLoaderStats("zygote");
./dalvik/vm/native/dalvik_system_Zygote.c:396: dvmDumpLoaderStats("zygote");
./frameworks/base/cmds/app_process/app_main.cpp:156: setArgv0(argv0, "zygote");
./frameworks/base/cmds/app_process/app_main.cpp:157: set_process_name("zygote");
./frameworks/base/cmds/dumpstate/utils.c:394: if (len <= 0 || !memcmp(data, "zygote", 6)) continue;
./frameworks/base/cmds/rawbu/backup.cpp:707: property_set("ctl.stop", "zygote"); ./frameworks/base/cmds/rawbu/backup.cpp:727: property_set("ctl.start", "zygote"); ./frameworks/base/core/java/android/os/Process.java:46: private static final String ZYGOTE_SOCKET = "zygote";
./frameworks/base/core/java/com/android/internal/os/SamplingProfilerIntegration.java:129: writeSnapshot(dir, "zygote");
./frameworks/base/tools/preload/PrintHtmlDiff.java:44: if (proc.name.equals("zygote")) {
./frameworks/base/tools/preload/Proc.java:79: return parent != null && parent.name.equals("zygote")
./frameworks/base/tools/preload/WritePreloadedClassFile.java:118: addAllClassesFrom("zygote", root, toPreload);
./libcore/dalvik/src/main/java/dalvik/system/Zygote.java:20: * Provides access to the Dalvik "zygote" feature, which allows a VM instance to
./system/core/libcutils/zygote.c:34:#define ZYGOTE_SOCKET "zygote"
./system/core/toolbox/start.c:15: property_set("ctl.start", "zygote");
./system/core/toolbox/stop.c:15: property_set("ctl.stop", "zygote");

すばらしく順調です。それっぽい箇所が2カ所ほど見つかりました。
一カ所は、android.os.Process ですね。
frameworks/base/core/java/android/os/Process.java
private static void openZygoteSocketIfNeeded() throws ZygoteStartFailedEx {
        :
        :
    sZygoteSocket = new LocalSocket();
    sZygoteSocket.connect(new LocalSocketAddress(ZYGOTE_SOCKET,
            LocalSocketAddress.Namespace.RESERVED));

    sZygoteInputStream
        = new DataInputStream(sZygoteSocket.getInputStream());

    sZygoteWriter =
        new BufferedWriter(
            new OutputStreamWriter(
                sZygoteSocket.getOutputStream()),
                256);
        :
        :
}

private static int zygoteSendArgsAndGetPid(ArrayList args) throws ZygoteStartFailedEx {
    int pid;
    openZygoteSocketIfNeeded();
        :
        :
    sZygoteWriter.write(Integer.toString(args.size()));
    sZygoteWriter.newLine();

    int sz = args.size();
    for (int i = 0; i < sz; i++) { 

        String arg = args.get(i); 
        if (arg.indexOf('\n') >= 0) {
            throw new ZygoteStartFailedEx("embedded newlines not allowed");
        }
        sZygoteWriter.write(arg);
        sZygoteWriter.newLine();
    }
     sZygoteWriter.flush();
     // Should there be a timeout on this?
    pid = sZygoteInputStream.readInt();
        :
        :
}


private static int startViaZygote(final String processClass,
                                                  final String niceName,
                                                  final int uid, final int gid,
                                                  final int[] gids,
                                                  int debugFlags,
                                                  String[] extraArgs) throws ZygoteStartFailedEx {
    int pid;
        :
    pid = zygoteSendArgsAndGetPid(argsForZygote);
        :
    return pid;
}

public static final int start(final String processClass,
                                        final String niceName,
                                        int uid, int gid, int[] gids,
                                        int debugFlags,
                                        String[] zygoteArgs)
{
    if (supportsProcesses()) {
        try {
            return startViaZygote(processClass, niceName, uid, gid, gids,
                                                debugFlags, zygoteArgs);
        } catch (ZygoteStartFailedEx ex) {
            Log.e(LOG_TAG,
                "Starting VM process through Zygote failed");
                throw new RuntimeException(
                "Starting VM process through Zygote failed", ex);
        }
    } else {
        // Running in single-process mode

        Runnable runnable = new Runnable() {
            public void run() {
                Process.invokeStaticMain(processClass);
            }
        };

        // Thread constructors must not be called with null names (see spec).
        if (niceName != null) {
            new Thread(runnable, niceName).start();
        } else {
            new Thread(runnable).start();
        }
        return 0;
    }
}

というわけで、一カ所はProcess.start()から始まるようです。が、適当にソースコードを探して見ましたが、呼び出していそうな箇所をまだ見つけられていません。

もう一カ所は、Nativeになります。
system/core/libcutils/zygote.c
int zygote_run_wait(int argc, const char **argv, void (*post_run_func)(int))
{
    int fd;
    int pid;
    int err;
    const char *newargv[argc + 1];

    fd = socket_local_client(ZYGOTE_SOCKET,
                ANDROID_SOCKET_NAMESPACE_RESERVED, AF_LOCAL);

    if (fd < 0) {
         return -1;
     }
    newargv[0] = "--peer-wait";
    memcpy(newargv + 1, argv, argc * sizeof(*argv));
     pid =     send_request(fd, 1, argc + 1, newargv);
     if (pid > 0 && post_run_func != NULL) {
        post_run_func(pid);
    }

    // Wait for socket to close
    do {
        int dummy;
        err = read(fd, &dummy, sizeof(dummy));
    } while ((err < 0 && errno == EINTR) || err != 0);

    do {
         err = close(fd);
    } while (err < 0 && errno == EINTR);
     return 0;
}

int zygote_run_oneshot(int sendStdio, int argc, const char **argv)
{
    int fd = -1;
    int err;
    int i;
    int retries;
    int pid;
    const char **newargv = argv;
    const int newargc = argc;
    for (retries = 0; (fd < 0) && (retries < ZYGOTE_RETRY_COUNT); retries++) {
        if (retries > 0) {
           struct timespec ts;
           memset(&ts, 0, sizeof(ts));
           ts.tv_nsec = ZYGOTE_RETRY_MILLIS * 1000 * 1000;
           do {
               err = nanosleep (&ts, &ts);
           } while (err < 0 && errno == EINTR);
        }
        fd = socket_local_client(ZYGOTE_SOCKET, AF_LOCAL,
                                              ANDROID_SOCKET_NAMESPACE_RESERVED);
    }
    if (fd < 0) {
        return -1;
    }
    pid = send_request(fd, 0, newargc, newargv);
    do {
        err = close(fd);
    } while (err < 0 && errno == EINTR);
    return pid;
}
というわけで、利用されているのは、zygote_run_waitとzygote_run_oneshotの2カ所でした。

zygote_run_waitが使われている箇所を調べると、
dalvik/dvz/dvz.c
int main (int argc, const char **argv) {
    int err;

    if (argc > 1 && 0 == strcmp(argv[1], "--help")) {
        usage(argv[0]);
        exit(0);
    }

    err = zygote_run_wait(argc - 1, argv + 1, post_run_func);

    if (err < 0) {
         fprintf(stderr, "%s error: no zygote process found\n", argv[0]);
         exit(-1);
     }
     exit(0);
}
ということで、/system/bin/dvz にたどり着きました。Usageを見てみると
# dvz --help
Usage: dvz [--help] [-classpath ]
        [additional zygote args] fully.qualified.java.ClassName [args]

Requests a new Dalvik VM instance to be spawned from the zygote
process. stdin, stdout, and stderr are hooked up. This process remains
while the spawned VM instance is alive and forwards some signals.
The exit code of the spawned VM instance is dropped.

というわけで、コマンドラインからクラスを指定してVMプロセスを起動するコマンドです。

もう一方のoneshotの方を探してみると
frameworks/base/cmds/runtime/main_runtime.cpp

extern "C"
int main(int argc, char* const argv[])
{
        :
        :
    if (proc->supportsProcesses()) {
        // If stdio logging is on, system_server should not inherit our stdio
        // The dalvikvm instance will copy stdio to the log on its own
        char propBuf[PROPERTY_VALUE_MAX];
        bool logStdio = false;
        property_get("log.redirect-stdio", propBuf, "");
        logStdio = (strcmp(propBuf, "true") == 0);

        zygote_run_oneshot((int)(!logStdio),
                sizeof(ZYGOTE_ARGV) / sizeof(ZYGOTE_ARGV[0]),
                    ZYGOTE_ARGV);
    } else {
#ifndef HAVE_ANDROID_OS
        QuickRuntime* runt = new QuickRuntime();
        runt->start("com/android/server/SystemServer",
        false /* spontaneously fork system server from zygote */);
#endif
    }
    finish_system_init(proc);
    run(proc);
        :
        :
}
ということですが、このコードをビルドするAndroid.mkを見るとifeq ($(TARGET_SIMULATOR),true)ということで、シミュレータ専用のようですから、とりあえず除外で良いでしょう。

というわけで、本日追いかけた限りでは、可能性があるのは
  • android.os.Process.start()を呼び出すコード
  • /system/bin/dvz
の辺りを攻めると、回答に近づけるかなぁ?といった雰囲気でした。

2011年1月27日木曜日

Zygoteは何をしているのか?

脱線コーナーを抜けて一休み後、とりあえず、続きです。

さて、/system/bin/app_processがZygoteの本体で、JavaのZygoteInit.main()を呼び出すという話を以前の勉強会で出しました。で、前回はそのまま、system_serverプロセスをforkするお話に突入してしまったわけですが、ここからは、その続きの始まりです。ここからは、コードを拾い出して書いて行きますが、かなり大幅に省略していたりしますのでご了承下さい。

public static void main(String argv[]) {
        :
        :
    registerZygoteSocket();
        :
        :
    if (argv[1].equals("true")) {
        startSystemServer();  //前回はここまで
    } else if (!argv[1].equals("false")) {
        throw new RuntimeException(argv[0] + USAGE_STRING);
    }

    Log.i(TAG, "Accepting command socket connections");

    if (ZYGOTE_FORK_MODE) {
        runForkMode();
    } else {
        runSelectLoopMode();
    }

    closeServerSocket();
}
まず先に、registerZygoteSocket()を見ておきます。名前からしてZygoteの必要なソケットの登録とわかります。
        :
        :
private static void registerZygoteSocket() {
    String env = System.getenv(ANDROID_SOCKET_ENV);
    fileDesc = Integer.parseInt(env);
    sServerSocket = new LocalServerSocket(
    createFileDescriptor(fileDesc));

とりあえず、環境変数を取得してLocalServerSocketを生成しています。

続いては前回入り込んだstartSystemServer()では、子プロセスをforkしていましたが、system_serverの生成が終わると、親のプロセスは普通にここまで戻ってきます。続きを見てみると、if文に囲まれた個所が目に入ります。このif文ですが
    private static final boolean ZYGOTE_FORK_MODE = false;
ということで、Gingerbreadさんのコードだと、runSelectLoopMode()に入ります。というわけで処理を追いかけて見ます。
private static void runSelectLoopMode() throws MethodAndArgsCaller {
        :
        :
   while (true) {
       int index;
       /*
       * Call gc() before we block in select().
       * It's work that has to be done anyway, and it's better
       * to avoid making every child do it. It will also
       * madvise() any free memory as a side-effect.
       *
       * Don't call it every time, because walking the entire
       * heap is a lot of overhead to free a few hundred bytes.
       */
       if (loopCount <= 0) {
            gc();
            loopCount = GC_LOOP_COUNT;
        } else {
            loopCount--;
        }
        try {
            fdArray = fds.toArray(fdArray);
            index = selectReadable(fdArray);
        } catch (IOException ex) {
            throw new RuntimeException("Error in select()", ex);
        }
        if (index < 0) {
            throw new RuntimeException("Error in select()");
        } else if (index == 0) {
            ZygoteConnection newPeer = acceptCommandPeer();
            peers.add(newPeer);
            fds.add(newPeer.getFileDesciptor());
        } else {
            boolean done; done = peers.get(index).runOnce();
            if (done) {
                peers.remove(index);
                fds.remove(index);
            }
        }
    }
}

「先生!!なんだか、Zygoteちゃんがgc()呼んでます!」的なコードが見える気がしますが、とりあえず今は無視します。「え〜」って声が聞こえる気はしますが、今回はそこはメインじゃないですし。誰か、ここを追いかけてPF部で発表してくれる人はいないものかな・・・・。

とりあえず進みます。ここでは保持しているfdのリストを元にselectReadable()を呼び出します。selectReadable()は、Nativeコードで、実体は frameworks/base/core/jni/com_android_internal_os_ZygoteInit.cpp にあります。
Jniのためコードがちょっと面倒なので簡単に何をやってるかだけ抜き出すと、引数で渡したfdのArrayをfdsetにFD_SETした後、

do {
    err = select (nfds, &fdset, NULL, NULL, NULL);
} while (err < 0 && errno == EINTR);

とまぁ、select待ちしたうえで、selectを抜けると、Arrayの何番目がFD_ISSETかを判定して返します。

index==0の場合、つまり、最初に待ち受けしているFDに接続要求が来ると
private static ZygoteConnection acceptCommandPeer() {
    try {
        return new ZygoteConnection(sServerSocket.accept());
    } catch (IOException ex) {
        throw new RuntimeException("IOException during accept()", ex);
    }
}

ということで、acceptしたfdを持ったZygoteConnectionを生成しており、それをarrayに追加した後、acceptしたfdをfdsに追加しているわけです。

で、もしも0以外のfd(つまりsServerSocket.accept()でacceptしたfd)がreadableになると、対象となるZygoteConnection#runOnce()を呼び出します。

boolean runOnce() throws ZygoteInit.MethodAndArgsCaller {
        :
        :
    args = readArgumentList();
        :
        :
   parsedArgs = new Arguments(args);

    applyUidSecurityPolicy(parsedArgs, peer);
    applyDebuggerSecurityPolicy(parsedArgs);
    applyRlimitSecurityPolicy(parsedArgs, peer);
    applyCapabilitiesSecurityPolicy(parsedArgs, peer);

    int[][] rlimits = null;

    if (parsedArgs.rlimits != null) {
        rlimits = parsedArgs.rlimits.toArray(intArray2d);
    }

    pid = Zygote.forkAndSpecialize(parsedArgs.uid, parsedArgs.gid,
                                                        parsedArgs.gids, parsedArgs.debugFlags, rlimits);
        :
        :
    if (pid == 0) {
        // in child
        handleChildProc(parsedArgs, descriptors, newStderr);
        // should never happen
        return true;
    } else { /* pid != 0 */
        // in parent...pid of < 0 means failure
        return handleParentProc(pid, descriptors, parsedArgs);
    }

readArgumentList()は、acceptした時のsocketから引数のリストを読み出しています。何をどう読み込んでパースしているのか、その後のチェック等は後日に回すものとして先に進みます。

取得した引数に従って、まずは子プロセスをforkしています。何となく想像がつくかと思いますが、uid,gid等の設定に従って子プロセスをSpecializeしているわけです。

その後、親プロセスであるZygoteの本体は
private boolean handleParentProc(int pid,FileDescriptor[] descriptors, Arguments parsedArgs) {
        :
        :
    ZygoteInit.setpgid(pid, ZygoteInit.getpgid(peer.getPid()));
        :
        :
    for (FileDescriptor fd: descriptors) {
        ZygoteInit.closeDescriptor(fd);
    }
        :
        :
    mSocketOutStream.writeInt(pid);
        :
        :
    if (parsedArgs.peerWait) {
        mSocket.close();
        return true;
    }
    return false;
}
のように、子プロセスをpeerのプロセスグループへの登録を行った後、descriptorを閉じ、要求をしてきた相手にpidを返した後、peerを維持する必要がなければ、socketを閉じて処理をLoopに戻し、ZygoteConnectionを削除します。

起こされた子プロセスは・・・というと
private void handleChildProc(Arguments parsedArgs, FileDescriptor[] descriptors,
                                               PrintStream newStderr) {
    if (parsedArgs.peerWait) {
        ZygoteInit.setCloseOnExec(mSocket.getFileDescriptor(), true);
        sPeerWaitSocket = mSocket;
    } else {
        closeSocket();
        ZygoteInit.closeServerSocket();
    }

    if (descriptors != null) {
        ZygoteInit.reopenStdio(descriptors[0],
        descriptors[1], descriptors[2]);

        for (FileDescriptor fd: descriptors) {
            ZygoteInit.closeDescriptor(fd);
        }
        newStderr = System.err;
    }
サーバー側のFDをcloseした後、
    if (parsedArgs.runtimeInit) {
        RuntimeInit.zygoteInit(parsedArgs.remainingArgs);
    } else {
        ClassLoader cloader;

        if (parsedArgs.classpath != null) {
            cloader
                = new PathClassLoader(parsedArgs.classpath,
                                                        ClassLoader.getSystemClassLoader());
        } else {
            cloader = ClassLoader.getSystemClassLoader();
        }
        String className;
        className = parsedArgs.remainingArgs[0];
        String[] mainArgs = new String[parsedArgs.remainingArgs.length - 1];

        System.arraycopy(parsedArgs.remainingArgs, 1,mainArgs, 0, mainArgs.length);
        ZygoteInit.invokeStaticMain(cloader, className, mainArgs);
    }
}
runtimeInitがどのような場合にtrueになっているのか調べ切れていませんが、後者の処理になった場合、引数で指定されたクラスをロードし、Static Mainを呼び出しているようです。

大ざっぱにまとめると、Zygoteは、socketを生成して待ち受け、要求が来ると、自身のコピーである子プロセスで送信されてきた引数を元にJavaのアプリケーションを起動していると考えて良いかと思います。

とりあえず、今夜の調査はここまでとしておきましょう。