서비스 관리자(Service Manager)

원격 인터페이스는 struct binder_transaction_data 의 target.handle 필드로 지정된다고 했다. 인터페이스 핸들(Interface Handle)을 등록하고 찾기 위해 바인더 IPC 드라이버는 내부에 전역변수를 하나 유지하고 있다. 이 전역변수는 컨텍스트 관리자(Context Manager)라고 부르는 것을 가리키는데, 인터페이스 핸들 값이 0인 모든 요청은 컨텍스트 관리자로 보내진다. 컨텍스트 관리자는 BINDER_SET_CONTEXT_MGR ioctl 명령으로 설정할 수 있다. 안드로이드는 부팅 과정에서 서비스 관리자(Service Manager) 라고 부르는 데몬(Daemon) 프로세스를 컨텍스트 관리자로 등록한다. 서비스 관리자는 응용 프로그램들이 시스템 서비스들을 찾도록 돕는 것을 목적으로 한다. 여기서 시스템 서비스라 함은 부팅 과정에서 시스템 계정으로 실행되는 데몬 프로세스들을 뜻한다. 안드로이드 SDK를 통해 제작한 사용자들의 서비스들은 서비스 관리자가 취급하지 않음에 유의해야 한다. 사용자 서비스는 나중에 다룰 액티비티 관리자(Activity Manager)를 통해 등록한다. 이 액티비티 관리자 역시 시스템 서비스다. 서비스 관리자는 C로 구현되어 있는데, 다음과 같은 인터페이스를 갖는다. 앞으로 특별한 언급이 없는 한 모든 인터페이스는 AIDL(Android Interface Definition Language)로 표현하겠다.

package android.os;

interface IServiceManager {
    IBinder getService(String name);
    IBinder checkService(String name);
    int addService(String name, IBinder service);
    String listServices(int n);
}

하지만 일단 PDK(Platform Development Kit)환경하에서, 이 인터페이스는 BpServiceManager 라는 C++ 클래스로 감싸져서 다음과 같은 형태의 인터페이스 인 것처럼 변형된다.

package android.os;

interface IServiceManager {
    IBinder getService(String name);
    IBinder checkService(String name);
    int addService(String name, IBinder service);
    List<String> listServices();
}

서비스 관리자가 컨텍스트 관리자로 등록된 후에, 시스템 서비스들이 순차적으로 실행되면서 addService() 를 원격 호출한다. 각 시스템 서비스들은 미리 고유의 이름이 지정되어 있다. 가령 액티비티 관리자의 경우 "activity" 라는 이름이 할당되어 있다. 따라서 액티비티 관리자는 addService("activity", service) 를 원격 호출하게 된다. 두 번째 인자로 전달되는 service 는 등록하고자 하는 인터페이스와 연결된 IBinder 인터페이스로서, struct flat_binder_object 구조체의 binder 포인터로 변환된다. 이는 다시 바인더 드라이버에 의해 인터페이스 핸들로 변환되어 서비스 관리자에게 전달된다. 서비스 관리자는 name 으로 부터 인터페이스 핸들을 찾는 맵(Map)을 유지하게 된다. 이 맵을 사용하여 서비스 관리자는 checkService() 로 요청된 이름에 대응하는 인터페이스 핸들을 전달할 수 있다. 원격 프로세스에 도착하는 인터페이스 핸들 역시 IBinder 인터페이스를 통해 표현된다. 서비스 관리자는 빈번하게 요구되는 시스템 서비스들을 찾을 목적으로 대단히 가볍게 설계되었다. IServiceManager 에서 볼 수 있듯이 일단 등록된 인터페이스를 제거할 수 있는 함수도 제공되지 않는다. 즉 모든 서비스들을 단 한번씩만 등록 받는다. 이 후에 같은 이름의 서비스가 등록을 요청하면 거절된다. 뿐만 아니라 등록을 요청하는 서비스들의 계정과 이름이 미리 정한 범위를 벗어나는 모든 요청을 거절한다. 좀 더 동적이고 풍부한 기능은 액티비티 관리자를 통해 제공된다. 따라서 (시스템 서비스가 아닌) 일반 서비스는 액티비티 관리자를 찾은 후에 서비스를 등록해야 한다. 액티비티 관리자를 통한 등록과 발견은 뒤에서 다시 다루기로 한다. 그러나 한가지 의문이 남는다. 앞에서 함수를 호출할 때 데이터 버퍼의 처음에 인터페이스의 이름을 전달해야 한다고 했다. 서비스 관리자로 등록할 때 사용하는 이름은 인터페이스의 정식이름이 아니다. 가령 액티비티 관리자는 checkService("activity") 로 찾을 수 있는데, 인터페이스의 정식 이름은 android.app.IActivityManager 다. 이 이름 역시 미리 기억해 두어야 하는 것일까? 사실은 모든 인터페이스에 저절로 제공되는 함수가 존재한다. 이 함수는 android.os.IBinder.INTERFACE_TRANSACTION (0x5f4e5446 = 1598968902) 이라는 code 값을 사용하고, 인터페이스 이름을 요구하지 않는다. 오히려 인터페이스 이름을 반환 값으로 제공한다. IServiceManager 인터페이스는 명령 행 도구 /system/bin/service 로 실험해볼 수 있다. Android 1.6 AVD(Android Virtual Device) 로 에뮬레이터(Emulator)를 실행한 경우 service list 명령은 다음과 같이 반응한다.

# service list
service list
Found 44 services:
0       phone: [com.android.internal.telephony.ITelephony]
1       iphonesubinfo: [com.android.internal.telephony.IPhoneSubInfo]
2       simphonebook: [com.android.internal.telephony.IIccPhoneBook]
3       isms: [com.android.internal.telephony.ISms]
4       appwidget: [com.android.internal.appwidget.IAppWidgetService]
5       audio: [android.media.IAudioService]
6       wallpaper: [android.app.IWallpaperService]
7       checkin: [android.os.ICheckinService]
8       search: [android.app.ISearchManager]
9       location: [android.location.ILocationManager]
10      devicestoragemonitor: []
11      mount: [android.os.IMountService]
12      notification: [android.app.INotificationManager]
13      accessibility: [android.view.accessibility.IAccessibilityManager]
14      connectivity: [android.net.IConnectivityManager]
15      wifi: [android.net.wifi.IWifiManager]
16      netstat: [android.os.INetStatService]
17      input_method: [com.android.internal.view.IInputMethodManager]
18      clipboard: [android.text.IClipboard]
19      statusbar: [android.app.IStatusBar]
20      window: [android.view.IWindowManager]
21      sensor: [android.hardware.ISensorService]
22      alarm: [android.app.IAlarmManager]
23      hardware: [android.os.IHardwareService]
24      battery: []
25      content: [android.content.IContentService]
26      permission: [android.os.IPermissionController]
27      activity.providers: []
28      activity.senders: []
29      activity.services: []
30      activity.broadcasts: []
31      cpuinfo: []
32      meminfo: []
33      activity: [android.app.IActivityManager]
34      package: [android.content.pm.IPackageManager]
35      telephony.registry: [com.android.internal.telephony.ITelephonyRegistry]
36      usagestats: [com.android.internal.app.IUsageStats]
37      batteryinfo: [com.android.internal.app.IBatteryStats]
38      power: [android.os.IPowerManager]
39      entropy: []
40      SurfaceFlinger: [android.ui.ISurfaceComposer]
41      media.camera: [android.hardware.ICameraService]
42      media.player: [android.hardware.IMediaPlayerService]
43      media.audio_flinger: [android.media.IAudioFlinger]
#

service list 는 IServiceManager.listServices() 로 얻은 간단한 인터페이스 이름(가령 33번 액티비티 관리자의 경우 "activity")을 사용하여 인터페이스에 접근한 후, INTERFACE_TRANSACTION 을 사용하여 정식 인터페이스 이름("android.app.IActivityManager") 을 찾아 인쇄한다. 이 과정을 살펴보기 위해 service call 명령을 사용할 수 있다.

# service call activity 1598968902
service call activity 1598968902
Result: Parcel(
  0x00000000: 0000001c 006e0061 00720064 0069006f '....a.n.d.r.o.i.'
  0x00000010: 002e0064 00700061 002e0070 00410049 'd...a.p.p...I.A.'
  0x00000020: 00740063 00760069 00740069 004d0079 'c.t.i.v.i.t.y.M.'
  0x00000030: 006e0061 00670061 00720065 00000000 'a.n.a.g.e.r.....')
#

INTERFACE_TRANSACTION 값 1598968902 를 사용하여 함수를 호출했다. 전달하는 인자는 없고, 반환 값은 인쇄된 내용과 같다. Parcel 은 struct binder_transaction_data 구조체의 데이터 버퍼를 추상화한 C++ 또는 Java 클래스로, 원격 호출시의 스택(Stack)을 표현한다고 보면 된다. 가장 왼쪽에는 바이트 오프셋이 나오고, 그 다음으로는 4바이트씩 정수로 해석한 값을 16진수로 표현한다. 바이트 단위의 16진수가 아님에 주의하라. Little Endian 시스템이기 때문에 바이트 순서가 뒤집힌 것을 관찰할 수 있다. 그 다음으로 바이트 순서대로 ASCII 문자로 해석한 값을 인쇄하고 있다. 결과 값으로 전달된 것은 앞에서 설명한 String16 형식의 인터페이스 이름이다. 유니코드로 표현된 인터페이스의 이름이 보인다. 첫 4바이트는 문자열의 길이 28을 뜻하고, 끝에 따라오는 4바이트의 0 중에서, 앞의 2바이트는 종료 문자이고, 뒤의 2바이트는 4바이트 정렬을 위한 채워 넣기 다. 지금까지 바인더의 역할과 사용법을 살펴보았다. 마지막으로 바인더의 한계와 전망에 대해 생각해보자.